My favorite libraries for a new Greenfield .NET project
I gathered to this blog post a list of external libraries that 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 streamline the developer's work and make code more maintainable and easier to follow & understand.
This post gives you a quick overview of 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 if the license is suitable for your purposes.
- Serilog
- Swashbuckle
- FluentValidation
- Ardalis Guard Clauses
- FluertAssertions
- Bogus
- BenchmarkDotNet
- Mapster
- Dapper
- Entity Framework Core
- FluentResults
- Carter
Logging
Serilog
Serilog is a flexible and extendable logging library (Apache-2.0 license) for .NET applications that supports structured logging. Serilog is a very well-known and popular library in the .NET community. It has a vital community that 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, a structured logging message has key-value pairs and it's much easier to read. Also logging analytic applications can filter logging messages 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 mechanisms to enrich log events in 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 the Open API checkbox while creating the new project via Visual Studio. The library also provides a user interface that 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 the Open API documentation site provided by Swashbuckle cannot be reached from the public internet, you can convert the Open API definition to the static HTML site with Redoc. I have used this a few times when API documentation had to be delivered to the external service provider by secure email.
Validation
FluentValidation
FluentValidation is an 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 the 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 the Validator class side by side with the actual Domain object. This approach enables validation rules to be 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 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 the 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 a 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 types 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) that provides tools for measuring and comparing the performance of your code. Attribute-based benchmark definitions powered by BenchmarkDotNet are flexible and powerful ways to determine which methods should be benchmarked.
[Benchmark]
public void GetProductById()
{
}
After benchmarking you'll receive a summary table with information about the 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 the Product object to ProductDto. Mapster supports also advanced rules for mapping if you need 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 an 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) that 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 the 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