.NET Architecture & Design Patterns

.NET Architecture & Design Patterns

Explore .net architecture & design patterns code snippets and tutorials

.NET Architecture & Design Patterns

Dependency Injection in ASP.NET Core: constructor injection, composition root, keyed services

A class receives its dependencies from outside (usually through the constructor) instead of creating them with new.

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// ======================================================================
// Q: What is Dependency Injection and why do we use it?
//
// A class receives its dependencies from outside (usually through the
// constructor) instead of creating them with new. It is how Inversion of Control
// is implemented. ASP.NET Core has a built-in container
// (Microsoft.Extensions.DependencyInjection). Benefits: loose coupling,
// testability (fakes / mocks), all the wiring in one place (composition root =
// Program.cs), and the container manages lifetimes (see Q5).
// - Service Locator (IServiceProvider.GetService inside business logic) is an
//   anti-pattern: it hides dependencies.
// - A constructor with 7-8+ dependencies signals that the class does too much
//   (an SRP violation).
// - Use TimeProvider (.NET 8) instead of DateTime.UtcNow, so tests can use
//   FakeTimeProvider.
// - DI is not DIP: DI is the mechanism, DIP (Q18) is the principle.
// ======================================================================

// WRONG: tight coupling, cannot be tested without a real ERP
public class OrderService
{
    private readonly ErpClient _erp = new ErpClient(new HttpClient());
}

// RIGHT: constructor injection (primary constructor, C# 12)
public class OrderService(IOrderRepository orders, IErpClient erp, TimeProvider clock) : IOrderService
{
    public async Task SubmitAsync(int orderId, CancellationToken ct) { /* ... */ }
}

// Composition root: Program.cs
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddScoped<IOrderRepository, EfOrderRepository>();
builder.Services.AddSingleton(TimeProvider.System);                              // testable time
builder.Services.Configure<ErpOptions>(builder.Configuration.GetSection("Erp")); // Options pattern
builder.Services.AddKeyedScoped<IShippingCalculator, DhlCalculator>("dhl");      // keyed services (.NET 8)
.NET Architecture & Design Patterns

MVC vs MVT: ASP.NET Core MVC controllers and Razor views compared with Django

It is the same idea (separation of concerns) under different names.

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// ======================================================================
// Q: What's the difference between MVC and MVT?
//
// It is the same idea (separation of concerns) under different names. Django's
// "View" does the job of the Controller in ASP.NET, and Django's "Template" is
// the ASP.NET View. In MVT the "controller" role (routing) is played by the
// framework itself.
// - Model: in ASP.NET Core MVC, domain entities and ViewModels / DTOs; in
//   Django, Django ORM models.
// - Receives the request: Controller (action method) in MVC; View (function or
//   class) in Django.
// - Renders HTML: View (Razor .cshtml) in MVC; Template (Django template
//   language) in Django.
// - Routing: routing middleware and attribute routes in MVC; URLconf (urls.py),
//   i.e. the framework, in Django.
// - .NET also has Razor Pages (Page Controller pattern: one PageModel per page,
//   good for form-based UI) and Web API controllers / Minimal APIs, where there
//   is no View: JSON is returned.
// - Thin controllers: the controller only handles HTTP (binding, validation,
//   status codes). Business logic goes in the application / domain layer.
// ======================================================================

// ASP.NET Core MVC: the Controller picks the Model and the View
public class ProductsController(IProductService products) : Controller
{
    public async Task<IActionResult> Details(int id, CancellationToken ct)
    {
        var vm = await products.GetDetailsAsync(id, ct);   // Model / ViewModel
        return vm is null ? NotFound() : View(vm);         // Views/Products/Details.cshtml
    }
}

@* Details.cshtml: the View *@
@model ProductDetailsVm
<h1>@Model.Name</h1>
<p>@Model.Price.ToString("C")</p>   @* Razor HTML-encodes automatically (XSS) *@

# Django (MVT): the "view" is the controller
def product_detail(request, id):
    product = get_object_or_404(Product, pk=id)
    return render(request, "products/detail.html", {"product": product})