.NET 6  - ASP .NET Core - Minimal APIs


 Hoje vou revisar os recursos que foram incorporados com a introdução das Minimal APIs.

As Minimal APIs ou APIs mínimas foram introduzidas no Preview 4 do .NET 6 e desde então foram habilitados um conjunto de recursos para melhorar e incrementar as Minimal APis. (Traduzido do original com alterações)

A seguir, fazendo uma revisão, veremos os principais recursos incorporados desde então.

1- Paridade com experiências existentes

As minimal APIs mudam fundamentalmente como o código de inicialização do aplicativo é estruturado. Isso significa que algumas das ferramentas e bibliotecas que enviamos que interagem com o aplicativo precisam ser atualizadas para lidar com esse novo padrão. As seguintes ferramentas e bibliotecas foram atualizadas em conformidade:

2- IResult para produzir respostas HTTP comuns

Na versões previews do .NET 6 anteriores, foram estendidas as implementações de IActionResult do MVC para oferecer suporte ao novo tipo IResult introduzido para APIs mínimas.

Agora, foram removidas as dependências entre IActionResult e IResult e foi adicionada uma nova classe de utilitário estática Results  para produzir respostas HTTP comuns. Exemplo:

app.MapPut("/todos/{id}", async (TodoDbContext db, int id, Todo todo) =>
{
    if (id != todo.Id)
    {
        return Results.BadRequest();
    }
    if (!await db.Todos.AnyAsync(x => x.Id == id))
    {
        return Results.NotFound();
    }
    db.Update(todo);
    await db.SaveChangesAsync();
    return Results.Ok();
});

3- Suporte a Request, Response e User para Actions

Foram adicionadas suporte para vincular os parâmetros HttpRequest, HttpResponse e ClaimsPrincipal em Actions.

Esses parâmetros vêm das propriedades Request, Response e User em HttpContext, respectivamente. Esta é uma adição ao suporte pré-existente para vincular o HttpContext diretamente e também um CancellationToken a partir de HttpContext.RequestAborted.

O exemplo abaixo aceita o usuário atual diretamente no método do manipulador de solicitação.

app.MapGet("/api/items", async (ITodoService service, ClaimsPrincipal user) =>
{
    return await service.GetListAsync(user.GetUserId());
})
.RequireAuthorization();

4- Melhorias no host e template

As APIs mínimas introduziram novas APIs de hospedagem e, combinadas com novos recursos do C# como Global using e instruções de nível superior, permitiram otimizar a experiência de inicialização do aplicativo.  Assim estes recursos foram implementados nos outros modelos de projeto ASP.NET Core.

Por exemplo, ao criar uma nova ASP.NET Core Web API usando o template padrão, você notará o seguinte:

Essas mudanças reduzem a quantidade de código padrão necessário para configurar e iniciar um novo aplicativo. Observe que o modelo de hospedagem existente e o padrão de inicialização continuarão a ser compatíveis com os aplicativos existentes.

5- Vinculação de parâmetro (Binding)

Na versão RC2,  foi incluído o suporte em TryParse e BindAsync para métodos herdados. Também é verificado se existem métodos TryParse e BindAsync públicos que não estão no formato correto que gerarão um um erro para que você saiba que usou a sintaxe errada para seu(s) método (s).

Além disso, o método BindAsync agora tem outra sobrecarga com suporte que não requer o ParameterInfo:

public static ValueTask <T?> BindAsync(HttpContext context)

Você pode adicionar um método TryParse estático em seu tipo personalizado, conforme mostrado abaixo, se desejar vincular valores de rota, atributos de cabeçalho e strings de consulta. O método TryParse deve ter os seguintes formatos:

public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out result);

Abaixo um exemplo usando TryParse para um tipo complexo Point :

app.MapGet("/map", (Point point) => $"Point: {point.X}, {point.Y}");

public class Point
{
    public double X { get; set; }
    public double Y { get; set; }

    public static bool TryParse(string? value, IFormatProvider? provider, out Point? point)
    {
        // Format is "(12.3,10.1)"
        var trimmedValue = value?.TrimStart('(').TrimEnd(')');
        var segments = trimmedValue?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);

        if (segments?.Length == 2
            && double.TryParse(segments[0], out var x)
            && double.TryParse(segments[1], out var y))
        {
            point = new Point { X = x, Y = y };
            return true;
        }

        point = null;
        return false;
    }
}

O Request  /map?point=(10.1, 11.4) para o endpoint acima retornará um objeto Point com os seguintes valores de propriedade:
 X = 10.1, Y = 11.4.

Além disso, se quiser assumir o controle do processo de vinculação para o seu tipo, você pode usar as seguintes formas de BindAsync:

public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo? parameter);
public static ValueTask<T?> BindAsync(HttpContext context);

6- OpenAPI

Foram incluídas melhorias para permitir que você descreva se o corpo da solicitação é necessário ou não usando o método de extensão Accepts<TRequest>() ou o atributo [Consume (typeof (Todo), "application/json", IsRequired = false)].

O método de extensão Accepts e o atributo Consumes permitem que você expresse o tipo Todo e o contentType application/json para seus documentos open-api gerados, conforme indicado no exemplo abaixo.

app.MapPost("/todo", async (HttpRequest request) =>
{
    var todo =  await request.Body.ReadAsJsonAsync<Todo>();
    return todo is Todo ? Results.Ok(todo) : Results.Ok();
})
.Accepts<Todo>(isRequired: false, contentType: "application/json");

app.MapPost("/todo", HandlePostTodo);
[Consumes(typeof(Todo), "application/json", IsRequired = false)]
IResult HandlePostTodo(HttpRequest request)
{
    var todo =  await request.Body.ReadAsJsonAsync<Todo>();
    return todo is Todo ? Results.Ok(todo) : Results.Ok();
}

 

Se desejar agrupar endpoints relacionados em coleções diferentes em documentos OpenAPI (Swagger), você pode fazer isso usando o método de extensão WithTags que permite fornecer os metadados de tags de agrupamento. Veja o exemplo de uso abaixo:

app.MapGet("/", () => "Hello World!")
    .WithTags("Examples");
app.MapGet("/todos/sample", () => new[]
    {
        new Todo { Id = 1, Title = "Do this" },
        new Todo { Id = 2, Title = "Do this too" }
    })
    .WithTags("Examples", "TodoApi");

 

Conclusão

Essas são algumas das novidades relacionadas com as Minimal APIs  que foram introduzidas até agora no  .NET 6. Como estamos na reta final do lançamento oficial acompanhe as novidades no blog oficial e registres suas experiências e problemas encontrados no GitHub.

"Porque todos devemos comparecer ante o tribunal de Cristo, para que cada um receba segundo o que tiver feito por meio do corpo, ou bem, ou mal."
2 Coríntios 5:10


Referências:


José Carlos Macoratti