Making C# Simple with Fluent Techniques

Niraj Ranasinghe
7 min readMay 27, 2024

--

The fluent interface pattern in C# allows for method chaining that reads like natural language, making code more intuitive and expressive. This pattern is prevalent in the .NET ecosystem, enhancing the usability of APIs across various libraries and frameworks. From configuring database mappings and validating data to writing unit tests and defining migrations, fluent interfaces simplify development, reduce errors, and improve productivity. In this post, I’ll discuss how fluent interfaces are used in C#, with practical examples to illustrate their benefits.

Photo by Jonathan Kemper on Unsplash

1. Fluent Interface

Fluent Interface is a design pattern used to provide an easy and readable way to interact with an API by chaining method calls. This pattern is particularly useful for constructing complex queries, configurations, or setting properties in a manner that reads like natural language.

Example:

Suppose we have a QueryBuilder class for building SQL queries.

public class QueryBuilder
{
private string _select;
private string _from;
private string _where;
private string _orderBy;

public QueryBuilder Select(params string[] columns)
{
_select = "SELECT " + string.Join(", ", columns);
return this;
}

public QueryBuilder From(string table)
{
_from = "FROM " + table;
return this;
}

public QueryBuilder Where(string condition)
{
_where = "WHERE " + condition;
return this;
}

public QueryBuilder OrderBy(string column)
{
_orderBy = "ORDER BY " + column;
return this;
}

public string Execute()
{
return $"{_select} {_from} {_where} {_orderBy}";
}
}

Usage:

var query = new QueryBuilder()
.Select("name", "age")
.From("users")
.Where("age > 18")
.OrderBy("name")
.Execute();

// Output: SELECT name, age FROM users WHERE age > 18 ORDER BY name

In this example, the QueryBuilder class allows you to chain methods to build a SQL query in a readable way.

2. Fluent Assertions

Fluent Assertions is a .NET library designed to help write assertions in unit tests in a more readable and expressive manner. This library allows you to write assertions that clearly communicate the intent of the test.

Example:

Using Fluent Assertions, you can write tests like this:

using FluentAssertions;
using Xunit;

public class MyTests
{
[Fact]
public void TestValue(+)
{
int value = 5;
value.Should().BeGreaterThan(3);
value.Should().BeLessThan(10);
}

[Fact]
public void TestString()
{
string name = "John";
name.Should().StartWith("J")
.And.EndWith("n")
.And.HaveLength(4);
}
}

These assertions are easy to read and understand, making your tests more maintainable.

3. Fluent NHibernate

Fluent NHibernate is an alternative to using XML mapping files for NHibernate, a popular ORM (Object-Relational Mapper) in .NET. Fluent NHibernate allows you to define your mappings in code using a fluent interface, which can be easier to manage and read than XML.

Example:

Here’s how you can define mappings for an Employee class:

public class Employee
{
public virtual int Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual Department Department { get; set; }
}

public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Id(x => x.Id);
Map(x => x.FirstName).Not.Nullable();
Map(x => x.LastName).Not.Nullable();
References(x => x.Department).Not.Nullable();
}
}

Usage:

In your NHibernate session setup, you would include the mappings:

var sessionFactory = Fluently.Configure()
.Database(MySQLConfiguration.Standard.ConnectionString("..."))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<EmployeeMap>())
.BuildSessionFactory();

This configuration replaces the need for XML mapping files with fluent code.

4. Fluent API in Entity Framework Core

The Fluent API in Entity Framework Core allows for more precise and complex configurations of your models. It is especially useful when data annotations are not sufficient to express the required mappings and configurations.

Example:

Consider the following models:

public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public ICollection<Post> Posts { get; set; }
}

public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}

Configuration with Fluent API:

public class MyDbContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasKey(b => b.BlogId);

modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired()
.HasMaxLength(200);

modelBuilder.Entity<Post>()
.HasKey(p => p.PostId);

modelBuilder.Entity<Post>()
.Property(p => p.Title)
.IsRequired()
.HasMaxLength(100);

modelBuilder.Entity<Post>()
.HasOne(p => p.Blog)
.WithMany(b => b.Posts)
.HasForeignKey(p => p.BlogId);
}
}

In this example, the Fluent API is used to configure primary keys, property constraints, and relationships between Blog and Post.

5. Fluent Builder Pattern

The Builder Pattern can be implemented using a fluent interface to construct complex objects step by step, making the construction process more readable and flexible.

Example:

Here’s how you might use a fluent interface to build a Car object:

public class Car
{
public string Make { get; set; }
public string Model { get; set; }
public int Year { get; set; }
}

public class CarBuilder
{
private Car _car = new Car();

public CarBuilder WithMake(string make)
{
_car.Make = make;
return this;
}

public CarBuilder WithModel(string model)
{
_car.Model = model;
return this;
}

public CarBuilder WithYear(int year)
{
_car.Year = year;
return this;
}

public Car Build()
{
return _car;
}
}

Usage:

var car = new CarBuilder()
.WithMake("Toyota")
.WithModel("Corolla")
.WithYear(2020)
.Build();

The CarBuilder class provides a fluent interface for constructing a Car object, making the code easy to read and understand.

6. FluentMigrator

FluentMigrator is a migration framework for .NET that allows you to define database migrations using a fluent interface. It provides a readable way to define how your database schema should evolve over time.

Example:

Here’s a simple migration to create a Users table:

[Migration(202105031200)]
public class AddUserTable : Migration
{
public override void Up()
{
Create.Table("Users")
.WithColumn("Id").AsInt32().PrimaryKey().Identity()
.WithColumn("Username").AsString(255).NotNullable()
.WithColumn("Email").AsString(255).NotNullable();
}

public override void Down()
{
Delete.Table("Users");
}
}

Usage:

To apply the migrations, you would typically use a command line tool or integrate it into your build process. The migrations are defined in a fluent manner, making them easy to read and write.

7. Fluent Validation

Fluent Validation is a popular .NET library for building strongly-typed validation rules for business objects using a fluent interface.

Example:

Here’s how you might define validation rules for a Person class:

using FluentValidation;

public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}

public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
RuleFor(x => x.FirstName).NotEmpty().WithMessage("First name is required.");
RuleFor(x => x.LastName).NotEmpty().WithMessage("Last name is required.");
RuleFor(x => x.Age).InclusiveBetween(18, 60).WithMessage("Age must be between 18 and 60.");
RuleFor(x => x.Email).EmailAddress().WithMessage("A valid email is required.");
}
}

Usage:

You can integrate this validation logic into your application, for instance, in an ASP.NET Core controller:

using Microsoft.AspNetCore.Mvc;
using FluentValidation.Results;

public class PersonController(IValidator<Person> validator) : Controller
{

[HttpPost]
public IActionResult Create(Person person)
{
ValidationResult result = validator.Validate(person);

if (!result.IsValid)
{
return BadRequest(result.Errors);
}

return Ok();
}
}

8. Fluent API for LINQ Queries

LINQ (Language-Integrated Query) in C# uses a fluent API to enable querying collections in a readable and expressive way.

Example:

Here’s a simple example of using LINQ to query a list of numbers:

using System;
using System.Collections.Generic;
using System.Linq;

public class LINQExample
{
public void Run()
{
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

var evenNumbers = numbers
.Where(n => n % 2 == 0)
.Select(n => new { Number = n, IsEven = true })
.OrderBy(n => n.Number);

foreach (var item in evenNumbers)
{
Console.WriteLine($"{item.Number} is even: {item.IsEven}");
}
}
}

9. Fluent Interface for Configuration

The fluent interface pattern is often used for configuring libraries or frameworks, making the configuration code more readable.

Example: AutoMapper

AutoMapper is a popular object-to-object mapping library in .NET, and it uses a fluent interface for configuration.

using AutoMapper;

public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Source, Destination>()
.ForMember(dest => dest.DestProperty, opt => opt.MapFrom(src => src.SourceProperty));
}
}

public class Source
{
public string SourceProperty { get; set; }
}

public class Destination
{
public string DestProperty { get; set; }
}

10. Fluent Interface for Logging

Logging libraries, such as Serilog, use a fluent interface to configure logging behavior.

Example: Serilog

using Serilog;

public class LoggingExample
{
public void ConfigureLogging()
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.File("logs/log.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();

Log.Information("This is a test log message");
}
}

Conclusion

Here is the summary of the key areas where the fluent pattern is used:

  1. Fluent Interface — General pattern for method chaining.
  2. Fluent Assertions — Improved readability in unit tests.
  3. Fluent NHibernate — Fluent interface for NHibernate mappings.
  4. Fluent API in Entity Framework Core — Configuring EF Core models.
  5. Fluent Builder Pattern — Constructing complex objects.
  6. FluentMigrator — Defining database migrations.
  7. Fluent Validation — Building validation rules.
  8. Fluent API for LINQ Queries — Querying collections.
  9. Fluent Interface for Configuration — Configuring libraries (e.g., AutoMapper).
  10. Fluent Interface for Logging — Configuring logging (e.g., Serilog).

The “fluent” concept is widely used across various libraries and frameworks to create APIs that are easy to read and write. These fluent interfaces enhance code readability, reduce the likelihood of errors, and improve our productivity by allowing for more intuitive interaction with the API.

I’d love to hear from you! If you have any questions or suggestions for future topics, please let me know. Your feedback is important. Thanks to AI tools for enhancing this post. Until next time, happy coding!

--

--

Niraj Ranasinghe

I love sharing insights on Software Development, Emerging Tech, and Industry Trends. Join me on my journey to explore the exciting World of Technology!