Manikandan — Manikandan
Updated on 6 min read Manikandan Design Patterns

Builder Design Pattern in .NET Core API

A beginner-friendly introduction to the Builder pattern in C# with a simple example, common builder variations, validation tips, and UML.

A beginner-friendly introduction to the Builder pattern in C# with a simple example, common builder variations, validation tips, and UML.

Intro

Builder is a creational design pattern used to create complex objects step by step. It helps you avoid large constructors with many parameters and keeps object creation readable.

What Is the Builder Pattern?

The Builder pattern separates object construction from the final object representation.

In simple terms:

  • You call small methods to set parts of an object.
  • You call Build() at the end to get the final object.
  • You can create different object variations using the same builder.

Builder Pattern Example

Suppose we want to build a House object with optional fields.

public class House
{
public string RoofType { get; set; } = string.Empty;
public int Rooms { get; set; }
public bool HasGarage { get; set; }
}
public class HouseBuilder
{
private readonly House _house = new();
public HouseBuilder SetRoof(string roofType)
{
_house.RoofType = roofType;
return this;
}
public HouseBuilder SetRooms(int rooms)
{
_house.Rooms = rooms;
return this;
}
public HouseBuilder AddGarage()
{
_house.HasGarage = true;
return this;
}
public House Build() => _house;
}

Usage

var house = new HouseBuilder()
.SetRoof("Concrete")
.SetRooms(3)
.AddGarage()
.Build();

Why This Works

  • Each builder method focuses on one part of object creation, so the calling code reads like a set of construction steps.
  • The partially constructed object stays inside the builder until Build() is called, which keeps creation logic in one place.
  • Optional values can be added only when needed, without creating many overloaded constructors.
  • The same builder structure can produce different final results by changing the order or combination of steps.

When to Use It

  • When object construction is complex and involves many optional steps or configurations.
  • When you want to avoid constructor telescoping (many overloaded constructors).
  • When you want cleaner and more readable creation code.
  • When you want to reuse the same construction process for different object variants.

Builder is most useful when creation itself has structure. If the object only needs two or three fixed values, a normal constructor or object initializer is usually enough.

Important Note for ASP.NET Core

In ASP.NET Core, Builder is common in framework configuration APIs even if you do not explicitly create your own builder class. The platform already uses this pattern in places like WebApplicationBuilder, options setup, and middleware registration.

You may also register your own builder-related services through dependency injection when object construction needs to stay reusable and testable.

builder.Services.AddTransient<HouseBuilder>();

That approach is helpful when the builder depends on other services, but in many cases a builder can remain a simple class created directly by the caller.

Core Components of the Pattern

PartPurposeExample in this article
BuilderStep-by-step constructionHouseBuilder
ProductThe object being builtHouse
ClientCalls builder methodsUsage code above

Common Builder Variations

You will usually see the Builder pattern implemented in these three forms:

01-FluentBuilder

This subtype returns the builder instance from each method, so you can chain calls in a readable flow.

Why it matters in implementation:

  • Clean and concise object construction code.
  • Easy to add optional steps without changing caller style.
  • Great fit for APIs that build one object from many optional values.

02-StepBuilder

This subtype enforces the order of build steps using interfaces. The caller must follow the defined sequence.

Why it matters in implementation:

  • Prevents invalid object states at compile time.
  • Useful when some steps are mandatory.
  • Improves correctness in complex construction flows.

03-DirectorBasedBuilder

This subtype uses a Director class to control and reuse construction sequences for common object variants.

Why it matters in implementation:

  • Reuses standard build recipes across the app.
  • Keeps repeated construction flow out of controllers/services.
  • Helps when you need multiple predefined object configurations.

How the Variations Differ

VariationMain ideaBest use caseTrade-off
Fluent BuilderReturns the same builder from each methodClean, readable construction with optional stepsCan still allow invalid step order
Step BuilderUses interfaces to enforce the build sequenceRequired steps and compile-time safetyMore interfaces and more setup
Director-Based BuilderMoves standard build flows into a directorRepeated object recipes across the appAdds an extra class for orchestration

The main difference is how much control you want over the construction flow. Fluent Builder focuses on readability, Step Builder focuses on correctness, and Director-Based Builder focuses on reuse of common build sequences.

SOLID Principles Behind It

SRP — Single Responsibility Principle

The builder handles construction logic, while the product class focuses on representing the final object. That keeps creation details out of the domain model.

OCP — Open/Closed Principle

You can extend the object creation process by adding new builder methods or a new concrete builder without changing the calling code pattern.

ISP — Interface Segregation Principle

In a Step Builder, each interface exposes only the next required action. Callers do not depend on methods they should not use yet.

DIP — Dependency Inversion Principle

The client can depend on an abstraction such as an IHouseBuilder interface instead of a concrete builder, which keeps the creation process easier to swap or test.

Advantages and Disadvantages

Advantages

  • Simplifies building complex objects.
  • Keeps code readable and clean.
  • Reduces constructor complexity.
  • Makes optional fields easier to manage.
  • Separates construction logic from the final object.
  • Makes repeated creation flows easier to test and reuse.

Disadvantages

  • Can introduce extra classes and interfaces if overused.
  • May be overkill for simple objects.
  • A poorly designed builder can allow incomplete or invalid object states.
  • Step builders and directors add more structure, which increases code volume.

UML Diagram

classDiagram
class Client
class Product
class Builder {
+Build() Product
}
class ConcreteBuilder
ConcreteBuilder --|> Builder
Builder --> Product
Client --> Builder

A Quick Usage Example

var familyHouse = new HouseBuilder()
.SetRoof("Concrete")
.SetRooms(4)
.AddGarage()
.Build();
var smallHouse = new HouseBuilder()
.SetRoof("Tiles")
.SetRooms(2)
.Build();

Both objects are created through the same builder, but each uses a different combination of steps. That is where Builder becomes practical.

How to Validate the Pattern

If you want to confirm your Builder implementation is working correctly, check these points:

  • the final object should contain the values set through the builder methods
  • optional steps should affect only the fields they are responsible for
  • the builder should produce valid objects even when optional steps are skipped
  • repeated builds should not accidentally leak state between different objects unless that behavior is intentional

A simple practical check is to build two different objects and verify that each one contains only the values that were configured for it.

Here is a small xUnit example:

[Fact]
public void HouseBuilder_Should_Create_House_With_Configured_Values()
{
var house = new HouseBuilder()
.SetRoof("Concrete")
.SetRooms(3)
.AddGarage()
.Build();
Assert.Equal("Concrete", house.RoofType);
Assert.Equal(3, house.Rooms);
Assert.True(house.HasGarage);
}

If you implement a Step Builder, validation should also confirm that required steps cannot be skipped. If you use a Director, test that each predefined build recipe returns the expected result.


Summary

The Builder pattern is useful when object creation has many steps or optional values. It improves readability, keeps constructors simple, and helps you create objects in a clean, maintainable way.

Share:
Back to Blog

Related Posts

View All Posts »