Menu

My favorite libraries for a new Greenfield .NET project

I gathered to this blog post a list of external libraries which I usually add to a greenfield .NET API project. Most of these libraries can be used also in other project types as well. These libraries streamlines the developer work and make code more maintainable and easier to follow & understand.

This post gives you a quick overview to those libraries and I recommend you to read more from the documentation of the libraries. Read always carefully the license of the library and evaluate is the license suitable for your purposes.

Logging

Serilog

Serilog is a flexible and extendable logging library (Apache-2.0 license) for .NET applications which supports structured logging. Serilog is very known and popular library in .NET community. It has vital community which further develops and supports the library. 

Typically non-structured logging message is something like this:

03/02/2019 3bc7bc46-2337-11ee-be56-0242ac120002 Error Error occured while loading the data from the database

Structure logging message:

JSON payload: { "userId": "3bc7bc46-2337-11ee-be56-0242ac120002", "type": "Error", "Message": "Error occured while loading the data from the database" }

As you can see, structured logging message has key value pairs and it's much easier to read. Also logging analytic applications can filter logging message a bit easier.

Serilog can be added as a third party logging provider to the .NET logging pipeline. It's very extendable and configurable. Serilog supports various different sinks for writing log events to storage in various formats. I have recently used Azure Blob Storage, Azure Service Bus and Azure EvenHub sinks in my projects.

Serilog enrichers are also powerful mechanism to enrich log events which additional data i.e. User Id of the authenticated user can be automatically added to all logs.

Documentation

Swashbuckle

Swashbuckle is a popular library (BSD-3-Clause license) for generating Open API documentation of the API. Swashbuckle will be added to your .NET Web API project, if you check Open API checkbox while creating the new project via Visual Studio. Library also provides an user interface which presents the API documentation.

All details of the API endpoint cannot be automatically identified by the library, but you can add metadata with decorator Attributes.

[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Create(Product product)

If Open API documentation site provided by Swashbuckle cannot be reached from the public internet, you can convert Open API definition to the static HTML site with Redoc. I have used this a few times when API documentation had to deliver for the external service provider by secure email.

Validation

FluentValidation

FluentValidation is a extendable validation library (Apache-2.0 license) for .NET which uses a fluent interface and lambda expressions for building strongly-typed validation rules.

When FluentValidation is added to the project, you can determine all validation rules for the object in Validator class which is inherited from AbstractValidator<T>. If Built-in validators are not enough, Custom validators enable to determine more complex rules if necessary. Typically, I locate Validator class side by side with actual Domain object. This approach enables that validation rules are easy to find from the solution structure in Visual Studio.

public class Product
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
}

public class ProductValidator : AbstractValidator<Product>
{
    public ProductValidator()
    {
        RuleFor(x => x.Id).NotNull();
        RuleFor(x => x.Name).NotNull();
        RuleFor(x => x.Name).Length(0, 25);
        RuleFor(x => x.Description).NotNull();
    }
}

With the manual validation approach, you need to inject the IValidator<T>into your API controller / Minimal API and then invoke it against the model.

[HttpPost]
public async Task<IActionResult> Create(Product product)
{
    ValidationResult result = await _validator.ValidateAsync(product);

    if (result.IsValid)
    {

    }
}

Ardalis Guard Clauses

Ardalis Guard Clauses is an extensible guard clause extension library (MIT license) for .NET which streamlines validation and makes everything more simpler. A guard clause is a software pattern that simplifies complex functions by "failing fast".

Typically, you can write argument validations like this.

public Product(string name, string description)
{
    if (name == null)
        throw new ArgumentNullException(nameof(name), "Name cannot be null.");

    Name = name;
    Description = description;
}

With Ardalis Guard Clauses you can write same argument validation much simpler and cleaner way.

public Product(string name, string description)
{
    Name = Guard.Against.NullOrWhiteSpace(name, nameof(name));
    Description = description;
}

Testing

FluertAssertions

Fluent Assertions is a library (Apache-2.0 license) with set of .NET extension methods that allow you to more naturally specify the expected outcome of a TDD or BDD-style unit test. This enables a simple intuitive syntax and makes very easy to read assertions.

string actual = "ABCDEFGHI";
actual.Should().StartWith("AB").And.EndWith("HI").And.Contain("EF").And.HaveLength(9);

Bogus

Do you need fake data for your testing purposes? If yes, then Bogus is the answer. Bogus is a simple fake data generator library (MIT License) for .NET. Bogus supports various different type of fake data sets from Commerce to Company data.

Using the Fluent API, you can easily generate i.e. random Products.

var testProducts = new Faker<Product>()
    .RuleFor(u => u.Name, (f, u) => f.Commerce.ProductName())
    .RuleFor(u => u.Description, (f, u) => f.Commerce.ProductDescription())
    .RuleFor(u => u.Id, (f, u) => Guid.NewGuid());

var products = testProducts.Generate();

BenchmarkDotNet

BenchmarkDotNet is a powerful .NET library (MIT license) which provides tools for measuring and comparing performance of your code. Attribute based benchmark definitions powered by BenchmarkDotNet are flexible and powerful way to determine which methods should be benchmarked. 

[Benchmark]
public void GetProductById()
{

}

After benchmarking you'll receive a summary table with information about benchmark run. 

BenchmarkDotNet v0.13.6, Windows 11 (10.0.22621.1992/22H2/2022Update/SunValley2)
AMD Ryzen 5 3600, 1 CPU, 12 logical and 6 physical cores
.NET SDK 6.0.400
  [Host]     : .NET 6.0.8 (6.0.822.36306), X64 RyuJIT AVX2
  DefaultJob : .NET 6.0.8 (6.0.822.36306), X64 RyuJIT AVX2

|         Method |    Mean |    Error |   StdDev |
|--------------- |--------:|---------:|---------:|
| GetProductById | 1.563 s | 0.0055 s | 0.0052 s |

Object Mapping

Mapster

Mapster is a developer-friendly and performance optimized object-to-object mapping library (MIT License) for .NET applications. Mapster supports Fluent API which makes it very intuitive to use. 

This maps Product object to ProductDto. Mapster supports also advanced rules for the mapping if you need a special handling or even transformation in advanced scenarios.

var productDto = product.Adapt<ProductDto>();

Mapster also provides a tool called Mapster.Tool which can be used to generate DTO classes automatically. 

Dapper

Dapper is a lightweight and high performance ORM (object-relational mapping) library (Apache 2.0) for .NET. Dapper is a good choice, especially in use cases where you want to write performant raw SQL queries. Fluent Migrator is a good companion with Dapper when considering, how to manage database migrations.

var products = new List<Product>();

using(var db = new SqlConnection(connectionString))
{
    products = db.Query("select * from products").ToList();
}

Entity Framework Core

Entity Framework Core is a Object-Relational Mapper (ORM) for .NET which enables developers to work with relational data in an object-oriented way using .NET objects. It supports LINQ queries, change tracking and schema migrations.

var products = new List<Product>();

using (var context = new DbContext())
{
    products = await context.Products.Where(x => x.Price > 10).ToList();
}

Common

FluentResults

FluentResults is a lightweight .NET library (MIT license) which returns an object indicating success or failure instead of throwing/using exceptions. Read best practices from here.

public Result Create(Product product)
{
    if (product == null)
        return Result.Fail("Product item was null.");

    return Result.Ok();
}

Carter

Carter is a framework (MIT license) that is a thin layer of extension methods and functionality over ASP.NET Core allowing the code to be more explicit and most importantly more enjoyable.

I especially like, how Carter enables you to organize your Minimal API endpoints more effectively. You can separate easily your Minimal API endpoints to e.g. Module classes which are inherited from ICarterModule.

public class ProductModuleEndpoints : ICarterModule
{
    private readonly IProductService _productService;

    public ProductModuleEndpoints(IProductService productService)
    {
        _productService = productService;
    }

    public void AddRoutes(IEndpointRouteBuilder app)
    {
        app.MapGet("/product", GetProductById)
            .WithName("GetProductById")
            .WithDisplayName("Get Product by Id")
            .WithTags("ProductModule")
            .Produces<ProductResponseModel>()
            .Produces(500);
    }

    private async Task<IResult> GetProductById(Guid productId, CancellationToken cancellationToken)
    {
        var product = await _productService.GetExampleByIdASync(productId, cancellationToken);
        return Results.Ok(product);
    }
}

Carter takes care of discovery of Minimal API endpoints when AddCarter extension method is called.

internal static class ServiceCollectionExtensions
{
    public static IServiceCollection AddCommonServices(this IServiceCollection services, WebApplicationBuilder builder)
    {     
        builder.Services.AddCarter();
        return builder.Services;
    }
}

Carter can do a lot more and I recommend you to check these samples.

Comments