The Startup.cs
file might look like a simple configuration hub, but it actually demonstrates multiple design patterns that shape the architecture of your app.
Let’s dissect a typical Startup.cs
line by line (or block by block) and explore which design pattern it represents and why it matters.
Sample Startup.cs
Below is a sample Startup.cs
from a .NET 6+ Web API:
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddScoped<IProductService, ProductService>();
services.AddSingleton<ICacheService, MemoryCacheService>();
services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.Use(async (context, next) =>
{
// Logging middleware
Console.WriteLine("Request received");
await next();
Console.WriteLine("Response sent");
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
1️⃣ services.AddControllers();
Pattern: Builder Pattern
Explanation:
Adds MVC controller support by building up a complex set of services (routing, model binding, etc.) under the hood.
Uses a fluent API to chain configurations step by step.
2️⃣ services.AddScoped<IProductService, ProductService>();
Pattern: Dependency Injection (DI)
Explanation:
Registers a service implementation so it can be injected via constructors into controllers or other services.
Promotes loose coupling between components.
Is Dependency Injection (DI) a Design Pattern?
Technically, Dependency Injection is a technique, not a classic Gang of Four (GoF) design pattern. It’s an implementation strategy for the Inversion of Control (IoC) Principle, which is a broader software design principle.
💡 So why is DI considered design-related?
Because it embodies separation of concerns, promotes testability, and decouples implementations from consumers — all core to design.
3️⃣ services.AddSingleton<ICacheService, MemoryCacheService>();
Pattern: Singleton Pattern
Explanation:
https://dotnetfullstackdev.medium.com/
Ensures a single instance of
MemoryCacheService
is used throughout the app’s lifecycle.Useful for stateless or thread-safe services like caching.
4️⃣ services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
Pattern: Options Pattern
Explanation:
Binds configuration data to a strongly-typed object (
MyOptions
).Promotes type-safety and separation of concerns between config and business logic.
5️⃣ if (env.IsDevelopment()) app.UseDeveloperExceptionPage();
Pattern: Factory Method (in the background)
Explanation:
UseDeveloperExceptionPage()
uses a factory internally to create the middleware pipeline for error handling.Makes the environment-specific middleware instantiation dynamic.
6️⃣ app.UseRouting(); app.UseAuthentication(); app.UseAuthorization();
Pattern: Chain of Responsibility Pattern
Explanation:
Each middleware handles or forwards the request to the next.
Allows building a flexible and modular HTTP pipeline.
7️⃣ app.Use(async (context, next) => { ... });
Pattern: Decorator Pattern
Explanation:
This custom middleware wraps the request pipeline, adding logging before and after calling the
next
middleware.Enhances or modifies the pipeline behavior dynamically.
8️⃣ app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
Pattern: Builder Pattern (again)
Explanation:
Defines the endpoints for routing requests to the correct controllers.
Uses a fluent API to assemble the endpoint pipeline.
💡 Bonus: Additional Patterns in the Background
Factory Pattern:
DI container acts as a factory, dynamically creating service instances.
Proxy Pattern:
Some middleware like reverse proxies or authentication proxies forward requests.
🎯 Conclusion
A single Startup.cs
file may seem simple, but it encapsulates multiple design patterns working together to create a modular, testable, and maintainable .NET Core application.
Understanding where and how these patterns appear is essential for writing professional, production-grade software.