Руководство. Создание минимального API с помощью ASP.NET Core
Примечание.
Это не последняя версия этой статьи. В текущей версии см. версию .NET 9 этой статьи.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске смотрите версию .NET 9 этой статьи.
Внимание
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
В текущем релизе смотрите версию этой статьи .NET 9.
Авторы: Рик Андерсон (Rick Anderson) и Том Дайкстра (Tom Dykstra)
Архитектура минимальных API позволяет создавать API для HTTP с минимальным числом зависимостей. Они идеально подходят для микрослужб и приложений, которые хотят включать только минимальные файлы, функции и зависимости в ASP.NET Core.
В этом руководстве описаны основы создания минимального API с помощью ASP.NET Core. Другим подходом к созданию API в ASP.NET Core является использование контроллеров. Сведения о выборе между минимальными API и API на основе контроллера см. в обзоре API. Руководство по созданию проекта API на основе контроллеров , содержащих дополнительные функции, см. в разделе "Создание веб-API".
Обзор
В этом руководстве создается следующий API-интерфейс:
API | Описание | Текст запроса | Текст ответа |
---|---|---|---|
GET /todoitems |
Получить все пункты задач | нет | Массив элементов задач |
GET /todoitems/complete |
Получение элементов выполненных заданий из списка | нет | Массив элементов задач |
GET /todoitems/{id} |
Получение элемента по идентификатору | нет | Задача |
POST /todoitems |
Добавление нового элемента | Пункт списка дел | Пункт задания |
PUT /todoitems/{id} |
Обновление существующего элемента | Элемент задачи | нет |
DELETE /todoitems/{id} |
Удаление элемента | нет | нет |
Предварительные условия
Visual Studio 2022 с рабочей нагрузкой ASP.NET и веб-разработка.
Создание проекта API
Запустите Visual Studio 2022 и нажмите Создать проект.
В диалоговом окне Создание нового проекта выполните следующие действия.
- Введите
Empty
в поле Поиск шаблонов. - Выберите шаблон ASP.NET Core Empty и нажмите кнопку "Далее".
- Введите
Присвойте проекту имя TodoApi и щелкните Далее.
В диалоговом окне Дополнительные сведения выполните следующие действия.
- Выберите .NET 9.0
- Снимите флажок Не использовать операторы верхнего уровня
- Нажмите кнопку Создать
Изучение кода
Файл Program.cs
содержит следующий код:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Предыдущий код:
- Создает WebApplicationBuilder и WebApplication с предварительно настроенными значениями по умолчанию.
- Создает конечную точку
/
HTTP GET, которая возвращаетHello World!
:
Выполнить приложение
Нажмите клавиши CTRL+F5, чтобы выполнить запуск без отладчика.
Visual Studio отображает следующее диалоговое окно.
Выберите Да, чтобы сделать SSL-сертификат IIS Express доверенным.
Отобразится следующее диалоговое окно.
Выберите Да, если согласны доверять сертификату разработки.
Сведения о доверии к браузеру Firefox см. в разделе Ошибка сертификата браузера Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
Visual Studio запускает Kestrel веб-сервер и открывает окно браузера.
Hello World!
отображается в браузере. Файл Program.cs
содержит минимальное, но полное приложение.
Закройте окно браузера.
Добавление пакетов NuGet
Для поддержки возможностей базы данных и диагностики, которые используются в этом руководстве, необходимо добавить пакеты NuGet.
- В меню Средства выберите Диспетчер пакетов NuGet > Управление пакетами NuGet для решения.
- Перейдите на вкладку Browse.
- Выберите " Включить предварительную версию".
- Введите Microsoft.EntityFrameworkCore.InMemory в поле поиска и щелкните
Microsoft.EntityFrameworkCore.InMemory
. - Установите флажок Проект в области справа и выберите Установить.
- Добавьте пакет
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
по представленным выше инструкциям.
Классы контекста базы данных и модели
- В папке проекта создайте файл с именем
Todo.cs
со следующим кодом:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Приведенный выше код создает модель для этого приложения. Класс модели представляет данные, которыми управляет наше приложение.
- Создайте файл с именем
TodoDb.cs
со следующим кодом:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Предыдущий код определяет контекст базы данных, который является основным классом, который координирует функциональные возможности Entity Framework для модели данных. Этот класс является производным от класса Microsoft.EntityFrameworkCore.DbContext.
Добавление кода API
- Замените содержимое файла
Program.cs
приведенным ниже кодом.
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
Выделенный ниже код добавляет контекст базы данных в контейнер внедрения зависимостей (DI) и позволяет отображать исключения, связанные с базой данных:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
Контейнер DI предоставляет доступ к контексту базы данных и другим службам.
В этом руководстве для тестирования API используются обозреватель конечных точек и HTTP-файлы .
Проверка публикации данных
В следующем коде создается конечная точка HTTP POST Program.cs
, которая добавляет данные в базу данных, находящуюся в оперативной памяти /todoitems
.
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Запустите приложение. В браузере отображается ошибка 404, так как конечная /
точка больше не существует.
Конечная точка POST будет использоваться для добавления данных в приложение.
Выберите Вид>Другие окна>Проводник конечных точек.
Щелкните правой кнопкой мыши конечную точку POST и выберите "Создать запрос".
Новый файл создается в папке
TodoApi.http
проекта с содержимым, аналогичным следующему примеру:@TodoApi_HostAddress = https://localhost:7031 POST {{TodoApi_HostAddress}}/todoitems ###
- Первая строка создает переменную, используемую для всех конечных точек.
- Следующая строка определяет запрос POST.
- Тройной хэштег (
###
) — это разделитель запросов: то, что следует после него, предназначено для другого запроса.
Запрос POST нуждается в заголовках и тексте. Чтобы определить эти части запроса, добавьте следующие строки сразу после строки запроса POST:
Content-Type: application/json { "name":"walk dog", "isComplete":true }
Предыдущий код добавляет заголовок Content-Type и текст запроса JSON. Теперь файл TodoApi.http должен выглядеть следующим образом, но с номером порта:
@TodoApi_HostAddress = https://localhost:7057 POST {{TodoApi_HostAddress}}/todoitems Content-Type: application/json { "name":"walk dog", "isComplete":true } ###
Запустить приложение.
Выберите ссылку "Отправить запрос", которая находится над строкой
POST
запроса.Запрос POST отправляется приложению, а ответ отображается в области ответа .
Изучение конечных точек GET
Пример приложения реализует несколько конечных точек GET путем вызова MapGet
:
API | Описание | Текст запроса | Содержимое ответа |
---|---|---|---|
GET /todoitems |
Получение всех элементов задач | нет | Список задач |
GET /todoitems/complete |
Получить все выполненные задания | нет | Массив задач |
GET /todoitems/{id} |
Получить объект по идентификатору | нет | Элемент списка задач |
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
Тестирование конечных точек GET
Протестируйте приложение, вызвав конечные точки GET
из браузера или используя Обозреватель конечных точек. Ниже приведены действия для обозревателя конечных точек.
В обозревателе конечных точек щелкните правой кнопкой мыши первую конечную точку GET и выберите команду "Создать запрос".
В файл добавляется следующее содержимое
TodoApi.http
:GET {{TodoApi_HostAddress}}/todoitems ###
Выберите ссылку "Отправить запрос", которая находится над новой
GET
строкой запроса.Запрос GET отправляется приложению, а ответ отображается в области ответа .
Текст ответа аналогичен следующему json:
[ { "id": 1, "name": "walk dog", "isComplete": true } ]
В обозревателе конечных точек щелкните правой кнопкой мыши на конечной точке
/todoitems/{id}
GET и выберите Создать запрос. В файл добавляется следующее содержимоеTodoApi.http
:GET {{TodoApi_HostAddress}}/todoitems/{id} ###
Замените
{id}
на1
.Выберите ссылку "Отправить запрос", которая находится над новой строкой запроса GET.
Запрос GET отправляется приложению, а ответ отображается в области ответа .
Текст ответа аналогичен следующему json:
{ "id": 1, "name": "walk dog", "isComplete": true }
Это приложение использует базу данных, размещённую в памяти. После перезапуска приложения запрос GET не возвращает никаких данных. Если данные не возвращаются, отправьте POST-запрос в приложение и попробуйте снова выполнить запрос GET.
Возвращаемые значения
ASP.NET Core автоматически сериализует объект в формат JSON и записывает данные JSON в тело сообщения ответа. Код ответа для этого типа возвращаемого значения равен 200 OK, что свидетельствует об отсутствии необработанных исключений. Необработанные исключения преобразуются в ошибки 5xx.
Типы возвращаемых значений могут представлять широкий спектр кодов состояний HTTP. Например, метод GET /todoitems/{id}
может возвращать два разных значения состояния:
- Если запрошенному идентификатору не соответствует ни один элемент, метод возвращает код ошибки 404NotFound.
- В противном случае метод возвращает код 200 с телом ответа JSON. Возврат
item
приводит к получению ответа HTTP 200.
Изучение конечной точки PUT
Этот пример приложения реализует одну конечную точку PUT с помощью MapPut
:
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
Этот метод отличается от метода MapPost
только тем, что использует метод HTTP PUT. Успешный ответ возвращает состояние 204 (без содержимого). Согласно спецификации HTTP, запрос PUT требует, чтобы клиент отправлял всю обновленную сущность, а не только изменения. Чтобы обеспечить поддержку частичных обновлений, используйте HTTP PATCH.
Тестирование конечной точки PUT
В этом примере используется база данных в памяти, которая должна быть инициирована при каждом запуске приложения. При выполнении вызова PUT в базе данных уже должен существовать какой-либо элемент. Для этого перед вызовом PUT выполните вызов GET, чтобы убедиться в наличии такого элемента в базе данных.
Обновите элемент списка дел с Id = 1
и установите его имя на "feed fish"
.
В обозревателе конечных точек щелкните правой кнопкой мыши конечную точку PUT и выберите " Создать запрос".
В файл добавляется следующее содержимое
TodoApi.http
:PUT {{TodoApi_HostAddress}}/todoitems/{id} ###
В строке запроса PUT замените
{id}
на1
.Добавьте следующие строки сразу после строки запроса PUT:
Content-Type: application/json { "id": 1, "name": "feed fish", "isComplete": false }
Предыдущий код добавляет заголовок Content-Type и текст запроса JSON.
Выберите ссылку "Отправить запрос", которая находится над новой строкой запроса PUT.
Запрос PUT отправляется приложению, а ответ отображается в области ответа . Текст ответа пуст, а код состояния — 204.
Изучение и тестирование конечной точки DELETE
Этот пример приложения реализует одну конечную точку DELETE с помощью MapDelete
:
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
В обозревателе конечных точек щелкните правой кнопкой мыши конечную точку DELETE и выберите "Создать запрос".
Запрос DELETE добавляется в
TodoApi.http
.Замените
{id}
строку запроса DELETE на1
. Запрос DELETE должен выглядеть следующим образом:DELETE {{TodoApi_HostAddress}}/todoitems/1 ###
Выберите ссылку "Отправить запрос" для запроса DELETE.
Запрос DELETE отправляется приложению, а ответ отображается в области ответа . Текст ответа пуст, а код состояния — 204.
Использование API MapGroup
Пример кода приложения повторяет todoitems
префикс URL-адреса при каждой настройке конечной точки. API часто имеют группы конечных точек с общим префиксом URL-адреса, а MapGroup метод доступен для организации таких групп. Это уменьшает повторяющийся код и позволяет настраивать целые группы конечных точек с одним вызовом методов, таких как RequireAuthorization и WithMetadata.
Замените все содержимое Program.cs
следующим кодом:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", async (TodoDb db) =>
await db.Todos.ToListAsync());
todoItems.MapGet("/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
todoItems.MapGet("/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
Предыдущий код имеет следующие изменения:
- Добавьте
var todoItems = app.MapGroup("/todoitems");
для настройки группы с помощью префикса URL-адреса/todoitems
. - Изменяет все методы
app.Map<HttpVerb>
наtodoItems.Map<HttpVerb>
. - Удаляет префикс
/todoitems
URL-адреса изMap<HttpVerb>
вызовов метода.
Проверьте конечные точки, чтобы убедиться, что они работают одинаково.
Использование API TypedResults
Возврат TypedResults вместо Results имеет несколько преимуществ, включая тестируемость и автоматическое возвращение метаданных типа ответа для OpenAPI, чтобы описать конечную точку. Дополнительные сведения см. в разделе TypedResults и Results.
Методы Map<HttpVerb>
могут вызывать методы обработчика маршрутов вместо использования лямбда-кодов. Чтобы просмотреть пример, обновите Program.cs со следующим кодом:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
Теперь Map<HttpVerb>
код вызывает методы вместо лямбда-кодов:
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
Эти методы возвращают объекты, реализующие IResult и определяемые следующими способами TypedResults:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
Модульные тесты могут вызывать эти методы и проверять, что они возвращают правильный тип. Например, если метод GetAllTodos
:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
Код модульного теста может убедиться, что объект типа Ok<Todo[]> возвращается из метода обработчика. Например:
public async Task GetAllTodos_ReturnsOkOfTodosResult()
{
// Arrange
var db = CreateDbContext();
// Act
var result = await TodosApi.GetAllTodos(db);
// Assert: Check for the correct returned type
Assert.IsType<Ok<Todo[]>>(result);
}
Предотвращение избыточной публикации
В настоящее время пример приложения предоставляет весь объект Todo
. В рабочих приложениях подмножество модели часто используется для ограничения данных, которые могут быть входными и возвращаемыми. Это связано с несколькими причинами, и безопасность является основной. Подмножество модели обычно называется объектом передачи данных (DTO), моделью ввода или моделью представления. В этой статье используется DTO.
DTO можно использовать для:
- Предотвращение избыточной публикации.
- Скрытие свойств, которые клиенты не должны просматривать.
- Опустить некоторые свойства для уменьшения размера полезной нагрузки.
- Упрощение графов объектов, содержащих вложенные объекты. Сведенные графы объектов могут быть удобнее для клиентов.
Чтобы продемонстрировать подход с применением DTO, обновите класс Todo
, включив в него поле секрета:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Поле секрета должно быть скрыто в этом приложении, однако административное приложение может отобразить его.
Убедитесь, что вы можете запостить и извлечь секретное поле.
Создайте файл с именем TodoItemDTO.cs
со следующим кодом:
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
Замените содержимое Program.cs
файла следующим кодом, чтобы использовать эту модель DTO:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
RouteGroupBuilder todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Select(x => new TodoItemDTO(x)).ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db) {
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).Select(x => new TodoItemDTO(x)).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(new TodoItemDTO(todo))
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(TodoItemDTO todoItemDTO, TodoDb db)
{
var todoItem = new Todo
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};
db.Todos.Add(todoItem);
await db.SaveChangesAsync();
todoItemDTO = new TodoItemDTO(todoItem);
return TypedResults.Created($"/todoitems/{todoItem.Id}", todoItemDTO);
}
static async Task<IResult> UpdateTodo(int id, TodoItemDTO todoItemDTO, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
Убедитесь, что вы можете опубликовать и получить все поля, кроме секретного поля.
Устранение неполадок с готовым образцом
Если вы столкнулись с проблемой, которую не можете решить, сравните свой код с кодом готового проекта. Просмотрите или скачайте завершенный проект (порядок загрузки).
Следующие шаги
- Настройте параметры сериализации JSON.
- Обработка ошибок и исключений. Страница исключений разработчика включена по умолчанию в среде разработки для минимальных приложений API. Сведения об обработке ошибок и исключений см. в разделе "Обработка ошибок" в ASP.NET основных API.
- Пример тестирования для минимального приложения API см. в этом примере на GitHub.
- Поддержка OpenAPI в минимальных API.
- Краткое руководство. Публикация в Azure.
- Упорядочение ASP.NET основных минимальных API.
Подробнее
Краткий справочник по минимальным API
Архитектура минимальных API позволяет создавать API для HTTP с минимальным числом зависимостей. Они идеально подходят для микрослужб и приложений, которым нужен небольшой набор файлов, компонентов и зависимостей на платформе ASP.NET Core.
В этом руководстве описаны основы создания минимального API с помощью ASP.NET Core. Другим подходом к созданию API в ASP.NET Core является использование контроллеров. Сведения о выборе между минимальными API и API на основе контроллера вы можете найти в Обзоре API. Руководство по созданию проекта API на основе контроллеров , содержащих дополнительные функции, см. в разделе "Создание веб-API".
Обзор
В этом руководстве создается следующий API-интерфейс:
АПИ | Описание | Текст запроса | Текст ответа |
---|---|---|---|
GET /todoitems |
Получение всех элементов задач | нет | Массив элементов задач |
GET /todoitems/complete |
Получить выполненные задания | нет | Массив задач |
GET /todoitems/{id} |
Получение объекта по идентификатору | нет | Пункт задачи |
POST /todoitems |
Добавление нового элемента | Элемент задачи | Элемент задачи |
PUT /todoitems/{id} |
Обновление существующего элемента | Задача в списке дел | нет |
DELETE /todoitems/{id} |
Удаление элемента | нет | нет |
Предварительные условия
Visual Studio 2022 с рабочей нагрузкой ASP.NET и веб-разработка.
Создание проекта API
Запустите Visual Studio 2022 и нажмите Создать проект.
В диалоговом окне Создание нового проекта выполните следующие действия.
- Введите
Empty
в поле Поиск шаблонов. - Выберите шаблон ASP.NET Core Empty и нажмите кнопку "Далее".
- Введите
Присвойте проекту имя TodoApi и щелкните Далее.
В диалоговом окне Дополнительные сведения выполните следующие действия.
- Выберите .NET 7.0
- Снять флажок "Не использовать операторы верхнего уровня"
- Нажмите кнопку Создать
Изучение кода
Файл Program.cs
содержит следующий код:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Предыдущий код:
- Создает WebApplicationBuilder и WebApplication с предварительно настроенными значениями по умолчанию.
- Создает конечную точку
/
HTTP GET, которая возвращаетHello World!
:
Выполнить приложение
Нажмите клавиши CTRL+F5, чтобы выполнить запуск без отладчика.
Visual Studio отображает следующее диалоговое окно.
Выберите Да, чтобы сделать SSL-сертификат IIS Express доверенным.
Отобразится следующее диалоговое окно.
Выберите Да, если согласны доверять сертификату разработки.
Информацию о доверии к браузеру Firefox см. в статье Ошибка сертификата браузера Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
Visual Studio запускает Kestrel веб-сервер и открывает окно браузера.
Hello World!
отображается в браузере. Файл Program.cs
содержит минимальное, но полное приложение.
Добавление пакетов NuGet
Для поддержки возможностей базы данных и диагностики, которые используются в этом руководстве, необходимо добавить пакеты NuGet.
- В меню Средства выберите Диспетчер пакетов NuGet > Управление пакетами NuGet для решения.
- Откройте вкладку Browse (Обзор).
- Введите Microsoft.EntityFrameworkCore.InMemory в поле поиска и щелкните
Microsoft.EntityFrameworkCore.InMemory
. - Установите флажок "Проект" в правой области.
- В раскрывающемся списке "Версия" выберите последнюю версию 7, например
7.0.17
, и нажмите кнопку "Установить". - Следуйте приведенным выше инструкциям, чтобы добавить
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
пакет с последней доступной версией 7.
Классы контекста базы данных и модели
В папке проекта создайте файл с именем Todo.cs
со следующим кодом:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Приведенный выше код создает модель для этого приложения. Класс модели представляет данные, которыми управляет наше приложение.
Создайте файл с именем TodoDb.cs
со следующим кодом:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Предыдущий код определяет контекст базы данных, который является основным классом, который координирует функциональные возможности Entity Framework для модели данных. Этот класс является производным от класса Microsoft.EntityFrameworkCore.DbContext.
Добавление кода API
Замените содержимое файла Program.cs
приведенным ниже кодом.
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
Выделенный ниже код добавляет контекст базы данных в контейнер внедрения зависимостей (DI) и позволяет отображать исключения, связанные с базой данных:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
Контейнер DI предоставляет доступ к контексту базы данных и другим службам.
Создание пользовательского интерфейса тестирования API с помощью Swagger
Существует множество доступных средств тестирования веб-API, которые можно выбрать, и вы можете следовать инструкциям в этом руководстве по тестированию API с помощью собственного предпочтительного средства.
В этом руководстве используется пакет .NET NSwag.AspNetCore, который интегрирует средства Swagger для создания тестового пользовательского интерфейса, который соответствует спецификации OpenAPI:
- NSwag: библиотека .NET, которая интегрирует Swagger непосредственно в приложения ASP.NET Core, предоставляя ПО промежуточного слоя и конфигурацию.
- Swagger: набор средств с открытым исходным кодом, таких как OpenAPIGenerator и SwaggerUI, создающий страницы тестирования API, которые соответствуют спецификации OpenAPI.
- Спецификация OpenAPI: документ, описывающий возможности API на основе заметок XML и атрибутов в контроллерах и моделях.
Дополнительные сведения об использовании OpenAPI и NSwag с ASP.NET см. в документации по ASP.NET Core Web API с использованием Swagger / OpenAPI.
Установка инструментов Swagger
Выполните следующую команду:
dotnet add package NSwag.AspNetCore
Предыдущая команда добавляет пакет NSwag.AspNetCore , содержащий средства для создания документов Swagger и пользовательского интерфейса.
Настройка ПО промежуточного слоя Swagger
Добавьте следующий выделенный код перед тем, как
app
определяется в строкеvar app = builder.Build();
.using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList")); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddOpenApiDocument(config => { config.DocumentName = "TodoAPI"; config.Title = "TodoAPI v1"; config.Version = "v1"; }); var app = builder.Build();
В предыдущем коде:
builder.Services.AddEndpointsApiExplorer();
: включает обозреватель API, который представляет собой службу, которая предоставляет метаданные о HTTP-API. Обозреватель API используется Swagger для создания документа Swagger.builder.Services.AddOpenApiDocument(config => {...});
: добавляет генератор документов Swagger OpenAPI в службы приложений и настраивает его для предоставления дополнительных сведений об API, таких как название и версия. Сведения о предоставлении более надежных сведений об API см. в статье "Начало работы с NSwag" и ASP.NET CoreДобавьте следующий выделенный код в следующую строку после того, как
app
определён в строкеvar app = builder.Build();
var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseOpenApi(); app.UseSwaggerUi(config => { config.DocumentTitle = "TodoAPI"; config.Path = "/swagger"; config.DocumentPath = "/swagger/{documentName}/swagger.json"; config.DocExpansion = "list"; }); }
Предыдущий код включает ПО промежуточного слоя Swagger для обслуживания созданного документа JSON и пользовательского интерфейса Swagger. Swagger включен только в среде разработки. Включение Swagger в рабочей среде может предоставлять потенциально конфиденциальные сведения о структуре и реализации API.
Проверка публикации данных
В следующем коде создается Program.cs
конечная точка /todoitems
HTTP POST, которая добавляет данные в базу данных в памяти:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Запустите приложение. В браузере отображается ошибка 404, так как конечная /
точка больше не существует.
Конечная точка POST будет использоваться для добавления данных в приложение.
Оставив приложение запущенным, в вашем браузере перейдите на
https://localhost:<port>/swagger
, чтобы открыть страницу тестирования API, сгенерированную Swagger.На странице тестирования API Swagger выберите Post /todoitems>Попробовать.
Обратите внимание, что поле текста запроса содержит созданный формат примера, отражающий параметры API.
В тексте запроса введите JSON для элемента списка дел, не указывая необязательное
id
:{ "name":"walk dog", "isComplete":true }
Выберите Выполнить.
Swagger предоставляет область "Ответы" под кнопкой "Выполнить ".
Обратите внимание на некоторые полезные сведения:
- cURL: Swagger предоставляет пример команды cURL в синтаксисе Unix/Linux, которая может выполняться в командной строке с любой оболочкой bash, используюющей синтаксис Unix/Linux, включая Git Bash из Git для Windows.
- URL-адрес запроса: упрощенное представление HTTP-запроса, сделанного кодом JavaScript пользовательского интерфейса Swagger для вызова API. Фактические запросы могут включать такие сведения, как заголовки и параметры запроса, а также текст запроса.
- Ответ сервера: включает текст ответа и заголовки. Тело ответа показывает, что
id
было установлено на1
. - Код ответа: возвращен код состояния 201
HTTP
, указывающий, что запрос успешно обработан и привел к созданию нового ресурса.
Изучение конечных точек GET
Пример приложения реализует несколько конечных точек GET путем вызова MapGet
:
интерфейс прикладного программирования (API) | Описание | Текст запроса | Текст ответа |
---|---|---|---|
GET /todoitems |
Получите все элементы списка дел | нет | Массив задач |
GET /todoitems/complete |
Получение всех выполненных элементов заданий | нет | Массив элементов задач |
GET /todoitems/{id} |
Получение объекта по идентификатору | нет | Пункт списка дел |
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
Тестирование конечных точек GET
Протестируйте приложение, вызвав контрольные точки из браузера или через Swagger.
В Swagger выберите GET /todoitems>Попробуйте выполнить>Выполнить.
Кроме того, вызовите GET /todoitems из браузера, введя URI
http://localhost:<port>/todoitems
. Например:http://localhost:5001/todoitems
Вызов метода GET /todoitems
создает ответ, аналогичный следующему:
[
{
"id": 1,
"name": "walk dog",
"isComplete": true
}
]
Вызовите GET /todoitems/{id} в Swagger, чтобы вернуть данные из определенного идентификатора:
- Выберите GET /todoitems>Попробовать.
- Задайте для поля идентификатора значение
1
и нажмите кнопку "Выполнить".
Либо вызовите GET /todoitems из браузера, введя URI
https://localhost:<port>/todoitems/1
. Например:https://localhost:5001/todoitems/1
Ответ аналогичен следующему:
{ "id": 1, "name": "walk dog", "isComplete": true }
Это приложение использует базу данных в оперативной памяти. После перезапуска приложения запрос GET не возвращает никаких данных. Если данные не возвращаются, отправьте данные POST в приложение и повторите этот запрос GET.
Возвращаемые значения
ASP.NET Core автоматически сериализует объект в формат JSON и записывает данные JSON в тело сообщения ответа. Код ответа для этого типа возвращаемого значения равен 200 OK, что свидетельствует об отсутствии необработанных исключений. Необработанные исключения преобразуются в ошибки 5xx.
Типы возвращаемых значений могут представлять широкий спектр кодов состояний HTTP. Например, метод GET /todoitems/{id}
может возвращать два разных значения состояния:
- Если запрошенному идентификатору не соответствует ни один элемент, метод возвращает код ошибки 404NotFound.
- В противном случае метод возвращает ответ с кодом 200 и телом ответа в формате JSON. При возвращении
item
выходит ответ HTTP 200.
Изучение конечной точки PUT
Этот пример приложения реализует одну конечную точку PUT с помощью MapPut
:
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
Этот метод отличается от метода MapPost
только тем, что использует метод HTTP PUT. Успешный ответ возвращает состояние 204 (без содержимого). Согласно спецификации HTTP, запрос PUT требует, чтобы клиент отправлял всю обновленную сущность, а не только изменения. Чтобы обеспечить поддержку частичных обновлений, используйте HTTP PATCH.
Тестирование конечной точки PUT
В этом примере используется база данных в памяти, которая должна быть инициирована при каждом запуске приложения. При выполнении вызова PUT в базе данных уже должен существовать какой-либо элемент. Для этого перед вызовом PUT выполните вызов GET, чтобы убедиться в наличии такого элемента в базе данных.
Обновите элемент списка дел с Id = 1
и установите его имя на "feed fish"
.
Используйте Swagger для отправки запроса PUT:
Выберите Put /todoitems/{id}>Попробовать.
Задайте для поля идентификатора значение
1
.Задайте для текста запроса следующий код JSON:
{ "name": "feed fish", "isComplete": false }
Выберите Выполнить.
Изучить и протестировать конечную точку DELETE
Этот пример приложения реализует одну конечную точку DELETE с помощью MapDelete
:
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
Используйте Swagger для отправки запроса DELETE:
Выберите DELETE /todoitems/{id}>Попробовать.
Задайте для поля идентификатора значение
1
и нажмите кнопку "Выполнить".Запрос DELETE отправляется приложению, а ответ отображается на панели "Ответы ". Текст ответа пуст, а код состояния ответа сервера — 204.
Использование API MapGroup
Пример кода приложения повторяет todoitems
префикс URL-адреса каждый раз, когда настраивается конечная точка. API часто имеют группы конечных точек с общим префиксом URL-адреса, а MapGroup метод доступен для организации таких групп. Это уменьшает повторяющийся код и позволяет настраивать целые группы конечных точек с одним вызовом методов, таких как RequireAuthorization и WithMetadata.
Замените все содержимое Program.cs
следующим кодом:
using NSwag.AspNetCore;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(config =>
{
config.DocumentName = "TodoAPI";
config.Title = "TodoAPI v1";
config.Version = "v1";
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseOpenApi();
app.UseSwaggerUi(config =>
{
config.DocumentTitle = "TodoAPI";
config.Path = "/swagger";
config.DocumentPath = "/swagger/{documentName}/swagger.json";
config.DocExpansion = "list";
});
}
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", async (TodoDb db) =>
await db.Todos.ToListAsync());
todoItems.MapGet("/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
todoItems.MapGet("/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
Предыдущий код имеет следующие изменения:
- Добавьте
var todoItems = app.MapGroup("/todoitems");
для настройки группы с помощью префикса URL-адреса/todoitems
. - Изменяет все методы
app.Map<HttpVerb>
наtodoItems.Map<HttpVerb>
. - Удаляет префикс
/todoitems
URL из вызовов методаMap<HttpVerb>
.
Проверьте конечные точки, чтобы убедиться, что они работают одинаково.
Используйте API TypedResults
Возврат TypedResults вместо Results имеет несколько преимуществ, включая тестируемость и автоматическое возвращение метаданных типа ответа для OpenAPI, чтобы описать конечную точку. Дополнительные сведения см. в статье TypedResults и Results.
Методы Map<HttpVerb>
могут вызывать методы обработчика маршрутов вместо использования лямбда-кодов. Чтобы просмотреть пример, обновите Program.cs со следующим кодом:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
Теперь Map<HttpVerb>
код вызывает методы вместо лямбда-кодов:
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
Эти методы возвращают объекты, реализующие IResult и определяемые следующими способами TypedResults:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
Модульные тесты могут вызывать эти методы и проверять, что они возвращают правильный тип. Например, если метод - это GetAllTodos
:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
Код модульного теста может убедиться, что объект типа Ok<Todo[]> возвращается из метода обработчика. Например:
public async Task GetAllTodos_ReturnsOkOfTodosResult()
{
// Arrange
var db = CreateDbContext();
// Act
var result = await TodosApi.GetAllTodos(db);
// Assert: Check for the correct returned type
Assert.IsType<Ok<Todo[]>>(result);
}
Предотвращение избыточной публикации
В настоящее время пример приложения предоставляет весь объект Todo
. Промышленные приложения В производственных приложениях подмножество модели часто используется для ограничения данных, которые могут быть вводимыми и возвращаемыми. Это связано с несколькими причинами, и безопасность является основной. Подмножество модели обычно называется объектом передачи данных (DTO), моделью ввода или моделью представления. В этой статье используется DTO.
DTO можно использовать для следующих целей:
- Предотвращение избыточной публикации.
- Скрытие свойств, которые клиенты не должны просматривать.
- Исключить некоторые свойства для уменьшения размера нагрузки.
- Выравнивание графов данных, содержащих вложенные объекты. Сведенные графы объектов могут быть удобнее для клиентов.
Чтобы продемонстрировать подход с применением DTO, обновите класс Todo
, включив в него поле секрета:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Поле секрета должно быть скрыто в этом приложении, однако административное приложение может отобразить его.
Убедитесь, что вы можете отправить и получить секретное поле.
Создайте файл с именем TodoItemDTO.cs
со следующим кодом:
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
Замените содержимое Program.cs
файла следующим кодом, чтобы использовать эту модель DTO:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.Select(x => new TodoItemDTO(x)).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
app.MapPost("/todoitems", async (TodoItemDTO todoItemDTO, TodoDb db) =>
{
var todoItem = new Todo
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};
db.Todos.Add(todoItem);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});
app.MapPut("/todoitems/{id}", async (int id, TodoItemDTO todoItemDTO, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Убедитесь, что вы можете опубликовать и получить все поля, кроме секретного поля.
Устранение неполадок с готовым примером
Если вы столкнулись с проблемой, которую не можете решить, сравните свой код с кодом готового проекта. Просмотрите или скачайте завершенный проект (порядок загрузки).
Следующие шаги
- Настройте параметры сериализации JSON.
- Обработка ошибок и исключений. Страница исключений разработчика включена по умолчанию в среде разработки для минимальных приложений API. Сведения об обработке ошибок и исключений см. в разделе "Обработка ошибок" в ASP.NET основных API.
- Пример тестирования для минимального приложения API см. в этом примере на GitHub.
- Поддержка OpenAPI в минимальных API.
- Краткое руководство. Публикация в Azure.
- Упорядочение ASP.NET основных минимальных API.
Подробнее
Краткий справочник по минимальным API
Архитектура минимальных API позволяет создавать API для HTTP с минимальным числом зависимостей. Они идеально подходят для микрослужб и приложений, которым нужен небольшой набор файлов, компонентов и зависимостей на платформе ASP.NET Core.
В этом руководстве описаны основы создания минимального API с помощью ASP.NET Core. Другим подходом к созданию API в ASP.NET Core является использование контроллеров. Для помощи в выборе между минимальными API и API на основе контроллеров см. обзор API. Руководство по созданию проекта API на основе контроллеров , содержащих дополнительные функции, см. в разделе "Создание веб-API".
Обзор
В этом руководстве создается следующий API-интерфейс:
API | Описание | Текст запроса | Текст ответа |
---|---|---|---|
GET /todoitems |
Получение всех элементов задач | нет | Массив задач |
GET /todoitems/complete |
Получение элементов выполненных заданий из списка | нет | Массив элементов задач |
GET /todoitems/{id} |
Получение объекта по идентификатору | нет | Пункт списка дел |
POST /todoitems |
Добавление нового элемента | Задача в списке дел | Задача в списке дел |
PUT /todoitems/{id} |
Обновление существующего элемента | Задача в списке дел | нет |
DELETE /todoitems/{id} |
Удаление элемента | нет | нет |
Предварительные условия
- Visual Studio 2022 с рабочей нагрузкой ASP.NET и веб-разработка.
- Пакет SDK для .NET 6.0
Создание проекта API
Запустите Visual Studio 2022 и нажмите Создать проект.
В диалоговом окне Создание нового проекта выполните следующие действия.
- Введите
Empty
в поле Поиск шаблонов. - Выберите шаблон ASP.NET Core Empty и нажмите кнопку "Далее".
- Введите
Присвойте проекту имя TodoApi и щелкните Далее.
В диалоговом окне Дополнительные сведения выполните следующие действия.
- Выберите .NET 6.0
- Снять флажок "Не использовать операторы верхнего уровня"
- Нажмите кнопку Создать
Изучение кода
Файл Program.cs
содержит следующий код:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Предыдущий код:
- Создает WebApplicationBuilder и WebApplication с предварительно настроенными значениями по умолчанию.
- Создает конечную точку
/
HTTP GET, которая возвращаетHello World!
:
Выполнить приложение
Нажмите клавиши CTRL+F5, чтобы выполнить запуск без отладчика.
Visual Studio отображает следующее диалоговое окно.
Выберите Да, чтобы сделать SSL-сертификат IIS Express доверенным.
Отобразится следующее диалоговое окно.
Выберите Да, если согласны доверять сертификату разработки.
Сведения о доверии к браузеру Firefox см. в разделе Ошибка сертификата браузера Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
Visual Studio запускает Kestrel веб-сервер и открывает окно браузера.
Hello World!
отображается в браузере. Файл Program.cs
содержит минимальное, но полное приложение.
Добавление пакетов NuGet
Для поддержки возможностей базы данных и диагностики, которые используются в этом руководстве, необходимо добавить пакеты NuGet.
- В меню Средства выберите Диспетчер пакетов NuGet > Управление пакетами NuGet для решения.
- Выберите вкладку Обзор.
- Введите Microsoft.EntityFrameworkCore.InMemory в поле поиска и щелкните
Microsoft.EntityFrameworkCore.InMemory
. - Установите флажок "Проект" в правой области.
- В раскрывающемся списке "Версия" выберите последнюю версию 7, например
6.0.28
, и нажмите кнопку "Установить". - Следуйте приведенным выше инструкциям, чтобы добавить
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
пакет с последней доступной версией 7.
Классы контекста базы данных и модели
В папке проекта создайте файл с именем Todo.cs
со следующим кодом:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Приведенный выше код создает модель для этого приложения. Класс модели представляет данные, которыми управляет наше приложение.
Создайте файл с именем TodoDb.cs
со следующим кодом:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Предыдущий код определяет контекст базы данных, который является основным классом, который координирует функциональные возможности Entity Framework для модели данных. Этот класс является производным от класса Microsoft.EntityFrameworkCore.DbContext.
Добавление кода API
Замените содержимое файла Program.cs
приведенным ниже кодом.
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
Выделенный ниже код добавляет контекст базы данных в контейнер внедрения зависимостей (DI) и позволяет отображать исключения, связанные с базой данных:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
Контейнер DI предоставляет доступ к контексту базы данных и другим службам.
Создание пользовательского интерфейса тестирования API с помощью Swagger
Существует множество доступных средств тестирования веб-API, которые можно выбрать, и вы можете следовать инструкциям в этом руководстве по тестированию API с помощью собственного предпочтительного средства.
В этом руководстве используется пакет .NET NSwag.AspNetCore, который интегрирует средства Swagger для создания тестового пользовательского интерфейса, который соответствует спецификации OpenAPI:
- NSwag: библиотека .NET, которая интегрирует Swagger непосредственно в приложения ASP.NET Core, предоставляя ПО промежуточного слоя и конфигурацию.
- Swagger: набор средств с открытым исходным кодом, таких как OpenAPIGenerator и SwaggerUI, создающий страницы тестирования API, которые соответствуют спецификации OpenAPI.
- Спецификация OpenAPI: документ, описывающий возможности API на основе заметок XML и атрибутов в контроллерах и моделях.
Дополнительные сведения об использовании OpenAPI и NSwag с ASP.NET см. в документации по ASP.NET Core веб-API с использованием Swagger / OpenAPI.
Установка инструментов Swagger
Выполните следующую команду:
dotnet add package NSwag.AspNetCore
Предыдущая команда добавляет пакет NSwag.AspNetCore , содержащий средства для создания документов Swagger и пользовательского интерфейса.
Настройка промежуточного ПО Swagger
В Program.cs добавьте следующие
using
инструкции в верхней части:using NSwag.AspNetCore;
Добавьте следующий выделенный код перед определением
app
на строкеvar app = builder.Build();
using NSwag.AspNetCore; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList")); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddOpenApiDocument(config => { config.DocumentName = "TodoAPI"; config.Title = "TodoAPI v1"; config.Version = "v1"; }); var app = builder.Build();
В предыдущем коде:
builder.Services.AddEndpointsApiExplorer();
: включает обозреватель API, который представляет собой службу, которая предоставляет метаданные о HTTP-API. Обозреватель API используется в Swagger для генерации документа Swagger.builder.Services.AddOpenApiDocument(config => {...});
: добавляет генератор документов Swagger OpenAPI в службы приложений и настраивает его для предоставления дополнительных сведений об API, таких как название и версия. Сведения о предоставлении более надежных сведений об API см. в статье "Начало работы с NSwag" и ASP.NET CoreДобавьте выделенный код в следующую строку после того, как
app
определен в строкеvar app = builder.Build();
.var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseOpenApi(); app.UseSwaggerUi(config => { config.DocumentTitle = "TodoAPI"; config.Path = "/swagger"; config.DocumentPath = "/swagger/{documentName}/swagger.json"; config.DocExpansion = "list"; }); }
Предыдущий код включает ПО промежуточного слоя Swagger для обслуживания созданного документа JSON и пользовательского интерфейса Swagger. Swagger включен только в среде разработки. Включение Swagger в рабочей среде может предоставлять потенциально конфиденциальные сведения о структуре и реализации API.
Проверка публикации данных
В следующем коде создается конечная точка HTTP POST, которая добавляет данные в базу данных, хранящуюся в оперативной памяти.
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Запустите приложение. В браузере отображается ошибка 404, так как конечная /
точка больше не существует.
Конечная точка POST будет использоваться для добавления данных в приложение.
Приложение должно оставаться запущенным; в браузере перейдите на
https://localhost:<port>/swagger
, чтобы открыть страницу тестирования API, созданную с помощью Swagger.На странице тестирования API Swagger выберите Post /todoitems, затем нажмите Try it out.
Обратите внимание, что поле текста запроса содержит созданный формат примера, отражающий параметры API.
В тексте запроса введите JSON для элемента списка дел, не указывая необязательное
id
:{ "name":"walk dog", "isComplete":true }
Выберите Выполнить.
Swagger предоставляет область "Ответы" под кнопкой "Выполнить ".
Обратите внимание на некоторые полезные сведения:
- cURL: Swagger предоставляет пример команды cURL в синтаксисе Unix/Linux, которая может выполняться в командной строке с любой оболочкой bash, используюющей синтаксис Unix/Linux, включая Git Bash из Git для Windows.
- URL-адрес запроса: упрощенное представление HTTP-запроса, сделанного кодом JavaScript пользовательского интерфейса Swagger для вызова API. Фактические запросы могут включать такие сведения, как заголовки и параметры запроса, а также текст запроса.
- Ответ сервера: включает текст ответа и заголовки. Текст ответа показывает, что
id
было установлено в1
. - Код ответа: возвращен код состояния 201
HTTP
, указывающий, что запрос успешно обработан и привел к созданию нового ресурса.
Изучение конечных точек GET
Пример приложения реализует несколько конечных точек GET путем вызова MapGet
:
интерфейс программирования приложений (API) | Описание | Текст запроса | Текст ответа |
---|---|---|---|
GET /todoitems |
Получить все задачи из списка дел | нет | Массив элементов задач |
GET /todoitems/complete |
Получение всех выполненных элементов заданий | нет | Массив задач на выполнение |
GET /todoitems/{id} |
Получение элемента по идентификатору | нет | Пункт списка дел |
app.MapGet("/", () => "Hello World!");
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
Тестирование конечных точек GET
Протестируйте приложение, вызвав конечные точки из браузера или Swagger.
В Swagger выберите GET /todoitems>Попробовать выполнить.>
Кроме того, вызовите GET /todoitems из браузера, введя универсальный код ресурса (URI
http://localhost:<port>/todoitems
). Например:http://localhost:5001/todoitems
Вызов метода GET /todoitems
создает ответ, аналогичный следующему:
[
{
"id": 1,
"name": "walk dog",
"isComplete": true
}
]
Вызовите GET /todoitems/{id} в Swagger, чтобы вернуть данные из определенного идентификатора:
- Выберите GET /todoitems>Попробуйте.
- Задайте для поля идентификатора значение
1
и нажмите кнопку "Выполнить".
Кроме того, выполните вызов GET /todoitems из браузера, введя URI
https://localhost:<port>/todoitems/1
. Например,https://localhost:5001/todoitems/1
Ответ аналогичен следующему:
{ "id": 1, "name": "walk dog", "isComplete": true }
Это приложение использует базу данных в оперативной памяти. После перезапуска приложения запрос GET не возвращает никаких данных. Если данные не возвращаются, отправьте данные с помощью POST в приложение и повторите попытку выполнить запрос GET.
Возвращаемые значения
ASP.NET Core автоматически сериализует объект в формат JSON и записывает данные JSON в тело сообщения ответа. Код ответа для этого типа возвращаемого значения равен 200 OK, что свидетельствует об отсутствии необработанных исключений. Необработанные исключения преобразуются в ошибки 5xx.
Типы возвращаемых значений могут представлять широкий спектр кодов состояний HTTP. Например, метод GET /todoitems/{id}
может возвращать два разных значения состояния:
- Если запрошенному идентификатору не соответствует ни один элемент, метод возвращает код ошибки 404NotFound.
- В противном случае метод возвращает код 200 с телом ответа JSON. Возврат
item
приводит к появлению ответа HTTP 200.
Изучение конечной точки PUT
Этот пример приложения реализует одну конечную точку PUT с помощью MapPut
:
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
Этот метод отличается от метода MapPost
только тем, что использует метод HTTP PUT. Успешный ответ возвращает состояние 204 (без содержимого). Согласно спецификации HTTP, запрос PUT требует, чтобы клиент отправлял всю обновленную сущность, а не только изменения. Чтобы обеспечить поддержку частичных обновлений, используйте HTTP PATCH.
Тестирование конечной точки PUT
В этом примере используется база данных в памяти, которая должна быть инициирована при каждом запуске приложения. При выполнении вызова PUT в базе данных уже должен существовать какой-либо элемент. Для этого перед вызовом PUT выполните вызов GET, чтобы убедиться в наличии такого элемента в базе данных.
Обновите элемент, который имеет Id = 1
, и установите его имя на "feed fish"
.
Используйте Swagger для отправки запроса PUT:
Выберите Put /todoitems/{id}>Попробуйте это.
Задайте для поля идентификатора значение
1
.Задайте для текста запроса следующий код JSON:
{ "name": "feed fish", "isComplete": false }
Выберите Выполнить.
Изучить и протестировать конечную точку DELETE
Этот пример приложения реализует одну конечную точку DELETE с помощью MapDelete
:
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
Используйте Swagger для отправки запроса DELETE:
Выберите DELETE /todoitems/{id}>Попробовать.
Задайте для поля идентификатора значение
1
и нажмите кнопку "Выполнить".Запрос DELETE отправляется приложению, а ответ отображается на панели "Ответы ". Текст ответа пуст, а код состояния ответа сервера — 204.
Предотвращение избыточной публикации
В настоящее время пример приложения предоставляет весь объект Todo
. В производственных приложениях часто используется подмножество модели для ограничения данных, которые могут быть введены и возвращены. Это связано с несколькими причинами, и безопасность является основной. Подмножество модели обычно называется объектом передачи данных (DTO), моделью ввода или моделью представления. В этой статье используется DTO.
DTO можно использовать для:
- Предотвращение избыточной публикации.
- Скрытие свойств, которые клиенты не должны просматривать.
- Исключить некоторые свойства для уменьшения размера данных.
- Уплощение графов объектов, содержащих вложенные объекты. Сведенные графы объектов могут быть удобнее для клиентов.
Чтобы продемонстрировать подход с применением DTO, обновите класс Todo
, включив в него поле секрета:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Поле секрета должно быть скрыто в этом приложении, однако административное приложение может отобразить его.
Убедитесь, что вы можете отправить и получить доступ к секретному полю.
Создайте файл с именем TodoItemDTO.cs
со следующим кодом:
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
Замените содержимое Program.cs
файла следующим кодом, чтобы использовать эту модель DTO:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.Select(x => new TodoItemDTO(x)).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
app.MapPost("/todoitems", async (TodoItemDTO todoItemDTO, TodoDb db) =>
{
var todoItem = new Todo
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};
db.Todos.Add(todoItem);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});
app.MapPut("/todoitems/{id}", async (int id, TodoItemDTO todoItemDTO, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Убедитесь, что вы можете опубликовать и получить все поля, кроме секретного поля.
Тестирование минимального API
Пример тестирования для минимального приложения API см. в этом примере на GitHub.
Публикация в Azure
Сведения о развертывании в Azure см. в разделе Краткое руководство. Развертывание веб-приложения ASP.NET.
Дополнительные ресурсы
Архитектура минимальных API позволяет создавать API для HTTP с минимальным числом зависимостей. Они идеально подходят для микрослужб и приложений, которые хотят включать только минимальные файлы, функции и зависимости в ASP.NET Core.
В этом руководстве описаны основы создания минимального API с помощью ASP.NET Core. Другим подходом к созданию API в ASP.NET Core является использование контроллеров. Сведения о выборе между минимальными API и API на основе контроллера см. в обзоре API. Руководство по созданию проекта API на основе контроллеров , содержащих дополнительные функции, см. в разделе "Создание веб-API".
Обзор
В этом руководстве создается следующий API-интерфейс:
API | Описание | Текст запроса | Текст ответа |
---|---|---|---|
GET /todoitems |
Получение всех элементов задач | нет | Массив задач |
GET /todoitems/complete |
Получение элементов выполненных заданий из списка | нет | Массив элементов задач |
GET /todoitems/{id} |
Получение элемента по идентификатору | нет | Пункт списка дел |
POST /todoitems |
Добавление нового элемента | Элемент задачи | Пункт задания |
PUT /todoitems/{id} |
Обновление существующего элемента | Пункт задачи | нет |
DELETE /todoitems/{id} |
Удаление элемента | нет | нет |
Требуемые условия
Visual Studio 2022 с рабочей нагрузкой ASP.NET и веб-разработка.
Создание проекта API
Запустите Visual Studio 2022 и нажмите Создать проект.
В диалоговом окне Создание нового проекта выполните следующие действия.
- Введите
Empty
в поле Поиск шаблонов. - Выберите шаблон ASP.NET Core Empty и нажмите кнопку "Далее".
- Введите
Присвойте проекту имя TodoApi и щелкните Далее.
В диалоговом окне Дополнительные сведения выполните следующие действия.
- Выберите .NET 8.0 (долгосрочная поддержка)
- Снимите флажок «Не использовать операторы верхнего уровня»
- Нажмите кнопку Создать
Изучение кода
Файл Program.cs
содержит следующий код:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Предыдущий код:
- Создает WebApplicationBuilder и WebApplication с предварительно настроенными значениями по умолчанию.
- Создает конечную точку
/
HTTP GET, которая возвращаетHello World!
:
Выполнить приложение
Нажмите клавиши CTRL+F5, чтобы выполнить запуск без отладчика.
Visual Studio отображает следующее диалоговое окно.
Выберите Да, чтобы сделать SSL-сертификат IIS Express доверенным.
Отобразится следующее диалоговое окно.
Выберите Да, если согласны доверять сертификату разработки.
Сведения о доверии к браузеру Firefox см. в разделе Ошибка сертификата браузера Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
Visual Studio запускает Kestrel веб-сервер и открывает окно браузера.
Hello World!
отображается в браузере. Файл Program.cs
содержит минимальное, но полное приложение.
Закройте окно браузера.
Добавление пакетов NuGet
Для поддержки возможностей базы данных и диагностики, которые используются в этом руководстве, необходимо добавить пакеты NuGet.
- В меню Средства выберите Диспетчер пакетов NuGet > Управление пакетами NuGet для решения.
- Откройте вкладку Browse.
- Введите Microsoft.EntityFrameworkCore.InMemory в поле поиска и щелкните
Microsoft.EntityFrameworkCore.InMemory
. - Установите флажок Проект в области справа и выберите Установить.
- Добавьте пакет
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
по представленным выше инструкциям.
Классы контекста базы данных и модели
- В папке проекта создайте файл с именем
Todo.cs
со следующим кодом:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Приведенный выше код создает модель для этого приложения. Класс модели представляет данные, которыми управляет наше приложение.
- Создайте файл с именем
TodoDb.cs
со следующим кодом:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Предыдущий код определяет контекст базы данных, который является основным классом, который координирует функциональные возможности Entity Framework для модели данных. Этот класс является производным от класса Microsoft.EntityFrameworkCore.DbContext.
Добавление кода API
- Замените содержимое файла
Program.cs
приведенным ниже кодом.
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
Выделенный ниже код добавляет контекст базы данных в контейнер внедрения зависимостей (DI) и позволяет отображать исключения, связанные с базой данных:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
Контейнер DI предоставляет доступ к контексту базы данных и другим службам.
В этом руководстве для тестирования API используются обозреватель конечных точек и HTTP-файлы .
Проверка публикации данных
В следующем коде создается конечная точка HTTP POST Program.cs
, которая добавляет данные в базу данных в оперативной памяти /todoitems
.
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Запустить приложение. В браузере отображается ошибка 404, так как конечная /
точка больше не существует.
Конечная точка POST будет использоваться для добавления данных в приложение.
Выберите Вид>Другие окна>Обозреватель конечных точек.
Щелкните правой кнопкой мыши конечную точку POST и выберите "Создать запрос".
Новый файл создается в папке
TodoApi.http
проекта с содержимым, аналогичным следующему примеру:@TodoApi_HostAddress = https://localhost:7031 Post {{TodoApi_HostAddress}}/todoitems ###
- Первая строка создает переменную, используемую для всех конечных точек.
- Следующая строка определяет запрос POST.
- Тройной хэштег (
###
) является разделителем запросов: то, что следует за ним, относится к другому запросу.
Запрос POST нуждается в заголовках и тексте. Чтобы определить эти части запроса, добавьте следующие строки сразу после строки запроса POST:
Content-Type: application/json { "name":"walk dog", "isComplete":true }
Предыдущий код добавляет заголовок Content-Type и текст запроса JSON. Теперь файл TodoApi.http должен выглядеть следующим образом, но с номером порта:
@TodoApi_HostAddress = https://localhost:7057 Post {{TodoApi_HostAddress}}/todoitems Content-Type: application/json { "name":"walk dog", "isComplete":true } ###
Запустите приложение.
Выберите ссылку "Отправить запрос", которая находится над строкой
POST
запроса.Запрос POST отправляется приложению, а ответ отображается в области ответа .
Изучение конечных точек GET
Пример приложения реализует несколько конечных точек GET путем вызова MapGet
:
API (Программный интерфейс приложения) | Описание | Текст запроса | Текст ответа |
---|---|---|---|
GET /todoitems |
Получение всех элементов задач | нет | Массив элементов задач |
GET /todoitems/complete |
Получение всех выполненных элементов заданий | нет | Список задач |
GET /todoitems/{id} |
Получение объекта по идентификатору | нет | Задача |
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
Тестирование конечных точек GET
Протестируйте приложение, вызвав GET
конечные точки из браузера или используя обозреватель конечных точек. Ниже приведены действия для обозревателя конечных точек.
В обозревателе конечных точек щелкните правой кнопкой мыши первую конечную точку GET и выберите команду "Создать запрос".
В файл добавляется следующее содержимое
TodoApi.http
:Get {{TodoApi_HostAddress}}/todoitems ###
Выберите ссылку "Отправить запрос", которая находится над новой
GET
строкой запроса.Запрос GET отправляется приложению, а ответ отображается в области ответа .
Текст ответа аналогичен следующему json:
[ { "id": 1, "name": "walk dog", "isComplete": true } ]
В обозревателе конечных точек щелкните правой кнопкой мыши на конечную точку
/todoitems/{id}
GET и выберите Создать запрос. В файл добавляется следующее содержимоеTodoApi.http
:GET {{TodoApi_HostAddress}}/todoitems/{id} ###
Замените
{id}
на1
.Выберите ссылку "Отправить запрос", которая находится над новой строкой GET запроса.
Запрос GET отправляется приложению, а ответ отображается в области ответа .
Текст ответа аналогичен следующему json:
{ "id": 1, "name": "walk dog", "isComplete": true }
Это приложение использует базу данных в оперативной памяти. После перезапуска приложения запрос GET не возвращает никаких данных. Если данные не возвращаются, отправьте данные с помощью POST в приложение и попытайтесь выполнить запрос, используя метод GET, еще раз.
Возвращаемые значения
ASP.NET Core автоматически сериализует объект в формат JSON и записывает данные JSON в тело сообщения ответа. Код ответа для этого типа возвращаемого значения равен 200 OK, что свидетельствует об отсутствии необработанных исключений. Необработанные исключения преобразуются в ошибки 5xx.
Типы возвращаемых значений могут представлять широкий спектр кодов состояний HTTP. Например, метод GET /todoitems/{id}
может возвращать два разных значения состояния:
- Если запрошенному идентификатору не соответствует ни один элемент, метод возвращает код ошибки 404NotFound.
- В противном случае метод возвращает код 200 с телом ответа JSON. При возврате
item
выдается ответ HTTP 200.
Изучение конечной точки PUT
Этот пример приложения реализует одну конечную точку PUT с помощью MapPut
:
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
Этот метод отличается от метода MapPost
только тем, что использует метод HTTP PUT. Успешный ответ возвращает состояние 204 (без содержимого). Согласно спецификации HTTP, запрос PUT требует, чтобы клиент отправлял всю обновленную сущность, а не только изменения. Чтобы обеспечить поддержку частичных обновлений, используйте HTTP PATCH.
Тестирование конечной точки PUT
В этом примере используется база данных в памяти, которая должна быть инициирована при каждом запуске приложения. При выполнении вызова PUT в базе данных уже должен существовать какой-либо элемент. Для этого перед вызовом PUT выполните вызов GET, чтобы убедиться в наличии такого элемента в базе данных.
Обновите элемент дел с Id = 1
и установите его имя как "feed fish"
.
В обозревателе конечных точек щелкните правой кнопкой мыши конечную точку PUT и выберите " Создать запрос".
В файл добавляется следующее содержимое
TodoApi.http
:Put {{TodoApi_HostAddress}}/todoitems/{id} ###
В строке запроса PUT замените
{id}
на1
.Добавьте следующие строки сразу после строки запроса PUT:
Content-Type: application/json { "name": "feed fish", "isComplete": false }
Предыдущий код добавляет заголовок Content-Type и текст запроса JSON.
Выберите ссылку "Отправить запрос", которая находится над строкой нового запроса PUT.
Запрос PUT отправляется приложению, а ответ отображается в области ответа . Текст ответа пуст, а код состояния — 204.
Изучите и тестируйте конечную точку DELETE
Этот пример приложения реализует одну конечную точку DELETE с помощью MapDelete
:
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
В обозревателе конечных точек щелкните правой кнопкой мыши конечную точку DELETE и выберите "Создать запрос".
Запрос DELETE добавляется в
TodoApi.http
.Замените
{id}
строку запроса DELETE на1
. Запрос DELETE должен выглядеть следующим образом:DELETE {{TodoApi_HostAddress}}/todoitems/1 ###
Выберите ссылку "Отправить запрос" для запроса DELETE.
Запрос DELETE отправляется приложению, а ответ отображается в области ответа . Текст ответа пуст, а код состояния — 204.
Использование API MapGroup
Пример кода приложения повторяет todoitems
префикс URL-адреса каждый раз при настройке конечной точки. API часто имеют группы конечных точек с общим префиксом URL-адреса, а MapGroup метод доступен для организации таких групп. Это уменьшает повторяющийся код и позволяет настраивать целые группы конечных точек с одним вызовом методов, таких как RequireAuthorization и WithMetadata.
Замените все содержимое Program.cs
следующим кодом:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", async (TodoDb db) =>
await db.Todos.ToListAsync());
todoItems.MapGet("/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
todoItems.MapGet("/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
Предыдущий код имеет следующие изменения:
- Добавьте
var todoItems = app.MapGroup("/todoitems");
для настройки группы, используя префикс URL-адреса/todoitems
. - Изменяет все методы
app.Map<HttpVerb>
наtodoItems.Map<HttpVerb>
. - Удаляет префикс URL-адреса
/todoitems
из вызовов методаMap<HttpVerb>
.
Проверьте конечные точки, чтобы убедиться, что они работают одинаково.
Использование API TypedResults
Возврат TypedResults, а не Results, имеет несколько преимуществ, включая тестируемость и автоматическое возвращение метаданных типа ответа, чтобы OpenAPI описал конечную точку. Дополнительные сведения см. в разделе TypedResults и Results.
Методы Map<HttpVerb>
могут вызывать методы обработчика маршрутов вместо использования лямбда-кодов. Чтобы просмотреть пример, обновите Program.cs со следующим кодом:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
Теперь Map<HttpVerb>
код вызывает методы вместо лямбда-кодов:
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
Эти методы возвращают объекты, реализующие IResult и определяемые следующими способами TypedResults:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
Модульные тесты могут вызывать эти методы и проверять, что они возвращают правильный тип. Например, если метод GetAllTodos
:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
Код модульного теста может убедиться, что объект типа Ok<Todo[]> возвращается из метода обработчика. Например:
public async Task GetAllTodos_ReturnsOkOfTodosResult()
{
// Arrange
var db = CreateDbContext();
// Act
var result = await TodosApi.GetAllTodos(db);
// Assert: Check for the correct returned type
Assert.IsType<Ok<Todo[]>>(result);
}
Предотвращение избыточной публикации
В настоящее время пример приложения предоставляет весь объект Todo
. Производственные приложения В производственных приложениях подмножество модели часто используется для ограничения данных, которые могут загружаться и возвращаться. Это связано с несколькими причинами, и безопасность является основной. Подмножество модели обычно называется объектом передачи данных (DTO), моделью ввода или моделью представления. В этой статье используется DTO.
DTO можно использовать для:
- Предотвращение избыточной публикации.
- Скрытие свойств, которые клиенты не должны просматривать.
- Исключите некоторые свойства, чтобы уменьшить размер нагрузки.
- Преобразование графов объектов для упрощения структуры, содержащей вложенные объекты. Сведенные графы объектов могут быть удобнее для клиентов.
Чтобы продемонстрировать подход с применением DTO, обновите класс Todo
, включив в него поле секрета:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Поле секрета должно быть скрыто в этом приложении, однако административное приложение может отобразить его.
Проверьте, что вы можете отправить и получить конфиденциальное поле.
Создайте файл с именем TodoItemDTO.cs
со следующим кодом:
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
Замените содержимое Program.cs
файла следующим кодом, чтобы использовать эту модель DTO:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
RouteGroupBuilder todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Select(x => new TodoItemDTO(x)).ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db) {
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).Select(x => new TodoItemDTO(x)).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(new TodoItemDTO(todo))
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(TodoItemDTO todoItemDTO, TodoDb db)
{
var todoItem = new Todo
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};
db.Todos.Add(todoItem);
await db.SaveChangesAsync();
todoItemDTO = new TodoItemDTO(todoItem);
return TypedResults.Created($"/todoitems/{todoItem.Id}", todoItemDTO);
}
static async Task<IResult> UpdateTodo(int id, TodoItemDTO todoItemDTO, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
Убедитесь, что вы можете опубликовать и получить все поля, кроме секретного поля.
Устранение неполадок с завершенным примером
Если вы столкнулись с проблемой, которую не можете решить, сравните свой код с кодом готового проекта. Просмотрите или скачайте завершенный проект (порядок загрузки).
Следующие шаги
- Настройте параметры сериализации JSON.
- Обработка ошибок и исключений. Страница исключений разработчика включена по умолчанию в среде разработки для минимальных приложений API. Сведения об обработке ошибок и исключений см. в разделе "Обработка ошибок" в ASP.NET основных API.
- Пример тестирования для минимального приложения API см. в этом примере на GitHub.
- Поддержка OpenAPI в минимальных API.
- Краткое руководство. Публикация в Azure.
- Упорядочение ASP.NET основных минимальных API.
Подробнее
Краткий справочник по минимальным API
ASP.NET Core