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

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

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

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

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

В этом руководстве показано, как создавать внутренние службы с помощью ASP.NET Core для поддержки собственных мобильных приложений. Он использует приложение Xamarin.Forms TodoRest в качестве собственного клиента, которое включает отдельные собственные клиенты для Android, iOS и Windows. Вы можете следовать связанному руководству, чтобы создать собственное приложение (и установить необходимые бесплатные средства Xamarin) и скачать пример решения Xamarin. Пример Xamarin включает проект служб веб-API ASP.NET Core, который заменяет приложение ASP.NET Core этой статьи (без изменений, необходимых клиенту).

Rest Список дел приложение, работающее на смартфоне Android

Функции

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

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

Если выбрать значок +, открывается диалоговое окно добавления элемента:

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

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

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

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

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

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

using Xamarin.Essentials;
using Xamarin.Forms;

namespace TodoREST
{
    public static class Constants
    {
        // URL of REST service
        //public static string RestUrl = "https://YOURPROJECT.azurewebsites.net:8081/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 RestUrl = DeviceInfo.Platform == DevicePlatform.Android ? "http://10.0.2.2:5000/api/todoitems/{0}" : "http://localhost:5000/api/todoitems/{0}";
    }
}

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

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

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

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

Приложение должно отвечать на все запросы, сделанные на порт 5000, включая трафик HTTP для нашего мобильного клиента. Обновление Startup.cs так UseHttpsRedirection не выполняется в разработке:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        // For mobile apps, allow http traffic.
        app.UseHttpsRedirection();
    }

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

Примечание.

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

Добавьте класс модели для представления элементов задач. Пометьте обязательные поля с помощью атрибута [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, который использует исходный пример Xamarin:

using System.Collections.Generic;
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 System.Collections.Generic;
using System.Linq;
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 for Mac",
                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);
        }
    }
}

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

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<ITodoRepository, TodoRepository>();
    services.AddControllers();
}

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

Добавьте новый контроллер в проект 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 'http://localhost:5000/api/todoitems/' | jq

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

  • -v: активирует подробный режим, предоставляя подробные сведения о ответе HTTP и полезно для тестирования и устранения неполадок API.
  • -X GET: указывает использование метода HTTP GET для запроса. Хотя curl часто может выводить предполагаемый метод HTTP, этот параметр делает его явным.
  • 'http://localhost:5000/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 for Mac",
    "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 'http://localhost:5000/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 'http://localhost:5000/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 'http://localhost:5000/api/todoitems/6bb8b868-dba1-4f1a-93b7-24ebce87e243'

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

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

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

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

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

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

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

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

Дополнительные ресурсы