Руководство. Создание минимального 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 | Description | Текст запроса | Текст ответа |
---|---|---|---|
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-файлы .
Проверка публикации данных
В следующем коде создается 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 будет использоваться для добавления данных в приложение.
Выберите "Просмотреть>другие конечные точки Windows".>
Щелкните правой кнопкой мыши конечную точку 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 | Description | Текст запроса | Текст ответа |
---|---|---|---|
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}
может возвращать два разных значения состояния:
- Если запрошенному идентификатору не соответствует ни один элемент, метод возвращает код ошибки 404 NotFound.
- В противном случае метод возвращает код 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");
для настройки группы с помощью префикса/todoitems
URL-адреса. - Изменяет все
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-интерфейс:
API | Description | Текст запроса | Текст ответа |
---|---|---|---|
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 см . в документации по веб-API ASP.NET Core с помощью 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 | Description | Текст запроса | Текст ответа |
---|---|---|---|
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>Try it.
- Задайте для поля идентификатора значение
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}
может возвращать два разных значения состояния:
- Если запрошенному идентификатору не соответствует ни один элемент, метод возвращает код ошибки 404 NotFound.
- В противном случае метод возвращает код 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}>Try it.
Задайте для поля идентификатора значение
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");
для настройки группы с помощью префикса/todoitems
URL-адреса. - Изменяет все
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 | Description | Текст запроса | Текст ответа |
---|---|---|---|
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 для решения.
- Откройте вкладку Browse (Обзор).
- Введите 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 см . в документации по веб-API ASP.NET Core с помощью 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.
Проверка публикации данных
В следующем коде создается 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 | Description | Текст запроса | Текст ответа |
---|---|---|---|
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>Try it.
- Задайте для поля идентификатора значение
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}
может возвращать два разных значения состояния:
- Если запрошенному идентификатору не соответствует ни один элемент, метод возвращает код ошибки 404 NotFound.
- В противном случае метод возвращает код 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}>Try it.
Задайте для поля идентификатора значение
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 | Description | Текст запроса | Текст ответа |
---|---|---|---|
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-файлы .
Проверка публикации данных
В следующем коде создается 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 будет использоваться для добавления данных в приложение.
Выберите "Просмотреть>другие конечные точки Windows".>
Щелкните правой кнопкой мыши конечную точку 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 | Description | Текст запроса | Текст ответа |
---|---|---|---|
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}
может возвращать два разных значения состояния:
- Если запрошенному идентификатору не соответствует ни один элемент, метод возвращает код ошибки 404 NotFound.
- В противном случае метод возвращает код 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");
для настройки группы с помощью префикса/todoitems
URL-адреса. - Изменяет все
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
ASP.NET Core