Eventos
Campeonato Mundial de Visualização de Dados do Power BI
14 de fev., 16 - 31 de mar., 16
Com 4 chances de participar, você pode ganhar um pacote de conferência e chegar à Grande Final AO VIVO em Las Vegas
Saiba maisNão há mais suporte para esse navegador.
Atualize o Microsoft Edge para aproveitar os recursos, o suporte técnico e as atualizações de segurança mais recentes.
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.
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 |
Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do ASP.NET.
Inicie o Visual Studio 2022 e selecione Criar um novo projeto.
Na caixa de diálogo Criar um projeto:
Empty
na caixa de pesquisa Pesquisar modelos .Nomeie o projeto como TodoApi e clique em Avançar.
Na caixa de diálogo Informações adicionais:
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:
/
do HTTP GET que retorna Hello World!
: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.
Os pacotes NuGet devem ser adicionados para dar suporte ao banco de dados e diagnósticos usados neste tutorial.
Microsoft.EntityFrameworkCore.InMemory
.Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
.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.
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.
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.
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
###
###
) é 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.
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());
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 arquivo TodoApi.http
:
GET {{TodoApi_HostAddress}}/todoitems/{id}
###
Substitua {id}
por 1
.
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.
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:
item
resulta em uma resposta HTTP 200.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.
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}
por 1
.
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.
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 com 1
. 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.
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:
var todoItems = app.MapGroup("/todoitems");
para configurar o grupo usando o prefixo /todoitems
da URL.app.Map<HttpVerb>
para todoItems.Map<HttpVerb>
./todoitems
da URL das chamadas de método Map<HttpVerb>
.Teste os pontos de extremidade para verificar se eles funcionam da mesma forma.
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);
}
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:
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.
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).
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.
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 |
Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do ASP.NET.
Inicie o Visual Studio 2022 e selecione Criar um novo projeto.
Na caixa de diálogo Criar um projeto:
Empty
na caixa de pesquisa Pesquisar modelos .Nomeie o projeto como TodoApi e clique em Avançar.
Na caixa de diálogo Informações adicionais:
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:
/
do HTTP GET que retorna Hello World!
: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.
Os pacotes NuGet devem ser adicionados para dar suporte ao banco de dados e diagnósticos usados neste tutorial.
Microsoft.EntityFrameworkCore.InMemory
.7.0.17
, e selecione Instalar.Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
com a versão 7 mais recente disponível.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.
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.
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:
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.
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.
Adicionar o código realçado a seguir antes app
de ser definido na linha var app = builder.Build();
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(config =>
{
config.DocumentName = "TodoAPI";
config.Title = "TodoAPI v1";
config.Version = "v1";
});
var app = builder.Build();
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 Core
Adicione o código realçado a seguir à próxima linha depois de app
ser definido na linha var app = builder.Build();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseOpenApi();
app.UseSwaggerUi(config =>
{
config.DocumentTitle = "TodoAPI";
config.Path = "/swagger";
config.DocumentPath = "/swagger/{documentName}/swagger.json";
config.DocExpansion = "list";
});
}
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.
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:
id
foi definido como 1
.HTTP
foi retornado, indicando que a solicitação foi processada com êxito e resultou na criação de um novo recurso.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());
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:
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.
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:
item
resulta em uma resposta HTTP 200.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.
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.
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.
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:
var todoItems = app.MapGroup("/todoitems");
para configurar o grupo usando o prefixo /todoitems
da URL.app.Map<HttpVerb>
para todoItems.Map<HttpVerb>
./todoitems
da URL das chamadas de método Map<HttpVerb>
.Teste os pontos de extremidade para verificar se eles funcionam da mesma forma.
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);
}
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:
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.
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).
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.
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 |
Inicie o Visual Studio 2022 e selecione Criar um novo projeto.
Na caixa de diálogo Criar um projeto:
Empty
na caixa de pesquisa Pesquisar modelos .Nomeie o projeto como TodoApi e clique em Avançar.
Na caixa de diálogo Informações adicionais:
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:
/
do HTTP GET que retorna Hello World!
: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.
Os pacotes NuGet devem ser adicionados para dar suporte ao banco de dados e diagnósticos usados neste tutorial.
Microsoft.EntityFrameworkCore.InMemory
.6.0.28
, e selecione Instalar.Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
com a versão 7 mais recente disponível.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.
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.
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:
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.
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.
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 linha var app = builder.Build();
using NSwag.AspNetCore;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(config =>
{
config.DocumentName = "TodoAPI";
config.Title = "TodoAPI v1";
config.Version = "v1";
});
var app = builder.Build();
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 Core
Adicione o código realçado a seguir à próxima linha depois de app
ser definido na linha var app = builder.Build();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseOpenApi();
app.UseSwaggerUi(config =>
{
config.DocumentTitle = "TodoAPI";
config.Path = "/swagger";
config.DocumentPath = "/swagger/{documentName}/swagger.json";
config.DocExpansion = "list";
});
}
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.
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:
id
foi definido como 1
.HTTP
foi retornado, indicando que a solicitação foi processada com êxito e resultou na criação de um novo recurso.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());
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:
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.
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:
item
resulta em uma resposta HTTP 200.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.
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.
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.
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:
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.
Para obter um exemplo de teste de um aplicativo de API mínima, consulte este exemplo do GitHub.
Para obter informações sobre como implantar no Azure, consulte Início Rápido: Implantar um aplicativo Web ASP.NET.
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.
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 |
Visual Studio 2022 com a carga de trabalho de desenvolvimento Web e do ASP.NET.
Inicie o Visual Studio 2022 e selecione Criar um novo projeto.
Na caixa de diálogo Criar um projeto:
Empty
na caixa de pesquisa Pesquisar modelos .Nomeie o projeto como TodoApi e clique em Avançar.
Na caixa de diálogo Informações adicionais:
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:
/
do HTTP GET que retorna Hello World!
: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.
Os pacotes NuGet devem ser adicionados para dar suporte ao banco de dados e diagnósticos usados neste tutorial.
Microsoft.EntityFrameworkCore.InMemory
.Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
.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.
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.
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.
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
###
###
) é 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.
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());
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 arquivo TodoApi.http
:
GET {{TodoApi_HostAddress}}/todoitems/{id}
###
Substitua {id}
por 1
.
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.
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:
item
resulta em uma resposta HTTP 200.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.
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}
por 1
.
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.
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 com 1
. 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.
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:
var todoItems = app.MapGroup("/todoitems");
para configurar o grupo usando o prefixo /todoitems
da URL.app.Map<HttpVerb>
para todoItems.Map<HttpVerb>
./todoitems
da URL das chamadas de método Map<HttpVerb>
.Teste os pontos de extremidade para verificar se eles funcionam da mesma forma.
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);
}
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:
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.
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).
Comentários do ASP.NET Core
O ASP.NET Core é um projeto código aberto. Selecione um link para fornecer comentários:
Eventos
Campeonato Mundial de Visualização de Dados do Power BI
14 de fev., 16 - 31 de mar., 16
Com 4 chances de participar, você pode ganhar um pacote de conferência e chegar à Grande Final AO VIVO em Las Vegas
Saiba maisTreinamento
Módulo
Criar uma API Web com API mínima, ASP.NET Core e .NET - Training
Saiba como criar uma API Web usando o .NET. Você também aprenderá a configurar rotas diferentes para lidar com leitura e gravação.