Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Note
Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 10 deste artigo.
Warning
Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.
Por Rick Anderson e Tom Dykstra
APIs mínimas são arquitetadas para criar APIs HTTP com dependências mínimas. Eles são ideais para microsserviços e aplicativos que desejam incluir apenas os arquivos, recursos e dependências mínimos no ASP.NET Core.
Este tutorial ensina os conceitos básicos da criação de uma API mínima com o ASP.NET Core. Outra abordagem para criar APIs no ASP.NET Core é usar controladores. Para obter ajuda com a escolha entre APIs mínimas e APIs baseadas em controlador, consulte Visão geral de APIs. Para obter um tutorial sobre como criar um projeto de API com base em controladores que contém mais recursos, consulte Criar uma API da Web.
Overview
Este tutorial cria a seguinte API:
| API | Description | Corpo de solicitação | Corpo da resposta |
|---|---|---|---|
GET /todoitems |
Consiga todos os itens to-do | None | Lista de tarefas a fazer |
GET /todoitems/complete |
Obtenha to-do itens concluídos | None | Lista de tarefas a fazer |
GET /todoitems/{id} |
Obter um item por ID | None | Item de tarefa |
POST /todoitems |
Adicionar um novo item | Item de tarefa | Item de tarefa |
PUT /todoitems/{id} |
Atualizar um item existente | Item de tarefa | None |
DELETE /todoitems/{id} |
Excluir um item | None | None |
Prerequisites
Visual Studio 2022 com a carga de trabalho de ASP.NET e desenvolvimento web .
Criar um projeto de API
Inicie o Visual Studio 2022 e selecione Criar um novo projeto.
Na caixa de diálogo Criar um novo projeto :
- Introduza
Emptyna caixa de pesquisa Procurar modelos . - Selecione o modelo ASP.NET Core Empty e selecione Next.
- Introduza
Nomeie o projeto TodoApi e selecione Next.
Na caixa de diálogo Informações adicionais :
- Selecione .NET 9.0
- Desmarque Não usar instruções de nível superior
- Selecione Criar
Examine o código
O arquivo Program.cs contém o seguinte código:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
O código anterior:
- Cria um WebApplicationBuilder e um WebApplication com padrões pré-configurados.
- Cria um ponto de extremidade
/HTTP GET que retornaHello World!.
Executar o aplicativo
Pressione Ctrl+F5 para executar sem o depurador.
O Visual Studio exibe a seguinte caixa de diálogo:
Selecione Sim se confiar no certificado SSL do IIS Express.
A seguinte caixa de diálogo é exibida:
Selecione Sim se concordar em confiar no certificado de desenvolvimento.
Para obter informações sobre como confiar no navegador Firefox, consulte o erro de certificado do Firefox SEC_ERROR_INADEQUATE_KEY_USAGE .
O Visual Studio inicia o servidor Web e abre uma janela do Kestrel navegador.
Hello World! é exibido no navegador. O arquivo Program.cs contém um aplicativo mínimo, mas completo.
Feche a janela do navegador.
Adicionar pacotes NuGet
Os pacotes NuGet devem ser adicionados para dar suporte ao banco de dados e ao diagnóstico usados neste tutorial.
- No menu Ferramentas, selecione Gerenciador de Pacotes NuGet > Gerenciar Pacotes NuGet para Solução.
- Selecione a guia Procurar.
- Selecione Incluir Prelease.
- Digite Microsoft.EntityFrameworkCore.InMemory na caixa de pesquisa e selecione
Microsoft.EntityFrameworkCore.InMemory. - Marque a caixa de seleção Projeto no painel direito e selecione Instalar.
- Siga as instruções anteriores para adicionar o pacote
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.
As classes de contexto de modelo e banco de dados
- Na pasta do projeto, crie um arquivo chamado
Todo.cscom o seguinte código:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
O código anterior cria o modelo para este aplicativo. Um modelo é uma classe que representa os dados que o aplicativo gerencia.
- Crie um arquivo chamado
TodoDb.cscom o seguinte código:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
O código anterior define o contexto do banco de dados, que é a classe principal que coordena a funcionalidade do Entity Framework para um modelo de dados. Esta classe deriva da classe Microsoft.EntityFrameworkCore.DbContext.
Adicionar o código da API
- Substitua o conteúdo do arquivo
Program.cscom o seguinte código:
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();
O código realçado a seguir adiciona o contexto do banco de dados ao contêiner de injeção de dependência (DI) e permite a exibição das exceções relacionadas ao banco de dados.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
O contêiner DI fornece acesso ao contexto do banco de dados e outros serviços.
Este tutorial usa o Endpoints Explorer e arquivos .http para testar a API.
Dados de publicação de teste
O código a seguir no Program.cs cria um ponto de extremidade HTTP POST /todoitems que adiciona dados ao banco de dados em memória:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Execute o aplicativo. O navegador exibe um erro 404 porque não há mais um endpoint /.
O endpoint POST será usado para adicionar dados à aplicação.
Selecione Ver>Outras Janelas>Explorador de Endpoints.
Clique com o botão direito em POST endpoint e selecione Gerar solicitação.
Um novo arquivo é criado na pasta do projeto chamada
TodoApi.http, com conteúdo semelhante ao exemplo a seguir:@TodoApi_HostAddress = https://localhost:7031 POST {{TodoApi_HostAddress}}/todoitems ###- A primeira linha cria uma variável que é usada para todos os pontos de extremidade.
- A próxima linha define uma solicitação POST.
- A linha de três hashtags (
###) é um delimitador de solicitação: o que vem depois é para uma solicitação diferente.
A solicitação POST precisa de cabeçalhos e um corpo. Para definir essas partes da solicitação, adicione as seguintes linhas imediatamente após a linha de solicitação POST:
Content-Type: application/json { "name":"walk dog", "isComplete":true }O código anterior adiciona um cabeçalho Content-Type e um corpo de solicitação JSON. O ficheiro TodoApi.http agora deve assemelhar-se ao exemplo abaixo, mas com o seu número de porta:
@TodoApi_HostAddress = https://localhost:7057 POST {{TodoApi_HostAddress}}/todoitems Content-Type: application/json { "name":"walk dog", "isComplete":true } ###Execute o aplicativo.
Selecione o link Enviar solicitação que está acima da linha de solicitação
POST.
A solicitação POST é enviada para a aplicação e a resposta é exibida no painel de Resposta .
Analise os endpoints GET
A aplicação de exemplo implementa vários endpoints GET chamando MapGet:
| API | Description | Corpo de solicitação | Corpo da resposta |
|---|---|---|---|
GET /todoitems |
Consiga todos os itens to-do | None | Lista de tarefas a fazer |
GET /todoitems/complete |
Obter todos os itens to-do concluídos | None | Lista de tarefas a fazer |
GET /todoitems/{id} |
Obter um item por ID | None | Item de tarefa |
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());
Testar os endpoints GET
Teste a aplicação chamando os endpoints GET de um navegador ou usando o Explorador de Endpoints. As etapas a seguir são para Endpoints Explorer.
No Explorador de Pontos de Extremidade, clique com o botão direito do rato no primeiro ponto de extremidade GET e selecione Gerar pedido.
O seguinte conteúdo é adicionado ao arquivo
TodoApi.http:GET {{TodoApi_HostAddress}}/todoitems ###Selecione o link Enviar solicitação que está acima da nova
GETlinha de solicitação.A solicitação GET é enviada para o aplicativo e a resposta é exibida no painel Resposta .
O corpo da resposta é semelhante ao seguinte JSON:
[ { "id": 1, "name": "walk dog", "isComplete": true } ]No Explorador de Endpoints, clique com o botão direito do rato no
/todoitems/{id}endpoint GET e selecionar Gerar pedido. O seguinte conteúdo é adicionado ao arquivoTodoApi.http:GET {{TodoApi_HostAddress}}/todoitems/{id} ###Substitua
{id}por1.Selecione o link Enviar solicitação que está acima da nova linha de solicitação GET.
A solicitação GET é enviada para o aplicativo e a resposta é exibida no painel Resposta .
O corpo da resposta é semelhante ao seguinte JSON:
{ "id": 1, "name": "walk dog", "isComplete": true }
Este aplicativo usa um banco de dados na memória. Se o aplicativo for reiniciado, a solicitação GET não retornará nenhum dado. Se nenhum dado for retornado, POSTE os dados no aplicativo e tente a solicitação GET novamente.
Valores de retorno
ASP.NET Core serializa automaticamente o objeto para JSON e grava o JSON no corpo da mensagem de resposta. O código de resposta para esse tipo de retorno é 200 OK, supondo que não haja exceções não tratadas. As exceções não tratadas são traduzidas em erros 5xx.
Os tipos de retorno podem representar uma ampla gama de códigos de status HTTP. Por exemplo, GET /todoitems/{id} pode retornar dois valores de status diferentes:
- Se nenhum item corresponder à ID solicitada, o método retornará um 404 statusNotFound código de erro.
- Caso contrário, o método retorna 200 com um corpo de resposta JSON. O retorno
itemresulta em uma resposta HTTP 200.
Examine o ponto de extremidade PUT
A aplicação de exemplo implementa um único endpoint PUT usando 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();
});
Este método é semelhante ao método MapPost, exceto que usa HTTP PUT. Uma resposta bem-sucedida retorna 204 (Sem Conteúdo). De acordo com a especificação HTTP, uma solicitação PUT requer que o cliente envie toda a entidade atualizada, não apenas as alterações. Para suportar atualizações parciais, use HTTP PATCH.
Testar o ponto de extremidade PUT
Este exemplo usa um banco de dados na memória que deve ser inicializado sempre que o aplicativo é iniciado. Deve haver um item no banco de dados antes de fazer uma chamada PUT. Chame GET para garantir que há um item no banco de dados antes de fazer uma chamada PUT.
Atualize o item to-do que tem Id = 1 e defina seu nome como "feed fish".
No Explorador de Pontos de Extremidade, clique com o botão direito do rato no ponto de extremidade PUT e selecione Gerar pedido.
O seguinte conteúdo é adicionado ao arquivo
TodoApi.http:PUT {{TodoApi_HostAddress}}/todoitems/{id} ###Na linha de solicitação PUT, substitua
{id}por1.Adicione as seguintes linhas imediatamente após a linha de solicitação PUT:
Content-Type: application/json { "id": 1, "name": "feed fish", "isComplete": false }O código anterior adiciona um cabeçalho Content-Type e um corpo de solicitação JSON.
Selecione o link Enviar solicitação que está acima da nova linha de solicitação PUT.
A solicitação PUT é enviada para o aplicativo e a resposta é exibida no painel Resposta . O corpo da resposta está vazio e o código de status é 204.
Examinar e testar o endpoint DELETE
O aplicativo de exemplo implementa um único endpoint DELETE usando 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();
});
No Endpoints Explorer, clique com o botão direito no endpoint DELETE e selecione Gerar solicitação.
Uma solicitação DELETE é adicionada ao
TodoApi.http.Substitua
{id}na linha de solicitação DELETE por1. A solicitação DELETE deve se parecer com o exemplo a seguir:DELETE {{TodoApi_HostAddress}}/todoitems/1 ###Selecione o link Enviar solicitação para a solicitação DELETE.
A solicitação DELETE é enviada para o aplicativo e a resposta é exibida no painel Resposta . O corpo da resposta está vazio e o código de status é 204.
Utilizar a API MapGroup
O código da aplicação de exemplo repete o prefixo de URL todoitems sempre que estabelece um endpoint. As APIs geralmente têm grupos de pontos de extremidade com um prefixo de URL comum, e o método MapGroup está disponível para ajudar a organizar esses grupos. Ele reduz o código repetitivo e permite personalizar grupos inteiros de endpoints com uma única chamada para métodos como RequireAuthorization e WithMetadata.
Substitua o conteúdo do Program.cs pelo seguinte código:
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();
O código anterior tem as seguintes alterações:
- Adiciona
var todoItems = app.MapGroup("/todoitems");para configurar o grupo usando o prefixo de URL/todoitems. - Altera todos os métodos
app.Map<HttpVerb>paratodoItems.Map<HttpVerb>. - Remove o prefixo de URL
/todoitemsdas chamadas de métodoMap<HttpVerb>.
Teste os pontos de extremidade para verificar se eles funcionam da mesma forma.
Usar uma API TypedResults
Retornar TypedResults em vez de Results possui várias vantagens, entre as quais se incluem a testabilidade e o retorno automático dos metadados do tipo de resposta para que a OpenAPI descreva o endpoint. Para obter mais informações, consulte TypedResults vs Results.
Os métodos Map<HttpVerb> podem chamar métodos de manipulador de rota em vez de usar funções lambda. Para ver um exemplo, atualize Program.cs com o seguinte código:
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();
}
O código Map<HttpVerb> agora chama métodos em vez de lambdas:
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);
Esses métodos retornam objetos que implementam IResult e são definidos por 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();
}
Os testes de unidade podem chamar esses métodos e testar se eles retornam o tipo correto. Por exemplo, se o método for GetAllTodos:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
O código de teste de unidade pode verificar se um objeto do tipo Ok<Todo[]> é retornado do método manipulador. Por exemplo:
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);
}
Evitar a sobrepostagem
Atualmente, o aplicativo de exemplo expõe todo o objeto Todo. Em aplicativos de produção, um subconjunto do modelo é frequentemente usado para restringir os dados que podem ser inseridos e retornados. Há várias razões por trás disso e a segurança é uma das principais. O subconjunto de um modelo é geralmente referido como um objeto de transferência de dados (DTO), modelo de entrada ou modelo de exibição.
DTO é usado neste artigo.
Um DTO pode ser usado para:
- Evite a sobrepublicação.
- Oculte propriedades que os clientes não devem visualizar.
- Omita algumas propriedades para reduzir o tamanho da carga útil.
- Nivelar gráficos de objetos que contêm objetos aninhados. Gráficos de objetos achatados podem ser mais convenientes para os clientes.
Para demonstrar a abordagem DTO, atualize a classe Todo para incluir um campo secreto:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
O campo secreto precisa ser oculto deste aplicativo, mas um aplicativo administrativo pode optar por expô-lo.
Verifique se você pode postar e obter o campo secreto.
Crie um arquivo chamado TodoItemDTO.cs com o seguinte código:
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);
}
Substitua o conteúdo do arquivo Program.cs com o seguinte código para usar esse modelo 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();
}
Verifique se você pode postar e obter todos os campos, exceto o campo secreto.
Solução de problemas com o exemplo concluído
Se você encontrar um problema que não pode resolver, compare seu código com o projeto concluído. Veja ou faça o download do projeto concluído (como fazer o download).
Próximos passos
- Configure as opções de serialização JSON.
- Lidar com erros e exceções: a página de exceção do desenvolvedor está ativada por padrão no ambiente de desenvolvimento para aplicações minimalistas de API. Para obter informações sobre como lidar com erros e exceções, consulte Manipular erros em ASP.NET APIs principais.
- Para obter um exemplo de teste de um aplicativo de API mínimo, consulte este exemplo do GitHub.
- Suporte a OpenAPI em APIs mínimas.
- Guia de início rápido: publique no Azure.
- Organizando ASP.NET APIs mínimas principais.
Learn more
APIs mínimas são arquitetadas para criar APIs HTTP com dependências mínimas. Eles são ideais para microsserviços e aplicativos que desejam incluir apenas os arquivos, recursos e dependências mínimos no ASP.NET Core.
Este tutorial ensina os conceitos básicos da criação de uma API mínima com o ASP.NET Core. Outra abordagem para criar APIs no ASP.NET Core é usar controladores. Para obter ajuda na escolha entre APIs mínimas e APIs baseadas em controlador, consulte Visão geral de APIs. Para obter um tutorial sobre como criar um projeto de API com base em controladores que contém mais recursos, consulte Criar uma API da Web.
Overview
Este tutorial cria a seguinte API:
| API | Description | Corpo de solicitação | Corpo da resposta |
|---|---|---|---|
GET /todoitems |
Consiga todos os itens to-do | None | Lista de tarefas a fazer |
GET /todoitems/complete |
Obtenha to-do itens concluídos | None | Lista de tarefas a fazer |
GET /todoitems/{id} |
Obter um item por ID | None | Item de tarefa |
POST /todoitems |
Adicionar um novo item | Item de tarefa | Item de tarefa |
PUT /todoitems/{id} |
Atualizar um item existente | Item de tarefa | None |
DELETE /todoitems/{id} |
Excluir um item | None | None |
Prerequisites
Visual Studio 2022 com a carga de trabalho de ASP.NET e desenvolvimento web .
Criar um projeto de API
Inicie o Visual Studio 2022 e selecione Criar um novo projeto.
Na caixa de diálogo Criar um novo projeto :
- Introduza
Emptyna caixa de pesquisa Procurar modelos . - Selecione o modelo ASP.NET Core Empty e selecione Next.
- Introduza
Nomeie o projeto TodoApi e selecione Next.
Na caixa de diálogo Informações adicionais :
- Selecione .NET 7.0
- Desmarque Não usar instruções de nível superior
- Selecione Criar
Examine o código
O arquivo Program.cs contém o seguinte código:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
O código anterior:
- Cria um WebApplicationBuilder e um WebApplication com padrões pré-configurados.
- Cria um ponto de extremidade HTTP GET
/que retornaHello World!:
Executar o aplicativo
Pressione Ctrl+F5 para executar sem o depurador.
O Visual Studio exibe a seguinte caixa de diálogo:
Selecione Sim se confiar no certificado SSL do IIS Express.
A seguinte caixa de diálogo é exibida:
Selecione Sim se concordar em confiar no certificado de desenvolvimento.
Para obter informações sobre como confiar no navegador Firefox, consulte o erro de certificado do Firefox SEC_ERROR_INADEQUATE_KEY_USAGE .
O Visual Studio inicia o servidor Web e abre uma janela do Kestrel navegador.
Hello World! é exibido no navegador. O arquivo Program.cs contém um aplicativo mínimo, mas completo.
Adicionar pacotes NuGet
Os pacotes NuGet devem ser adicionados para dar suporte ao banco de dados e ao diagnóstico usados neste tutorial.
- No menu Ferramentas, selecione Gerenciador de Pacotes NuGet > Gerenciar Pacotes NuGet para Solução.
- Selecione a guia Procurar.
- Digite Microsoft.EntityFrameworkCore.InMemory na caixa de pesquisa e selecione
Microsoft.EntityFrameworkCore.InMemory. - Marque a caixa de seleção Projeto no painel direito.
- No menu suspenso Versão , selecione a versão 7 mais recente disponível, por exemplo
7.0.17, e selecione Instalar. - Siga as instruções anteriores para adicionar o pacote
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCorecom a versão mais recente 7 disponível.
As classes de contexto de modelo e banco de dados
Na pasta do projeto, crie um arquivo chamado Todo.cs com o seguinte código:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
O código anterior cria o modelo para este aplicativo. Um modelo é uma classe que representa os dados que o aplicativo gerencia.
Crie um arquivo chamado TodoDb.cs com o seguinte código:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
O código anterior define o contexto do banco de dados, que é a classe principal que coordena a funcionalidade do Entity Framework para um modelo de dados. Esta classe deriva da classe Microsoft.EntityFrameworkCore.DbContext.
Adicionar o código da API
Substitua o conteúdo do arquivo Program.cs com o seguinte código:
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();
O código realçado a seguir adiciona o contexto do banco de dados ao contêiner de injeção de dependência (DI) e permite a exibição das exceções relacionadas ao banco de dados.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
O contêiner DI fornece acesso ao contexto do banco de dados e outros serviços.
Criar interface para teste de API com Swagger
Há muitas ferramentas de teste de API da Web disponíveis para escolher, e você pode seguir as etapas introdutórias de teste de API deste tutorial com sua própria ferramenta preferida.
Este tutorial utiliza o pacote .NET NSwag.AspNetCore, que integra ferramentas Swagger para gerar uma interface do usuário de teste aderente à especificação OpenAPI:
- NSwag: Uma biblioteca .NET que integra o Swagger diretamente em aplicativos ASP.NET Core, fornecendo middleware e configuração.
- Swagger: Um conjunto de ferramentas de código aberto, como OpenAPIGenerator e SwaggerUI, que geram páginas de teste de API que seguem a especificação OpenAPI.
- Especificação OpenAPI: um documento que descreve os recursos da API, com base no XML e anotações de atributo dentro dos controladores e modelos.
Para obter mais informações sobre como usar OpenAPI e NSwag com ASP.NET, consulte documentação da ASP.NET Core para Web API com Swagger/OpenAPI.
Instalar ferramentas Swagger
Execute o seguinte comando:
dotnet add package NSwag.AspNetCore
O comando anterior adiciona o pacote NSwag.AspNetCore , que contém ferramentas para gerar documentos e interface do usuário do Swagger.
Configurar middleware do Swagger
Adicione o seguinte código realçado antes
appseja definido na linhavar 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();
No código anterior:
builder.Services.AddEndpointsApiExplorer();: Habilita o API Explorer, que é um serviço que fornece metadados sobre a API HTTP. O API Explorer é utilizado pelo Swagger para gerar o documento do Swagger.builder.Services.AddOpenApiDocument(config => {...});: Adiciona o gerador de documentos Swagger OpenAPI aos serviços do aplicativo e o configura para fornecer mais informações sobre a API, como seu título e versão. Para obter informações sobre como fornecer detalhes de API mais robustos, consulte Introdução ao NSwag e ao ASP.NET CoreAdicione o seguinte código realçado à próxima linha depois que
appfor definido na linhavar 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"; }); }O código anterior habilita o middleware do Swagger para servir o documento JSON gerado e a interface do usuário do Swagger. O Swagger só é habilitado em um ambiente de desenvolvimento. Habilitar o Swagger em um ambiente de produção pode expor detalhes potencialmente confidenciais sobre a estrutura e a implementação da API.
Dados de publicação de teste
O código a seguir no Program.cs cria um ponto de extremidade HTTP POST /todoitems que adiciona dados ao banco de dados em memória:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Execute o aplicativo. O navegador exibe um erro 404 porque não há mais um endpoint /.
O endpoint POST será usado para adicionar dados à aplicação.
Com o aplicativo ainda em execução, no navegador, navegue até
https://localhost:<port>/swaggerpara exibir a página de teste de API gerada pelo Swagger.
Na página de teste da API do Swagger, selecione Post /todoitems>Experimente.
Observe que o campo Corpo da solicitação contém um formato de exemplo gerado que reflete os parâmetros da API.
No corpo da solicitação, insira JSON para um item to-do, sem especificar o
idopcional:{ "name":"walk dog", "isComplete":true }Selecione Executar.
O Swagger fornece um painel Respostas abaixo do botão Executar .
Observe alguns dos detalhes úteis:
- cURL: Swagger fornece um exemplo de comando cURL na sintaxe Unix/Linux, que pode ser executado na linha de comando com qualquer shell bash que use sintaxe Unix/Linux, incluindo Git Bash do Git para Windows.
- URL de solicitação: uma representação simplificada da solicitação HTTP feita pelo código JavaScript da interface do usuário do Swagger para a chamada de API. As solicitações reais podem incluir detalhes como cabeçalhos e parâmetros de consulta e um corpo de solicitação.
- Resposta do servidor: Inclui o corpo da resposta e os cabeçalhos. O corpo da resposta mostra que o
idfoi configurado como1. - Código de resposta: Um código de status de
HTTP201 foi retornado, indicando que a solicitação foi processada com êxito e resultou na criação de um novo recurso.
Analise os endpoints GET
A aplicação de exemplo implementa vários endpoints GET chamando MapGet:
| API | Description | Corpo de solicitação | Corpo da resposta |
|---|---|---|---|
GET /todoitems |
Consiga todos os itens to-do | None | Lista de tarefas a fazer |
GET /todoitems/complete |
Obter todos os itens to-do concluídos | None | Lista de tarefas a fazer |
GET /todoitems/{id} |
Obter um item por ID | None | Item de tarefa |
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());
Testar os endpoints GET
Teste a aplicação chamando os endpoints a partir de um navegador ou Swagger.
Em Swagger, selecione GET /todoitems>Experimente já>Executar.
Como alternativa, chame GET /todoitems de um navegador inserindo o URI
http://localhost:<port>/todoitems. Por exemplo,http://localhost:5001/todoitems
A chamada para GET /todoitems produz uma resposta semelhante à seguinte:
[
{
"id": 1,
"name": "walk dog",
"isComplete": true
}
]
Chame GET /todoitems/{id} no Swagger para retornar dados de uma id específica:
- Selecione GET /todoitems>Experimente.
- Defina o campo id como
1e selecione Executar.
Como alternativa, chame GET /todoitems de um navegador inserindo o URI
https://localhost:<port>/todoitems/1. Por exemplo,https://localhost:5001/todoitems/1A resposta é semelhante à seguinte:
{ "id": 1, "name": "walk dog", "isComplete": true }
Este aplicativo usa um banco de dados na memória. Se o aplicativo for reiniciado, a solicitação GET não retornará nenhum dado. Se nenhum dado for retornado, POSTE os dados no aplicativo e tente a solicitação GET novamente.
Valores de retorno
ASP.NET Core serializa automaticamente o objeto para JSON e grava o JSON no corpo da mensagem de resposta. O código de resposta para esse tipo de retorno é 200 OK, supondo que não haja exceções não tratadas. As exceções não tratadas são traduzidas em erros 5xx.
Os tipos de retorno podem representar uma ampla gama de códigos de status HTTP. Por exemplo, GET /todoitems/{id} pode retornar dois valores de status diferentes:
- Se nenhum item corresponder à ID solicitada, o método retornará um 404 statusNotFound código de erro.
- Caso contrário, o método retorna 200 com um corpo de resposta JSON. O retorno
itemresulta em uma resposta HTTP 200.
Examine o ponto de extremidade PUT
A aplicação de exemplo implementa um único endpoint PUT usando 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();
});
Este método é semelhante ao método MapPost, exceto que usa HTTP PUT. Uma resposta bem-sucedida retorna 204 (Sem Conteúdo). De acordo com a especificação HTTP, uma solicitação PUT requer que o cliente envie toda a entidade atualizada, não apenas as alterações. Para suportar atualizações parciais, use HTTP PATCH.
Testar o ponto de extremidade PUT
Este exemplo usa um banco de dados na memória que deve ser inicializado sempre que o aplicativo é iniciado. Deve haver um item no banco de dados antes de fazer uma chamada PUT. Chame GET para garantir que há um item no banco de dados antes de fazer uma chamada PUT.
Atualize o item to-do que tem Id = 1 e defina seu nome como "feed fish".
Use Swagger para enviar uma solicitação PUT:
Selecione Colocar /todoitems/{id}>Experimente.
Defina o campo id como
1.Defina o corpo da solicitação para o seguinte JSON:
{ "name": "feed fish", "isComplete": false }Selecione Executar.
Examinar e testar o endpoint DELETE
O aplicativo de exemplo implementa um único endpoint DELETE usando 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();
});
Use Swagger para enviar uma solicitação DELETE:
Selecione DELETE /todoitems/{id}>Experimente.
Defina o campo ID como
1e selecione Executar.A solicitação DELETE é enviada para a aplicação e a resposta é exibida no painel de Respostas do . O corpo da resposta está vazio e o código de status da resposta do Server é 204.
Utilizar a API MapGroup
O código da aplicação de exemplo repete o prefixo de URL todoitems sempre que estabelece um endpoint. As APIs geralmente têm grupos de pontos de extremidade com um prefixo de URL comum, e o método MapGroup está disponível para ajudar a organizar esses grupos. Ele reduz o código repetitivo e permite personalizar grupos inteiros de endpoints com uma única chamada para métodos como RequireAuthorization e WithMetadata.
Substitua o conteúdo do Program.cs pelo seguinte código:
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();
O código anterior tem as seguintes alterações:
- Adiciona
var todoItems = app.MapGroup("/todoitems");para configurar o grupo usando o prefixo de URL/todoitems. - Altera todos os métodos
app.Map<HttpVerb>paratodoItems.Map<HttpVerb>. - Remove o prefixo de URL
/todoitemsdas chamadas de métodoMap<HttpVerb>.
Teste os pontos de extremidade para verificar se eles funcionam da mesma forma.
Usar uma API TypedResults
Retornar TypedResults em vez de Results possui várias vantagens, entre as quais se incluem a testabilidade e o retorno automático dos metadados do tipo de resposta para que a OpenAPI descreva o endpoint. Para obter mais informações, consulte TypedResults vs Results.
Os métodos Map<HttpVerb> podem chamar métodos de manipulador de rota em vez de usar funções lambda. Para ver um exemplo, atualize Program.cs com o seguinte código:
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();
}
O código Map<HttpVerb> agora chama métodos em vez de lambdas:
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);
Esses métodos retornam objetos que implementam IResult e são definidos por 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();
}
Os testes de unidade podem chamar esses métodos e testar se eles retornam o tipo correto. Por exemplo, se o método for GetAllTodos:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
O código de teste de unidade pode verificar se um objeto do tipo Ok<Todo[]> é retornado do método manipulador. Por exemplo:
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);
}
Evitar a sobrepostagem
Atualmente, o aplicativo de exemplo expõe todo o objeto Todo. Aplicativos de produção Em aplicativos de produção, um subconjunto do modelo é frequentemente usado para restringir os dados que podem ser inseridos e retornados. Há várias razões por trás disso e a segurança é uma das principais. O subconjunto de um modelo é geralmente referido como um objeto de transferência de dados (DTO), modelo de entrada ou modelo de exibição.
DTO é usado neste artigo.
Um DTO pode ser usado para:
- Evite a sobrepublicação.
- Oculte propriedades que os clientes não devem visualizar.
- Omita algumas propriedades para reduzir o tamanho da carga útil.
- Nivelar gráficos de objetos que contêm objetos aninhados. Gráficos de objetos achatados podem ser mais convenientes para os clientes.
Para demonstrar a abordagem DTO, atualize a classe Todo para incluir um campo secreto:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
O campo secreto precisa ser oculto deste aplicativo, mas um aplicativo administrativo pode optar por expô-lo.
Verifique se você pode postar e obter o campo secreto.
Crie um arquivo chamado TodoItemDTO.cs com o seguinte código:
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);
}
Substitua o conteúdo do arquivo Program.cs com o seguinte código para usar esse modelo 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>();
}
Verifique se você pode postar e obter todos os campos, exceto o campo secreto.
Solução de problemas com o exemplo concluído
Se você encontrar um problema que não pode resolver, compare seu código com o projeto concluído. Veja ou faça o download do projeto concluído (como fazer o download).
Próximos passos
- Configure as opções de serialização JSON.
- Lidar com erros e exceções: a página de exceção do desenvolvedor está ativada por padrão no ambiente de desenvolvimento para aplicações minimalistas de API. Para obter informações sobre como lidar com erros e exceções, consulte Manipular erros em ASP.NET APIs principais.
- Para obter um exemplo de teste de um aplicativo de API mínimo, consulte este exemplo do GitHub.
- Suporte a OpenAPI em APIs mínimas.
- Guia de início rápido: publique no Azure.
- Organizando ASP.NET APIs mínimas principais.
Learn more
APIs mínimas são arquitetadas para criar APIs HTTP com dependências mínimas. Eles são ideais para microsserviços e aplicativos que desejam incluir apenas os arquivos, recursos e dependências mínimos no ASP.NET Core.
Este tutorial ensina os conceitos básicos da criação de uma API mínima com o ASP.NET Core. Outra abordagem para criar APIs no ASP.NET Core é usar controladores. Para obter ajuda na escolha entre APIs mínimas e APIs baseadas em controlador, consulte Visão geral de APIs. Para obter um tutorial sobre como criar um projeto de API com base em controladores que contém mais recursos, consulte Criar uma API da Web.
Overview
Este tutorial cria a seguinte API:
| API | Description | Corpo de solicitação | Corpo da resposta |
|---|---|---|---|
GET /todoitems |
Consiga todos os itens to-do | None | Lista de tarefas a fazer |
GET /todoitems/complete |
Obtenha to-do itens concluídos | None | Lista de tarefas a fazer |
GET /todoitems/{id} |
Obter um item por ID | None | Item de tarefa |
POST /todoitems |
Adicionar um novo item | Item de tarefa | Item de tarefa |
PUT /todoitems/{id} |
Atualizar um item existente | Item de tarefa | None |
DELETE /todoitems/{id} |
Excluir um item | None | None |
Prerequisites
- Visual Studio 2022 com a carga de trabalho de ASP.NET e desenvolvimento web .
- SDK do .NET 6
Criar um projeto de API
Inicie o Visual Studio 2022 e selecione Criar um novo projeto.
Na caixa de diálogo Criar um novo projeto :
- Introduza
Emptyna caixa de pesquisa Procurar modelos . - Selecione o modelo ASP.NET Core Empty e selecione Next.
- Introduza
Nomeie o projeto TodoApi e selecione Next.
Na caixa de diálogo Informações adicionais :
- Selecione .NET 6.0
- Desmarque Não usar instruções de nível superior
- Selecione Criar
Examine o código
O arquivo Program.cs contém o seguinte código:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
O código anterior:
- Cria um WebApplicationBuilder e um WebApplication com padrões pré-configurados.
- Cria um ponto de extremidade HTTP GET
/que retornaHello World!:
Executar o aplicativo
Pressione Ctrl+F5 para executar sem o depurador.
O Visual Studio exibe a seguinte caixa de diálogo:
Selecione Sim se confiar no certificado SSL do IIS Express.
A seguinte caixa de diálogo é exibida:
Selecione Sim se concordar em confiar no certificado de desenvolvimento.
Para obter informações sobre como confiar no navegador Firefox, consulte o erro de certificado do Firefox SEC_ERROR_INADEQUATE_KEY_USAGE .
O Visual Studio inicia o servidor Web e abre uma janela do Kestrel navegador.
Hello World! é exibido no navegador. O arquivo Program.cs contém um aplicativo mínimo, mas completo.
Adicionar pacotes NuGet
Os pacotes NuGet devem ser adicionados para dar suporte ao banco de dados e ao diagnóstico usados neste tutorial.
- No menu Ferramentas, selecione Gerenciador de Pacotes NuGet > Gerenciar Pacotes NuGet para Solução.
- Selecione a guia Procurar.
- Digite Microsoft.EntityFrameworkCore.InMemory na caixa de pesquisa e selecione
Microsoft.EntityFrameworkCore.InMemory. - Marque a caixa de seleção Projeto no painel direito.
- No menu suspenso Versão , selecione a versão 7 mais recente disponível, por exemplo
6.0.28, e selecione Instalar. - Siga as instruções anteriores para adicionar o pacote
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCorecom a versão mais recente 7 disponível.
As classes de contexto de modelo e banco de dados
Na pasta do projeto, crie um arquivo chamado Todo.cs com o seguinte código:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
O código anterior cria o modelo para este aplicativo. Um modelo é uma classe que representa os dados que o aplicativo gerencia.
Crie um arquivo chamado TodoDb.cs com o seguinte código:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
O código anterior define o contexto do banco de dados, que é a classe principal que coordena a funcionalidade do Entity Framework para um modelo de dados. Esta classe deriva da classe Microsoft.EntityFrameworkCore.DbContext.
Adicionar o código da API
Substitua o conteúdo do arquivo Program.cs com o seguinte código:
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();
O código realçado a seguir adiciona o contexto do banco de dados ao contêiner de injeção de dependência (DI) e permite a exibição das exceções relacionadas ao banco de dados.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
O contêiner DI fornece acesso ao contexto do banco de dados e outros serviços.
Criar interface para teste de API com Swagger
Há muitas ferramentas de teste de API da Web disponíveis para escolher, e você pode seguir as etapas introdutórias de teste de API deste tutorial com sua própria ferramenta preferida.
Este tutorial utiliza o pacote .NET NSwag.AspNetCore, que integra ferramentas Swagger para gerar uma interface do usuário de teste aderente à especificação OpenAPI:
- NSwag: Uma biblioteca .NET que integra o Swagger diretamente em aplicativos ASP.NET Core, fornecendo middleware e configuração.
- Swagger: Um conjunto de ferramentas de código aberto, como OpenAPIGenerator e SwaggerUI, que geram páginas de teste de API que seguem a especificação OpenAPI.
- Especificação OpenAPI: um documento que descreve os recursos da API, com base no XML e anotações de atributo dentro dos controladores e modelos.
Para obter mais informações sobre como usar OpenAPI e NSwag com ASP.NET, consulte documentação da ASP.NET Core para Web API com Swagger/OpenAPI.
Instalar ferramentas Swagger
Execute o seguinte comando:
dotnet add package NSwag.AspNetCore
O comando anterior adiciona o pacote NSwag.AspNetCore , que contém ferramentas para gerar documentos e interface do usuário do Swagger.
Configurar middleware do Swagger
Em Program.cs adicione as seguintes instruções
usingna parte superior:using NSwag.AspNetCore;Adicione o seguinte código realçado antes
appseja definido na linhavar 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();
No código anterior:
builder.Services.AddEndpointsApiExplorer();: Habilita o API Explorer, que é um serviço que fornece metadados sobre a API HTTP. O API Explorer é utilizado pelo Swagger para gerar o documento do Swagger.builder.Services.AddOpenApiDocument(config => {...});: Adiciona o gerador de documentos Swagger OpenAPI aos serviços do aplicativo e o configura para fornecer mais informações sobre a API, como seu título e versão. Para obter informações sobre como fornecer detalhes de API mais robustos, consulte Introdução ao NSwag e ao ASP.NET CoreAdicione o seguinte código realçado à próxima linha depois que
appfor definido na linhavar 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"; }); }O código anterior habilita o middleware do Swagger para servir o documento JSON gerado e a interface do usuário do Swagger. O Swagger só é habilitado em um ambiente de desenvolvimento. Habilitar o Swagger em um ambiente de produção pode expor detalhes potencialmente confidenciais sobre a estrutura e a implementação da API.
Dados de publicação de teste
O código a seguir no Program.cs cria um ponto de extremidade HTTP POST /todoitems que adiciona dados ao banco de dados em memória:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Execute o aplicativo. O navegador exibe um erro 404 porque não há mais um endpoint /.
O endpoint POST será usado para adicionar dados à aplicação.
Com o aplicativo ainda em execução, no navegador, navegue até
https://localhost:<port>/swaggerpara exibir a página de teste de API gerada pelo Swagger.
Na página de teste da API do Swagger, selecione Post /todoitems>Experimente.
Observe que o campo Corpo da solicitação contém um formato de exemplo gerado que reflete os parâmetros da API.
No corpo da solicitação, insira JSON para um item to-do, sem especificar o
idopcional:{ "name":"walk dog", "isComplete":true }Selecione Executar.
O Swagger fornece um painel Respostas abaixo do botão Executar .
Observe alguns dos detalhes úteis:
- cURL: Swagger fornece um exemplo de comando cURL na sintaxe Unix/Linux, que pode ser executado na linha de comando com qualquer shell bash que use sintaxe Unix/Linux, incluindo Git Bash do Git para Windows.
- URL de solicitação: uma representação simplificada da solicitação HTTP feita pelo código JavaScript da interface do usuário do Swagger para a chamada de API. As solicitações reais podem incluir detalhes como cabeçalhos e parâmetros de consulta e um corpo de solicitação.
- Resposta do servidor: Inclui o corpo da resposta e os cabeçalhos. O corpo da resposta mostra que o
idfoi configurado como1. - Código de resposta: Um código de status de
HTTP201 foi retornado, indicando que a solicitação foi processada com êxito e resultou na criação de um novo recurso.
Analise os endpoints GET
A aplicação de exemplo implementa vários endpoints GET chamando MapGet:
| API | Description | Corpo de solicitação | Corpo da resposta |
|---|---|---|---|
GET /todoitems |
Consiga todos os itens to-do | None | Lista de tarefas a fazer |
GET /todoitems/complete |
Obter todos os itens to-do concluídos | None | Lista de tarefas a fazer |
GET /todoitems/{id} |
Obter um item por ID | None | Item de tarefa |
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());
Testar os endpoints GET
Teste a aplicação chamando os endpoints a partir de um navegador ou Swagger.
Em Swagger, selecione GET /todoitems>Experimente já>Executar.
Como alternativa, chame GET /todoitems de um navegador inserindo o URI
http://localhost:<port>/todoitems. Por exemplo,http://localhost:5001/todoitems
A chamada para GET /todoitems produz uma resposta semelhante à seguinte:
[
{
"id": 1,
"name": "walk dog",
"isComplete": true
}
]
Chame GET /todoitems/{id} no Swagger para retornar dados de uma id específica:
- Selecione GET /todoitems>Experimente.
- Defina o campo id como
1e selecione Executar.
Como alternativa, chame GET /todoitems de um navegador inserindo o URI
https://localhost:<port>/todoitems/1. Por exemplo, Por exemplo,https://localhost:5001/todoitems/1A resposta é semelhante à seguinte:
{ "id": 1, "name": "walk dog", "isComplete": true }
Este aplicativo usa um banco de dados na memória. Se o aplicativo for reiniciado, a solicitação GET não retornará nenhum dado. Se nenhum dado for retornado, POSTE os dados no aplicativo e tente a solicitação GET novamente.
Valores de retorno
ASP.NET Core serializa automaticamente o objeto para JSON e grava o JSON no corpo da mensagem de resposta. O código de resposta para esse tipo de retorno é 200 OK, supondo que não haja exceções não tratadas. As exceções não tratadas são traduzidas em erros 5xx.
Os tipos de retorno podem representar uma ampla gama de códigos de status HTTP. Por exemplo, GET /todoitems/{id} pode retornar dois valores de status diferentes:
- Se nenhum item corresponder à ID solicitada, o método retornará um 404 statusNotFound código de erro.
- Caso contrário, o método retorna 200 com um corpo de resposta JSON. O retorno
itemresulta em uma resposta HTTP 200.
Examine o ponto de extremidade PUT
A aplicação de exemplo implementa um único endpoint PUT usando 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();
});
Este método é semelhante ao método MapPost, exceto que usa HTTP PUT. Uma resposta bem-sucedida retorna 204 (Sem Conteúdo). De acordo com a especificação HTTP, uma solicitação PUT requer que o cliente envie toda a entidade atualizada, não apenas as alterações. Para suportar atualizações parciais, use HTTP PATCH.
Testar o ponto de extremidade PUT
Este exemplo usa um banco de dados na memória que deve ser inicializado sempre que o aplicativo é iniciado. Deve haver um item no banco de dados antes de fazer uma chamada PUT. Chame GET para garantir que há um item no banco de dados antes de fazer uma chamada PUT.
Atualize o item to-do que tem Id = 1 e defina seu nome como "feed fish".
Use Swagger para enviar uma solicitação PUT:
Selecione Colocar /todoitems/{id}>Experimente.
Defina o campo id como
1.Defina o corpo da solicitação para o seguinte JSON:
{ "name": "feed fish", "isComplete": false }Selecione Executar.
Examinar e testar o endpoint DELETE
O aplicativo de exemplo implementa um único endpoint DELETE usando 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();
});
Use Swagger para enviar uma solicitação DELETE:
Selecione DELETE /todoitems/{id}>Experimente.
Defina o campo ID como
1e selecione Executar.A solicitação DELETE é enviada para a aplicação e a resposta é exibida no painel de Respostas do . O corpo da resposta está vazio e o código de status da resposta do Server é 204.
Evitar a sobrepostagem
Atualmente, o aplicativo de exemplo expõe todo o objeto Todo. Aplicativos de produção Em aplicativos de produção, um subconjunto do modelo é frequentemente usado para restringir os dados que podem ser inseridos e retornados. Há várias razões por trás disso e a segurança é uma das principais. O subconjunto de um modelo é geralmente referido como um objeto de transferência de dados (DTO), modelo de entrada ou modelo de exibição.
DTO é usado neste artigo.
Um DTO pode ser usado para:
- Evite a sobrepublicação.
- Oculte propriedades que os clientes não devem visualizar.
- Omita algumas propriedades para reduzir o tamanho da carga útil.
- Nivelar gráficos de objetos que contêm objetos aninhados. Gráficos de objetos achatados podem ser mais convenientes para os clientes.
Para demonstrar a abordagem DTO, atualize a classe Todo para incluir um campo secreto:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
O campo secreto precisa ser oculto deste aplicativo, mas um aplicativo administrativo pode optar por expô-lo.
Verifique se você pode postar e obter o campo secreto.
Crie um arquivo chamado TodoItemDTO.cs com o seguinte código:
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);
}
Substitua o conteúdo do arquivo Program.cs com o seguinte código para usar esse modelo 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>();
}
Verifique se você pode postar e obter todos os campos, exceto o campo secreto.
Teste a API mínima
Para obter um exemplo de teste de um aplicativo de API mínimo, consulte este exemplo do GitHub.
Publicar no Azure
Para obter informações sobre como implantar no Azure, consulte Guia de início rápido: implantar um aplicativo Web ASP.NET.
Recursos adicionais
APIs mínimas são arquitetadas para criar APIs HTTP com dependências mínimas. Eles são ideais para microsserviços e aplicativos que desejam incluir apenas os arquivos, recursos e dependências mínimos no ASP.NET Core.
Este tutorial ensina os conceitos básicos da criação de uma API mínima com o ASP.NET Core. Outra abordagem para criar APIs no ASP.NET Core é usar controladores. Para obter ajuda com a escolha entre APIs mínimas e APIs baseadas em controlador, consulte Visão geral de APIs. Para obter um tutorial sobre como criar um projeto de API com base em controladores que contém mais recursos, consulte Criar uma API da Web.
Overview
Este tutorial cria a seguinte API:
| API | Description | Corpo de solicitação | Corpo da resposta |
|---|---|---|---|
GET /todoitems |
Consiga todos os itens to-do | None | Lista de tarefas a fazer |
GET /todoitems/complete |
Obtenha to-do itens concluídos | None | Lista de tarefas a fazer |
GET /todoitems/{id} |
Obter um item por ID | None | Item de tarefa |
POST /todoitems |
Adicionar um novo item | Item de tarefa | Item de tarefa |
PUT /todoitems/{id} |
Atualizar um item existente | Item de tarefa | None |
DELETE /todoitems/{id} |
Excluir um item | None | None |
Prerequisites
Visual Studio 2022 com a carga de trabalho de ASP.NET e desenvolvimento web .
Criar um projeto de API
Inicie o Visual Studio 2022 e selecione Criar um novo projeto.
Na caixa de diálogo Criar um novo projeto :
- Introduza
Emptyna caixa de pesquisa Procurar modelos . - Selecione o modelo ASP.NET Core Empty e selecione Next.
- Introduza
Nomeie o projeto TodoApi e selecione Next.
Na caixa de diálogo Informações adicionais :
- Selecione .NET 8.0 (Suporte de longo prazo)
- Desmarque Não usar instruções de nível superior
- Selecione Criar
Examine o código
O arquivo Program.cs contém o seguinte código:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
O código anterior:
- Cria um WebApplicationBuilder e um WebApplication com padrões pré-configurados.
- Cria um ponto de extremidade HTTP GET
/que retornaHello World!:
Executar o aplicativo
Pressione Ctrl+F5 para executar sem o depurador.
O Visual Studio exibe a seguinte caixa de diálogo:
Selecione Sim se confiar no certificado SSL do IIS Express.
A seguinte caixa de diálogo é exibida:
Selecione Sim se concordar em confiar no certificado de desenvolvimento.
Para obter informações sobre como confiar no navegador Firefox, consulte o erro de certificado do Firefox SEC_ERROR_INADEQUATE_KEY_USAGE .
O Visual Studio inicia o servidor Web e abre uma janela do Kestrel navegador.
Hello World! é exibido no navegador. O arquivo Program.cs contém um aplicativo mínimo, mas completo.
Feche a janela do navegador.
Adicionar pacotes NuGet
Os pacotes NuGet devem ser adicionados para dar suporte ao banco de dados e ao diagnóstico usados neste tutorial.
- No menu Ferramentas, selecione Gerenciador de Pacotes NuGet > Gerenciar Pacotes NuGet para Solução.
- Selecione a guia Procurar.
- Digite Microsoft.EntityFrameworkCore.InMemory na caixa de pesquisa e selecione
Microsoft.EntityFrameworkCore.InMemory. - Marque a caixa de seleção Projeto no painel direito e selecione Instalar.
- Siga as instruções anteriores para adicionar o pacote
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.
As classes de contexto de modelo e banco de dados
- Na pasta do projeto, crie um arquivo chamado
Todo.cscom o seguinte código:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
O código anterior cria o modelo para este aplicativo. Um modelo é uma classe que representa os dados que o aplicativo gerencia.
- Crie um arquivo chamado
TodoDb.cscom o seguinte código:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
O código anterior define o contexto do banco de dados, que é a classe principal que coordena a funcionalidade do Entity Framework para um modelo de dados. Esta classe deriva da classe Microsoft.EntityFrameworkCore.DbContext.
Adicionar o código da API
- Substitua o conteúdo do arquivo
Program.cscom o seguinte código:
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();
O código realçado a seguir adiciona o contexto do banco de dados ao contêiner de injeção de dependência (DI) e permite a exibição das exceções relacionadas ao banco de dados.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
O contêiner DI fornece acesso ao contexto do banco de dados e outros serviços.
Este tutorial usa o Endpoints Explorer e arquivos .http para testar a API.
Dados de publicação de teste
O código a seguir no Program.cs cria um ponto de extremidade HTTP POST /todoitems que adiciona dados ao banco de dados em memória:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Execute o aplicativo. O navegador exibe um erro 404 porque não há mais um endpoint /.
O endpoint POST será usado para adicionar dados à aplicação.
Selecione Ver>Outras Janelas>Explorador de Endpoints.
Clique com o botão direito em POST endpoint e selecione Gerar solicitação.
Um novo arquivo é criado na pasta do projeto chamada
TodoApi.http, com conteúdo semelhante ao exemplo a seguir:@TodoApi_HostAddress = https://localhost:7031 Post {{TodoApi_HostAddress}}/todoitems ###- A primeira linha cria uma variável que é usada para todos os pontos de extremidade.
- A próxima linha define uma solicitação POST.
- A linha de três hashtags (
###) é um delimitador de solicitação: o que vem depois é para uma solicitação diferente.
A solicitação POST precisa de cabeçalhos e um corpo. Para definir essas partes da solicitação, adicione as seguintes linhas imediatamente após a linha de solicitação POST:
Content-Type: application/json { "name":"walk dog", "isComplete":true }O código anterior adiciona um cabeçalho Content-Type e um corpo de solicitação JSON. O ficheiro TodoApi.http agora deve assemelhar-se ao exemplo abaixo, mas com o seu número de porta:
@TodoApi_HostAddress = https://localhost:7057 Post {{TodoApi_HostAddress}}/todoitems Content-Type: application/json { "name":"walk dog", "isComplete":true } ###Execute o aplicativo.
Selecione o link Enviar solicitação que está acima da linha de solicitação
POST.
A solicitação POST é enviada para a aplicação e a resposta é exibida no painel de Resposta .
Analise os endpoints GET
A aplicação de exemplo implementa vários endpoints GET chamando MapGet:
| API | Description | Corpo de solicitação | Corpo da resposta |
|---|---|---|---|
GET /todoitems |
Consiga todos os itens to-do | None | Lista de tarefas a fazer |
GET /todoitems/complete |
Obter todos os itens to-do concluídos | None | Lista de tarefas a fazer |
GET /todoitems/{id} |
Obter um item por ID | None | Item de tarefa |
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());
Testar os endpoints GET
Teste a aplicação chamando os endpoints GET de um navegador ou usando o Explorador de Endpoints. As etapas a seguir são para Endpoints Explorer.
No Explorador de Pontos de Extremidade, clique com o botão direito do rato no primeiro ponto de extremidade GET e selecione Gerar pedido.
O seguinte conteúdo é adicionado ao arquivo
TodoApi.http:Get {{TodoApi_HostAddress}}/todoitems ###Selecione o link Enviar solicitação que está acima da nova
GETlinha de solicitação.A solicitação GET é enviada para o aplicativo e a resposta é exibida no painel Resposta .
O corpo da resposta é semelhante ao seguinte JSON:
[ { "id": 1, "name": "walk dog", "isComplete": true } ]No Explorador de Endpoints, clique com o botão direito do rato no
/todoitems/{id}endpoint GET e selecionar Gerar pedido. O seguinte conteúdo é adicionado ao arquivoTodoApi.http:GET {{TodoApi_HostAddress}}/todoitems/{id} ###Substitua
{id}por1.Selecione o link Enviar solicitação que está acima da nova linha de solicitação GET.
A solicitação GET é enviada para o aplicativo e a resposta é exibida no painel Resposta .
O corpo da resposta é semelhante ao seguinte JSON:
{ "id": 1, "name": "walk dog", "isComplete": true }
Este aplicativo usa um banco de dados na memória. Se o aplicativo for reiniciado, a solicitação GET não retornará nenhum dado. Se nenhum dado for retornado, POSTE os dados no aplicativo e tente a solicitação GET novamente.
Valores de retorno
ASP.NET Core serializa automaticamente o objeto para JSON e grava o JSON no corpo da mensagem de resposta. O código de resposta para esse tipo de retorno é 200 OK, supondo que não haja exceções não tratadas. As exceções não tratadas são traduzidas em erros 5xx.
Os tipos de retorno podem representar uma ampla gama de códigos de status HTTP. Por exemplo, GET /todoitems/{id} pode retornar dois valores de status diferentes:
- Se nenhum item corresponder à ID solicitada, o método retornará um 404 statusNotFound código de erro.
- Caso contrário, o método retorna 200 com um corpo de resposta JSON. O retorno
itemresulta em uma resposta HTTP 200.
Examine o ponto de extremidade PUT
A aplicação de exemplo implementa um único endpoint PUT usando 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();
});
Este método é semelhante ao método MapPost, exceto que usa HTTP PUT. Uma resposta bem-sucedida retorna 204 (Sem Conteúdo). De acordo com a especificação HTTP, uma solicitação PUT requer que o cliente envie toda a entidade atualizada, não apenas as alterações. Para suportar atualizações parciais, use HTTP PATCH.
Testar o ponto de extremidade PUT
Este exemplo usa um banco de dados na memória que deve ser inicializado sempre que o aplicativo é iniciado. Deve haver um item no banco de dados antes de fazer uma chamada PUT. Chame GET para garantir que há um item no banco de dados antes de fazer uma chamada PUT.
Atualize o item to-do que tem Id = 1 e defina seu nome como "feed fish".
No Explorador de Pontos de Extremidade, clique com o botão direito do rato no ponto de extremidade PUT e selecione Gerar pedido.
O seguinte conteúdo é adicionado ao arquivo
TodoApi.http:Put {{TodoApi_HostAddress}}/todoitems/{id} ###Na linha de solicitação PUT, substitua
{id}por1.Adicione as seguintes linhas imediatamente após a linha de solicitação PUT:
Content-Type: application/json { "name": "feed fish", "isComplete": false }O código anterior adiciona um cabeçalho Content-Type e um corpo de solicitação JSON.
Selecione o link Enviar solicitação que está acima da nova linha de solicitação PUT.
A solicitação PUT é enviada para o aplicativo e a resposta é exibida no painel Resposta . O corpo da resposta está vazio e o código de status é 204.
Examinar e testar o endpoint DELETE
O aplicativo de exemplo implementa um único endpoint DELETE usando 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();
});
No Endpoints Explorer, clique com o botão direito no endpoint DELETE e selecione Gerar solicitação.
Uma solicitação DELETE é adicionada ao
TodoApi.http.Substitua
{id}na linha de solicitação DELETE por1. A solicitação DELETE deve se parecer com o exemplo a seguir:DELETE {{TodoApi_HostAddress}}/todoitems/1 ###Selecione o link Enviar solicitação para a solicitação DELETE.
A solicitação DELETE é enviada para o aplicativo e a resposta é exibida no painel Resposta . O corpo da resposta está vazio e o código de status é 204.
Utilizar a API MapGroup
O código da aplicação de exemplo repete o prefixo de URL todoitems sempre que estabelece um endpoint. As APIs geralmente têm grupos de pontos de extremidade com um prefixo de URL comum, e o método MapGroup está disponível para ajudar a organizar esses grupos. Ele reduz o código repetitivo e permite personalizar grupos inteiros de endpoints com uma única chamada para métodos como RequireAuthorization e WithMetadata.
Substitua o conteúdo do Program.cs pelo seguinte código:
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();
O código anterior tem as seguintes alterações:
- Adiciona
var todoItems = app.MapGroup("/todoitems");para configurar o grupo usando o prefixo de URL/todoitems. - Altera todos os métodos
app.Map<HttpVerb>paratodoItems.Map<HttpVerb>. - Remove o prefixo de URL
/todoitemsdas chamadas de métodoMap<HttpVerb>.
Teste os pontos de extremidade para verificar se eles funcionam da mesma forma.
Usar uma API TypedResults
Retornar TypedResults em vez de Results possui várias vantagens, entre as quais se incluem a testabilidade e o retorno automático dos metadados do tipo de resposta para que a OpenAPI descreva o endpoint. Para obter mais informações, consulte TypedResults vs Results.
Os métodos Map<HttpVerb> podem chamar métodos de manipulador de rota em vez de usar funções lambda. Para ver um exemplo, atualize Program.cs com o seguinte código:
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();
}
O código Map<HttpVerb> agora chama métodos em vez de lambdas:
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);
Esses métodos retornam objetos que implementam IResult e são definidos por 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();
}
Os testes de unidade podem chamar esses métodos e testar se eles retornam o tipo correto. Por exemplo, se o método for GetAllTodos:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
O código de teste de unidade pode verificar se um objeto do tipo Ok<Todo[]> é retornado do método manipulador. Por exemplo:
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);
}
Evitar a sobrepostagem
Atualmente, o aplicativo de exemplo expõe todo o objeto Todo. Aplicativos de produção Em aplicativos de produção, um subconjunto do modelo é frequentemente usado para restringir os dados que podem ser inseridos e retornados. Há várias razões por trás disso e a segurança é uma das principais. O subconjunto de um modelo é geralmente referido como um objeto de transferência de dados (DTO), modelo de entrada ou modelo de exibição.
DTO é usado neste artigo.
Um DTO pode ser usado para:
- Evite a sobrepublicação.
- Oculte propriedades que os clientes não devem visualizar.
- Omita algumas propriedades para reduzir o tamanho da carga útil.
- Nivelar gráficos de objetos que contêm objetos aninhados. Gráficos de objetos achatados podem ser mais convenientes para os clientes.
Para demonstrar a abordagem DTO, atualize a classe Todo para incluir um campo secreto:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
O campo secreto precisa ser oculto deste aplicativo, mas um aplicativo administrativo pode optar por expô-lo.
Verifique se você pode postar e obter o campo secreto.
Crie um arquivo chamado TodoItemDTO.cs com o seguinte código:
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);
}
Substitua o conteúdo do arquivo Program.cs com o seguinte código para usar esse modelo 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();
}
Verifique se você pode postar e obter todos os campos, exceto o campo secreto.
Solução de problemas com o exemplo concluído
Se você encontrar um problema que não pode resolver, compare seu código com o projeto concluído. Veja ou faça o download do projeto concluído (como fazer o download).
Próximos passos
- Configure as opções de serialização JSON.
- Lidar com erros e exceções: a página de exceção do desenvolvedor está ativada por padrão no ambiente de desenvolvimento para aplicações minimalistas de API. Para obter informações sobre como lidar com erros e exceções, consulte Manipular erros em ASP.NET APIs principais.
- Para obter um exemplo de teste de um aplicativo de API mínimo, consulte este exemplo do GitHub.
- Suporte a OpenAPI em APIs mínimas.
- Guia de início rápido: publique no Azure.
- Organizando ASP.NET APIs mínimas principais.