Telerik blogs

.NET 7 arrived and brought three great features to minimal APIs. Check out in this blog post what’s new with practical examples.

Minimal APIs arrived with the promise of simplifying the creation of microservices and Web REST APIs, and although they already had several features in their launch in .NET 6, there are still many features that are under development. With the recent arrival of .NET 7, three new features are available for minimal APIs.

Throughout the article, we will explore each of them through practical examples.

A Brief About Minimal APIs

Minimal APIs are ASP.NET Core templates that arrived with .NET 6 and were very well received by the public due to their versatility and simplicity.

With a few lines of code and without ceremony, minimal APIs allow developers to create complete microservices without much effort, as they eliminate many requirements of traditional MVC, such as creating controllers (although it is still possible to use them, they are not mandatory now).

The Past and Present of Minimal APIs

Minimal APIs brought several features ready to be used, such as routing, authorization and CORS that were inherited from MVC, but the great news is that extra features were also present since the official release, such as top-level statements, natural types for lambdas and method groups, default global usings, a new WebApplication host and new Map methods.

Through these resources, it is possible to create a new API with a few lines of code, just instantiating the web application host. Thus all the resources are ready for use, allowing the developer to focus on the application’s business rules and not waste more time with previously obligatory ceremonies.

With the official release of .NET 7 on November 8, 2022, some previously speculated improvements are now 100% functional and available for use, such as endpoint filters, route groups and typed results, which makes minimal APIs even more powerful and efficient.

What New Features Did .NET 7 Bring to Minimal APIs?

As expected, .NET 7 brought significant improvements to several technologies in the .NET universe such as gRPC, SignalR, Blazor, etc. And minimal APIs were not left out! Three big improvements are now available for use. Below we will explore each one.

💡 All examples shown in this article are 100% functional and can be accessed in this repository: source code.

1. Endpoint Filters

Validating a URL Parameter

Endpoint filters are useful for validating the parameters and body sent in a request to an endpoint, logging information about the request and response, and validating that the request is being redirected to a valid API version.

Note in the example below we are adding the extension method “AddEndpointFilter” that validates if the data sent in the request URL has more than 10 characters. If so, it returns an error message; otherwise, a success message.

A new feature of .NET 7 is also being used, which is the host’s native log provider, so now it is no longer mandatory to use other alternatives to run logs.

app.MapPost("sellers/create/{sellerName}", (string sellerName, SellerDb db) =>
{
    var newSeller = new Seller(Guid.NewGuid(), sellerName);
    db.Add(newSeller);
    db.SaveChanges();
    return Results.Ok("Seller created successfully");
})
.AddEndpointFilter(async (invocationContext, next) =>
{
    var sellerName = invocationContext.GetArgument<string>(0);

    if (sellerName.Length > 10)
    {
        app.Logger.LogInformation($"Error when creating the new seller");
        return Results.Problem("Seller Name must be a maximum of 10 characters!");
    }
    return await next(invocationContext);
});

Validating an Object

In addition to URL parameters, it is also possible to validate objects sent by the request body, as shown in the example below:

app.MapPut("/sellers/update/{id}", async (Seller seller, int id, SellerDb db) =>
{
    var dbSeller = await db.Sellers.FindAsync(id);

    if (dbSeller is null) return Results.NotFound();

    dbSeller.Name = seller.Name;

    await db.SaveChangesAsync();

    return Results.NoContent();
}).AddEndpointFilter(async (efiContext, next) =>
{
    var tdparam = efiContext.GetArgument<Seller>(0);

    var validationError = Utilities.IsValid(tdparam);

    if (!string.IsNullOrEmpty(validationError))
    {
        app.Logger.LogInformation($"Error when updating the new seller: {validationError}");
        return Results.Problem(validationError);
    }
    return await next(efiContext);
});

Note that in a simple way it is possible to validate one or more objects passed by the request body even before the endpoint is executed. The “IsValid” method is a custom static method to validate the sent data and return an error message if it is invalid.

public static class Utilities
{
    public static string IsValid(Seller seller)
    {
        string errorMessage = string.Empty;

        if (string.IsNullOrEmpty(seller.Name))
            errorMessage = "Seller name is required";

        return errorMessage;
    }
}

Validating the data before the endpoint prevents simple errors from reaching the service layer and being quickly intercepted.

2. Typed Results

With typed results, it is possible to return results strongly typed by minimal APIs.

Note in the example below the GetSeller method returns a TypedResults regardless of the result, in a simple way and with little code.

app.MapGet("sellers/getSeller/{sellerId}", (Guid id, SellerDb db) =>
{
    return GetSeller(id, db);
});

static async Task<Results<Ok<Seller>, NotFound>> GetSeller(Guid id, SellerDb db) =>
    await db.Sellers.FirstOrDefaultAsync(x => x.Id == id) is Seller item ? TypedResults.Ok(item) : TypedResults.NotFound();

3. Route Groups

Routed groups are a new feature in .NET 7 allowing you to organize groups of endpoints with a common prefix.

Using MapGroup, you can reduce repetitive code and also customize entire groups of endpoints with a single call to methods like RequireAuthorization and WithMetadata, which add endpoint metadata.

Observe in the following code how the organization of route groups can be done:

app.MapGroup("/public/sellers")
    .MapSellersApi()
    .WithTags("Public");

app.MapGroup("/private/sellers")
    .MapSellersApi()
    .WithTags("Private")
    .RequireAuthorization("Manager");

The code above is defining two groups of routes, the first public and the second private.

Now notice how the “MapSellersApi” method is implemented—it receives and returns an object of type “RouteGroupBuilder” and implements the “GetAllSellers” method, which returns a list of sellers.

public static class MapSellers
{
    public static RouteGroupBuilder MapSellersApi(this RouteGroupBuilder group)
    {
        group.MapGet("/", GetAllSellers);
        return group;
    }

    public static Ok<List<Seller>> GetAllSellers(SellerDb db)
    {
        var sellers = db.Sellers;
        return TypedResults.Ok(sellers.ToList());
    }
}

In the Swagger interface, the endpoints appear separated by the Public and Private tags as shown in the image below:

Swagger endpoints

Conclusion

Minimal APIs are a very useful feature and have gained attention from Microsoft due to their great public reception.

As shown throughout the article, .NET 7 brought three new features that make it even easier to develop ASP.NET Core applications. So, whenever there is an opportunity, be sure to use them.

References


assis-zang-bio
About the Author

Assis Zang

Assis Zang is a software developer from Brazil, developing in the .NET platform since 2017. In his free time, he enjoys playing video games and reading good books. You can follow him at: LinkedIn and Github.

Related Posts

Comments

Comments are disabled in preview mode.