Diferente de uma API Rest usual, a minial API é enxuta, direta com o mínimo de configurações possíveis e não utiliza Controllers.
E por não utilizar Controllers devemos nos ter atenção com algumas adaptações necessárias para usar autenticação e o Swagger e realizar o mapeamento puro dos endpoints.
Abra o visual studio 2022 e selecione para criar um novo projeto, em seguida selecione a opção API Web do ASP.NET Core com C# e clique no botão próximo.
Informe o nome e o local onde ficará seu projeto.
Framework: .NET 6
Tipo de Autenticação: None (Faremos a autenticação manualmente)
Configurar para HTTPS: SIM
Habilitar Docker: Opcional
Usar Controladores (desmarque para usar APIs mínimas): Aqui está o segredo, se ficar selecionado irá criar uma API normal com Controllers, como queremos uma minimal API devemos não marca-la.
Habilitar o suporte a OpenAPI (Swagger): SIM.
Feito todas as configurações clique no botão criar e o Visual Studio irá criar o nosso projeto.
Importante: Com o ASP.NET 6 a classe Startup deixou de ser usada e foi integrada diretamente na classe Program.cs.
Nesta pasta na estrutura do programa fica o arquivo launchSettings.json onde estão as informações da url e porta que será carrega a nossa API.
Por padrão o url do locahost e as portas são
https://localhost:7143
http://localhost:5143
Esse arquivo é onde iremos colocar nossas secret Keys, para serem utilizadas em nossa API.
É onde tudo acontece quando falamos de minimal API.
var builder = WebApplication.CreateBuilder(args);
CreateBuilder -> A classe Program.cs inicia com a criação de uma variável builder, e é nela onde o container da aplicação é criado e os serviços e configurações serão adicionados, é a base da nossa aplicação web API.
builder.Services.AddEndpointsApiExplorer(); <br>
Como não utilizamos controllers em APIs mínimas, tudo é feito manualmente.
Assim esse comando cria um metadados para a criação dos endpoints.
builder.Services.AddSwaggerGen(); <br>
O Swagger irá utilizar os metadados criados para os endpoints acima para a geração da documentação dos endpoints da nossa API.
var app = builder.Build(); <br>
Esse comando será sempre realizado por último, após as configurações dos nossos serviços.
Com esse comando será gerado o build da nossa aplicação web. Esse app nada mais é que a construção da no API com todos os serviços configurados e adicionados anteriormente.
Os próximos comando definirão todo o fluxo das requisições, como as requisições irão trabalhar.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
Nesse comando ele verifica se estamos em ambiente de desenvolvimento e adiciona o Swagger.
app.UseHttpsRedirection(); <br>
Neste comando informa que as requisições serão sempre redirecionadas para HTTPS.
app.MapGet("/endpoint"... <br>
Como não existem controllers, usamos a sintaxe app.Map+Verbo(GET,POST,PUT ou DELETE) e o nome do nosso endpoint onde as requisições deverão ser direcionadas.
E em seguida colocamos a ação que nosso endpoint irá realizar.
.WithName("GetPelidoEndpoint"); <br>
Podemos através do WithName setar apelidos para nossos endpoints, ou seja, podemos acessar pelo endpoint original ou pelo apelido declarado no WithName.
Por fim temos app.Run que realização a execução da API, deixando-a funcionando para podermos acessá-la.
Ao executar a aplicação no Visual Studio será aberto no navegador algo próximo a isso.
Sendo acessado pelo endpoint: https://localhost:7143/swagger/index.html
Esse endpoint é criado automaticamente pelo Visual Studio, onde através de uma requisição GET você obtém a resposta como nas imagens demonstradas abaixo.
Para podermos criar nossos próprios endpoints, precisamos primeiramente remover o endpoint criado pelo Visual Studio, devendo nossa classe Program.cs ficar semelhante a imagem abaixo.
Primeiramente vamos criar uma pasta chamada Model, onde iremos colocar nossos modelos e dentro dessa pasta iremos criar uma nova classe chamada AtividadeModel.cs com o código abaixo.
namespace minimal_api_todo.Models
{
public class AtividadeModel
{
public Guid Id { get; set; }
public string Titulo { get; set; }
public string Descricao { get; set; }
public int Status { get; set; }
public bool Ativo{ get; set; }
}
}
Primeiramente vamos criar uma pasta chamada Data, onde iremos colocar nossos dataContext e dentro dessa pasta iremos criar uma nova classe chamada DataBaseContext.cs com o código abaixo.
using Microsoft.EntityFrameworkCore;
using minimal_api_todo.Models;
namespace minimal_api_todo.Data
{
public class DataBaseContext : DbContext
{
public DataBaseContext(DbContextOptions<DataBaseContext> options) : base(options) { }
public DbSet<AtividadeModel> Atividades { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AtividadeModel>()
.ToTable("tb_cad_atividade");
modelBuilder.Entity<AtividadeModel>()
.HasKey(p => p.Id);
modelBuilder.Entity<AtividadeModel>()
.Property(p => p.Titulo)
.IsRequired()
.HasColumnType("varchar(100)");
modelBuilder.Entity<AtividadeModel>()
.Property(p => p.Descricao)
.IsRequired()
.HasColumnType("varchar(250)");
modelBuilder.Entity<AtividadeModel>()
.Property(p => p.Status)
.IsRequired()
.HasColumnType("integer");
base.OnModelCreating(modelBuilder);
}
}
}
Iremos trabalhar com EntityFramework.Core e EntityFramework.InMemory para neste momento criarmos um banco de dados em memória e conseguir realizar nossos testes sem a necessidade de ter um banco de dados SQL Server. Para isso teremos que instalar esses dois pacotes, você realizar a intalação por package install, Nuget ou no arquivo .csproj inserir as informações abaixo dentro da tag
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational.Design" Version="1.1.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Em seguida rode o comando dotnet retore para que seja realizada a restauração e instalação de todos as dependências dos pacotes. Com nosso DataBaseContext criado e todas as dependências do EF Core instaladas, devemos colocar nossa string de conexão entre a API e nosso banco de dados em memória. Para isso vá até a classe Program.cs e logo abaixo do comando ### builder.Services.AddSwaggerGen(); insira o código abaixo.
builder.Services.AddDbContext<DataBaseContext>(options => options.UseInMemoryDatabase("BancoDados"));
Esse commando irá adicionar o serviço de DbContext e criar uma banco em memória chamado BancoDados.
Como não existem controllers, os endpoints serão criados diretamente em nossa classe Program.cs Iremos criar um endpoint chamado “atividades” para listar todas as atividades de nossa tabela chamada tb_cad_atividade. Para isso utilizamos o código abaixo antes do comando app.Run().
app.MapGet("/atividades", async (
DataBaseContext context) =>
await context.Atividades.ToListAsync())
.WithName("GetAtividades")
.WithTags("Atividades");
app.MapGet("/atividades", async (): Cria um endpoint chamado atividades para uma requisição assincrona do tipo GET . DataBaseContext context) =>: Adiciona nosso contexto para ações no banco de dados.
await context.Atividades.ToListAsync()): Realiza a listagem de forma assíncrona de todos os dados da tabela tb_cad_atividade. .WithName("GetAtividades"): Insere um apelido ao nosso endpoint. .WithTags("Atividades"): Insere uma tag para podermos agruparmos nossos endpoints em nossoa documentação.
Agora ao rodar a aplicação, devemos ter um endpoint chamado atividades e devemos conseguir realizar uma requisição GET para listar os dados, não irá carregar nada porque não temos dados inserido ainda, mas a requisição deve retornar um http status 200 – Sucess.
Para carregar uma atividades através do Id, devemos passar como parâmetro através do endpoint, buscar essa informação no banco e verificar se existe ou não. Nosso código ficará.
app.MapGet("/atividades/{id}", async (
Guid id,
DataBaseContext context) =>
await context.Atividades.FindAsync(id)
is AtividadeModel atividade ? Results.Ok(atividade) : Results.NotFound())
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.WithName("GetAtividadesById")
.WithTags("Atividades");
Para criar nosso endpoint com requisição POST, precisaremos validar se o Json recebido pelo body é valido e para isso precisaremos utilizar um pacote chamado MiniValitaion que pode ser encontrado no github do DamianEdwards pelo link https://github.com/DamianEdwards/MiniValidation Para sua instalação basta utilizar o comando abaixo. dotnet add package MinimalApis.Extensions --prerelease
Em seguida nosso endpoint para o POST ficará da seguinte forma.
app.MapPost("/atividades", async (
DataBaseContext context,
AtividadeModel atividade) =>
{
if (!MiniValidator.TryValidate(atividade, out var errors))
return Results.ValidationProblem(errors);
context.Atividades.Add(atividade);
var result = await context.SaveChangesAsync();
return result > 0
? Results.Created($"/atividades/{atividade.Id}", atividade)
: Results.BadRequest("Houve um problema a tentar salvar o registro.");
})
.ProducesValidationProblem()
.Produces<AtividadeModel>(StatusCodes.Status201Created)
.Produces(StatusCodes.Status400BadRequest)
.WithName("PostAtividades")
.WithTags("Atividades");
Para a validação do JSON recebido estamos utilizando o seguinte trecho do código.
if (!MiniValidator.TryValidate(atividade, out var errors))
return Results.ValidationProblem(errors);
O código para nosso PUT ficou da seguinte forma.
app.MapPut("/atividades/{id}", async (
Guid id,
DataBaseContext context,
AtividadeModel atividade) =>
{
var _atividade = await context.Atividades.AsNoTracking<AtividadeModel>()
.FirstOrDefaultAsync(x => x.Id == id);
if (_atividade == null) return Results.NotFound();
if (!MiniValidator.TryValidate(atividade, out var errors))
return Results.ValidationProblem(errors);
context.Atividades.Update(atividade);
var result = await context.SaveChangesAsync();
return result > 0
? Results.NoContent()
: Results.BadRequest("Houve um problema a tentar atualizar o registro.");
})
.ProducesValidationProblem()
.Produces(StatusCodes.Status204NoContent)
.Produces(StatusCodes.Status400BadRequest)
.WithName("PutAtividades")
.WithTags("Atividades");
E por último, nosso endpoint DELETE ficou desse jeito.
app.MapDelete("/atividades/{id}", async (
Guid id,
DataBaseContext context) =>
{
var atividade = await context.Atividades.FindAsync(id);
if (atividade == null) return Results.NotFound();
context.Atividades.Remove(atividade);
var result = await context.SaveChangesAsync();
return result > 0
? Results.NoContent()
: Results.BadRequest("Houve um problema a tentar remover o registro.");
})
.ProducesValidationProblem()
.Produces(StatusCodes.Status204NoContent)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status204NoContent)
.WithName("DeleteAtividades")
.WithTags("Atividades");
Por fim, com todos os endpoints criados podemos analisar a versão final de nossa documentação.