In this article, we’ll look closely at the Carter library and how we can use it in our ASP.NET Core projects.

To download the source code for this article, you can visit our GitHub repository.

Let’s get started!

What Is the Carter Library

The Carter library is an open-source project providing an abstraction layer for ASP.NET Core applications. This layer serves as a way to manage our minimal APIs easily and efficiently. The package provides us with a set of interfaces and methods that help with endpoint management.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

So, let’s examine this in action:

app.MapGet("/books", async (IBookService service) =>
{
    var books = await service.GetAllAsync();

    return Results.Ok(books);
})
.WithOpenApi();

app.MapGet("/books/{id:guid}", async (Guid id, IBookService service) =>
{
    var book = await service.GetByIdAsync(id);

    return Results.Ok(book);
})
.WithOpenApi();

app.MapPost("/books", async (CreateBookRequest request, IBookService service) =>
{
    var book = await service.CreateAsync(request);

    return Results.Created($"/books/{book.Id}", book);
})
.WithOpenApi();

// Other CRUD endpoints PUT, DELETE

Here, we have a minimal API with CRUD endpoints for a book management application. Furthermore, the endpoints are located in our Program class and take up about 40 lines.

This is already a bit cumbersome to navigate and maintain. It will only get more complex as our application grows. Here is where Carter comes into play.

If you want to have a detailed overview of minimal APIs, check out our article Minimal APIs in .NET 6.

Next, we’ll see how it can make our lives easier.

How to Use Carter With ASP.NET Core Minimal APIs

Before we do anything with our code, we need to install the Carter library:

dotnet add package Carter

Once this is done, we can start working with it:

public class BookModule : ICarterModule
{
    public void AddRoutes(IEndpointRouteBuilder app)
    {
    }
}

The library revolves around modules, so we start by creating the BookModule and implementing the ICarterModule interface. The interface comes with the AddRoutes() method.

Let’s implement it:

public class BookModule : ICarterModule
{
    public void AddRoutes(IEndpointRouteBuilder app)
    {
        app.MapGet("/books", async (IBookService service) =>
        {
            var books = await service.GetAllAsync();

            return Results.Ok(books);
        })
        .WithOpenApi();

        app.MapGet("/books/{id:guid}", async (Guid id, IBookService service) =>
        {
            var book = await service.GetByIdAsync(id);

            return Results.Ok(book);
        })
        .WithOpenApi();

        // Other CRUD endpoints POST, PUT, DELETE
    }
}

The AddRoutes() method takes one parameter of IEndpointRouteBuilder type. Thanks to this, the only thing we have to do with the module, is to move our endpoints from the Program class to the AddRoutes() method.

Next, we must register our module:

builder.Services.AddCarter();

In our Program class, we use the AddCarter() extension method on our service collection. This will scan our project for all implementations of the ICarterModule interface and register them with a Singleton lifetime.

Finally, we map our endpoints:

app.MapCarter();

We call the MapCarter() method on our WebApplication instance. By doing this, we map our endpoints and make them visible to any users of our API.

If you want to discover another way of registering minimal APIs, pay a visit to our article Automatic Registration of Minimal API Endpoints in .NET.

Carter and Model Validation in ASP.NET Core

With Carter, we also get a FluentValidation integration, which is installed alongside the Carter NuGet package. So, let’s utilize it:

public class CreateBookRequestValidator : AbstractValidator<CreateBookRequest>
{
    public CreateBookRequestValidator()
    {
        RuleFor(x => x.Title).NotNull().NotEmpty();
        RuleFor(x => x.Author).NotNull().NotEmpty();
        RuleFor(x => x.ISBN).NotNull().NotEmpty();
    }
}

We start by creating the CreateBookRequestValidator class. Next, we implement the AbstractValidator<T> abstract class, where T is our CreateBookRequest entity. After this, we write some validation rules – in our case, we want the Title, Author, and ISBN property not to be null or empty.

Let’s incorporate the validation into our CREATE endpoint:

app.MapPost("/books", async (
    HttpContext context,
    CreateBookRequest request, 
    IBookService service) =>
{
    var result = context.Request.Validate(request);

    if (!result.IsValid)
    {
        context.Response.StatusCode = (int)HttpStatusCode.UnprocessableEntity;
        await context.Response.Negotiate(result.GetFormattedErrors());

        return Results.UnprocessableEntity();
    }

    var book = await service.CreateAsync(request);

    return Results.Created($"/books/{book.Id}", book);
})
.WithOpenApi();

In our BookModule class, we make some updates to our /books endpoint. We start by adding the HttpContext of the request as a parameter.

Then, we use the Validate() method, which is an extension method on the HttpRequest class that we get from Carter, passing the CreateBookRequest parameter. The method uses an internal implementation of the IValidatorLocator interface to try to resolve an appropriate validator. Consequently, this removes the need for us to register our CreateBookRequestValidator in the DI container. If there is no matching validator, we’ll get an InvalidOperationException exception.

Finally, we use two other Carter methods – Negotiate() and GetFormattedErrors(). With the latter, we get a formatted output with the property name and error message for each error encountered by the validator. The Negotiate() method executes content negotiation on the current HttpResponse instance and tries to utilize an accepted media type if possible and if none is found – it will default to application/json.

Advanced Endpoint Configurations With Carter in ASP.NET Core

With Carter, we can go a step further with some advanced configuration:

public class BookModule : CarterModule
{
    public BookModule()
        : base("/api")
    {
        WithTags("Books");
        IncludeInOpenApi();		
    }

    public override void AddRoutes(IEndpointRouteBuilder app)
    {
        // CRUD endpoints
    }
}

Here, we make our BookModule class implement the CarterModule abstract class and not the ICarterModule interface. The first thing we do after that is to add the override operator to the AddRoutes() method. 

Next, we create a constructor for the BookModule class, call the CarterModule‘s base one, and pass /api to it. This will add a prefix of /api to all of our endpoints in this module. Inside the constructor, we use the WithTags() method to group all endpoints under the Books tag.

Finally, we call the IncludeInOpenApi() method. With it, we add OpenAPI annotation to all endpoints in the module and include all routes in the OpenAPI output. Therefore, we don’t need to call the WithOpenApi() method on all of our endpoints.

The Carter library also provides us with various other methods for further customization of our modules. For example, if our application has authorization set up, we can activate it for all endpoints for a given module by calling the RequireAuthorization() method. If we have a rate-limiting setup, we can either enlist the endpoints by using the RequireRateLimiting() method and passing the name of the rate-limiting policy to it, or we can use the DisableRateLimiting() method to disable rate-limiting.

Conclusion

In this article, we explored the Carter library and the streamlined approach to managing ASP.NET Core APIs it offers. By leveraging its modular structure, we can organize our endpoints efficiently into separate modules, enhancing code readability and maintainability.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!