Thursday, May 12, 2022

.NET 7 Preview 4: Introducing Self-describing Support for Minimal APIs in ASP.NET Core

.NET 7 Preview 4 is released and it includes some nice features related to ASP.NET Core Minimal APIs. One of them is the support for self-describing API endpoints. 

In this post, let's have a look at how it works.

Consider the below Minimal API endpoints prior to .NET 7 Preview 4.

app.MapGet("/employees"async (EmployeeContext dbContext) =>
{
    return Results.Ok(await dbContext.Employees.ToListAsync());
});

Now if we have a look at the Swagger document, I can see something like this.

GET: /employees
It only says the endpoint returns 200, but nothing about the response type.

Let's have a look at another example. Consider the below endpoint.

app.MapGet("/employees/{id}"async (int id, EmployeeContext dbContext) =>
{
    Employee employee = await dbContext.Employees.FindAsync(id);
    if (employee is null)
    {
        return Results.NotFound();
    }
 
    return Results.Ok(employee);
});

And this would appear in the Swagger document as follows.

GET: /employees/{id}
Again nothing about the Response Type, and obviously no sign about the endpoint returning 404.
 
If we are to enrich these missing details, we will have to update the code as follows.

app
    .MapGet("/employees"async (EmployeeContext dbContext) =>
    {
        return Results.Ok(await dbContext.Employees.ToListAsync());
    })
    .Produces<List<Employee>>();
 
app
    .MapGet("/employees/{id}"async (int id, EmployeeContext dbContext) =>
    {
        Employee employee = await dbContext.Employees.FindAsync(id);
        if (employee is null)
        {
            return Results.NotFound();
        }
 
        return Results.Ok(employee);
    })
    .Produces<Employee>()
    .Produces(StatusCodes.Status404NotFound);

And now we can see the Swagger document is updated.

GET: /employees
GET: /employees/{id}
But what if we can let the APIs describe themselves without adding additional annotations.

With .NET 7 Preview 4, I can change the above endpoints as follows.

app.MapGet("/employees"async (EmployeeContext dbContext) =>
{
    return TypedResults.Ok(await dbContext.Employees.ToListAsync());
});

This will describe the endpoint the same way it did with annotations. 

The only change I did here is use the new TypedResults factory class instead of Results factory class when generating the result. The new TypedResults factory class will create Typed results (as the name suggests of course) instead of IResult as it did with Results factory class. And all these Typed results implement a new interface IEndpointMetadataProvider.

public interface IEndpointMetadataProvider
{
    static abstract void PopulateMetadata(EndpointMetadataContext context);
}

The framework will call PopulateMetadata() when the endpoint is built and that adds the necessary endpoint metadata to describe the HTTP response type.

Now when we have multiple return types, we need to explicitly specify the return types as follows.

app.MapGet("/employees/{id}"async Task<Results<Ok<Employee>, NotFound>> (int id, EmployeeContext dbContext) =>
{
    Employee employee = await dbContext.Employees.FindAsync(id);
    if (employee is null)
    {
        return TypedResults.NotFound();
    }
 
    return TypedResults.Ok(employee);
});

And this also will describe the endpoint the same way it did with annotations. 

You can find the complete sample code here.
   https://github.com/jaliyaudagedara/minimal-api

More Read
   ASP.NET Core updates in .NET 7 Preview 4

Hope this helps.

Happy Coding.

Regards,
Jaliya

No comments:

Post a Comment