Refactoring Minimal APIs with Carter
Tags: aspnetcore, libraries, bogus, carterframework
Published: 2022-11-05 15:56:24

---
title: Refactoring Minimal APIs with Carter
published: true
date: 2022-08-09 06:37:04 UTC
tags: ASPNETCore,libraries,Bogus,carterframework
canonical_url: https://coderethinked.com/refactoring-minimal-apis-with-carter/
---

In the previous post, we saw [what are Minimal APIs and how we can create them in ASP.NET Core 6](https://dev.to/karthikchintala/what-are-minimal-apis-in-aspnet-core-6-184k-temp-slug-8339504). In this post, we’ll see how we can refactor our Minimal APIs using Carter framework.

We had `UserRepository` in our previous post, I’m using the same repository for this example as well so that I don’t have to re-create `UserRepository` again.

Along with `UserRepository` (which has basic ~~C~~ RUD operations) let’s have a products repository as well.

## Adding products repository

Let’s create a product repository.

```csharp
public class Product
{
    public int ProductId { get; set; }
    public string ProductName { get; set; }
    public double Price { get; set; }
    public DateTime CreatedOn { get; set; }

    public bool IsValid()
    {
        return 
            ProductId != 0 &&
            !string.IsNullOrWhiteSpace(ProductName) &&
            Price >= 0;
    }
}

public interface IProductsRepository
{
    bool CreateProduct(Product product);
    Product GetProductById(int id);
    bool UpdateProduct(int id, Product product);
    bool DeleteProduct(int id);
}

public class ProductsRepository : IProductsRepository
{
    private List<Product> products;
    private readonly ILogger<IProductsRepository> _logger;

    public ProductsRepository(ILogger<IProductsRepository> logger)
    {
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        
        GenerateSeedData();
    }

    public bool CreateProduct(Product product)
    {
        var productNameExistsAlready = products.Any(p => p.ProductName == product.ProductName);
        if (productNameExistsAlready)
        {
            _logger.LogError("Product exists already!");
            return false;
        }

        products.Add(product);
        return true;
    }

    public Product GetProductById(int id)
    {
        return products.FirstOrDefault(a => a.ProductId == id);
    }

    public bool UpdateProduct(int id, Product product)
    {
        var p = products.FirstOrDefault(a => a.ProductId == id);
        
        p.ProductName = product.ProductName;
        p.CreatedOn = DateTime.Now;
        p.Price = product.Price;

        return true;
    }

    public bool DeleteProduct(int id)
    {
        var product = products.FirstOrDefault(p => p.ProductId == id);
        if (product == null)
        {
            return false;
        }

        products.Remove(product);
        return true;
    }

    private void GenerateSeedData()
    {
        products = new Faker<Product>()
            .RuleFor(p => p.ProductId, p => p.IndexFaker)
            .RuleFor(p => p.ProductName, p => p.Random.Words(2))
            .RuleFor(p => p.Price, p => Convert.ToDouble(p.Finance.Amount()))
            .RuleFor(p => p.CreatedOn, p => p.Date.Recent())
            .Generate(50);
    }
}
```

The products repository has 4 basic CRUD (Create, Read, Update, Delete) operations and a product model class. And we’ve generated our seed data for our products repo with the help of [Bogus for C#](https://dev.to/karthikchintala/how-to-generate-fake-data-in-c-2f4p-temp-slug-9485001) (a fake data generator)

## Registering API’s in Program.cs

Now, let’s register this product’s repository as a singleton in `program.cs`.

```csharp
builder.Services.AddSingleton<IProductsRepository, ProductsRepository>();
```

After registering the Products repository, let’s also define the endpoints/API’s for Products.

```csharp
// PRODUCTS operations
app.MapPost("/product/create", (Product product, IProductsRepository productsRepo) =>
{
   if (!product.IsValid()) return Results.BadRequest();
   productsRepo.CreateProduct(product);
   return Results.StatusCode(201);
});

app.MapGet("/product/{id}", (int id, IProductsRepository repo) =>
{
   if (id <= 0) return Results.BadRequest();

   return Results.Ok(repo.GetProductById(id));
});

app.MapPut("/product/update/{id}", (int id, Product product, IProductsRepository productRepo) =>
{
   if (!product.IsValid()) return Results.BadRequest();

   productRepo.UpdateProduct(id, product);
   return Results.Ok();
});

app.MapDelete("/product/delete/{id}", (int id, IProductsRepository productsRepo) =>
{
   return productsRepo.DeleteProduct(id);
});

```

## Program.cs hell

Let’s see what we have in our `Program.cs` class. Here is the complete file (with UserRepository operations included).

```csharp
using MinimalAPIs.Repositories;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddSingleton<IUserRepository, UserRepository>();
builder.Services.AddSingleton<IProductsRepository, ProductsRepository>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

#region API's

// USER operations
app.MapGet("/user/{id}", async (int id, IUserRepository userRepository, CancellationToken cancellationToken) =>
{
   var user = await userRepository.GetUserAsync(id, cancellationToken);
   if (user is null) return Results.BadRequest();

   return Results.Ok(user);
}).WithName("GetUsers");

app.MapPut("/user/update/{id}", (int id, User user, IUserRepository userRepository) =>
{
   return userRepository.UpdateUser(id, user);
}).WithName("UpdateUsers");

app.MapDelete("/user/{id}", (int id, IUserRepository userRepository) =>
{
   userRepository.DeleteUser(id);
   return Results.Ok();
}).WithName("DeleteUser");

// PRODUCTS operations
app.MapPost("/product/create", (Product product, IProductsRepository productsRepo) =>
{
   if (!product.IsValid()) return Results.BadRequest();
   productsRepo.CreateProduct(product);
   return Results.StatusCode(201);
});

app.MapGet("/product/{id}", (int id, IProductsRepository repo) =>
{
   if (id <= 0) return Results.BadRequest();

   return Results.Ok(repo.GetProductById(id));
});

app.MapPut("/product/update/{id}", (int id, Product product, IProductsRepository productRepo) =>
{
   if (!product.IsValid()) return Results.BadRequest();

   productRepo.UpdateProduct(id, product);
   return Results.Ok();
});

app.MapDelete("/product/delete/{id}", (int id, IProductsRepository productsRepo) =>
{
   return productsRepo.DeleteProduct(id);
});
#endregion

app.Run();

```

With just products and user operations, the `program.cs` file already looks absurd. As the project goes on, we may end up searching for endpoints in Program.cs file.

So, let’s refactor this mess of endpoints with the Carter framework.

## What is Carter framework?

> Carter is framework that is a thin layer of extension methods and functionality over ASP.NET Core allowing code to be more explicit and most importantly more enjoyable.
> 
> <cite> <br> <br> From <a href="https://github.com/CarterCommunity/Carter">https://github.com/CarterCommunity/Carter</a></cite>

## Installing Carter

As always we can install this package in a few different ways.

1. Head over to Nuget package manager in visual studio and search for `Carter` and install it
2. Run `dotnet add package carter` in command prompt in your project folder.
3. Run `Install-Package Carter` in Nuget package manager console to install.

## Setup carter for Minimal API’s

After installing the carter framework, we have to modify our `Program.cs` file to look like this.

```csharp
var builder = WebApplication.CreateBuilder(args);
// all our dependancies and other services goes here
builder.Services.AddCarter();

var app = builder.Build();
// any pipeline configuration goes here

app.MapCarter();
app.Run();
```

All we have to do is add Carter to our services and to our pipeline and we should be good.

We used to write `app.MapGet(...)` just before the `app.Run()` statement. With carter, there is no need to write it here. We can have them in separate modules per entity.

## Creating Carter Modules

Before running the app, we’ve to create classes that implement `ICarterModule` interface. In the framework, they call these classes Modules. So, let’s create two modules. One for user and the other for products.

```csharp
public class UserModule : ICarterModule
{
    public void AddRoutes(IEndpointRouteBuilder app)
    {
        app.MapGet("/user/{id}", async (int id, IUserRepository userRepository, CancellationToken cancellationToken) =>
        {
            var user = await userRepository.GetUserAsync(id, cancellationToken);
            if (user is null) return Results.BadRequest();

            return Results.Ok(user);
        }).WithName("GetUsers");

        app.MapPut("/user/update/{id}", (int id, User user, IUserRepository userRepository) =>
        {
            return userRepository.UpdateUser(id, user);
        }).WithName("UpdateUsers");

        app.MapDelete("/user/{id}", (int id, IUserRepository userRepository) =>
        {
            userRepository.DeleteUser(id);
            return Results.Ok();
        }).WithName("DeleteUser");
    }
}

public class ProductsModule : ICarterModule
{
    public void AddRoutes(IEndpointRouteBuilder app)
    {
        app.MapPost("/product/create", (Product product, IProductsRepository productsRepo) =>
        {
            if (!product.IsValid()) return Results.BadRequest();
            productsRepo.CreateProduct(product);
            return Results.StatusCode(201);
        });

        app.MapGet("/product/{id}", (int id, IProductsRepository repo) =>
        {
            if (id <= 0) return Results.BadRequest();

            return Results.Ok(repo.GetProductById(id));
        });

        app.MapPut("/product/update/{id}", (int id, Product product, IProductsRepository productRepo) =>
        {
            if (!product.IsValid()) return Results.BadRequest();

            productRepo.UpdateProduct(id, product);
            return Results.Ok();
        });

        app.MapDelete("/product/delete/{id}", (int id, IProductsRepository productsRepo) =>
        {
            return productsRepo.DeleteProduct(id);
        });
    }
}

```

We don’t have to register these modules in `Program.cs` anymore because the classes that implement `ICarterModule` interface are already registered as Singletons through `AddCarter()` method. It’s automatic.

Let’s run and see if this works.

## More refactoring

The `AddRoutes` method in both `UserModule` and `ProductsModule` doesn’t look nice as the new endpoint implementations are within `MapGet/MapPost/MapPut/MapDelete` endpoints.

Our endpoints are really simple but if they are getting more complex we can move those implementations to be private within the module.

Let’s move the implementations to private methods in the products module.

```csharp
public class ProductsModule : ICarterModule
{
    public void AddRoutes(IEndpointRouteBuilder app)
    {
        app.MapPost("/product/create", CreateProduct);

        app.MapGet("/product/{id}", GetProductById);

        app.MapPut("/product/update/{id}", UpdateProduct);

        app.MapDelete("/product/delete/{id}", DeleteProduct);
    }

    private IResult CreateProduct(Product product, IProductsRepository productsRepo)
    {
        if (!product.IsValid()) return Results.BadRequest();
        productsRepo.CreateProduct(product);
        return Results.StatusCode(201);
    }

    private IResult GetProductById(int id, IProductsRepository repo)
    {
        if (id <= 0) return Results.BadRequest();

        return Results.Ok(repo.GetProductById(id));
    }

    private IResult UpdateProduct(int id, Product product, IProductsRepository productRepo)
    {
        if (!product.IsValid()) return Results.BadRequest();

        productRepo.UpdateProduct(id, product);
        return Results.Ok();
    }

    private bool DeleteProduct(int id, IProductsRepository repo)
    {
        return repo.DeleteProduct(id);
    }
}

```

Notice all the endpoints return `IResult` except `DeleteProduct` method which returns a boolean. You don’t have to wrap the result of the endpoint within the Results object every time. It’s up to you to return whatever you want from an endpoint.

Now our endpoints in the `AddRoutes` method looks clean after we moved our endpoint implementations to private methods within `ProductsModule` class.

We can do the same thing for `UserModule` as well. I don’t want to bloat this article with that. I’ll do it in the [GitHub repository](https://github.com/karthikchintala1/minimal-api).

That’s it! We’ve refactored our minimal APIs with the Carter framework by moving them into separate modules.

## Conclusion

Carter is an open source lightweight framework that separates registering endpoints from `Program.cs` file and makes our endpoints look clean.

The fluent validation dependency for Carter is also an excellent addition for writing validations. If you don’t know it, consider taking a peek at it. This library will help write more readable validations.

Carter framework also has a response negotiator as minimal APIs do not support content negotiation by default. As this is a little off-topic for this blog I’m not going into it.

## References

- [Refactored Minimal APIs code with Carter framework](https://github.com/karthikchintala1/minimal-api/tree/refactored-minimal-api) 
- [What are Minimal APIs](https://dev.to/karthikchintala/what-are-minimal-apis-in-aspnet-core-6-184k-temp-slug-8339504)
- [Carter Framework](https://github.com/CarterCommunity/Carter)
- [Bogus for C#](https://dev.to/karthikchintala/how-to-generate-fake-data-in-c-2f4p-temp-slug-9485001)
- [Fluent Validation](https://docs.fluentvalidation.net/en/latest/)

The post [Refactoring Minimal APIs with Carter](https://coderethinked.com/refactoring-minimal-apis-with-carter/) appeared first on [Code Rethinked](https://coderethinked.com).