Поделиться через


Создание внутренних служб для собственных мобильных приложений в ASP.NET Core

Джеймс Монтемагно

Мобильные приложения могут взаимодействовать с внутренними службами ASP.NET Core. Инструкции по подключению локальных веб-служб из симуляторов iOS и эмуляторов Android см. в статье Подключение к локальным веб-службам из эмуляторов Android и симуляторов iOS.

Просмотреть или скачать пример кода внутренней службы

Пример собственного мобильного приложения

В этом руководстве показано, как создавать внутренние службы с помощью ASP.NET Core для поддержки собственных мобильных приложений. Он использует приложение .NET MAUI в качестве своего собственного клиента. В этом примере представлен проект служб веб-API ASP.NET Core, который в этой статье показано, как создать.

Приложение To Do Rest, работающее на смартфоне Android

Функции

Приложение TodoREST поддерживает перечисление, добавление, удаление и обновление элементов todo. Каждый элемент имеет идентификатор, имя, заметки и свойство, указывающее, выполнено ли оно еще.

В предыдущем примере основное представление элементов содержит имя каждого элемента и указывает, выполняется ли оно с помощью флажка.

Коснитесь значка +, чтобы перейти на страницу добавления элемента.

Диалоговое окно добавления элемента

Касание элемента на главной странице переходит на страницу редактирования, в которой можно изменить имя элемента, заметки и выполненные параметры, или элемент можно удалить:

Диалоговое окно изменения элемента

Чтобы самостоятельно протестировать его против приложения ASP.NET Core, созданного в следующем разделе, если вы разместите его онлайн, обновите константу RestUrl приложения. В противном случае приложение будет взаимодействовать с приложением ASP.NET Core, размещенным локально на компьютере.

Эмуляторы Android не выполняются на локальном компьютере и используют IP-адрес обратного цикла (10.0.2.2) для взаимодействия с локальным компьютером. Используйте класс .NET MAUIDeviceInfo для обнаружения операционной системы, на котором работает приложение, чтобы использовать правильный URL-адрес.

Перейдите TodoREST к проекту и откройте Constants.cs файл. Файл Constants.cs содержит следующую конфигурацию.

namespace TodoREST
{
    public static class Constants
    {
        // URL of REST service
        //public static string RestUrl = "https://dotnetmauitodorest.azurewebsites.net/api/todoitems/{0}";

        // URL of REST service (Android does not use localhost)
        // Use http cleartext for local deployment. Change to https for production
        public static string LocalhostUrl = DeviceInfo.Platform == DevicePlatform.Android ? "10.0.2.2" : "localhost";
        public static string Scheme = "https"; // or http
        public static string Port = "5001";
        public static string RestUrl = $"{Scheme}://{LocalhostUrl}:{Port}/api/todoitems/{{0}}";
    }
}

При необходимости можно развернуть веб-службу в облачной службе, например Azure, и обновить ее RestUrl.

Создание проекта ASP.NET Core

Создайте веб-приложение ASP.NET Core в Visual Studio. Выберите шаблон веб-API. Назовите проект TodoAPI.

Диалоговое окно создания веб-приложения ASP.NET с выбранным шаблоном проекта веб-API

Приложение должно отвечать на все запросы, сделанные по протоколу HTTPS, через порт 5001.

Примечание.

Запустите приложение напрямую, а не за IIS Express. Служба IIS Express игнорирует не локальные запросы по умолчанию. Запустите dotnet run из командной строки или выберите профиль имени приложения в раскрывающемся списке "Целевой объект отладки" на панели инструментов Visual Studio.

Добавьте класс модели для представления элементов todo. Пометьте обязательные поля с помощью атрибута [Required]:

using System.ComponentModel.DataAnnotations;

namespace TodoAPI.Models
{
    public class TodoItem
    {
        [Required]
        public string ID { get; set; }

        [Required]
        public string Name { get; set; }

        [Required]
        public string Notes { get; set; }

        public bool Done { get; set; }
    }
}

Методы API требуют определения для работы с данными. Используйте тот же ITodoRepository интерфейс, который использует пример:

using TodoAPI.Models;

namespace TodoAPI.Interfaces
{
    public interface ITodoRepository
    {
        bool DoesItemExist(string id);
        IEnumerable<TodoItem> All { get; }
        TodoItem Find(string id);
        void Insert(TodoItem item);
        void Update(TodoItem item);
        void Delete(string id);
    }
}

В этом примере реализация репозитория просто использует частную коллекцию элементов:

using TodoAPI.Interfaces;
using TodoAPI.Models;

namespace TodoAPI.Services
{
    public class TodoRepository : ITodoRepository
    {
        private List<TodoItem> _todoList;

        public TodoRepository()
        {
            InitializeData();
        }

        public IEnumerable<TodoItem> All
        {
            get { return _todoList; }
        }

        public bool DoesItemExist(string id)
        {
            return _todoList.Any(item => item.ID == id);
        }

        public TodoItem Find(string id)
        {
            return _todoList.FirstOrDefault(item => item.ID == id);
        }

        public void Insert(TodoItem item)
        {
            _todoList.Add(item);
        }

        public void Update(TodoItem item)
        {
            var todoItem = this.Find(item.ID);
            var index = _todoList.IndexOf(todoItem);
            _todoList.RemoveAt(index);
            _todoList.Insert(index, item);
        }

        public void Delete(string id)
        {
            _todoList.Remove(this.Find(id));
        }

        private void InitializeData()
        {
            _todoList = new List<TodoItem>();

            var todoItem1 = new TodoItem
            {
                ID = "6bb8a868-dba1-4f1a-93b7-24ebce87e243",
                Name = "Learn app development",
                Notes = "Take Microsoft Learn Courses",
                Done = true
            };

            var todoItem2 = new TodoItem
            {
                ID = "b94afb54-a1cb-4313-8af3-b7511551b33b",
                Name = "Develop apps",
                Notes = "Use Visual Studio and Visual Studio Code",
                Done = false
            };

            var todoItem3 = new TodoItem
            {
                ID = "ecfa6f80-3671-4911-aabe-63cc442c1ecf",
                Name = "Publish apps",
                Notes = "All app stores",
                Done = false,
            };

            _todoList.Add(todoItem1);
            _todoList.Add(todoItem2);
            _todoList.Add(todoItem3);
        }
    }
}

Настройка реализации в Program.cs:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddSingleton<TodoAPI.Interfaces.ITodoRepository, TodoAPI.Services.TodoRepository>();
builder.Services.AddControllers();

var app = builder.Build();

// Configure the HTTP request pipeline.

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Создание контроллера

Добавьте новый контроллер в проект TodoItemsController. Он должен наследоваться от ControllerBase. Добавьте атрибут, указывающий Route , что контроллер обрабатывает запросы, сделанные к путям, начиная с api/todoitems. Токен [controller] в маршруте заменяется на имя контроллера (суффикс Controller опускается) и особенно удобен для глобальных маршрутов. Дополнительные сведения о маршрутизации.

Для работы контроллеру нужен ITodoRepository; запросите экземпляр этого типа через конструктор контроллера. Во время выполнения этот экземпляр предоставляется с помощью поддержки платформы внедрения зависимостей.

[ApiController]
[Route("api/[controller]")]
public class TodoItemsController : ControllerBase
{
    private readonly ITodoRepository _todoRepository;

    public TodoItemsController(ITodoRepository todoRepository)
    {
        _todoRepository = todoRepository;
    }

Этот API поддерживает четыре различных HTTP-команды для выполнения операций CRUD (создание, чтение, обновление, удаление) с источником данных. Самым простым из них является операция чтения, которая соответствует HTTP-запросу GET .

Тестирование API с помощью curl

Вы можете протестировать метод API с помощью различных средств. В этом руководстве используются следующие открытый код средства командной строки:

  • curl: передает данные с помощью различных протоколов, включая HTTP и HTTPS. Curl используется в этом руководстве для вызова API с помощью методов GETHTTP , POSTи PUTDELETE.
  • jq: обработчик JSON, используемый в этом руководстве для форматирования данных JSON, чтобы легко считывать ответ API.

Установка curl и jq

curl предварительно установлен в macOS и используется непосредственно в приложении терминала macOS. Дополнительные сведения об установке curl см . на официальном веб-сайте curl.

jq можно установить из Homebrew из терминала:

Установите Homebrew, если он еще не установлен, с помощью следующей команды:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Следуйте инструкциям, представленным установщиком.

Установите jq с помощью Homebrew с помощью следующей команды:

brew install jq

Дополнительные сведения об установке Homebrew и jq см. в разделе Homebrew и jq.

Чтение элементов

Запрос списка элементов, выполняется с помощью отправки запроса GET в метод List. Атрибут[HttpGet] в методе List указывает, что это действие должно обрабатывать только запросы GET. Маршрут для этого действия соответствует маршруту, указанному на контроллере. Использовать имя действия в составе маршрута необязательно. Нужно лишь убедиться, что каждое действие имеет уникальный и однозначный маршрут. Атрибуты маршрутизации можно применять на уровне как контроллера, так и метода для создания определенных маршрутов.

[HttpGet]
public IActionResult List()
{
    return Ok(_todoRepository.All);
}

В терминале вызовите следующую команду curl:

curl -v -X GET 'https://localhost:5001/api/todoitems/' | jq

Предыдущая команда curl включает следующие компоненты:

  • -v: активирует подробный режим, предоставляя подробные сведения о ответе HTTP и полезно для тестирования и устранения неполадок API.
  • -X GET: указывает использование метода HTTP GET для запроса. Хотя curl часто может выводить предполагаемый метод HTTP, этот параметр делает его явным.
  • 'https://localhost:5001/api/todoitems/': это целевой URL-адрес запроса. В этом экземпляре это конечная REST точка API.
  • | jq: этот сегмент не связан с curl напрямую. | Канал — это оператор оболочки, который принимает выходные данные команды слева и "каналы" в команду справа. jq — это обработчик JSON командной строки. Хотя и не требуется, jq возвращаемые данные JSON проще считывать.

Метод List возвращает код отклика 200 OK и все элементы Todo, сериализованные в формате JSON:

[
  {
    "id": "6bb8a868-dba1-4f1a-93b7-24ebce87e243",
    "name": "Learn app development",
    "notes": "Take Microsoft Learn Courses",
    "done": true
  },
  {
    "id": "b94afb54-a1cb-4313-8af3-b7511551b33b",
    "name": "Develop apps",
    "notes": "Use Visual Studio and Visual Studio Code",
    "done": false
  },
  {
    "id": "ecfa6f80-3671-4911-aabe-63cc442c1ecf",
    "name": "Publish apps",
    "notes": "All app stores",
    "done": false
  }
]

Создание элементов

По соглашению создание новых элементов данных сопоставляется с http-командой POST . Метод Create имеет примененный к нему атрибут [HttpPost] и принимает экземпляр TodoItem. Так как аргумент item передается в текст POST, этот параметр указывает атрибут [FromBody].

Внутри метода элемент проверяется на допустимость и предшествующее существование в хранилище данных, а при отсутствии проблем он добавляется с помощью репозитория. Проверка ModelState.IsValid приводит к проверке модели и должна выполняться в каждом методе API, принимающем вводимые пользователем данные.

[HttpPost]
public IActionResult Create([FromBody]TodoItem item)
{
    try
    {
        if (item == null || !ModelState.IsValid)
        {
            return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
        }
        bool itemExists = _todoRepository.DoesItemExist(item.ID);
        if (itemExists)
        {
            return StatusCode(StatusCodes.Status409Conflict, ErrorCode.TodoItemIDInUse.ToString());
        }
        _todoRepository.Insert(item);
    }
    catch (Exception)
    {
        return BadRequest(ErrorCode.CouldNotCreateItem.ToString());
    }
    return Ok(item);
}

В примере используется enum содержащий коды ошибок, передаваемые мобильному клиенту:

public enum ErrorCode
{
    TodoItemNameAndNotesRequired,
    TodoItemIDInUse,
    RecordNotFound,
    CouldNotCreateItem,
    CouldNotUpdateItem,
    CouldNotDeleteItem
}

В терминале проверьте добавление новых элементов, вызвав следующую команду curl с помощью POST команды и предоставив новый объект в формате JSON в тексте запроса.

curl -v -X POST 'https://localhost:5001/api/todoitems/' \
--header 'Content-Type: application/json' \
--data '{
  "id": "6bb8b868-dba1-4f1a-93b7-24ebce87e243",
  "name": "A Test Item",
  "notes": "asdf",
  "done": false
}' | jq

Предыдущая команда curl включает следующие параметры:

  • --header 'Content-Type: application/json': задает Content-Type заголовок в значение application/json, указывающее, что текст запроса содержит данные JSON.
  • --data '{...}': отправляет указанные данные в тексте запроса.

Метод возвращает вновь созданный элемент в отклике.

Обновление элементов

Изменение записей достигается с помощью HTTP-PUT запросов. За исключением этого изменения, метод Edit практически идентичен Create. Если запись не найдена, Edit действие возвращает NotFound ответ (404).

[HttpPut]
public IActionResult Edit([FromBody] TodoItem item)
{
    try
    {
        if (item == null || !ModelState.IsValid)
        {
            return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
        }
        var existingItem = _todoRepository.Find(item.ID);
        if (existingItem == null)
        {
            return NotFound(ErrorCode.RecordNotFound.ToString());
        }
        _todoRepository.Update(item);
    }
    catch (Exception)
    {
        return BadRequest(ErrorCode.CouldNotUpdateItem.ToString());
    }
    return NoContent();
}

Чтобы проверить с помощью curl, измените команду PUTна . Укажите обновленные данные объекта в тексте запроса.

curl -v -X PUT 'https://localhost:5001/api/todoitems/' \
--header 'Content-Type: application/json' \
--data '{
  "id": "6bb8b868-dba1-4f1a-93b7-24ebce87e243",
  "name": "A Test Item",
  "notes": "asdf",
  "done": true
}' | jq

Этот метод возвращает отклик NoContent (204) при успешном выполнении, чтобы обеспечить согласованность с ранее существовавшими API.

Удаление элементов

Удаление записей выполняется путем выполнения DELETE запросов к службе и передачи идентификатора элемента, который требуется удалить. Как и в случае с обновлениями, запросы на элементы, которые не существуют, получают NotFound ответы. В противном случае успешный NoContent запрос возвращает ответ (204).

[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
    try
    {
        var item = _todoRepository.Find(id);
        if (item == null)
        {
            return NotFound(ErrorCode.RecordNotFound.ToString());
        }
        _todoRepository.Delete(id);
    }
    catch (Exception)
    {
        return BadRequest(ErrorCode.CouldNotDeleteItem.ToString());
    }
    return NoContent();
}

Проверьте с помощью curl, изменив HTTP-команду DELETE на и добавив идентификатор объекта данных для удаления в конце URL-адреса. В тексте запроса ничего не требуется.

curl -v -X DELETE 'https://localhost:5001/api/todoitems/6bb8b868-dba1-4f1a-93b7-24ebce87e243'

Предотвращение избыточной публикации

В настоящее время пример приложения предоставляет весь объект TodoItem. Рабочие приложения обычно ограничивают вводимые данные и возвращают их с помощью подмножества модели. Это связано с несколькими причинами, и безопасность является основной. Подмножество модели обычно называется объектом передачи данных (DTO), моделью ввода или моделью представления. В этой статье используется DTO.

DTO можно использовать для следующего:

  • Предотвращение избыточной публикации.
  • Скрытие свойств, которые клиенты не должны просматривать.
  • Опустить некоторые свойства для уменьшения размера полезных данных.
  • Сведение графов объектов, содержащих вложенные объекты. Сведенные графы объектов могут быть удобнее для клиентов.

Чтобы продемонстрировать подход DTO, см. статью "Запрет чрезмерной публикации"

Общие соглашения веб-API

При разработке внутренних служб для вашего приложения вы хотите создать согласованный набор соглашений или политик для обработки перекрестных проблем. Например, в службе, показанной ранее, запросы на определенные записи, которые не были найдены, получили NotFound ответ, а не BadRequest ответ. Аналогичным образом, команды, выполненные для этой службы, которые передавались в привязанные типы модели, всегда проверяли ModelState.IsValid и возвращали BadRequest для недопустимых типов модели.

Определив общую политику для своих API, вы обычно можете инкапсулировать ее в фильтр. Дополнительные сведения о том, как инкапсулировать общие политики API в приложения ASP.NET Core MVC.

См. также