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





Второй цикл статей по ASP.NET Core


  1. Создание серверных служб для мобильных приложений.
  2. Разработка приложений ASP.NET Core с помощью dotnet watch.
  3. Создание справочных страниц веб-API ASP.NET с помощью Swagger.
  4. Открытый веб-интерфейс для .NET (OWIN).
  5. Выбор правильной среды разработки .NET на сервере.

Первый цикл статей можно найти здесь.


Образец мобильного приложения


Мобильные приложения могут без труда обмениваться данными с серверными службами ASP.NET Core. Здесь можно скачать образец кода серверных служб.


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





Функции


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


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


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




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




Этот образец по умолчанию использует сервисные службы, размещённые по адресу developer.xamarin.com, и позволяет выполнять только чтение. Чтобы самостоятельно протестировать его с приложением ASP.NET Core, которое будет создано в следующем разделе и будет работать на вашем компьютере, нужно обновить константу RestUrl. Перейдите к проекту ToDoREST и откройте файл Constants.cs. Замените RestUrl на IP-адрес вашего компьютера (это не должен быть localhost или 127.0.0.1, поскольку адрес используется из эмулятора устройства, а не из вашего ПК). Также добавьте номер порта (5000). Чтобы службы работали на устройстве, не забудьте выключить брандмауэр, блокирующий доступ к этому порту.


// URL of REST service (Xamarin ReadOnly Service)
//public static string RestUrl = "http://developer.xamarin.com:8081/api/todoitems{0}";

// use your machine's IP address
public static string RestUrl = "http://192.168.1.207:5000/api/todoitems/{0}";

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


Создайте новое веб-приложение ASP.NET Core в Visual Studio. Выберите шаблон веб-API и отключите аутентификацию. Присвойте проекту имя ToDoApi.




Приложение должно отвечать на все запросы к порту 5000. Добавьте в Program.cs .UseUrls("http://*:5000"), чтобы получить следующий результат:


var host = new WebHostBuilder()
    .UseKestrel()
    .UseUrls("http://*:5000")
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseIISIntegration()
    .UseStartup<Startup>()
    .Build();

Примечание: обязательно запустите приложение напрямую, а не через IIS Express, который по умолчанию игнорирует не локальные запросы. Выполните dotnet run из командной строки либо выберите название приложения из раскрывающегося меню Debug Target на панели инструментов Visual Studio.


Добавьте класс модели для представления элементов To-Do. Отметьте обязательные поля с помощью атрибута [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 = "Attend Xamarin University",
                Done = true
            };

            var todoItem2 = new ToDoItem
            {
                ID = "b94afb54-a1cb-4313-8af3-b7511551b33b",
                Name = "Develop apps",
                Notes = "Use Xamarin Studio/Visual Studio",
                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)
{
    // Add framework services.
    services.AddMvc();

    services.AddSingleton<IToDoRepository,ToDoRepository>();
}

Теперь можно перейти к созданию ToDoItemsController.


Подробнее о создании веб-API можно узнать в статьей «Создание первого веб-API с использованием ASP.NET Core MVC и Visual Studio».


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


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


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


using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using ToDoApi.Interfaces;
using ToDoApi.Models;

namespace ToDoApi.Controllers
{
    [Route("api/[controller]")]
    public class ToDoItemsController : Controller
    {
        private readonly IToDoRepository _toDoRepository;

        public ToDoItemsController(IToDoRepository toDoRepository)
        {
            _toDoRepository = toDoRepository;
        }

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


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


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


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

Метод List выдает код ответа 200 OK и список всех элементов ToDo, сериализованных как JSON.


Вы можете протестировать новый метод API с помощью ряда инструментов, например Postman, как показано ниже:



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


По соглашению, создание новых элементов данных сопоставляется с командой HTTP POST. К методу Create применен атрибут [HttpPost]; кроме того, метод принимает параметр ID и экземпляр ToDoItem. Такие командные атрибуты, как [HttpPost], могут принять строку маршрута (в этом примере{id}). Результат будет таким же, что и при добавлении атрибута [Route] к действию. Поскольку аргумент item будет передан в теле POST, этот параметр украшается атрибутом [FromBody].


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


[HttpPost("{id}")]
public IActionResult Create(string id, [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);
}

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


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

Чтобы проверить добавление новых элементов, используйте Postman: выберите команду POST, которая предоставляет новый объект в формате JSON в теле запроса. Также добавьте заголовок запроса, который указывает Content-Type для application/json.




В ответе метод выдает только что созданный элемент.


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


Изменять записи можно с помощью запросов HTTP PUT. Кроме того, метод Edit практически идентичен Create. Помните, что если запись не будет найдена, действие Edit выдаст ответ NotFound (404).


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

Чтобы протестировать Postman, измените команду на PUT и добавьте ID обновляемой записи в URL. Укажите данные обновляемого объекта в теле запроса.




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


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


Для удаления записей нужно сделать запросы DELETE к сервису и передать ID удаляемого элемента. После этих обновлений запросы для несуществующих элементов получат ответы 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();
}

Помните, что при тестировании функции удаления в тело запроса добавлять ничего не нужно.





Распространенные соглашения веб-API


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


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

Поделиться с друзьями
-->

Комментарии (15)


  1. Diaskhan
    16.01.2017 12:20

    Лучше бы сделали достойный ORM.
    EF хоть и хорош но почему все жалуются на его производительность ??
    Выкинули бы Команду которая EF создавала, и поглотили бы http://www.llblgen.com/


    Как до это сделали с Xamarin и Datazen !!


    Что EF6 что EF7 до сих пор тормознуто.
    http://ppanyukov.github.io/2015/05/20/entity-framework-7-performance.html


    Да и почему то на https://www.techempower.com до сих пор нету тестов под Windows платформу !


    Весь стек Asp net core должен быть производительным а не только отдача простого TXT-json файла.


    1. ZOXEXIVO
      16.01.2017 14:19

      Почему LLBLGen это хорошо?


    1. justserega
      16.01.2017 14:20

      Непонятно за что минусуют, все по делу. Добавлю от себя: непонятно зачем web-фреймворку orm с трэкингом состояний… это же stateless среда. От этого никакой пользы нет, только оверхэд.


      1. TheRealDoge
        16.01.2017 22:09

        Если не ошибаюсь, трэкинг в EF можно выключить.


        1. justserega
          16.01.2017 22:19

          Можно, если ничего в базу писать не будете. Лишние заморочки...


    1. atri24
      16.01.2017 14:21
      +1

      Вместо даппера посмотрите на linq2db


      1. justserega
        16.01.2017 14:23

        Отличная библиотека! От ее полноценного использования останавливает только наличие миграций в EF. Не знаете есть ли что-то приличное для миграций с linq2db?


        1. atri24
          16.01.2017 14:36

          Доводилось работать с FluentMigrator несколько лет назад. Но миграция в EF всё-таки лучше сделана :)
          Теоретически ничего не мешает делать миграцию с помощью EF, а потом работать с данными с помощью linq2db.


          1. dima117
            16.01.2017 17:29

            Написано, что для FluentMigrator нужен Ruby о_О


            1. atri24
              17.01.2017 06:24

              Раньше не требовалось, сейчас не знаю. Он в nuget есть.
              FluentMigrator


        1. dima117
          16.01.2017 17:28
          +2

          Пару лет назад я участвовал в разработке ecm7migrator (форк Migrator.NET, постепенно полностью переписанный). Он имеет простой API, не завязан на ORM и покрыт тестами. Использовал мигратор в нескольких проектах с NHibernate и очень доволен. В принципе, остальные, кому рекомендовал — тоже довольны.

          Сейчас делаю большой проект на .NET Core. Там использую EF, т.к. особого выбора нет. Пробовал его миграции, но не подошли, т.к. неудобно писать руками + они не умеют вести параллельно несколько «линий» версионирования (в моем проекте нужно, чтобы плагины могли создавать себе нужную структуру БД и для каждого плагина отдельно велся учет версий).

          В результате портировал на .NET Core ядро ecm7migrator и модуль, поддерживающий PostgreSQL. Всё завелось легко и тесты прошли без проблем.

          Посмотрите его, возможно, вам покажется удобнее остального. Я готов оказать помочь в использовании и в портировании на .NET Core модулей для поддержки других СУБД.


    1. egorist
      16.01.2017 14:22
      +1

      Можно использовать Dapper и MicroOrm.Dapper.Repositories.
      Значительный прирост производительности. Конечно придется писать больше кода, чтобы все это обернуть.
      Использую EF в производительных проектах исключительно для миграций.


    1. mrigi
      16.01.2017 15:36

      Все равно в конечном итоге приходишь к логике на стороне sql (stored procedures, views, etc), как ни крути. Гонять промежуточные выборки в приложение достаточно быстро перестает быть вариантом. А это значит, что от интерфейса с БД требуются примитивные возможности. А EF с откюченными трекингом, прокси и остальной фигней работает вполне себе быстро. С другой стороны, а требуется ли вообще тянуть этого тяжеловесного монстра для простых действий? Это уже дело личного выбора.


  1. pilman
    16.01.2017 15:36

    При реализации Delete бросать NotFound не правильно. По RESTfull принципам этот тип запроса должен быть идемпотентным.


  1. worldxaker
    16.01.2017 22:09

    хотелось бы увидеть статью по создание авторизации для web API