Учебник. Создание веб-API с помощью ASP.NET Core

Авторы: Рик Андерсон (Rick Anderson), Кирк Ларкин (Kirk Larkin) и Майк Уоссон (Mike Wasson)

В этом учебнике приводятся основные сведения о создании веб-API с помощью ASP.NET Core.

В этом руководстве вы узнаете, как:

  • Создание проекта веб-API.
  • Добавление класса модели и контекста базы данных.
  • Формирование шаблонов контроллера с использованием методов CRUD.
  • Настройка маршрутизации, URL-пути и возвращаемых значений.
  • Вызов веб-API с помощью Postman.

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

Обзор

В этом руководстве создается следующий API-интерфейс:

API Описание Текст запроса Текст ответа
GET /api/TodoItems Получение всех элементов задач Отсутствуют Массив элементов задач
GET /api/TodoItems/{id} Получение объекта по идентификатору Отсутствуют Элемент задачи
POST /api/TodoItems Добавление нового элемента Элемент задачи Элемент задачи
PUT /api/TodoItems/{id} Обновление существующего элемента   Элемент задачи Отсутствуют
DELETE /api/TodoItems/{id}     Удаление элемента     Отсутствуют Отсутствуют

На следующем рисунке показана структура приложения.

Клиент представлен прямоугольником слева. Он отправляет запрос и получает ответ от приложения (прямоугольник справа). В прямоугольнике приложения также расположены три прямоугольника, представляющих контроллер, модель и уровень доступа к данным. Запрос поступает на контроллер приложения, который выполняет операции чтения и записи между контроллером и уровнем доступа к данным. Модель сериализуется и возвращается клиенту в ответе.

Предварительные требования

Создайте веб-проект.

  • В меню Файл выберите пункт Создать > Проект.
  • Выберите шаблон Веб-API ASP.NET Core и нажмите кнопку Далее.
  • Назовите проект TodoApi и нажмите Создать.
  • Убедитесь, что в диалоговом окне Создание веб-приложения ASP.NET Core выбраны платформы .NET Core и ASP.NET Core 5.0. Выберите шаблон API и нажмите кнопку Создать.

Диалоговое окно создания проекта VS

Тестирование проекта

Шаблон проекта создает API WeatherForecast с поддержкой Swagger.

Нажмите клавиши CTRL+F5, чтобы выполнить запуск без отладчика.

Visual Studio отображает следующее диалоговое окно.

Этот проект настроен для использования SSL. Чтобы избежать предупреждений о SSL в браузере, вы можете сделать самозаверяющий сертификат, созданный IIS Express, доверенным. Вы хотите сделать SSL-сертификат IIS Express доверенным?

Выберите Да, чтобы сделать SSL-сертификат IIS Express доверенным.

Отобразится следующее диалоговое окно.

Диалоговое окно "Предупреждение о безопасности"

Выберите Да, если согласны доверять сертификату разработки.

Сведения о доверии к браузеру Firefox см. в разделе Ошибка сертификата браузера Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.

Visual Studio запустит:

  • веб-сервер IIS Express;
  • браузер по умолчанию и перейдет к https://localhost:<port>/swagger/index.html, где <port> — это номер порта, выбранный случайным образом.

Откроется страница Swagger /swagger/index.html. Выберите Get (Получить) > Try it out (Попробовать) > Execute (Выполнить). На странице отобразятся:

  • команда Curl для тестирования API WeatherForecast;
  • URL-адрес для тестирования API WeatherForecast;
  • код, текст и заголовки ответа;
  • раскрывающийся список с типами мультимедиа и примером значения и схемы.

Если страница Swagger не отображается, см. эту проблему на сайте GitHub.

Swagger используется для создания полезной документации и страниц справки для веб-API. В этом учебнике рассматривается создание веб-API. Дополнительные сведения о Swagger: Документация по веб-API ASP.NET Core с использованием Swagger (OpenAPI).

Скопируйте и вставьте URL-адрес запроса в адресную строку браузера: https://localhost:<port>/WeatherForecast

Возвращаемые данные JSON будут выглядеть примерно так:

[
    {
        "date": "2019-07-16T19:04:05.7257911-06:00",
        "temperatureC": 52,
        "temperatureF": 125,
        "summary": "Mild"
    },
    {
        "date": "2019-07-17T19:04:05.7258461-06:00",
        "temperatureC": 36,
        "temperatureF": 96,
        "summary": "Warm"
    },
    {
        "date": "2019-07-18T19:04:05.7258467-06:00",
        "temperatureC": 39,
        "temperatureF": 102,
        "summary": "Cool"
    },
    {
        "date": "2019-07-19T19:04:05.7258471-06:00",
        "temperatureC": 10,
        "temperatureF": 49,
        "summary": "Bracing"
    },
    {
        "date": "2019-07-20T19:04:05.7258474-06:00",
        "temperatureC": -1,
        "temperatureF": 31,
        "summary": "Chilly"
    }
]

Обновление launchUrl

В файле Properties/launchSettings.json обновите launchUrl с "swagger" на "api/TodoItems".

"launchUrl": "api/TodoItems",

Поскольку Swagger удален, предыдущая разметка изменит URL-адрес, который запускается в методе GET контроллера, добавленного в следующих разделах.

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

Модель — это набор классов, представляющих данные, которыми управляет приложение. Модель этого приложения содержит единственный класс TodoItem.

  • В обозревателе решений щелкните проект правой кнопкой мыши. Выберите Добавить > Новая папка. Назовите папку Models .

  • Щелкните папку Models правой кнопкой мыши и выберите пункты Добавить > Класс. Присвойте классу имя TodoItem и выберите Добавить.

  • Замените код шаблона следующим кодом:

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

Свойство Id выступает в качестве уникального ключа реляционной базы данных.

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

Добавление контекста базы данных

Контекст базы данных —это основной класс, который координирует функциональные возможности Entity Framework для модели данных. Этот класс является производным от класса Microsoft.EntityFrameworkCore.DbContext.

Добавление пакетов NuGet

  • В меню Сервис выберите Диспетчер пакетов NuGet > Управление пакетами NuGet для решения.
  • Перейдите на вкладку Обзор и введите Microsoft.EntityFrameworkCore.InMemory в поле поиска.
  • В области слева щелкните Microsoft.EntityFrameworkCore.InMemory.
  • Установите флажок Проект на правой панели и выберите Установить.

Диспетчер пакетов NuGet

Добавление контекста базы данных TodoContext

  • Щелкните папку Models правой кнопкой мыши и выберите пункты Добавить > Класс. Назовите класс TodoContext и нажмите Добавить.
  • Введите следующий код:

    using Microsoft.EntityFrameworkCore;
    
    namespace TodoApi.Models
    {
        public class TodoContext : DbContext
        {
            public TodoContext(DbContextOptions<TodoContext> options)
                : base(options)
            {
            }
    
            public DbSet<TodoItem> TodoItems { get; set; }
        }
    }
    

Регистрация контекста базы данных

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

Обновите файл Startup.cs, используя следующий код:

// Unused usings removed
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<TodoContext>(opt =>
                                               opt.UseInMemoryDatabase("TodoList"));
            services.AddControllers();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();
            app.UseRouting();

            app.UseAuthorization();

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

Предыдущий код:

  • Удаляет вызовы Swagger.
  • Удалите неиспользуемые объявления using.
  • Добавляет контекст базы данных в контейнер внедрения зависимостей.
  • Указывает, что контекст базы данных будет использовать базу данных в памяти.

Формирование шаблонов контроллера

  • Щелкните папку Controllers правой кнопкой мыши.

  • Щелкните Добавить > Создать шаблонный элемент.

  • Выберите Контроллер API с действиями, использующий Entity Framework, а затем выберите Добавить.

  • В диалоговом окне Контроллер API с действиями, использующий Entity Framework сделайте следующее:

    • Выберите TodoItem (TodoApi.Models) в поле Класс модели.
    • Выберите TodoContext (TodoApi.Models) в поле Класс контекста данных.
    • Нажмите Добавить.

Сформированный код:

  • Пометьте этот класс атрибутом [ApiController]. Этот атрибут указывает, что контроллер отвечает на запросы веб-API. Дополнительные сведения о поведении, которое реализует этот атрибут, см. в Создание веб-API с помощью ASP.NET Core.
  • Использует внедрение зависимостей для внедрения контекста базы данных (TodoContext) в контроллер. Контекст базы данных используется в каждом методе создания, чтения, обновления и удаления в контроллере.

Шаблоны ASP.NET Core для:

  • Контроллеры с представлениями включают [action] в шаблоне маршрута.
  • Контроллеры API не включают [action] в шаблоне маршрута.

Если токен [action] не находится в шаблоне маршрута, имя действия исключается из маршрута. То есть имя связанного метода действия не используется в соответствующем маршруте.

Обновление метода создания PostTodoItem

Измените инструкцию возврата в PostTodoItem и используйте оператор nameof:

// POST: api/TodoItems
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    //return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem);
}

Предыдущий код является методом HTTP POST, обозначенным атрибутом [HttpPost]. Этот метод получает значение элемента списка дел из текста HTTP-запроса.

Дополнительные сведения см. в разделе Маршрутизация атрибутов с помощью атрибутов Http[Verb].

Метод CreatedAtAction:

  • В случае успеха возвращает код состояния HTTP 201. HTTP 201 представляет собой стандартный ответ для метода HTTP POST, создающий ресурс на сервере.
  • Добавляет в ответ заголовок Location. Заголовок Location указывает URI новой созданной задачи. Дополнительные сведения см. в статье 10.2.2 201 "Создан ресурс".
  • Указывает действие GetTodoItem для создания URI заголовка Location. Ключевое слово nameof C# используется для предотвращения жесткого программирования имени действия в вызове CreatedAtAction.

Установка Postman

В этом учебнике для тестирования веб-API используется Postman.

  • Установка Postman
  • Запустите веб-приложение.
  • Запустите Postman.
  • Отключение параметра Проверка SSL-сертификата
    • В меню Файл > Параметры (вкладка Общие), отключите параметр Проверка SSL-сертификата.

      Предупреждение

      После тестирования контроллера снова включите проверку SSL-сертификата.

Тестирование PostTodoItem с использованием Postman

  • Создайте новый запрос.

  • Установите HTTP-метод POST.

  • Задайте для URI значение https://localhost:<port>/api/TodoItems. Например, https://localhost:5001/api/TodoItems.

  • Откройте вкладку Тело.

  • Установите переключатель без обработки.

  • Задайте тип JSON (приложение/json) .

  • В теле запроса введите код JSON для элемента списка дел:

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • Нажмите кнопку Отправить.

    Postman с запросом Create

Тестирование URI заголовка расположения

URI заголовка расположения можно протестировать в браузере. Скопируйте и вставьте URI заголовка расположения в строку браузера.

Чтобы протестировать в Postman, выполните следующие действия.

  • Перейдите на вкладку Заголовки в области Ответ.

  • Скопируйте значение заголовка Расположение:

    Вкладка "Заголовки" в консоли Postman

  • Установите HTTP-метод GET.

  • Задайте для URI значение https://localhost:<port>/api/TodoItems/1. Например, https://localhost:5001/api/TodoItems/1.

  • Нажмите кнопку Отправить.

Знакомство с методами GET

Реализуются две конечные точки GET:

  • GET /api/TodoItems
  • GET /api/TodoItems/{id}

Протестируйте приложение, вызвав эти две конечные точки в браузере или в Postman. Пример:

  • https://localhost:5001/api/TodoItems
  • https://localhost:5001/api/TodoItems/1

При вызове GetTodoItems возвращается примерно такой ответ:

[
  {
    "id": 1,
    "name": "Item1",
    "isComplete": false
  }
]

Тестирование Get с использованием Postman

  • Создайте новый запрос.
  • Укажите метод HTTP GET.
  • Задайте для URI запроса значение https://localhost:<port>/api/TodoItems. Например, https://localhost:5001/api/TodoItems.
  • Выберите режим Представление с двумя областями в Postman.
  • Нажмите кнопку Отправить.

Это приложение использует выполняющуюся в памяти базу данных. Если остановить и вновь запустить его, предшествующий запрос GET не возвратит никаких данных. Если данные не возвращаются, данные для приложения получаются методом POST.

Маршрутизация и пути URL

Атрибут [HttpGet] обозначает метод, который отвечает на запрос HTTP GET. Путь URL для каждого метода формируется следующим образом:

  • Возьмите строку шаблона в атрибуте Route контроллера:

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    {
        private readonly TodoContext _context;
    
        public TodoItemsController(TodoContext context)
        {
            _context = context;
        }
    
  • Замените [controller] именем контроллера (по соглашению это имя класса контроллера без суффикса "Controller"). В этом примере класс контроллера имеет имя TodoItems, а сам контроллер, соответственно, — "TodoItems". В ASP.NET Core маршрутизация реализуется без учета регистра символов.

  • Если атрибут [HttpGet] имеет шаблон маршрута (например, [HttpGet("products")]), добавьте его к пути. В этом примере шаблон не используется. Дополнительные сведения см. в разделе Маршрутизация атрибутов с помощью атрибутов Http[Verb].

В следующем методе GetTodoItem``"{id}" — это переменная-заполнитель для уникального идентификатора элемента задачи. При вызове GetTodoItem параметру метода id присваивается значение "{id}" в URL-адресе.

// GET: api/TodoItems/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return todoItem;
}

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

Возвращаемый тип методов GetTodoItems и GetTodoItem — ActionResult<T>. ASP.NET Core автоматически сериализует объект в формат JSON и записывает данные JSON в тело сообщения ответа. Код ответа для этого типа возвращаемого значения равен 200 OK, что свидетельствует об отсутствии необработанных исключений. Необработанные исключения преобразуются в ошибки 5xx.

Типы возвращаемых значений ActionResult могут представлять широкий спектр кодов состояний HTTP. Например, метод GetTodoItem может возвращать два разных значения состояния:

  • Если запрошенному идентификатору не соответствует ни один элемент, метод возвращает код ошибки 404 NotFound.
  • В противном случае метод возвращает код 200 с телом ответа JSON. При возвращении item возвращается ответ HTTP 200.

Метод PutTodoItem

Проверьте метод PutTodoItem.

// PUT: api/TodoItems/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
    if (id != todoItem.Id)
    {
        return BadRequest();
    }

    _context.Entry(todoItem).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!TodoItemExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

Страница PutTodoItem аналогична странице PostTodoItem, но использует запрос HTTP PUT. Ответ — 204 (Нет содержимого). Согласно спецификации HTTP, запрос PUT требует, чтобы клиент отправлял всю обновленную сущность, а не только изменения. Чтобы обеспечить поддержку частичных обновлений, используйте HTTP PATCH.

Если возникнет ошибка вызова PutTodoItem, вызовите GET, чтобы в базе данных был один элемент.

Тестирование метода PutTodoItem

В этом примере используется база данных в памяти, которая должна быть инициирована при каждом запуске приложения. При выполнении вызова PUT в базе данных уже должен существовать какой-либо элемент. Для этого перед вызовом PUT выполните вызов GET, чтобы убедиться в наличии такого элемента в базе данных.

Обновите элемент списка дел с идентификатором 1 и присвойте ему имя "feed fish":

  {
    "Id":1,
    "name":"feed fish",
    "isComplete":true
  }

На следующем рисунке показан процесс обновления Postman:

Консоль Postman с ответом 204 (Нет содержимого)

Метод DeleteTodoItem

Проверьте метод DeleteTodoItem.

// DELETE: api/TodoItems/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}

Тестирование метода DeleteTodoItem

Удалите элемент списка дел с помощью Postman:

  • Укажите метод DELETE.
  • Укажите URI удаляемого объекта (например, https://localhost:5001/api/TodoItems/1).
  • Нажмите кнопку Отправить.

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

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

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

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

Чтобы продемонстрировать подход с применением DTO, обновите класс TodoItem, включив в него поле секрета:

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

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

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

Создайте модель DTO:

public class TodoItemDTO
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
}

Обновите TodoItemsController для использования TodoItemDTO:

// GET: api/TodoItems
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
{
    return await _context.TodoItems
        .Select(x => ItemToDTO(x))
        .ToListAsync();
}

[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return ItemToDTO(todoItem);
}

[HttpPut("{id}")]
public async Task<IActionResult> UpdateTodoItem(long id, TodoItemDTO todoItemDTO)
{
    if (id != todoItemDTO.Id)
    {
        return BadRequest();
    }

    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    todoItem.Name = todoItemDTO.Name;
    todoItem.IsComplete = todoItemDTO.IsComplete;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
    {
        return NotFound();
    }

    return NoContent();
}

[HttpPost]
public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO)
{
    var todoItem = new TodoItem
    {
        IsComplete = todoItemDTO.IsComplete,
        Name = todoItemDTO.Name
    };

    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    return CreatedAtAction(
        nameof(GetTodoItem),
        new { id = todoItem.Id },
        ItemToDTO(todoItem));
}

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}

private bool TodoItemExists(long id) =>
     _context.TodoItems.Any(e => e.Id == id);

private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
    new TodoItemDTO
    {
        Id = todoItem.Id,
        Name = todoItem.Name,
        IsComplete = todoItem.IsComplete
    };

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

Вызов веб-API с помощью JavaScript

См. руководство по : Вызовите веб-API ASP.NET Core с помощью JavaScript.

В этом руководстве вы узнаете, как:

  • Создание проекта веб-API.
  • Добавление класса модели и контекста базы данных.
  • Формирование шаблонов контроллера с использованием методов CRUD.
  • Настройка маршрутизации, URL-пути и возвращаемых значений.
  • Вызов веб-API с помощью Postman.

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

Обзор

В этом руководстве создается следующий API-интерфейс:

API Описание Текст запроса Текст ответа
GET /api/TodoItems Получение всех элементов задач Отсутствуют Массив элементов задач
GET /api/TodoItems/{id} Получение объекта по идентификатору Отсутствуют Элемент задачи
POST /api/TodoItems Добавление нового элемента Элемент задачи Элемент задачи
PUT /api/TodoItems/{id} Обновление существующего элемента   Элемент задачи Отсутствуют
DELETE /api/TodoItems/{id}     Удаление элемента     Отсутствуют Отсутствуют

На следующем рисунке показана структура приложения.

Клиент представлен прямоугольником слева. Он отправляет запрос и получает ответ от приложения (прямоугольник справа). В прямоугольнике приложения также расположены три прямоугольника, представляющих контроллер, модель и уровень доступа к данным. Запрос поступает на контроллер приложения, который выполняет операции чтения и записи между контроллером и уровнем доступа к данным. Модель сериализуется и возвращается клиенту в ответе.

Предварительные требования

Создайте веб-проект.

  • В меню Файл выберите пункт Создать > Проект.
  • Выберите шаблон Веб-приложение ASP.NET Core и нажмите Далее.
  • Назовите проект TodoApi и нажмите Создать.
  • В диалоговом окне Создание веб-приложения ASP.NET Core убедитесь в том, что выбраны платформы .NET Core и ASP.NET Core 3.1. Выберите шаблон API и нажмите кнопку Создать.

Диалоговое окно создания проекта VS

Тестирование API

Шаблон проекта создает API WeatherForecast. Вызовите метод Get из браузера для тестирования приложения.

Нажмите клавиши CTRL+F5, чтобы запустить приложение. Visual Studio запустит браузер и перейдет к https://localhost:<port>/WeatherForecast, где <port> — это номер порта, выбранный случайным образом.

Если появится диалоговое окно с запросом о необходимости доверять сертификату IIS Express, выберите Да. В появляющемся следом диалоговом окне Предупреждение системы безопасности выберите Да.

Возвращаемые данные JSON будут выглядеть примерно так:

[
    {
        "date": "2019-07-16T19:04:05.7257911-06:00",
        "temperatureC": 52,
        "temperatureF": 125,
        "summary": "Mild"
    },
    {
        "date": "2019-07-17T19:04:05.7258461-06:00",
        "temperatureC": 36,
        "temperatureF": 96,
        "summary": "Warm"
    },
    {
        "date": "2019-07-18T19:04:05.7258467-06:00",
        "temperatureC": 39,
        "temperatureF": 102,
        "summary": "Cool"
    },
    {
        "date": "2019-07-19T19:04:05.7258471-06:00",
        "temperatureC": 10,
        "temperatureF": 49,
        "summary": "Bracing"
    },
    {
        "date": "2019-07-20T19:04:05.7258474-06:00",
        "temperatureC": -1,
        "temperatureF": 31,
        "summary": "Chilly"
    }
]

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

Модель — это набор классов, представляющих данные, которыми управляет приложение. Модель этого приложения содержит единственный класс TodoItem.

  • В обозревателе решений щелкните проект правой кнопкой мыши. Выберите Добавить > Новая папка. Назовите папку Models .

  • Щелкните папку Models правой кнопкой мыши и выберите пункты Добавить > Класс. Присвойте классу имя TodoItem и выберите Добавить.

  • Замените код шаблона следующим кодом:

public class TodoItem
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
}

Свойство Id выступает в качестве уникального ключа реляционной базы данных.

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

Добавление контекста базы данных

Контекст базы данных —это основной класс, который координирует функциональные возможности Entity Framework для модели данных. Этот класс является производным от класса Microsoft.EntityFrameworkCore.DbContext.

Добавление пакетов NuGet

  • В меню Сервис выберите Диспетчер пакетов NuGet > Управление пакетами NuGet для решения.
  • Перейдите на вкладку Обзор и введите Microsoft.EntityFrameworkCore.InMemory в поле поиска.
  • На панели слева выберите Microsoft.EntityFrameworkCore.InMemory.
  • Установите флажок Проект на правой панели и выберите Установить.

Диспетчер пакетов NuGet

Добавление контекста базы данных TodoContext

  • Щелкните папку Models правой кнопкой мыши и выберите пункты Добавить > Класс. Назовите класс TodoContext и нажмите Добавить.
  • Введите следующий код:

    using Microsoft.EntityFrameworkCore;
    
    namespace TodoApi.Models
    {
        public class TodoContext : DbContext
        {
            public TodoContext(DbContextOptions<TodoContext> options)
                : base(options)
            {
            }
    
            public DbSet<TodoItem> TodoItems { get; set; }
        }
    }
    

Регистрация контекста базы данных

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

Обновите файл Startup.cs, используя следующий выделенный код:

// Unused usings removed
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<TodoContext>(opt =>
               opt.UseInMemoryDatabase("TodoList"));
            services.AddControllers();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

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

Предыдущий код:

  • Удалите неиспользуемые объявления using.
  • Добавляет контекст базы данных в контейнер внедрения зависимостей.
  • Указывает, что контекст базы данных будет использовать базу данных в памяти.

Формирование шаблонов контроллера

  • Щелкните папку Controllers правой кнопкой мыши.

  • Щелкните Добавить > Создать шаблонный элемент.

  • Выберите Контроллер API с действиями, использующий Entity Framework, а затем выберите Добавить.

  • В диалоговом окне Контроллер API с действиями, использующий Entity Framework сделайте следующее:

    • Выберите TodoItem (TodoApi.Models) в поле Класс модели.
    • Выберите TodoContext (TodoApi.Models) в поле Класс контекста данных.
    • Нажмите Добавить.

Сформированный код:

  • Пометьте этот класс атрибутом [ApiController]. Этот атрибут указывает, что контроллер отвечает на запросы веб-API. Дополнительные сведения о поведении, которое реализует этот атрибут, см. в Создание веб-API с помощью ASP.NET Core.
  • Использует внедрение зависимостей для внедрения контекста базы данных (TodoContext) в контроллер. Контекст базы данных используется в каждом методе создания, чтения, обновления и удаления в контроллере.

Шаблоны ASP.NET Core для:

  • Контроллеры с представлениями включают [action] в шаблоне маршрута.
  • Контроллеры API не включают [action] в шаблоне маршрута.

Если токен [action] не находится в шаблоне маршрута, имя действия исключается из маршрута. То есть имя связанного метода действия не используется в соответствующем маршруте.

Знакомство с методом создания PostTodoItem

Измените инструкцию возврата в PostTodoItem и используйте оператор nameof:

// POST: api/TodoItems
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    //return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem);
}

Предыдущий код является методом HTTP POST, обозначенным атрибутом [HttpPost]. Этот метод получает значение элемента списка дел из текста HTTP-запроса.

Дополнительные сведения см. в разделе Маршрутизация атрибутов с помощью атрибутов Http[Verb].

Метод CreatedAtAction:

  • В случае успеха возвращает код состояния HTTP 201. HTTP 201 представляет собой стандартный ответ для метода HTTP POST, создающий ресурс на сервере.
  • Добавляет в ответ заголовок Location. Заголовок Location указывает URI новой созданной задачи. Дополнительные сведения см. в статье 10.2.2 201 "Создан ресурс".
  • Указывает действие GetTodoItem для создания URI заголовка Location. Ключевое слово nameof C# используется для предотвращения жесткого программирования имени действия в вызове CreatedAtAction.

Установка Postman

В этом учебнике для тестирования веб-API используется Postman.

  • Установка Postman
  • Запустите веб-приложение.
  • Запустите Postman.
  • Отключение параметра Проверка SSL-сертификата
    • В меню Файл > Параметры (вкладка Общие), отключите параметр Проверка SSL-сертификата.

      Предупреждение

      После тестирования контроллера снова включите проверку SSL-сертификата.

Тестирование PostTodoItem с использованием Postman

  • Создайте новый запрос.

  • Установите HTTP-метод POST.

  • Задайте для URI значение https://localhost:<port>/api/TodoItems. Например, https://localhost:5001/api/TodoItems.

  • Откройте вкладку Тело.

  • Установите переключатель без обработки.

  • Задайте тип JSON (приложение/json) .

  • В теле запроса введите код JSON для элемента списка дел:

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • Нажмите кнопку Отправить.

    Postman с запросом Create

Тестирование URI заголовка расположения с помощью Postman

  • Перейдите на вкладку Заголовки в области Ответ.

  • Скопируйте значение заголовка Расположение:

    Вкладка "Заголовки" в консоли Postman

  • Установите HTTP-метод GET.

  • Задайте для URI значение https://localhost:<port>/api/TodoItems/1. Например, https://localhost:5001/api/TodoItems/1.

  • Нажмите кнопку Отправить.

Знакомство с методами GET

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

  • GET /api/TodoItems
  • GET /api/TodoItems/{id}

Протестируйте приложение, вызвав эти две конечные точки в браузере или в Postman. Пример:

  • https://localhost:5001/api/TodoItems
  • https://localhost:5001/api/TodoItems/1

При вызове GetTodoItems возвращается примерно такой ответ:

[
  {
    "id": 1,
    "name": "Item1",
    "isComplete": false
  }
]

Тестирование Get с использованием Postman

  • Создайте новый запрос.
  • Укажите метод HTTP GET.
  • Задайте для URI запроса значение https://localhost:<port>/api/TodoItems. Например, https://localhost:5001/api/TodoItems.
  • Выберите режим Представление с двумя областями в Postman.
  • Нажмите кнопку Отправить.

Это приложение использует выполняющуюся в памяти базу данных. Если остановить и вновь запустить его, предшествующий запрос GET не возвратит никаких данных. Если данные не возвращаются, данные для приложения получаются методом POST.

Маршрутизация и пути URL

Атрибут [HttpGet] обозначает метод, который отвечает на запрос HTTP GET. Путь URL для каждого метода формируется следующим образом:

  • Возьмите строку шаблона в атрибуте Route контроллера:

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    {
        private readonly TodoContext _context;
    
        public TodoItemsController(TodoContext context)
        {
            _context = context;
        }
    
  • Замените [controller] именем контроллера (по соглашению это имя класса контроллера без суффикса "Controller"). В этом примере класс контроллера имеет имя TodoItems, а сам контроллер, соответственно, — "TodoItems". В ASP.NET Core маршрутизация реализуется без учета регистра символов.

  • Если атрибут [HttpGet] имеет шаблон маршрута (например, [HttpGet("products")]), добавьте его к пути. В этом примере шаблон не используется. Дополнительные сведения см. в разделе Маршрутизация атрибутов с помощью атрибутов Http[Verb].

В следующем методе GetTodoItem``"{id}" — это переменная-заполнитель для уникального идентификатора элемента задачи. При вызове GetTodoItem параметру метода id присваивается значение "{id}" в URL-адресе.

// GET: api/TodoItems/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return todoItem;
}

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

Возвращаемый тип методов GetTodoItems и GetTodoItem — ActionResult<T>. ASP.NET Core автоматически сериализует объект в формат JSON и записывает данные JSON в тело сообщения ответа. Код ответа для этого типа возвращаемого значения равен 200, что свидетельствует об отсутствии необработанных исключений. Необработанные исключения преобразуются в ошибки 5xx.

Типы возвращаемых значений ActionResult могут представлять широкий спектр кодов состояний HTTP. Например, метод GetTodoItem может возвращать два разных значения состояния:

  • Если запрошенному идентификатору не соответствует ни один элемент, метод возвращает ошибку 404 (NotFound).
  • В противном случае метод возвращает код 200 с телом ответа JSON. При возвращении item возвращается ответ HTTP 200.

Метод PutTodoItem

Проверьте метод PutTodoItem.

// PUT: api/TodoItems/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
    if (id != todoItem.Id)
    {
        return BadRequest();
    }

    _context.Entry(todoItem).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!TodoItemExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

Страница PutTodoItem аналогична странице PostTodoItem, но использует запрос HTTP PUT. Ответ — 204 (Нет содержимого). Согласно спецификации HTTP, запрос PUT требует, чтобы клиент отправлял всю обновленную сущность, а не только изменения. Чтобы обеспечить поддержку частичных обновлений, используйте HTTP PATCH.

Если возникнет ошибка вызова PutTodoItem, вызовите GET, чтобы в базе данных был один элемент.

Тестирование метода PutTodoItem

В этом примере используется база данных в памяти, которая должна быть инициирована при каждом запуске приложения. При выполнении вызова PUT в базе данных уже должен существовать какой-либо элемент. Для этого перед вызовом PUT выполните вызов GET, чтобы убедиться в наличии такого элемента в базе данных.

Обновите элемент списка дел с идентификатором 1 и присвойте ему имя "feed fish":

  {
    "id":1,
    "name":"feed fish",
    "isComplete":true
  }

На следующем рисунке показан процесс обновления Postman:

Консоль Postman с ответом 204 (Нет содержимого)

Метод DeleteTodoItem

Проверьте метод DeleteTodoItem.

// DELETE: api/TodoItems/5
[HttpDelete("{id}")]
public async Task<ActionResult<TodoItem>> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return todoItem;
}

Тестирование метода DeleteTodoItem

Удалите элемент списка дел с помощью Postman:

  • Укажите метод DELETE.
  • Укажите URI удаляемого объекта (например, https://localhost:5001/api/TodoItems/1).
  • Нажмите кнопку Отправить.

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

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

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

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

Чтобы продемонстрировать подход с применением DTO, обновите класс TodoItem, включив в него поле секрета:

public class TodoItem
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
    public string Secret { get; set; }
}

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

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

Создайте модель DTO:

public class TodoItemDTO
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
}

Обновите TodoItemsController для использования TodoItemDTO:

    [HttpGet]
    public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
    {
        return await _context.TodoItems
            .Select(x => ItemToDTO(x))
            .ToListAsync();
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);

        if (todoItem == null)
        {
            return NotFound();
        }

        return ItemToDTO(todoItem);
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateTodoItem(long id, TodoItemDTO todoItemDTO)
    {
        if (id != todoItemDTO.Id)
        {
            return BadRequest();
        }

        var todoItem = await _context.TodoItems.FindAsync(id);
        if (todoItem == null)
        {
            return NotFound();
        }

        todoItem.Name = todoItemDTO.Name;
        todoItem.IsComplete = todoItemDTO.IsComplete;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
        {
            return NotFound();
        }

        return NoContent();
    }

    [HttpPost]
    public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO)
    {
        var todoItem = new TodoItem
        {
            IsComplete = todoItemDTO.IsComplete,
            Name = todoItemDTO.Name
        };

        _context.TodoItems.Add(todoItem);
        await _context.SaveChangesAsync();

        return CreatedAtAction(
            nameof(GetTodoItem),
            new { id = todoItem.Id },
            ItemToDTO(todoItem));
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);

        if (todoItem == null)
        {
            return NotFound();
        }

        _context.TodoItems.Remove(todoItem);
        await _context.SaveChangesAsync();

        return NoContent();
    }

    private bool TodoItemExists(long id) =>
         _context.TodoItems.Any(e => e.Id == id);

    private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
        new TodoItemDTO
        {
            Id = todoItem.Id,
            Name = todoItem.Name,
            IsComplete = todoItem.IsComplete
        };       
}

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

Вызов веб-API с помощью JavaScript

См. руководство по : Вызовите веб-API ASP.NET Core с помощью JavaScript.

В этом руководстве вы узнаете, как:

  • Создание проекта веб-API.
  • Добавление класса модели и контекста базы данных.
  • Добавление контроллера.
  • Добавление методов CRUD.
  • Настройка маршрутизации и путей URL.
  • Указание возвращаемых значений.
  • Вызов веб-API с помощью Postman.
  • Вызовите веб-API с помощью JavaScript.

В конечном итоге вы получите веб-API, обеспечивающий управление элементами списка дел, хранящимися в реляционной базе данных.

2.1. Обзор

В этом руководстве создается следующий API-интерфейс:

API Описание Текст запроса Текст ответа
GET /api/TodoItems Получение всех элементов задач Отсутствуют Массив элементов задач
GET /api/TodoItems/{id} Получение объекта по идентификатору Отсутствуют Элемент задачи
POST /api/TodoItems Добавление нового элемента Элемент задачи Элемент задачи
PUT /api/TodoItems/{id} Обновление существующего элемента   Элемент задачи Отсутствуют
DELETE /api/TodoItems/{id}     Удаление элемента     Отсутствуют Отсутствуют

На следующем рисунке показана структура приложения.

Клиент представлен прямоугольником слева. Он отправляет запрос и получает ответ от приложения (прямоугольник справа). В прямоугольнике приложения также расположены три прямоугольника, представляющих контроллер, модель и уровень доступа к данным. Запрос поступает на контроллер приложения, который выполняет операции чтения и записи между контроллером и уровнем доступа к данным. Модель сериализуется и возвращается клиенту в ответе.

2.1. Предварительные требования

Предупреждение

Если вы используете Visual Studio 2017, см. обсуждение dotnet/sdk issue #3124 для получения сведений о версиях пакета SDK для .NET Core, которые не работают с Visual Studio.

2.1. Создание веб-проекта

  • В меню Файл выберите пункт Создать > Проект.
  • Выберите шаблон Веб-приложение ASP.NET Core и нажмите Далее.
  • Назовите проект TodoApi и нажмите Создать.
  • В диалоговом окне Создание веб-приложения ASP.NET Core убедитесь в том, что выбраны платформы .NET Core и ASP.NET Core 2.2. Выберите шаблон API и нажмите кнопку Создать. Не выбирайте Включение поддержки Docker.

Диалоговое окно создания проекта VS

2.1. Тестирование API

Шаблон проекта создает API values. Вызовите метод Get из браузера для тестирования приложения.

Нажмите клавиши CTRL+F5, чтобы запустить приложение. Visual Studio запустит браузер и перейдет к https://localhost:<port>/api/values, где <port> — это номер порта, выбранный случайным образом.

Если появится диалоговое окно с запросом о необходимости доверять сертификату IIS Express, выберите Да. В появляющемся следом диалоговом окне Предупреждение системы безопасности выберите Да.

Возвращается следующий файл JSON:

["value1","value2"]

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

Модель — это набор классов, представляющих данные, которыми управляет приложение. Модель этого приложения содержит единственный класс TodoItem.

  • В обозревателе решений щелкните проект правой кнопкой мыши. Выберите Добавить > Новая папка. Назовите папку Models .

  • Щелкните папку Models правой кнопкой мыши и выберите пункты Добавить > Класс. Присвойте классу имя TodoItem и выберите Добавить.

  • Замените код шаблона следующим кодом:

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

Свойство Id выступает в качестве уникального ключа реляционной базы данных.

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

2.1. Добавление контекста базы данных

Контекст базы данных —это основной класс, который координирует функциональные возможности Entity Framework для модели данных. Этот класс является производным от класса Microsoft.EntityFrameworkCore.DbContext.

  • Щелкните папку Models правой кнопкой мыши и выберите пункты Добавить > Класс. Назовите класс TodoContext и нажмите Добавить.
  • Замените код шаблона следующим кодом:

    using Microsoft.EntityFrameworkCore;
    
    namespace TodoApi.Models
    {
        public class TodoContext : DbContext
        {
            public TodoContext(DbContextOptions<TodoContext> options)
                : base(options)
            {
            }
    
            public DbSet<TodoItem> TodoItems { get; set; }
        }
    }
    

2.1. Регистрация контекста базы данных

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

Обновите файл Startup.cs, используя следующий выделенный код:

// Unused usings removed
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using TodoApi.Models;

namespace TodoApi
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the 
        //container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<TodoContext>(opt =>
                opt.UseInMemoryDatabase("TodoList"));
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP 
        //request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                // The default HSTS value is 30 days. You may want to change this for 
                // production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }
}

Предыдущий код:

  • Удалите неиспользуемые объявления using.
  • Добавляет контекст базы данных в контейнер внедрения зависимостей.
  • Указывает, что контекст базы данных будет использовать базу данных в памяти.

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

  • Щелкните папку Controllers правой кнопкой мыши.

  • Щелкните Добавить > Создать элемент.

  • В диалоговом окне Добавить новый элемент выберите шаблон Класс контроллера API.

  • Присвойте классу имя TodoController и выберите Добавить.

    Диалоговое окно добавления элемента с контроллером в поле поиска и выбранным контроллером веб-API

  • Замените код шаблона следующим кодом:

    using Microsoft.AspNetCore.Mvc;
    using Microsoft.EntityFrameworkCore;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using TodoApi.Models;
    
    namespace TodoApi.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class TodoController : ControllerBase
        {
            private readonly TodoContext _context;
    
            public TodoController(TodoContext context)
            {
                _context = context;
    
                if (_context.TodoItems.Count() == 0)
                {
                    // Create a new TodoItem if collection is empty,
                    // which means you can't delete all TodoItems.
                    _context.TodoItems.Add(new TodoItem { Name = "Item1" });
                    _context.SaveChanges();
                }
            }
        }
    }
    

Предыдущий код:

  • Определяет класс контроллера API без методов.
  • Пометьте этот класс атрибутом [ApiController]. Этот атрибут указывает, что контроллер отвечает на запросы веб-API. Дополнительные сведения о поведении, которое реализует этот атрибут, см. в Создание веб-API с помощью ASP.NET Core.
  • Использует внедрение зависимостей для внедрения контекста базы данных (TodoContext) в контроллер. Контекст базы данных используется в каждом методе создания, чтения, обновления и удаления в контроллере.
  • Добавляет элемент Item1 в базу данных, если она пуста. Этот код находится в конструкторе и выполняется каждый раз при обнаружении нового HTTP-запроса. Если вы удалите все элементы, конструктор создаст Item1 при следующем вызове метода API. Поэтому может создаться впечатление, что удаление не было выполнено, хотя это не так.

2.1. Добавление методов Get

Чтобы получить API, который извлекает элементы списка дел, добавьте следующие методы в класс TodoController:

// GET: api/Todo
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
{
    return await _context.TodoItems.ToListAsync();
}

// GET: api/Todo/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return todoItem;
}

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

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

Если приложение все еще выполняется, оно останавливается. Затем оно запускается снова с последними изменениями.

Протестируйте приложение, вызвав эти две конечные точки в браузере. Пример:

  • https://localhost:<port>/api/todo
  • https://localhost:<port>/api/todo/1

При вызове GetTodoItems возвращается следующий ответ HTTP:

[
  {
    "id": 1,
    "name": "Item1",
    "isComplete": false
  }
]

2.1. Маршрутизация и пути URL

Атрибут [HttpGet] обозначает метод, который отвечает на запрос HTTP GET. Путь URL для каждого метода формируется следующим образом:

  • Возьмите строку шаблона в атрибуте Route контроллера:

    namespace TodoApi.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class TodoController : ControllerBase
        {
            private readonly TodoContext _context;
    
  • Замените [controller] именем контроллера (по соглашению это имя класса контроллера без суффикса "Controller"). В этом примере класс контроллера носит имя Todo, а сам контроллер, соответственно, — "todo". В ASP.NET Core маршрутизация реализуется без учета регистра символов.

  • Если атрибут [HttpGet] имеет шаблон маршрута (например, [HttpGet("products")]), добавьте его к пути. В этом примере шаблон не используется. Дополнительные сведения см. в разделе Маршрутизация атрибутов с помощью атрибутов Http[Verb].

В следующем методе GetTodoItem``"{id}" — это переменная-заполнитель для уникального идентификатора элемента задачи. При вызове GetTodoItem параметру метода id присваивается значение "{id}" в URL-адресе.

// GET: api/Todo/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return todoItem;
}

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

Возвращаемый тип методов GetTodoItems и GetTodoItem — ActionResult<T>. ASP.NET Core автоматически сериализует объект в формат JSON и записывает данные JSON в тело сообщения ответа. Код ответа для этого типа возвращаемого значения равен 200, что свидетельствует об отсутствии необработанных исключений. Необработанные исключения преобразуются в ошибки 5xx.

Типы возвращаемых значений ActionResult могут представлять широкий спектр кодов состояний HTTP. Например, метод GetTodoItem может возвращать два разных значения состояния:

  • Если запрошенному идентификатору не соответствует ни один элемент, метод возвращает ошибку 404 (NotFound).
  • В противном случае метод возвращает код 200 с телом ответа JSON. При возвращении item возвращается ответ HTTP 200.

2.1. Тестирование метода GetTodoItems

В этом учебнике для тестирования веб-API используется Postman.

  • Установите Postman.
  • Запустите веб-приложение.
  • Запустите Postman.
  • Отключите параметр Проверка SSL-сертификата.
  • В меню Файл > Параметры (вкладка Общие), отключите параметр Проверка SSL-сертификата.

Предупреждение

После тестирования контроллера снова включите проверку SSL-сертификата.

  • Создайте новый запрос.
    • Укажите метод HTTP GET.
    • Задайте для URI запроса значение https://localhost:<port>/api/todo. Например, https://localhost:5001/api/todo.
  • Выберите режим Представление с двумя областями в Postman.
  • Нажмите кнопку Отправить.

Postman с запросом Get

2.1. Добавление метода Create

Добавьте следующий метод PostTodoItem в Controllers/TodoController.cs:

// POST: api/Todo
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem item)
{
    _context.TodoItems.Add(item);
    await _context.SaveChangesAsync();

    return CreatedAtAction(nameof(GetTodoItem), new { id = item.Id }, item);
}

Предыдущий код является методом HTTP POST, обозначенным атрибутом [HttpPost]. Этот метод получает значение элемента списка дел из текста HTTP-запроса.

Метод CreatedAtAction:

  • В случае успеха возвращает код состояния HTTP 201. HTTP 201 представляет собой стандартный ответ для метода HTTP POST, создающий ресурс на сервере.

  • Добавляет заголовок Location в ответ. Заголовок Location расположения указывает URI вновь созданной задачи. Дополнительные сведения см. в статье 10.2.2 201 "Создан ресурс".

  • Указывает действие GetTodoItem для создания URI заголовка Location. Ключевое слово nameof C# используется для предотвращения жесткого программирования имени действия в вызове CreatedAtAction.

    // GET: api/Todo/5
    [HttpGet("{id}")]
    public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);
    
        if (todoItem == null)
        {
            return NotFound();
        }
    
        return todoItem;
    }
    

2.1. Тестирование метода PostTodoItem

  • Выполните построение проекта.

  • В Postman укажите метод HTTP POST.

  • Задайте для URI значение https://localhost:<port>/api/Todo. Например, https://localhost:5001/api/Todo.

  • Откройте вкладку Тело.

  • Установите переключатель без обработки.

  • Задайте тип JSON (приложение/json) .

  • В теле запроса введите код JSON для элемента списка дел:

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • Нажмите кнопку Отправить.

    Postman с запросом Create

    Если вы получаете ошибку 405 "Недопустимый метод", это может свидетельствовать о том, что после добавления метода PostTodoItem не была выполнена компиляция проекта.

2.1. Тестирование URI заголовка расположения

  • Перейдите на вкладку Заголовки в области Ответ.

  • Скопируйте значение заголовка Расположение:

    Вкладка "Заголовки" в консоли Postman

  • Укажите метод GET.

  • Задайте для URI значение https://localhost:<port>/api/TodoItems/2. Например, https://localhost:5001/api/TodoItems/2.

  • Нажмите кнопку Отправить.

2.1. Добавление метода PutTodoItem

Добавьте приведенный ниже метод PutTodoItem.

// PUT: api/Todo/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem item)
{
    if (id != item.Id)
    {
        return BadRequest();
    }

    _context.Entry(item).State = EntityState.Modified;
    await _context.SaveChangesAsync();

    return NoContent();
}

Страница PutTodoItem аналогична странице PostTodoItem, но использует запрос HTTP PUT. Ответ — 204 (Нет содержимого). Согласно спецификации HTTP, запрос PUT требует, чтобы клиент отправлял всю обновленную сущность, а не только изменения. Чтобы обеспечить поддержку частичных обновлений, используйте HTTP PATCH.

Если возникнет ошибка вызова PutTodoItem, вызовите GET, чтобы в базе данных был один элемент.

2.1. Тестирование метода PutTodoItem

В этом примере используется база данных в памяти, которая должна быть инициирована при каждом запуске приложения. При выполнении вызова PUT в базе данных уже должен существовать какой-либо элемент. Для этого перед вызовом PUT выполните вызов GET, чтобы убедиться в наличии такого элемента в базе данных.

Обновите элемент списка дел с идентификатором 1 и присвойте ему имя "feed fish":

  {
    "id":1,
    "name":"feed fish",
    "isComplete":true
  }

На следующем рисунке показан процесс обновления Postman:

Консоль Postman с ответом 204 (Нет содержимого)

2.1. Добавление метода DeleteTodoItem

Добавьте приведенный ниже метод DeleteTodoItem.

// DELETE: api/Todo/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}

DeleteTodoItemОтвет — 204 (нет содержимого).

2.1. Тестирование метода DeleteTodoItem

Удалите элемент списка дел с помощью Postman:

  • Укажите метод DELETE.
  • Укажите URI удаляемого объекта (например, https://localhost:5001/api/todo/1).
  • Нажмите кнопку Отправить.

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

2.1. Вызов веб-API с помощью JavaScript

В этом разделе описано, как добавить HTML-страницу, которая использует JavaScript для вызова веб-API. jQuery инициирует запрос. JavaScript изменяет страницу, используя сведения из ответа API.

Настройте приложение для обслуживания статических файлов и включения сопоставления файлов по умолчанию, обновив Startup.cs следующим выделенным кодом:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        // The default HSTS value is 30 days. You may want to change this for 
        // production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }

    app.UseDefaultFiles();
    app.UseStaticFiles();
    app.UseHttpsRedirection();
    app.UseMvc();
}

Создайте папку wwwroot в каталоге проекта.

Добавьте HTML-файл index.html в каталог wwwroot. Замените его содержимое следующей разметкой:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>To-do CRUD</title>
    <style>
        input[type='submit'], button, [aria-label] {
            cursor: pointer;
        }

        #spoiler {
            display: none;
        }

        table {
            font-family: Arial, sans-serif;
            border: 1px solid;
            border-collapse: collapse;
        }

        th {
            background-color: #0066CC;
            color: white;
        }

        td {
            border: 1px solid;
            padding: 5px;
        }
    </style>
</head>
<body>
    <h1>To-do CRUD</h1>
    <h3>Add</h3>
    <form action="javascript:void(0);" method="POST" onsubmit="addItem()">
        <input type="text" id="add-name" placeholder="New to-do">
        <input type="submit" value="Add">
    </form>

    <div id="spoiler">
        <h3>Edit</h3>
        <form class="my-form">
            <input type="hidden" id="edit-id">
            <input type="checkbox" id="edit-isComplete">
            <input type="text" id="edit-name">
            <input type="submit" value="Save">
            <a onclick="closeInput()" aria-label="Close">&#10006;</a>
        </form>
    </div>

    <p id="counter"></p>

    <table>
        <tr>
            <th>Is Complete</th>
            <th>Name</th>
            <th></th>
            <th></th>
        </tr>
        <tbody id="todos"></tbody>
    </table>

    <script src="https://code.jquery.com/jquery-3.3.1.min.js"
            integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
            crossorigin="anonymous"></script>
    <script src="site.js"></script>
</body>
</html>

Добавьте файл JavaScript с именем site.js в каталог wwwroot. Замените его содержимое следующим кодом:

const uri = "api/todo";
let todos = null;
function getCount(data) {
  const el = $("#counter");
  let name = "to-do";
  if (data) {
    if (data > 1) {
      name = "to-dos";
    }
    el.text(data + " " + name);
  } else {
    el.text("No " + name);
  }
}

$(document).ready(function() {
  getData();
});

function getData() {
  $.ajax({
    type: "GET",
    url: uri,
    cache: false,
    success: function(data) {
      const tBody = $("#todos");

      $(tBody).empty();

      getCount(data.length);

      $.each(data, function(key, item) {
        const tr = $("<tr></tr>")
          .append(
            $("<td></td>").append(
              $("<input/>", {
                type: "checkbox",
                disabled: true,
                checked: item.isComplete
              })
            )
          )
          .append($("<td></td>").text(item.name))
          .append(
            $("<td></td>").append(
              $("<button>Edit</button>").on("click", function() {
                editItem(item.id);
              })
            )
          )
          .append(
            $("<td></td>").append(
              $("<button>Delete</button>").on("click", function() {
                deleteItem(item.id);
              })
            )
          );

        tr.appendTo(tBody);
      });

      todos = data;
    }
  });
}

function addItem() {
  const item = {
    name: $("#add-name").val(),
    isComplete: false
  };

  $.ajax({
    type: "POST",
    accepts: "application/json",
    url: uri,
    contentType: "application/json",
    data: JSON.stringify(item),
    error: function(jqXHR, textStatus, errorThrown) {
      alert("Something went wrong!");
    },
    success: function(result) {
      getData();
      $("#add-name").val("");
    }
  });
}

function deleteItem(id) {
  $.ajax({
    url: uri + "/" + id,
    type: "DELETE",
    success: function(result) {
      getData();
    }
  });
}

function editItem(id) {
  $.each(todos, function(key, item) {
    if (item.id === id) {
      $("#edit-name").val(item.name);
      $("#edit-id").val(item.id);
      $("#edit-isComplete")[0].checked = item.isComplete;
    }
  });
  $("#spoiler").css({ display: "block" });
}

$(".my-form").on("submit", function() {
  const item = {
    name: $("#edit-name").val(),
    isComplete: $("#edit-isComplete").is(":checked"),
    id: $("#edit-id").val()
  };

  $.ajax({
    url: uri + "/" + $("#edit-id").val(),
    type: "PUT",
    accepts: "application/json",
    contentType: "application/json",
    data: JSON.stringify(item),
    success: function(result) {
      getData();
    }
  });

  closeInput();
  return false;
});

function closeInput() {
  $("#spoiler").css({ display: "none" });
}

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

  • Откройте файл Properties\launchSettings.json.
  • Удалите свойство launchUrl, чтобы приложение открылось через index.html — файл проекта по умолчанию.

В этом примере вызываются все методы CRUD в веб-API. Ниже приводится пояснение вызовов API.

2.1. Получение списка элементов задач

jQuery отправляет запрос HTTP GET к веб-API, который возвращает ответ JSON, представляющий массив элементов списка дел. В случае успешного запроса используется функция обратного вызова success. При обратном вызове в модель DOM вносятся данные о задачах.

$(document).ready(function() {
  getData();
});

function getData() {
  $.ajax({
    type: "GET",
    url: uri,
    cache: false,
    success: function(data) {
      const tBody = $("#todos");

      $(tBody).empty();

      getCount(data.length);

      $.each(data, function(key, item) {
        const tr = $("<tr></tr>")
          .append(
            $("<td></td>").append(
              $("<input/>", {
                type: "checkbox",
                disabled: true,
                checked: item.isComplete
              })
            )
          )
          .append($("<td></td>").text(item.name))
          .append(
            $("<td></td>").append(
              $("<button>Edit</button>").on("click", function() {
                editItem(item.id);
              })
            )
          )
          .append(
            $("<td></td>").append(
              $("<button>Delete</button>").on("click", function() {
                deleteItem(item.id);
              })
            )
          );

        tr.appendTo(tBody);
      });

      todos = data;
    }
  });
}

2.1. Добавление элемента задачи

jQuery отправляет запрос HTTP POST с элементом списка дел в тексте запроса. Для параметров accepts и contentType указывается application/json, чтобы указать тип носителя при получении и отправке. Элемент списка дел преобразуется в JSON с помощью JSON.stringify. Если интерфейс API возвращает код состояния успешного выполнения, вызывается функция getData для обновления HTML-таблицы.

function addItem() {
  const item = {
    name: $("#add-name").val(),
    isComplete: false
  };

  $.ajax({
    type: "POST",
    accepts: "application/json",
    url: uri,
    contentType: "application/json",
    data: JSON.stringify(item),
    error: function(jqXHR, textStatus, errorThrown) {
      alert("Something went wrong!");
    },
    success: function(result) {
      getData();
      $("#add-name").val("");
    }
  });
}

2.1. Обновление элемента задачи

Обновление элемента списка дел выполняется аналогично его добавлению. url изменяется, чтобы добавить уникальный идентификатор для элемента, а в качестве type устанавливается PUT.

$.ajax({
  url: uri + "/" + $("#edit-id").val(),
  type: "PUT",
  accepts: "application/json",
  contentType: "application/json",
  data: JSON.stringify(item),
  success: function(result) {
    getData();
  }
});

2.1. Удаление элемента задачи

Чтобы удалить элемент списка дел, установите для type в вызове AJAX значение DELETE и укажите уникальный идентификатор элемента в URL-адресе.

$.ajax({
  url: uri + "/" + id,
  type: "DELETE",
  success: function(result) {
    getData();
  }
});

2.1. Добавление поддержки аутентификации в веб-API

ASP.NET Core Identity позволяет использовать функцию входа в пользовательском интерфейсе для веб-приложений ASP.NET Core. Чтобы защитить веб-API и одностраничные приложения, используйте один из следующих способов:

IdentityServer4 — это платформа OpenID Connect и OAuth 2.0 для ASP.NET Core. IdentityServer4 включает следующие функции безопасности:

  • Проверка подлинности как услуга (AaaS)
  • Единый вход (SSO) для нескольких типов приложений
  • Контроль доступа для API
  • Шлюз федерации

Дополнительные сведения см. в разделе Добро пожаловать в IdentityServer4.

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

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

Дополнительные сведения см. в следующих ресурсах: