Создание первого Web API с помощью ASP.NET Core MVC и Visual Studio

Mike Wasson и Rick Anderson

HTTP предназначен не только для подачи веб страниц. Он также является мощной платформой для создания API, которые раскрывают сервисы и данные. HTTP прост, гибок и общедоступен. Фактически у любой платформы есть HTTP библиотека, так что HTTP сервисы могут достичь широкого круга клиентов, включая браузеры, мобильные устройства и традиционные десктоп приложения.

В этом руководстве мы создадим просто веб API для управления списком элементов “to-do”. Никакого UI здесь мы создавать не будем.

В ASP.NET Core есть встроенная поддержка MVC Web API. Унификация двух фреймворков упрощает создание приложения, которые включают в себя и UI (HTML), и API, поскольку они сейчас делят между собой и код, и поток.

Примечание

Если вы портируете существующее Web API приложение в ASP.NET Core, см. Migrating from ASP.NET Web API

Обзор

Вот API, который мы создадим:

API Описание Тело запроса Тело ответа
GET /api/todo Получает все элементы to-do Нету Массив элементов to-do
GET /api/todo/{id} Получает элемент по ID Нету Элемент To-do
POST /api/todo Добавляет новый элемент Элемент To-do Элемент To-do
PUT /api/todo/{id} Обновляет существующий элемент Элемент To-do Нету
PATCH /api/todo/{id} Обновляет существующий элемент Элемент To-do Нету
DELETE /api/todo/{id} Удаляет элемент Нету Нету

На следующей диаграмме показан базовый дизайн приложения.

../_images/architecture.png
  • Клиентом является что-то, что потребляет API (браузер, мобильное приложение и тд). В этом руководстве мы не будем писать клиент. Для тестирования приложения мы будем использовать Postman.
  • Модель - это объект, который представляет в приложении данные. В данном случае единственной моделью является элемент to-do. Модели представлены как простые C# классы (POCO).
  • Контроллер - это объект, который обрабатывает HTTP запросы и создает HTTP ответы. В этом приложении будет один контроллер.
  • Для упрощения приложения мы не будем использовать БД. Элементы to-do будут храниться в памяти. Но мы включим слой доступа к данным, чтобы показать разделение между веб API и слоем данных. Если вы хотите использовать БД, просмотрите Создание первого ASP.NET Core MVC приложения с помощью Visual Studio.

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

Запустите Visual Studio. Из меню File выберите New > Project.

Выберите шаблон ASP.NET Core Web Application (.NET Core). Назовите проект TodoApi, очистите Host in the cloud и нажмите OK.

../_images/new-project3.png

В диалоговом окне New ASP.NET Core Web Application (.NET Core) - TodoApi выберите шаблон Web API. Нажмите OK.

../_images/web-api-project.png

Добавление класса модели

Модель - это объект, который представляет в приложении данные. В данном случае модель - это элемент to-do.

Добавьте папку “Models”. В Solution Explorer кликните правой клавишей мышки по проекту. Выберите Add > New Folder. Назовите папку Models.

../_images/add-folder.png

Примечание

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

Добавьте класс TodoItem. Кликните правой клавишей мышки по папке Models и выберите Add > Class. Назовите класс TodoItem и нажмите Add.

Замените сгенерированный код следующим:

namespace TodoApi.Models
{
    public class TodoItem
    {
        public string Key { get; set; }
        public string Name { get; set; }
        public bool IsComplete { get; set; }
    }
}

Добавления класса репозитория

Репозиторий - это объект, который инкапсулирует слой данных и содержит логику для получения данных и приведения их к модели. Хотя мы не используем БД, полезно посмотреть, как внедрять репозиторий в контроллеры. Создайте код для репозитория в папке Models.

Определите интерфейс репозитория с именем ITodoRepository. Используйте шаблон (Add New Item > Class).

using System.Collections.Generic;

namespace TodoApi.Models
{
    public interface ITodoRepository
    {
        void Add(TodoItem item);
        IEnumerable<TodoItem> GetAll();
        TodoItem Find(string key);
        TodoItem Remove(string key);
        void Update(TodoItem item);
    }
}

Этот интерфейс определяет базовые операции CRUDы.

Далее, добавьте класс TodoRepository, который реализует ITodoRepository:

using System;
using System.Collections.Generic;
using System.Collections.Concurrent;

namespace TodoApi.Models
{
    public class TodoRepository : ITodoRepository
    {
        private static ConcurrentDictionary<string, TodoItem> _todos =
              new ConcurrentDictionary<string, TodoItem>();

        public TodoRepository()
        {
            Add(new TodoItem { Name = "Item1" });
        }

        public IEnumerable<TodoItem> GetAll()
        {
            return _todos.Values;
        }

        public void Add(TodoItem item)
        {
            item.Key = Guid.NewGuid().ToString();
            _todos[item.Key] = item;
        }

        public TodoItem Find(string key)
        {
            TodoItem item;
            _todos.TryGetValue(key, out item);
            return item;
        }

        public TodoItem Remove(string key)
        {
            TodoItem item;
            _todos.TryRemove(key, out item);
            return item;
        }

        public void Update(TodoItem item)
        {
            _todos[item.Key] = item;
        }
    }
}

Соберите приложение, чтобы проверить, что ошибок компиляции нет.

Регистрация репозитория

При определении интерфейса репозитория мы можем отделить класс репозитория от MVC контроллера, который его использует. Вместо создания экземпляра TodoRepository внутри контроллера мы внедрим ITodoRepository, используя встроенную поддержку ASP.NET Core для DI.

Такой подход упрощает модульное тестирование контроллеров. Юнит тесты используют “mock” или “stub” версию ITodoRepository. Тогда тесты нацелены на логику контроллера, а не на слой доступа к данным.

Чтобы внедрить репозиторий в контроллер, нам нужно зарегистрировать его с помощью DI контейнера. Откройте файл Startup.cs. Добавьте следующую директиву using:

using TodoApi.Models;

В метод ConfigureServices добавьте выделенный код:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();

    services.AddSingleton<ITodoRepository, TodoRepository>();
}

Добавление контроллера

В Solution Explorer кликните правой клавишей мышки по папке Controllers. Выберите Add > New Item. В диалоговом окне Add New Item выберите шаблон Web API Controller Class. Назовите класс TodoController.

Замените сгенерированный код следующим:

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using TodoApi.Models;

namespace TodoApi.Controllers
{
    [Route("api/[controller]")]
    public class TodoController : Controller
    {
        public TodoController(ITodoRepository todoItems)
        {
            TodoItems = todoItems;
        }
        public ITodoRepository TodoItems { get; set; }
    }
}

Это определит класс контроллера. В следующем разделе мы добавим методы, чтобы реализовать API.

Получение элементов to-do

Чтобы получить элементы to-do, добавьте следующие методы в класс TodoController.

public IEnumerable<TodoItem> GetAll()
{
    return TodoItems.GetAll();
}

[HttpGet("{id}", Name = "GetTodo")]
public IActionResult GetById(string id)
{
    var item = TodoItems.Find(id);
    if (item == null)
    {
        return NotFound();
    }
    return new ObjectResult(item);
}

Эти методы реализуют два метода GET:

  • GET /api/todo
  • GET /api/todo/{id}

Вот пример HTTP ответа для метода GetAll:

HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Server: Microsoft-IIS/10.0 Date: Thu, 18 Jun 2015 20:51:10 GMT Content-Length: 82

[{“Key”:”4f67d7c5-a2a9-4aae-b030-16003dd829ae”,”Name”:”Item1”,”IsComplete”:false}]

Далее в руководстве я покажу, как просмотреть HTTP ответ с помощью инструмента Postman.

Роутинг и URL пути

Атрибут [HttpGet] (:dn:cls:`~Microsoft.AspNetCore.Mvc.HttpGetAttribute`) указывает, что это HTTP GET методы. URL путь для каждого метода построен следующим образом:

  • Возьмите строку шаблона из атрибута route контроллера, [Route("api/[controller]")]
  • Замените “[Controller]” именем контроллера, что является именем класса контроллера минус суффикс “Controller”. В данном примере именем класса контроллера является TodoController, а именем коревой директории - “todo”. ASP.NET MVC Core роутинг не чувствителен к регистру.
  • Если у атрибута [HttpGet] также есть строка шаблона, добавьте ее к пути. В данном примере не используется строка шаблона.

Для метода GetById “{id}” - это переменная-заменитель. В актуальном HTTP запросе клиент будет использовать ID элемента todo. В рантайме, когда MVC вызывает GetById, он присваивает значение “{id}” URL параметра метода id.

В методе GetById:

[HttpGet("{id}", Name = "GetTodo")]
public IActionResult GetById(string id)

"{id}" - это переменная-заменитель для ID элемента todo. Когда вызывается GetById, он присваивает значение “{id}” в URL параметру метода id.

Name = "GetTodo" создает именованный роут и позволяет вам ссылаться на этот роут в HTTP ответе. Позже я это поясню.

Возвращаемые значения

Метод GetAll возвращает IEnumerable. MVC автоматически сериализует объект в JSON и записывает JSON в тело ответа. Кодом ответа для этого метода является 200, если нет необработанных исключений. (Необработанные исключения переводятся в ошибки 5xx).

Метод GetById наоборот возвращает более общий тип IActionResult, который представляет широкий спектр возвращаемых типов. У GetById есть два возвращаемых типа:

  • Если ни один элемент не соответствует запрашиваемому ID, метод возвращает ошибку 404, NotFound.
  • Иначе метод возвращает 200 с телом ответа JSON путем возвращения :dn:cls:`~Microsoft.AspNetCore.Mvc.ObjectResult`.

Запуск приложения

В Visual Studio нажмите CTRL+F5, чтобы запустить приложение. Visual Studio запускает браузер и переходит по http://localhost:port/api/values, где port - это случайно выбранный номер порта. Если вы используете Chrome, Edge или Firefox, будут отображены данные. Если вы используете IE, IE предложит вам открыть или сохранить файл values.json.

Реализация других операций CRUD

Наконец, нам надо добавить в контроллер методы Create, Update и Delete. Я просто покажу вам код и выделю главные отличия. Соберите проект после добавления или изменения кода.

Create

[HttpPost]
public IActionResult Create([FromBody] TodoItem item)
{
    if (item == null)
    {
        return BadRequest();
    }
    TodoItems.Add(item);
    return CreatedAtRoute("GetTodo", new { id = item.Key }, item);
}

Это метод HTTP POST, указанный атрибутом [HttpPost]. Атрибут [FromBody] говорит MVC, чтобы он получил значение элемента to-do из тела HTTP запроса.

Метод CreatedAtRoute возвращает ответ 201, являющимся стандартным ответом для метода HTTP POST, который создает новый источник на сервере. CreateAtRoute также добавляет в ответ заголовок Location. Заголовок Location определяет URI нового элемента to-do. См. 10.2.2 201 Created.

Использование Postman для отправки Create запроса

../_images/pmc.png
  • Установите HTTP метод на POST
  • Нажмите кнопку Body
  • Нажмите кнопку raw
  • Установите тип на JSON
  • В редакторе “ключ-значение” введите элемент Todo, например {"Name":"<your to-do item>"}
  • Нажмите Send
  • Нажмите на вкладку Headers и скопируйте заголовок Location:
../_images/pmget.png

Вы можете использовать заголовка Location, чтобы получить доступ к ресурсу, который создали. Снова вызовите метод GetById, созданный именованным роутом "GetTodo":

[HttpGet("{id}", Name = "GetTodo")]
public IActionResult GetById(string id)

Update

[HttpPut("{id}")]
public IActionResult Update(string id, [FromBody] TodoItem item)
{
    if (item == null || item.Key != id)
    {
        return BadRequest();
    }

    var todo = TodoItems.Find(id);
    if (todo == null)
    {
        return NotFound();
    }

    TodoItems.Update(item);
    return new NoContentResult();
}

Update похож на Create, но он использует HTTP PUT. Ответом является 204 (No Content). В соответствии со HTTP спецификацией, запросу PUT требуется, чтобы клиент отправлял полностью обновленные данные, а не только дельты. Для частичных обновлений используется HTTP PATCH.

../_images/pmcput.png

Обновление с Patch

Этот вариант похож на Update, но здесь используется HTTP PATCH. Ответом является 204 (No Content).

[HttpPatch("{id}")]
public IActionResult Update([FromBody] TodoItem item, string id)
{
    if (item == null)
    {
        return BadRequest();
    }

    var todo = TodoItems.Find(id);
    if (todo == null)
    {
        return NotFound();
    }

    item.Key = todo.Key;

    TodoItems.Update(item);
    return new NoContentResult();
}
../_images/pmcpat.png

Delete

[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
    var todo = TodoItems.Find(id);
    if (todo == null)
    {
        return NotFound();
    }

    TodoItems.Remove(id);
    return new NoContentResult();
}

Ответом является 204 (No Content).

../_images/pmd.png
Поделись хорошей новостью с друзьями!
Следи за новостями!