Tutorial: Criar uma API mínima com o ASP.NET Core
Observação
Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.
Aviso
Esta versão do ASP.NET Core não tem mais suporte. 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.
Importante
Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.
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. Elas 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, veja 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 Web.
Visão geral
Este tutorial cria a seguinte API:
API | Descrição | Corpo da solicitação | Corpo da resposta |
---|---|---|---|
GET /todoitems |
Obter todos os itens de tarefas pendentes | Nenhum | Matriz de itens de tarefas pendentes |
GET /todoitems/complete |
Obter itens pendentes concluídos | Nenhum | Matriz de itens de tarefas pendentes |
GET /todoitems/{id} |
Obter um item por ID | Nenhum | Item de tarefas pendentes |
POST /todoitems |
Adicionar um novo item | Item de tarefas pendentes | Item de tarefas pendentes |
PUT /todoitems/{id} |
Atualizar um item existente | Item de tarefas pendentes | Nenhum |
DELETE /todoitems/{id} |
Excluir um item | Nenhum | Nenhum |
Pré-requisitos
Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do ASP.NET.
Criar um projeto de API
Inicie o Visual Studio 2022 e selecione Criar um novo projeto.
Na caixa de diálogo Criar um projeto:
- Insira
Empty
na caixa de pesquisa Pesquisar modelos . - Selecione o modelo ASP.NET Core Vazio e clique em Avançar.
- Insira
Nomeie o projeto como TodoApi e clique em Avançar.
Na caixa de diálogo Informações adicionais:
- Selecione .NET 9.0
- Desmarque Não usar declarações de nível superior
- Escolha Criar
Examinar 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
/
do HTTP GET que retornaHello World!
:
Executar o aplicativo
Pressione Ctrl + F5 para execução sem o depurador.
O Visual Studio exibe a caixa de diálogo a seguir:
Selecione Sim se você confia no certificado SSL do IIS Express.
A seguinte caixa de diálogo é exibida:
Selecione Sim se você concordar com confiar no certificado de desenvolvimento.
Para obter informações sobre como confiar no navegador Firefox, confira Erro de certificado Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
O Visual Studio inicia o Kestrelservidor Web e abre uma janela do 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 do NuGet
Os pacotes NuGet devem ser adicionados para dar suporte ao banco de dados e diagnósticos usados neste tutorial.
- No menu Ferramentas, selecione Gerenciador de Pacotes do NuGet > Gerenciar Pacotes do NuGet para a Solução.
- Selecione a guia Procurar.
- Selecione Incluir Pré-lançamento.
- Na caixa de pesquisa, insira Microsoft.EntityFrameworkCore.InMemory e selecione
Microsoft.EntityFrameworkCore.InMemory
. - Marque a caixa de seleção Projeto no painel direito e selecione Instalar.
- Use 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.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 é um conjunto de classes que representam os dados gerenciados pelo aplicativo.
- Crie um arquivo chamado
TodoDb.cs
com o código a seguir:
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 de banco de dados, que é a classe principal que coordena a funcionalidade do Entity Framework para um modelo de dados. A classe é derivada da classe Microsoft.EntityFrameworkCore.DbContext.
Adicionar o código da API
- Substitua o conteúdo do arquivo
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();
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 DI (injeção de dependência) e permite exibir 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 de DI fornece acesso ao contexto do banco de dados e a outros serviços.
Este tutorial usa o Gerenciador de Pontos de Extremidade e arquivos .http para testar a API.
Testar os dados de postagem
O código a seguir em Program.cs
cria um ponto de extremidade HTTP POST/todoitems
que adiciona dados ao banco de dados na 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 ponto de extremidade /
.
O ponto de extremidade POST será usado para adicionar dados ao aplicativo.
Selecione Exibir>Outras Janelas>Gerenciador de Pontos de Extremidade.
Clique com o botão direito do mouse no ponto de extremidade POST e selecione Gerar solicitação.
Um novo arquivo é criado na pasta do projeto chamada
TodoApi.http
, com conteúdo semelhante ao seguinte exemplo:@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 tripla de hashtag (
###
) é um delimitador de solicitação: o que vem depois dela é 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 arquivo TodoApi.http agora deve se parece com o exemplo a seguir, mas com o número da 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 acima da linha de solicitação
POST
.A solicitação POST é enviada ao aplicativo e a resposta é exibida no painel Resposta.
Examinar os pontos de extremidade GET
O aplicativo de exemplo implementa vários pontos de extremidade GET chamando MapGet
:
API | Descrição | Corpo da solicitação | Corpo da resposta |
---|---|---|---|
GET /todoitems |
Obter todos os itens de tarefas pendentes | Nenhum | Matriz de itens de tarefas pendentes |
GET /todoitems/complete |
Obter itens pendentes concluídos | Nenhum | Matriz de itens de tarefas pendentes |
GET /todoitems/{id} |
Obter um item por ID | Nenhum | Item de tarefas pendentes |
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 pontos de extremidade GET
Teste o aplicativo chamando os pontos de extremidade GET
de um navegador ou usando o Gerenciador de Pontos de Extremidade. As etapas a seguir são para o Gerenciador de Pontos de Extremidade.
No Gerenciador de Pontos de Extremidade, clique com o botão direito do mouse no primeiro ponto de extremidade GET e selecione Gerar solicitação.
O conteúdo a seguir é adicionado ao arquivo
TodoApi.http
:Get {{TodoApi_HostAddress}}/todoitems ###
Selecione o link Enviar solicitação acima da nova linha de solicitação
GET
.A solicitação GET é enviada ao aplicativo e a resposta é exibida no painel Resposta.
O corpo de resposta é semelhante ao seguinte JSON:
[ { "id": 1, "name": "walk dog", "isComplete": true } ]
No Gerenciador de Pontos de Extremidade, clique com o botão direito do mouse no
/todoitems/{id}
GET e selecione Gerar solicitação. O conteúdo a seguir é adicionado ao arquivoTodoApi.http
:GET {{TodoApi_HostAddress}}/todoitems/{id} ###
Substitua
{id}
por1
.Selecione o link Enviar solicitação acima da nova linha solicitação GET.
A solicitação GET é enviada ao aplicativo e a resposta é exibida no painel Resposta.
O corpo de resposta é semelhante ao seguinte JSON:
{ "id": 1, "name": "walk dog", "isComplete": true }
Este aplicativo usa um banco de dados em memória. Se o aplicativo for reiniciado, a solicitação GET não retornará nenhum dado. Se nenhum dado for retornado, envie os dados para o aplicativo via POST e tente a solicitação GET novamente.
Valores de retorno
O ASP.NET Core serializa automaticamente o objeto em 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 nenhuma exceção sem tratamento. As exceções sem tratamento são convertidas em erros 5xx.
Os tipos de retorno podem representar uma ampla variedade de códigos de status HTTP. Por exemplo, GET /todoitems/{id}
pode retornar dois valores de status diferentes:
- Se nenhum item corresponder às ID solicitadas, o método retornará um código de erro status 404NotFound.
- Caso contrário, o método retornará 200 com um corpo de resposta JSON. Retornar
item
resulta em uma resposta HTTP 200.
Examinar o ponto de extremidade PUT
O aplicativo de exemplo implementa um único ponto de extremidade 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();
});
Esse método é semelhante ao método MapPost
, exceto que ele usa HTTP PUT. Uma resposta bem-sucedida retorna 204 (sem conteúdo). De acordo com a especificação de HTTP, uma solicitação PUT exige que o cliente envie a entidade inteira atualizada, não apenas as alterações. Para dar suporte a atualizações parciais, use HTTP PATCH.
Testar o ponto de extremidade PUT
Este exemplo usa um banco de dados em memória que precisará ser iniciado sempre que o aplicativo for iniciado. Deverá haver um item no banco de dados antes de você fazer uma chamada PUT. Chame GET para garantir a existência de um item no banco de dados antes de fazer uma chamada PUT.
Atualize o item pendente que tem Id = 1
e defina o nome dele como "feed fish"
.
No Gerenciador de Pontos de Extremidade, clique com o botão direito do mouse no ponto de extremidade PUT e selecione Gerar solicitação.
O conteúdo a seguir é 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 acima da nova linha de solicitação PUT.
A solicitação PUT é enviada ao aplicativo e a resposta é exibida no painel Resposta. O corpo da resposta está vazio e código de status é 204.
Examinar e testar o ponto de extremidade DELETE
O aplicativo de exemplo implementa um único ponto de extremidade 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 Gerenciador de Pontos de Extremidade, clique com o botão direito do mouse do ponto de extremidade DELETE e selecione Gerar solicitação.
Uma solicitação DELETE é adicionada a
TodoApi.http
.Substitua
{id}
na linha de solicitação DELETE com1
. A solicitação DELETE deve ter uma aparência semelhante ao exemplo a seguir:DELETE {{TodoApi_HostAddress}}/todoitems/1 ###
Selecione o link Enviar solicitação para a solicitação DELETE.
A solicitação DELETE é enviada ao aplicativo e a resposta é exibida no painel Resposta. O corpo da resposta está vazio e código de status é 204.
Usar a API do MapGroup
O código do aplicativo de exemplo repete o prefixo de URL todoitems
sempre que configura um ponto de extremidade. 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. Isso reduz o código repetitivo e permite personalizar grupos inteiros de pontos de extremidade com uma única chamada a métodos como RequireAuthorization e WithMetadata.
Substitua o conteúdo de 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 faz as seguintes alterações:
- Adiciona
var todoItems = app.MapGroup("/todoitems");
para configurar o grupo usando o prefixo/todoitems
da URL. - Altera todos os métodos
app.Map<HttpVerb>
paratodoItems.Map<HttpVerb>
. - Remove o prefixo
/todoitems
da URL das chamadas de métodoMap<HttpVerb>
.
Teste os pontos de extremidade para verificar se eles funcionam da mesma forma.
Usar a API TypedResults
Retornar TypedResults em vez de Results tem várias vantagens, incluindo capacidade de teste e retornar automaticamente os metadados de tipo de resposta para OpenAPI para descrever o ponto de extremidade. Para obter mais informações, consulte TypedResults vs Resultados.
Os métodos Map<HttpVerb>
podem chamar métodos de manipulador de rotas em vez de usar lambdas. 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 de 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);
}
Impedir o excesso de postagem
Atualmente, o aplicativo de exemplo expõe todo o objeto Todo
. Aplicativos de produção Em aplicativos de produção, um subconjunto do modelo geralmente é 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 é chamado de DTO (Objeto de Transferência de Dados), modelo de entrada ou modelo de exibição. O DTO é usado neste artigo.
Um DTO pode ser usado para:
- Impedir o excesso de postagem.
- Oculte propriedades que os clientes não deveriam visualizar.
- Omita algumas propriedades para reduzir o tamanho do conteúdo.
- Nivelar gráficos de objetos que contenham objetos aninhados. Os grafos de objeto nivelados podem ser mais convenientes para os clientes.
Para demonstrar a abordagem de 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 ocultado neste aplicativo, mas um aplicativo administrativo poderia optar por mostrá-lo.
Verifique se você pode postar e obter o campo secreto.
Crie um arquivo chamado TodoItemDTO.cs
com o código a seguir:
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
pelo seguinte código para usar este modelo de 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 de segredo.
Solução de problemas com o exemplo concluído
Se você encontrar um problema que não possa resolver, compare seu código com o projeto concluído. Exibir ou baixar projeto concluído (como baixar).
Próximas etapas
- Configurar opções de serialização JSON.
- Trate erros e exceções: a página de exceção do desenvolvedor é habilitada por padrão no ambiente de desenvolvimento para aplicativos de API mínima. Para obter informações sobre como lidar com erros e exceções, consulte Manipular erros em APIs de ASP.NET Core.
- Para obter um exemplo de teste de um aplicativo de API mínima, consulte este exemplo do GitHub.
- Suporte do OpenAPI em APIs mínimas.
- Início rápido: Publicar no Azure.
- Organização de APIs mínimas do ASP.NET Core.
Saiba mais
APIs mínimas são arquitetadas para criar APIs HTTP com dependências mínimas. Elas 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, confira a visão geral das 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 Web.
Visão geral
Este tutorial cria a seguinte API:
API | Descrição | Corpo da solicitação | Corpo da resposta |
---|---|---|---|
GET /todoitems |
Obter todos os itens de tarefas pendentes | Nenhum | Matriz de itens de tarefas pendentes |
GET /todoitems/complete |
Obter itens pendentes concluídos | Nenhum | Matriz de itens de tarefas pendentes |
GET /todoitems/{id} |
Obter um item por ID | Nenhum | Item de tarefas pendentes |
POST /todoitems |
Adicionar um novo item | Item de tarefas pendentes | Item de tarefas pendentes |
PUT /todoitems/{id} |
Atualizar um item existente | Item de tarefas pendentes | Nenhum |
DELETE /todoitems/{id} |
Excluir um item | Nenhum | Nenhum |
Pré-requisitos
Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do ASP.NET.
Criar um projeto de API
Inicie o Visual Studio 2022 e selecione Criar um novo projeto.
Na caixa de diálogo Criar um projeto:
- Insira
Empty
na caixa de pesquisa Pesquisar modelos . - Selecione o modelo ASP.NET Core Vazio e clique em Avançar.
- Insira
Nomeie o projeto como TodoApi e clique em Avançar.
Na caixa de diálogo Informações adicionais:
- Selecione o .NET 7.0
- Desmarque Não usar declarações de nível superior
- Escolha Criar
Examinar 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
/
do HTTP GET que retornaHello World!
:
Executar o aplicativo
Pressione Ctrl + F5 para execução sem o depurador.
O Visual Studio exibe a caixa de diálogo a seguir:
Selecione Sim se você confia no certificado SSL do IIS Express.
A seguinte caixa de diálogo é exibida:
Selecione Sim se você concordar com confiar no certificado de desenvolvimento.
Para obter informações sobre como confiar no navegador Firefox, confira Erro de certificado Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
O Visual Studio inicia o Kestrelservidor Web e abre uma janela do navegador.
Hello World!
é exibido no navegador. O arquivo Program.cs
contém um aplicativo mínimo, mas completo.
Adicionar pacotes do NuGet
Os pacotes NuGet devem ser adicionados para dar suporte ao banco de dados e diagnósticos usados neste tutorial.
- No menu Ferramentas, selecione Gerenciador de Pacotes do NuGet > Gerenciar Pacotes do NuGet para a Solução.
- Selecione a guia Procurar.
- Na caixa de pesquisa, insira Microsoft.EntityFrameworkCore.InMemory e selecione
Microsoft.EntityFrameworkCore.InMemory
. - Marque a caixa de seleção Projeto no painel direito.
- Na lista suspensa 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.EntityFrameworkCore
com a versão 7 mais recente 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 é um conjunto de classes que representam os dados gerenciados pelo aplicativo.
Crie um arquivo chamado TodoDb.cs
com o código a seguir:
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 de banco de dados, que é a classe principal que coordena a funcionalidade do Entity Framework para um modelo de dados. A classe é derivada da classe Microsoft.EntityFrameworkCore.DbContext.
Adicionar o código da API
Substitua o conteúdo do arquivo 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();
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 DI (injeção de dependência) e permite exibir 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 de DI fornece acesso ao contexto do banco de dados e a outros serviços.
Criar interface do usuário de teste de API com o Swagger
Há muitas ferramentas de teste de API Web disponíveis para escolher e você pode seguir as etapas de teste de API introdutórias deste tutorial com sua ferramenta preferida.
Este tutorial utiliza o pacote .NET NSwag.AspNetCore, que integra as ferramentas do Swagger para gerar um teste de interface do usuário aderindo à especificação OpenAPI:
- NSwag: uma biblioteca .NET que integra o Swagger diretamente aos 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 do 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 o OpenAPI e o NSwag com ASP.NET, consulte Documentação da API Web do ASP.NET Core com Swagger/OpenAPI.
Instalar ferramentas do Swagger
Execute o comando a seguir:
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 o middleware do Swagger
Adicionar o código realçado a seguir antes
app
de ser 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 Gerenciador de API, que é um serviço que fornece metadados sobre a API HTTP. O Gerenciador de API é usado pelo Swagger para gerar o documento do Swagger.builder.Services.AddOpenApiDocument(config => {...});
: adiciona o gerador de documentos OpenAPI do Swagger aos serviços de aplicativo e o configura para fornecer mais informações sobre a API, como o título e a versão dela. Para obter informações sobre como fornecer detalhes mais robustos sobre a API, consulte Introdução ao NSwag e ao ASP.NET CoreAdicione o código realçado a seguir à próxima linha depois de
app
ser 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 exibir o documento JSON gerado e a IU 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.
Testar os dados de postagem
O código a seguir em Program.cs
cria um ponto de extremidade HTTP POST/todoitems
que adiciona dados ao banco de dados na 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 ponto de extremidade /
.
O ponto de extremidade POST será usado para adicionar dados ao aplicativo.
Com o aplicativo ainda em execução, no navegador, navegue até
https://localhost:<port>/swagger
para exibir a página de teste de API gerada pelo Swagger.Na página de teste da API do Swagger, selecione Post /todoitems>Experimentar.
Observe que o campo Corpo da solicitação contém um formato de exemplo gerado que reflete os parâmetros da API.
No corpo de solicitação, insira o JSON para um item de tarefas pendentes, sem especificar o
id
opcional:{ "name":"walk dog", "isComplete":true }
Selecione Executar.
O Swagger fornece um painel Respostas abaixo do botão Executar.
Observe alguns dos detalhes úteis:
- cURL: o Swagger fornece um comando cURL de exemplo na sintaxe Unix/Linux, que pode ser executado na linha de comando com qualquer shell bash que use a sintaxe Unix/Linux, incluindo o 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 à API. As solicitações reais podem incluir detalhes, como cabeçalhos e parâmetros de consulta e um corpo da solicitação.
- Resposta do servidor: inclui o corpo da resposta e os cabeçalhos. O corpo da resposta mostra que
id
foi definido como1
. - Código de resposta: um código de status 201
HTTP
foi retornado, indicando que a solicitação foi processada com êxito e resultou na criação de um novo recurso.
Examinar os pontos de extremidade GET
O aplicativo de exemplo implementa vários pontos de extremidade GET chamando MapGet
:
API | Descrição | Corpo da solicitação | Corpo da resposta |
---|---|---|---|
GET /todoitems |
Obter todos os itens de tarefas pendentes | Nenhum | Matriz de itens de tarefas pendentes |
GET /todoitems/complete |
Obter itens pendentes concluídos | Nenhum | Matriz de itens de tarefas pendentes |
GET /todoitems/{id} |
Obter um item por ID | Nenhum | Item de tarefas pendentes |
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 pontos de extremidade GET
Teste o aplicativo chamando os pontos de extremidade de um navegador ou do Swagger.
No Swagger, selecione GET /todoitems>Experimentar>Executar.
Como alternativa, chame GET /todoitems de um navegador inserindo o URI
http://localhost:<port>/todoitems
. Por exemplo,http://localhost:5001/todoitems
Uma resposta semelhante à seguinte é produzida pela chamada a GET /todoitems
:
[
{
"id": 1,
"name": "walk dog",
"isComplete": true
}
]
Chame GET /todoitems/{id} no Swagger para retornar dados de uma ID específica:
- Selecione GET /todoitems>Experimentar.
- Defina o campo de id para
1
e selecione Executar.
Como alternativa, chame GET /todoitems de um navegador inserindo o URI
https://localhost:<port>/todoitems/1
. Por exemplo,https://localhost:5001/todoitems/1
A resposta é semelhante ao descrito a seguir:
{ "id": 1, "name": "walk dog", "isComplete": true }
Este aplicativo usa um banco de dados em memória. Se o aplicativo for reiniciado, a solicitação GET não retornará nenhum dado. Se nenhum dado for retornado, envie os dados para o aplicativo via POST e tente a solicitação GET novamente.
Valores de retorno
O ASP.NET Core serializa automaticamente o objeto em 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 nenhuma exceção sem tratamento. As exceções sem tratamento são convertidas em erros 5xx.
Os tipos de retorno podem representar uma ampla variedade de códigos de status HTTP. Por exemplo, GET /todoitems/{id}
pode retornar dois valores de status diferentes:
- Se nenhum item corresponder às ID solicitadas, o método retornará um código de erro status 404NotFound.
- Caso contrário, o método retornará 200 com um corpo de resposta JSON. Retornar
item
resulta em uma resposta HTTP 200.
Examinar o ponto de extremidade PUT
O aplicativo de exemplo implementa um único ponto de extremidade 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();
});
Esse método é semelhante ao método MapPost
, exceto que ele usa HTTP PUT. Uma resposta bem-sucedida retorna 204 (sem conteúdo). De acordo com a especificação de HTTP, uma solicitação PUT exige que o cliente envie a entidade inteira atualizada, não apenas as alterações. Para dar suporte a atualizações parciais, use HTTP PATCH.
Testar o ponto de extremidade PUT
Este exemplo usa um banco de dados em memória que precisará ser iniciado sempre que o aplicativo for iniciado. Deverá haver um item no banco de dados antes de você fazer uma chamada PUT. Chame GET para garantir a existência de um item no banco de dados antes de fazer uma chamada PUT.
Atualize o item pendente que tem Id = 1
e defina o nome dele como "feed fish"
.
Use o Swagger para enviar uma solicitação PUT:
Selecione Put /todoitems/{id}>Experimentar.
Defina o campo id como
1
.Defina o corpo de solicitação para o seguinte JSON:
{ "name": "feed fish", "isComplete": false }
Selecione Executar.
Examinar e testar o ponto de extremidade DELETE
O aplicativo de exemplo implementa um único ponto de extremidade 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 o Swagger para enviar uma solicitação DELETE:
Selecione DELETE /todoitems/{id}>Experimentar.
Defina o campo ID como
1
e selecione Executar.A solicitação DELETE é enviada ao aplicativo e a resposta é exibida no painel Respostas. O corpo da resposta está vazio e código de status Resposta do servidor é 204.
Usar a API do MapGroup
O código do aplicativo de exemplo repete o prefixo de URL todoitems
sempre que configura um ponto de extremidade. 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. Isso reduz o código repetitivo e permite personalizar grupos inteiros de pontos de extremidade com uma única chamada a métodos como RequireAuthorization e WithMetadata.
Substitua o conteúdo de 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 faz as seguintes alterações:
- Adiciona
var todoItems = app.MapGroup("/todoitems");
para configurar o grupo usando o prefixo/todoitems
da URL. - Altera todos os métodos
app.Map<HttpVerb>
paratodoItems.Map<HttpVerb>
. - Remove o prefixo
/todoitems
da URL das chamadas de métodoMap<HttpVerb>
.
Teste os pontos de extremidade para verificar se eles funcionam da mesma forma.
Usar a API TypedResults
Retornar TypedResults em vez de Results tem várias vantagens, incluindo capacidade de teste e retornar automaticamente os metadados de tipo de resposta para OpenAPI para descrever o ponto de extremidade. Para obter mais informações, consulte TypedResults vs Resultados.
Os métodos Map<HttpVerb>
podem chamar métodos de manipulador de rotas em vez de usar lambdas. 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 de 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);
}
Impedir o excesso de postagem
Atualmente, o aplicativo de exemplo expõe todo o objeto Todo
. Aplicativos de produção Em aplicativos de produção, um subconjunto do modelo geralmente é 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 é chamado de DTO (Objeto de Transferência de Dados), modelo de entrada ou modelo de exibição. O DTO é usado neste artigo.
Um DTO pode ser usado para:
- Impedir o excesso de postagem.
- Oculte propriedades que os clientes não deveriam visualizar.
- Omita algumas propriedades para reduzir o tamanho do conteúdo.
- Nivelar gráficos de objetos que contenham objetos aninhados. Os grafos de objeto nivelados podem ser mais convenientes para os clientes.
Para demonstrar a abordagem de 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 ocultado neste aplicativo, mas um aplicativo administrativo poderia optar por mostrá-lo.
Verifique se você pode postar e obter o campo secreto.
Crie um arquivo chamado TodoItemDTO.cs
com o código a seguir:
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
pelo seguinte código para usar este modelo de 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 de segredo.
Solução de problemas com o exemplo concluído
Se você encontrar um problema que não possa resolver, compare seu código com o projeto concluído. Exibir ou baixar projeto concluído (como baixar).
Próximas etapas
- Configurar opções de serialização JSON.
- Trate erros e exceções: a página de exceção do desenvolvedor é habilitada por padrão no ambiente de desenvolvimento para aplicativos de API mínima. Para obter informações sobre como lidar com erros e exceções, consulte Manipular erros em APIs de ASP.NET Core.
- Para obter um exemplo de teste de um aplicativo de API mínima, consulte este exemplo do GitHub.
- Suporte do OpenAPI em APIs mínimas.
- Início rápido: Publicar no Azure.
- Organização de APIs mínimas do ASP.NET Core.
Saiba mais
APIs mínimas são arquitetadas para criar APIs HTTP com dependências mínimas. Elas 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, confira a visão geral das 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 Web.
Visão geral
Este tutorial cria a seguinte API:
API | Descrição | Corpo da solicitação | Corpo da resposta |
---|---|---|---|
GET /todoitems |
Obter todos os itens de tarefas pendentes | Nenhum | Matriz de itens de tarefas pendentes |
GET /todoitems/complete |
Obter itens pendentes concluídos | Nenhum | Matriz de itens de tarefas pendentes |
GET /todoitems/{id} |
Obter um item por ID | Nenhum | Item de tarefas pendentes |
POST /todoitems |
Adicionar um novo item | Item de tarefas pendentes | Item de tarefas pendentes |
PUT /todoitems/{id} |
Atualizar um item existente | Item de tarefas pendentes | Nenhum |
DELETE /todoitems/{id} |
Excluir um item | Nenhum | Nenhum |
Pré-requisitos
- Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do ASP.NET.
- SDK do .NET 6.0
Criar um projeto de API
Inicie o Visual Studio 2022 e selecione Criar um novo projeto.
Na caixa de diálogo Criar um projeto:
- Insira
Empty
na caixa de pesquisa Pesquisar modelos . - Selecione o modelo ASP.NET Core Vazio e clique em Avançar.
- Insira
Nomeie o projeto como TodoApi e clique em Avançar.
Na caixa de diálogo Informações adicionais:
- Selecionar o .NET 6.0
- Desmarque Não usar declarações de nível superior
- Escolha Criar
Examinar 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
/
do HTTP GET que retornaHello World!
:
Executar o aplicativo
Pressione Ctrl + F5 para execução sem o depurador.
O Visual Studio exibe a caixa de diálogo a seguir:
Selecione Sim se você confia no certificado SSL do IIS Express.
A seguinte caixa de diálogo é exibida:
Selecione Sim se você concordar com confiar no certificado de desenvolvimento.
Para obter informações sobre como confiar no navegador Firefox, confira Erro de certificado Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
O Visual Studio inicia o Kestrelservidor Web e abre uma janela do navegador.
Hello World!
é exibido no navegador. O arquivo Program.cs
contém um aplicativo mínimo, mas completo.
Adicionar pacotes do NuGet
Os pacotes NuGet devem ser adicionados para dar suporte ao banco de dados e diagnósticos usados neste tutorial.
- No menu Ferramentas, selecione Gerenciador de Pacotes do NuGet > Gerenciar Pacotes do NuGet para a Solução.
- Selecione a guia Procurar.
- Na caixa de pesquisa, insira Microsoft.EntityFrameworkCore.InMemory e selecione
Microsoft.EntityFrameworkCore.InMemory
. - Marque a caixa de seleção Projeto no painel direito.
- Na lista suspensa 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.EntityFrameworkCore
com a versão 7 mais recente 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 é um conjunto de classes que representam os dados gerenciados pelo aplicativo.
Crie um arquivo chamado TodoDb.cs
com o código a seguir:
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 de banco de dados, que é a classe principal que coordena a funcionalidade do Entity Framework para um modelo de dados. A classe é derivada da classe Microsoft.EntityFrameworkCore.DbContext.
Adicionar o código da API
Substitua o conteúdo do arquivo 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();
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 DI (injeção de dependência) e permite exibir 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 de DI fornece acesso ao contexto do banco de dados e a outros serviços.
Criar interface do usuário de teste de API com o Swagger
Há muitas ferramentas de teste de API Web disponíveis para escolher e você pode seguir as etapas de teste de API introdutórias deste tutorial com sua ferramenta preferida.
Este tutorial utiliza o pacote .NET NSwag.AspNetCore, que integra as ferramentas do Swagger para gerar um teste de interface do usuário aderindo à especificação OpenAPI:
- NSwag: uma biblioteca .NET que integra o Swagger diretamente aos 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 do 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 o OpenAPI e o NSwag com ASP.NET, consulte Documentação da API Web do ASP.NET Core com Swagger/OpenAPI.
Instalar ferramentas do Swagger
Execute o comando a seguir:
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 o middleware do Swagger
No Program.cs, adicione os seguintes demonstrativos do
using
à parte superior:using NSwag.AspNetCore;
Adicionar o código realçado a seguir antes
app
de ser 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 Gerenciador de API, que é um serviço que fornece metadados sobre a API HTTP. O Gerenciador de API é usado pelo Swagger para gerar o documento do Swagger.builder.Services.AddOpenApiDocument(config => {...});
: adiciona o gerador de documentos OpenAPI do Swagger aos serviços de aplicativo e o configura para fornecer mais informações sobre a API, como o título e a versão dela. Para obter informações sobre como fornecer detalhes mais robustos sobre a API, consulte Introdução ao NSwag e ao ASP.NET CoreAdicione o código realçado a seguir à próxima linha depois de
app
ser 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 exibir o documento JSON gerado e a IU 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.
Testar os dados de postagem
O código a seguir em Program.cs
cria um ponto de extremidade HTTP POST/todoitems
que adiciona dados ao banco de dados na 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 ponto de extremidade /
.
O ponto de extremidade POST será usado para adicionar dados ao aplicativo.
Com o aplicativo ainda em execução, no navegador, navegue até
https://localhost:<port>/swagger
para exibir a página de teste de API gerada pelo Swagger.Na página de teste da API do Swagger, selecione Post /todoitems>Experimentar.
Observe que o campo Corpo da solicitação contém um formato de exemplo gerado que reflete os parâmetros da API.
No corpo de solicitação, insira o JSON para um item de tarefas pendentes, sem especificar o
id
opcional:{ "name":"walk dog", "isComplete":true }
Selecione Executar.
O Swagger fornece um painel Respostas abaixo do botão Executar.
Observe alguns dos detalhes úteis:
- cURL: o Swagger fornece um comando cURL de exemplo na sintaxe Unix/Linux, que pode ser executado na linha de comando com qualquer shell bash que use a sintaxe Unix/Linux, incluindo o 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 à API. As solicitações reais podem incluir detalhes, como cabeçalhos e parâmetros de consulta e um corpo da solicitação.
- Resposta do servidor: inclui o corpo da resposta e os cabeçalhos. O corpo da resposta mostra que
id
foi definido como1
. - Código de resposta: um código de status 201
HTTP
foi retornado, indicando que a solicitação foi processada com êxito e resultou na criação de um novo recurso.
Examinar os pontos de extremidade GET
O aplicativo de exemplo implementa vários pontos de extremidade GET chamando MapGet
:
API | Descrição | Corpo da solicitação | Corpo da resposta |
---|---|---|---|
GET /todoitems |
Obter todos os itens de tarefas pendentes | Nenhum | Matriz de itens de tarefas pendentes |
GET /todoitems/complete |
Obter itens pendentes concluídos | Nenhum | Matriz de itens de tarefas pendentes |
GET /todoitems/{id} |
Obter um item por ID | Nenhum | Item de tarefas pendentes |
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 pontos de extremidade GET
Teste o aplicativo chamando os pontos de extremidade de um navegador ou do Swagger.
No Swagger, selecione GET /todoitems>Experimentar>Executar.
Como alternativa, chame GET /todoitems de um navegador inserindo o URI
http://localhost:<port>/todoitems
. Por exemplo,http://localhost:5001/todoitems
Uma resposta semelhante à seguinte é produzida pela chamada a GET /todoitems
:
[
{
"id": 1,
"name": "walk dog",
"isComplete": true
}
]
Chame GET /todoitems/{id} no Swagger para retornar dados de uma ID específica:
- Selecione GET /todoitems>Experimentar.
- Defina o campo de id para
1
e selecione Executar.
Como alternativa, chame GET /todoitems de um navegador inserindo o URI
https://localhost:<port>/todoitems/1
. Por exemplo,https://localhost:5001/todoitems/1
A resposta é semelhante ao descrito a seguir:
{ "id": 1, "name": "walk dog", "isComplete": true }
Este aplicativo usa um banco de dados em memória. Se o aplicativo for reiniciado, a solicitação GET não retornará nenhum dado. Se nenhum dado for retornado, envie os dados para o aplicativo via POST e tente a solicitação GET novamente.
Valores de retorno
O ASP.NET Core serializa automaticamente o objeto em 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 nenhuma exceção sem tratamento. As exceções sem tratamento são convertidas em erros 5xx.
Os tipos de retorno podem representar uma ampla variedade de códigos de status HTTP. Por exemplo, GET /todoitems/{id}
pode retornar dois valores de status diferentes:
- Se nenhum item corresponder às ID solicitadas, o método retornará um código de erro status 404NotFound.
- Caso contrário, o método retornará 200 com um corpo de resposta JSON. Retornar
item
resulta em uma resposta HTTP 200.
Examinar o ponto de extremidade PUT
O aplicativo de exemplo implementa um único ponto de extremidade 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();
});
Esse método é semelhante ao método MapPost
, exceto que ele usa HTTP PUT. Uma resposta bem-sucedida retorna 204 (sem conteúdo). De acordo com a especificação de HTTP, uma solicitação PUT exige que o cliente envie a entidade inteira atualizada, não apenas as alterações. Para dar suporte a atualizações parciais, use HTTP PATCH.
Testar o ponto de extremidade PUT
Este exemplo usa um banco de dados em memória que precisará ser iniciado sempre que o aplicativo for iniciado. Deverá haver um item no banco de dados antes de você fazer uma chamada PUT. Chame GET para garantir a existência de um item no banco de dados antes de fazer uma chamada PUT.
Atualize o item pendente que tem Id = 1
e defina o nome dele como "feed fish"
.
Use o Swagger para enviar uma solicitação PUT:
Selecione Put /todoitems/{id}>Experimentar.
Defina o campo id como
1
.Defina o corpo de solicitação para o seguinte JSON:
{ "name": "feed fish", "isComplete": false }
Selecione Executar.
Examinar e testar o ponto de extremidade DELETE
O aplicativo de exemplo implementa um único ponto de extremidade 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 o Swagger para enviar uma solicitação DELETE:
Selecione DELETE /todoitems/{id}>Experimentar.
Defina o campo ID como
1
e selecione Executar.A solicitação DELETE é enviada ao aplicativo e a resposta é exibida no painel Respostas. O corpo da resposta está vazio e código de status Resposta do servidor é 204.
Impedir o excesso de postagem
Atualmente, o aplicativo de exemplo expõe todo o objeto Todo
. Aplicativos de produção Em aplicativos de produção, um subconjunto do modelo geralmente é 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 é chamado de DTO (Objeto de Transferência de Dados), modelo de entrada ou modelo de exibição. O DTO é usado neste artigo.
Um DTO pode ser usado para:
- Impedir o excesso de postagem.
- Oculte propriedades que os clientes não deveriam visualizar.
- Omita algumas propriedades para reduzir o tamanho do conteúdo.
- Nivelar gráficos de objetos que contenham objetos aninhados. Os grafos de objeto nivelados podem ser mais convenientes para os clientes.
Para demonstrar a abordagem de 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 ocultado neste aplicativo, mas um aplicativo administrativo poderia optar por mostrá-lo.
Verifique se você pode postar e obter o campo secreto.
Crie um arquivo chamado TodoItemDTO.cs
com o código a seguir:
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
pelo seguinte código para usar este modelo de 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 de segredo.
Testar APIs mínimas
Para obter um exemplo de teste de um aplicativo de API mínima, consulte este exemplo do GitHub.
Publicar no Azure
Para obter informações sobre como implantar no Azure, consulte 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. Elas 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, veja 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 Web.
Visão geral
Este tutorial cria a seguinte API:
API | Descrição | Corpo da solicitação | Corpo da resposta |
---|---|---|---|
GET /todoitems |
Obter todos os itens de tarefas pendentes | Nenhum | Matriz de itens de tarefas pendentes |
GET /todoitems/complete |
Obter itens pendentes concluídos | Nenhum | Matriz de itens de tarefas pendentes |
GET /todoitems/{id} |
Obter um item por ID | Nenhum | Item de tarefas pendentes |
POST /todoitems |
Adicionar um novo item | Item de tarefas pendentes | Item de tarefas pendentes |
PUT /todoitems/{id} |
Atualizar um item existente | Item de tarefas pendentes | Nenhum |
DELETE /todoitems/{id} |
Excluir um item | Nenhum | Nenhum |
Pré-requisitos
Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do ASP.NET.
Criar um projeto de API
Inicie o Visual Studio 2022 e selecione Criar um novo projeto.
Na caixa de diálogo Criar um projeto:
- Insira
Empty
na caixa de pesquisa Pesquisar modelos . - Selecione o modelo ASP.NET Core Vazio e clique em Avançar.
- Insira
Nomeie o projeto como TodoApi e clique em Avançar.
Na caixa de diálogo Informações adicionais:
- Selecione .NET 8.0 (suporte a longo prazo)
- Desmarque Não usar declarações de nível superior
- Escolha Criar
Examinar 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
/
do HTTP GET que retornaHello World!
:
Executar o aplicativo
Pressione Ctrl + F5 para execução sem o depurador.
O Visual Studio exibe a caixa de diálogo a seguir:
Selecione Sim se você confia no certificado SSL do IIS Express.
A seguinte caixa de diálogo é exibida:
Selecione Sim se você concordar com confiar no certificado de desenvolvimento.
Para obter informações sobre como confiar no navegador Firefox, confira Erro de certificado Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
O Visual Studio inicia o Kestrelservidor Web e abre uma janela do 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 do NuGet
Os pacotes NuGet devem ser adicionados para dar suporte ao banco de dados e diagnósticos usados neste tutorial.
- No menu Ferramentas, selecione Gerenciador de Pacotes do NuGet > Gerenciar Pacotes do NuGet para a Solução.
- Selecione a guia Procurar.
- Na caixa de pesquisa, insira Microsoft.EntityFrameworkCore.InMemory e selecione
Microsoft.EntityFrameworkCore.InMemory
. - Marque a caixa de seleção Projeto no painel direito e selecione Instalar.
- Use 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.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 é um conjunto de classes que representam os dados gerenciados pelo aplicativo.
- Crie um arquivo chamado
TodoDb.cs
com o código a seguir:
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 de banco de dados, que é a classe principal que coordena a funcionalidade do Entity Framework para um modelo de dados. A classe é derivada da classe Microsoft.EntityFrameworkCore.DbContext.
Adicionar o código da API
- Substitua o conteúdo do arquivo
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();
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 DI (injeção de dependência) e permite exibir 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 de DI fornece acesso ao contexto do banco de dados e a outros serviços.
Este tutorial usa o Gerenciador de Pontos de Extremidade e arquivos .http para testar a API.
Testar os dados de postagem
O código a seguir em Program.cs
cria um ponto de extremidade HTTP POST/todoitems
que adiciona dados ao banco de dados na 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 ponto de extremidade /
.
O ponto de extremidade POST será usado para adicionar dados ao aplicativo.
Selecione Exibir>Outras Janelas>Gerenciador de Pontos de Extremidade.
Clique com o botão direito do mouse no ponto de extremidade POST e selecione Gerar solicitação.
Um novo arquivo é criado na pasta do projeto chamada
TodoApi.http
, com conteúdo semelhante ao seguinte exemplo:@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 tripla de hashtag (
###
) é um delimitador de solicitação: o que vem depois dela é 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 arquivo TodoApi.http agora deve se parece com o exemplo a seguir, mas com o número da 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 acima da linha de solicitação
POST
.A solicitação POST é enviada ao aplicativo e a resposta é exibida no painel Resposta.
Examinar os pontos de extremidade GET
O aplicativo de exemplo implementa vários pontos de extremidade GET chamando MapGet
:
API | Descrição | Corpo da solicitação | Corpo da resposta |
---|---|---|---|
GET /todoitems |
Obter todos os itens de tarefas pendentes | Nenhum | Matriz de itens de tarefas pendentes |
GET /todoitems/complete |
Obter itens pendentes concluídos | Nenhum | Matriz de itens de tarefas pendentes |
GET /todoitems/{id} |
Obter um item por ID | Nenhum | Item de tarefas pendentes |
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 pontos de extremidade GET
Teste o aplicativo chamando os pontos de extremidade GET
de um navegador ou usando o Gerenciador de Pontos de Extremidade. As etapas a seguir são para o Gerenciador de Pontos de Extremidade.
No Gerenciador de Pontos de Extremidade, clique com o botão direito do mouse no primeiro ponto de extremidade GET e selecione Gerar solicitação.
O conteúdo a seguir é adicionado ao arquivo
TodoApi.http
:Get {{TodoApi_HostAddress}}/todoitems ###
Selecione o link Enviar solicitação acima da nova linha de solicitação
GET
.A solicitação GET é enviada ao aplicativo e a resposta é exibida no painel Resposta.
O corpo de resposta é semelhante ao seguinte JSON:
[ { "id": 1, "name": "walk dog", "isComplete": true } ]
No Gerenciador de Pontos de Extremidade, clique com o botão direito do mouse no
/todoitems/{id}
GET e selecione Gerar solicitação. O conteúdo a seguir é adicionado ao arquivoTodoApi.http
:GET {{TodoApi_HostAddress}}/todoitems/{id} ###
Substitua
{id}
por1
.Selecione o link Enviar solicitação acima da nova linha solicitação GET.
A solicitação GET é enviada ao aplicativo e a resposta é exibida no painel Resposta.
O corpo de resposta é semelhante ao seguinte JSON:
{ "id": 1, "name": "walk dog", "isComplete": true }
Este aplicativo usa um banco de dados em memória. Se o aplicativo for reiniciado, a solicitação GET não retornará nenhum dado. Se nenhum dado for retornado, envie os dados para o aplicativo via POST e tente a solicitação GET novamente.
Valores de retorno
O ASP.NET Core serializa automaticamente o objeto em 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 nenhuma exceção sem tratamento. As exceções sem tratamento são convertidas em erros 5xx.
Os tipos de retorno podem representar uma ampla variedade de códigos de status HTTP. Por exemplo, GET /todoitems/{id}
pode retornar dois valores de status diferentes:
- Se nenhum item corresponder às ID solicitadas, o método retornará um código de erro status 404NotFound.
- Caso contrário, o método retornará 200 com um corpo de resposta JSON. Retornar
item
resulta em uma resposta HTTP 200.
Examinar o ponto de extremidade PUT
O aplicativo de exemplo implementa um único ponto de extremidade 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();
});
Esse método é semelhante ao método MapPost
, exceto que ele usa HTTP PUT. Uma resposta bem-sucedida retorna 204 (sem conteúdo). De acordo com a especificação de HTTP, uma solicitação PUT exige que o cliente envie a entidade inteira atualizada, não apenas as alterações. Para dar suporte a atualizações parciais, use HTTP PATCH.
Testar o ponto de extremidade PUT
Este exemplo usa um banco de dados em memória que precisará ser iniciado sempre que o aplicativo for iniciado. Deverá haver um item no banco de dados antes de você fazer uma chamada PUT. Chame GET para garantir a existência de um item no banco de dados antes de fazer uma chamada PUT.
Atualize o item pendente que tem Id = 1
e defina o nome dele como "feed fish"
.
No Gerenciador de Pontos de Extremidade, clique com o botão direito do mouse no ponto de extremidade PUT e selecione Gerar solicitação.
O conteúdo a seguir é 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 acima da nova linha de solicitação PUT.
A solicitação PUT é enviada ao aplicativo e a resposta é exibida no painel Resposta. O corpo da resposta está vazio e código de status é 204.
Examinar e testar o ponto de extremidade DELETE
O aplicativo de exemplo implementa um único ponto de extremidade 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 Gerenciador de Pontos de Extremidade, clique com o botão direito do mouse do ponto de extremidade DELETE e selecione Gerar solicitação.
Uma solicitação DELETE é adicionada a
TodoApi.http
.Substitua
{id}
na linha de solicitação DELETE com1
. A solicitação DELETE deve ter uma aparência semelhante ao exemplo a seguir:DELETE {{TodoApi_HostAddress}}/todoitems/1 ###
Selecione o link Enviar solicitação para a solicitação DELETE.
A solicitação DELETE é enviada ao aplicativo e a resposta é exibida no painel Resposta. O corpo da resposta está vazio e código de status é 204.
Usar a API do MapGroup
O código do aplicativo de exemplo repete o prefixo de URL todoitems
sempre que configura um ponto de extremidade. 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. Isso reduz o código repetitivo e permite personalizar grupos inteiros de pontos de extremidade com uma única chamada a métodos como RequireAuthorization e WithMetadata.
Substitua o conteúdo de 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 faz as seguintes alterações:
- Adiciona
var todoItems = app.MapGroup("/todoitems");
para configurar o grupo usando o prefixo/todoitems
da URL. - Altera todos os métodos
app.Map<HttpVerb>
paratodoItems.Map<HttpVerb>
. - Remove o prefixo
/todoitems
da URL das chamadas de métodoMap<HttpVerb>
.
Teste os pontos de extremidade para verificar se eles funcionam da mesma forma.
Usar a API TypedResults
Retornar TypedResults em vez de Results tem várias vantagens, incluindo capacidade de teste e retornar automaticamente os metadados de tipo de resposta para OpenAPI para descrever o ponto de extremidade. Para obter mais informações, consulte TypedResults vs Resultados.
Os métodos Map<HttpVerb>
podem chamar métodos de manipulador de rotas em vez de usar lambdas. 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 de 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);
}
Impedir o excesso de postagem
Atualmente, o aplicativo de exemplo expõe todo o objeto Todo
. Aplicativos de produção Em aplicativos de produção, um subconjunto do modelo geralmente é 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 é chamado de DTO (Objeto de Transferência de Dados), modelo de entrada ou modelo de exibição. O DTO é usado neste artigo.
Um DTO pode ser usado para:
- Impedir o excesso de postagem.
- Oculte propriedades que os clientes não deveriam visualizar.
- Omita algumas propriedades para reduzir o tamanho do conteúdo.
- Nivelar gráficos de objetos que contenham objetos aninhados. Os grafos de objeto nivelados podem ser mais convenientes para os clientes.
Para demonstrar a abordagem de 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 ocultado neste aplicativo, mas um aplicativo administrativo poderia optar por mostrá-lo.
Verifique se você pode postar e obter o campo secreto.
Crie um arquivo chamado TodoItemDTO.cs
com o código a seguir:
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
pelo seguinte código para usar este modelo de 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 de segredo.
Solução de problemas com o exemplo concluído
Se você encontrar um problema que não possa resolver, compare seu código com o projeto concluído. Exibir ou baixar projeto concluído (como baixar).
Próximas etapas
- Configurar opções de serialização JSON.
- Trate erros e exceções: a página de exceção do desenvolvedor é habilitada por padrão no ambiente de desenvolvimento para aplicativos de API mínima. Para obter informações sobre como lidar com erros e exceções, consulte Manipular erros em APIs de ASP.NET Core.
- Para obter um exemplo de teste de um aplicativo de API mínima, consulte este exemplo do GitHub.
- Suporte do OpenAPI em APIs mínimas.
- Início rápido: Publicar no Azure.
- Organização de APIs mínimas do ASP.NET Core.