DTO controller – Model-View-Controller

To solve our problems, we reimplement the controller using DTOs. To make it easier to follow along, here are all the DTOs as a reference:

namespace Shared.DTO;
public record class ContractDetails(
    int Id,
    string Name,
    string Description,
    int StatusTotalWork,
    int StatusWorkDone,
    string StatusWorkState,
    string PrimaryContactFirstName,
    string PrimaryContactLastName,
    string PrimaryContactEmail
);
public record class CustomerDetails(
    int Id,
    string Name,
    IEnumerable<ContractDetails> Contracts
);
public record class CustomerSummary(
    int Id,
    string Name,
    int TotalNumberOfContracts,
    int NumberOfOpenContracts
);
public record class CreateCustomer(string Name);
public record class UpdateCustomer(string Name);

First, let’s fix our update problem, starting with the reimplementation of the update endpoint leveraging DTOs (see the DTOCustomersController.cs file):

// PUT dto/customers/1
[HttpPut(“{customerId}”)]
public async Task<ActionResult<CustomerDetails>> PutAsync(
        int customerId,
        [FromBody] UpdateCustomer input,
        ICustomerRepository customerRepository)
{
    // Get the customer
    var customer = await customerRepository.FindAsync(
        customerId,
        HttpContext.RequestAborted
    );
    if (customer == null)
    {
        return NotFound();
    }
    // Update the customer’s name using the UpdateCustomer DTO
    var updatedCustomer = await customerRepository.UpdateAsync(
        customer with { Name = input.Name },
        HttpContext.RequestAborted
    );
    if (updatedCustomer == null)
    {
        return Conflict();
    }
    // Map the updated customer to a CustomerDetails DTO
    var dto = MapCustomerToCustomerDetails(updatedCustomer);
    // Return the DTO
    return dto;
}

In the preceding code, the main differences are (highlighted):

  • The request body is now bound to the UpdateCustomer class instead of the Customer itself.
  • The action method returns an instance of the CustomerDetails class instead of the Customer itself.

However, we can see more code in our controller action than before. That’s because the controller now handles the data changes instead of the clients. The action now does:

  1. Load the data from the database.
  2. Ensure the entity exists.
  3. Use the input DTO to update the data, limiting the clients to a subset of properties.
  4. Proceed with the update.
  5. Ensure the entity still exists (handles conflicts).
  6. Copy the Customer into the output DTO and return it.

By doing this, we now control what the clients can do when they send a PUT request through the input DTO (UpdateCustomer). Moreover, we encapsulated the logic to calculate the statistics on the server. We hid the computation behind the output DTO (CustomerDetails), which lowers the complexity of our user interface and allows us to improve the performance without impacting any of our clients (loose coupling).Furthermore, we now use the customerId parameter.If we send the same HTTP request as before, which sends more data than we accept, only the customer’s name will change. On top of that, we get all the data we need to display the customer’s statistics. Here’s a response example:

{
  “id”: 1,
  “name”: “Some new name”,
  “contracts”: [
    {
      “id”: 1,
      “name”: “First contract”,
      “description”: “This is the first contract.”,
      “statusTotalWork”: 100,
      “statusWorkDone”: 100,
      “statusWorkState”: “Completed”,
      “primaryContactFirstName”: “John”,
      “primaryContactLastName”: “Doe”,
      “primaryContactEmail”: “[email protected]
    },
    {
      “id”: 2,
      “name”: “Some other contract”,
      “description”: “This is another contract.”,
      “statusTotalWork”: 100,
      “statusWorkDone”: 25,
      “statusWorkState”: “InProgress”,
      “primaryContactFirstName”: “Jane”,
      “primaryContactLastName”: “Doe”,
      “primaryContactEmail”: “[email protected]
    }
  ]
}

As we can see from the preceding response, only the customer’s name changed, but we now received the statusWorkDone and statusTotalWork fields. Lastly, we flattened the data structure.

DTOs are a great resource to flatten data structures, but you don’t have to. You must always design your systems, including DTOs and data contracts, for specific use cases.

As for the dashboard, the “get all customers” endpoint achieves this by doing something similar. It outputs a collection of CustomerSummary objects instead of the customers themselves. In this case, the controller executes the calculations and copies the entity’s relevant properties to the DTO. Here’s the code:

// GET: dto/customers
[HttpGet]
public async Task<IEnumerable<CustomerSummary>> GetAllAsync(
    ICustomerRepository customerRepository)
{
    // Get all customers
    var customers = await customerRepository.AllAsync(
        HttpContext.RequestAborted
    );
    // Map customers to CustomerSummary DTOs
    var customersSummary = customers
        .Select(customer => new CustomerSummary(
            Id: customer.Id,
            Name: customer.Name,
            TotalNumberOfContracts: customer.Contracts.Count,
            NumberOfOpenContracts: customer.Contracts.Count(x => x.Status.State != WorkState.Completed)
        ))
    ;
    // Return the DTOs
    return customersSummary;
}

In the preceding code, the action method:

  1. Read the entities
  2. Create the DTOs and calculate the number of open contracts.
  3. Return the DTOs.

As simple as that, we now encapsulated the computation on the server.

You should optimize such code based on your real-life data source. In this case, a static List<T> is low latency. However, querying the whole database to get a count can become a bottleneck.

Calling the endpoint results in the following:

[
  {
    “id”: 1,
    “name”: “Some new name”,
    “totalNumberOfContracts”: 2,
    “numberOfOpenContracts”: 1
  },
  {
    “id”: 2,
    “name”: “Some mega-corporation”,
    “totalNumberOfContracts”: 1,
    “numberOfOpenContracts”: 1
  }
]

It is now super easy to build our dashboard. We can query that endpoint once and display the data in the UI. The UI offloaded the calculation to the backend.

User interfaces tend to be more complex than APIs because they are stateful. As such, offloading as much complexity to the backend helps. You can use a Backend-for-frontend (BFF) to help with this task. We explore ways to layer APIs, including the BFF pattern in Chapter 19, Introduction to Microservices Architecture.

Lastly, you can play with the API using the HTTP requests in the MVC.API.DTO.http file. I implemented all the endpoints using a similar technique. If your controller logic becomes too complex, it is good practice to encapsulate it into other classes. We explore many techniques to organize application code in Section 4: Applications patterns.

Project – Strategy – Dependency Injection

In the Strategy project, we delve into various methods of injecting dependencies, transitioning from the Control Freak approach to a SOLID one. Through this exploration, we evaluate the advantages and drawbacks of each technique.The project takes the form of a travel agency’s location API, initially returning only hardcoded cities. We’ve implemented the same endpoint five times across different controllers to facilitate comparison and trace the progression. Each controller comes in pair except for one. The pairs comprise a base controller that uses an in-memory service (dev) and an updated controller that simulates a SQL database (production). Here’s the breakdown of each controller:

  • The ControlFreakLocationsController instantiates the InMemoryLocationService class using the new keyword.
  • The ControlFreakUpdatedLocationsController instantiates the SqlLocationService class and its dependency using the new keyword.
  • The InjectImplementationLocationsController leverages constructor injection to get an instance of the InMemoryLocationService class from the container.
  • The InjectImplementationUpdatedLocationsController leverages constructor injection to get an instance of the SqlLocationService class from the container.
  • The InjectAbstractionLocationsController leverages dependency injection and interfaces to let its consumers change its behavior at runtime.

The controllers share the same building blocks; let’s start there.

Shared building blocks

The Location data structure is the following:

namespace Strategy.Models;
public record class Location(int Id, string Name, string CountryCode);

The LocationSummary DTO returned by the controller is the following:

namespace Strategy.Controllers;
public record class LocationSummary(int Id, string Name);

The service interface is the following and has only one method that returns one or more Location objects:

using Strategy.Models;
namespace Strategy.Services;
public interface ILocationService
{
    Task<IEnumerable<Location>> FetchAllAsync(CancellationToken cancellationToken);
}

The two implementations of this interface are an in-memory version to use when developing and a SQL version to use when deploying (let’s call this production to keep it simple).The in-memory service returns a predefined list of cities:

using Strategy.Models;
namespace Strategy.Services;
public class InMemoryLocationService : ILocationService
{
    public async Task<IEnumerable<Location>> FetchAllAsync(CancellationToken cancellationToken)
    {
        await Task.Delay(Random.Shared.Next(1, 100), cancellationToken);
        return new Location[] {
            new Location(1, “Paris”, “FR”),
            new Location(2, “New York City”, “US”),
            new Location(3, “Tokyo”, “JP”),
            new Location(4, “Rome”, “IT”),
            new Location(5, “Sydney”, “AU”),
            new Location(6, “Cape Town”, “ZA”),
            new Location(7, “Istanbul”, “TR”),
            new Location(8, “Bangkok”, “TH”),
            new Location(9, “Rio de Janeiro”, “BR”),
            new Location(10, “Toronto”, “CA”),
        };
    }
}

The SQL implementation uses an IDatabase interface to access the data:

using Strategy.Data;
using Strategy.Models;
namespace Strategy.Services;
public class SqlLocationService : ILocationService
{
    private readonly IDatabase _database;
    public SqlLocationService(IDatabase database) {
        _database = database;
    }
    public Task<IEnumerable<Location>> FetchAllAsync(CancellationToken cancellationToken) {
        return _database.ReadManyAsync<Location>(
            “SELECT * FROM Location”,
            cancellationToken
        );
    }
}

That database access interface is simply the following:

namespace Strategy.Data;
public interface IDatabase
{
    Task<IEnumerable<T>> ReadManyAsync<T>(string sql, CancellationToken cancellationToken);
}

In the project itself, the IDatabase interface has only the NotImplementedDatabase implementation, which throws a NotImplementedException when its ReadManyAsync method is called:

namespace Strategy.Data;
public class NotImplementedDatabase : IDatabase
{
    public Task<IEnumerable<T>> ReadManyAsync<T>(string sql, CancellationToken cancellationToken)
        => throw new NotImplementedException();
}

Since the goal is not learning database access, I mocked that part in a test case in a xUnit test using the controller and the SqlLocationService class.

With those shared pieces, we can start with the first two controllers.

Method injection – Dependency Injection

ASP.NET Core supports method injection only at a few locations, such as in a controller’s actions (methods), the Startup class (if you are using the pre-.NET 6 hosting model), and the middleware’s Invoke or InvokeAsync methods. We cannot liberally use method injection in our classes without some work on our part.Method injection is also used to inject optional dependencies into classes. We can also validate those at runtime using null checks or any other required logic.

I recommend aiming for constructor injection whenever you can. We should only resort to method injection when it’s our sole option or when it brings added value to our design.

For example, in a controller, injecting a transient service in the only action that needs it instead of using constructor injection could save a lot of useless object instantiation and, by doing so, increase performance (less instantiation and less garbage collection). This can also lower the number of class-level dependencies a single class has.

Manually injecting a dependency in a method as an argument is valid. Here’s an example, starting with the classes:

namespace CompositionRoot.ManualMethodInjection;
public class Subject
{
    public int Operation(Context context)
    {
        // …
       
return context.Number;
    }
}
public class Context
{
    public required int Number { get; init; }
}

The preceding code represents the Subject class that consumes an instance of the Context from its Operation method. It then returns the value of its Number property.

This example follows a similar pattern to injecting an HttpContext into an endpoint delegate. In that case, the HttpContext represents the current HTTP request. In our case, the Context contains only an arbitrary number we use in the consuming code next.

To test that our code does as it should, we can write the following test:

[Fact]
public void Should_return_the_value_of_the_Context_Number_property()
{
    // Arrange
    var subject = new Subject();
    var context = new Context { Number = 44 };
    // Act
    var result = subject.Operation(context);
    // Assert
    Assert.Equal(44, result);
}

When we run the test, it works. We successfully injected the context into the subject. Now to simulate a more complex system, let’s have a look at a theory that does the same more dynamically:

[Theory]
[MemberData(nameof(GetData))]
public void Showcase_manual_method_injection(
    Subject subject, Context context, int expectedNumber)
{
    // Manually injecting the context into the
    // Operation method of the subject.
   
var number = subject.Operation(context);
    // Validate that we got the specified context.
   
Assert.Equal(expectedNumber, number);
}

The preceding code showcases the same concept, but xUnit injects the dependencies into the method, which is closer to what would happen in a real program. Remember, we want to remove the new keywords from our life!

The rest of the implementation is not important. I only pieced the simulation together to showcase this scenario. One interesting detail is that the Subject is always the same (singleton) while the Context is always different (transient), leading to a different outcome every time (Context { Number = 0 }, Context { Number = 1 }, and Context { Number = 2 }).

Having explored how to inject dependencies, we are ready to roll up our sleeves and dive into hands-on coding.

Revisiting the Strategy pattern – Dependency Injection

In this section, we leverage the Strategy pattern to compose complex object trees and use DI to dynamically create those instances without using the new keyword, moving away from being control freaks and toward writing DI-ready code.The Strategy pattern is a behavioral design pattern we can use to compose object trees at runtime, allowing extra flexibility and control over objects’ behavior. Composing our objects using the Strategy pattern makes our classes smaller, easier to test and maintain, and puts us on the SOLID path.From now on, we want to compose objects and lower the amount of inheritance to a minimum. We call that principle composition over inheritance. The goal is to inject dependencies (composition) into the current class instead of depending on base class features (inheritance). Additionally, this approach enables us to pull out behaviors and place them in separate classes, adhering to the Single Responsibility Principle (SRP) and Interface Segregation Principle (ISP). We can reuse these behaviors in one or more different classes through their interface, embodying the Dependency Inversion Principle (DIP). This strategy promotes code reuse and composition.The following list covers the most popular ways of injecting dependencies into objects, allowing us to control their behaviors from the outside by composing our objects:

  • Constructor injection
  • Property injection
  • Method injection

We can also get dependencies directly from the container. This is known as the Service Locator (anti-)pattern. We explore the Service Locator pattern later in this chapter.

Let’s look at some theory and then jump into the code to see DI in action.

Constructor injection

Constructor injection consists of injecting dependencies into the constructor as parameters. This is the most popular and preferred technique by far. Constructor injection is useful for injecting required dependencies; you can add null checks to ensure that, also known as the guard clause (see the Adding a guard clause section).

Property injection

The built-in IoC container does not support property injection out of the box. The concept is to inject optional dependencies into properties. Most of the time, you want to avoid doing this because property injection leads to optional dependencies, leading to nullable properties, more null checks, and often avoidable code complexity. So when we think about it, it is good that ASP.NET Core left this one out of the built-in container.You can usually remove the property injection requirements by reworking your design, leading to a better design. If you cannot avoid using property injection, use a third-party container or find a way to build the dependency tree yourself (maybe leveraging one of the Factory patterns).Nevertheless, from a high-level view, the container would do something like this:

  1. Create a new instance of the class and inject all required dependencies into the constructor.
  2. Find extension points by scanning properties (this could be attributes, contextual bindings, or something else).
  3. For each extension point, inject (set) a dependency, leaving unconfigured properties untouched, hence its definition of an optional dependency.

There are a couple of exceptions to the previous statement regarding the lack of support:

  • Razor components (Blazor) support property injection using the [Inject] attribute.
  • Razor contains the @inject directive, which generates a property to hold a dependency (ASP.NET Core manages to inject it).

We can’t call that property injection per se because they are not optional but required, and the @inject directive is more about generating code than doing DI. They are more about an internal workaround than “real” property injection. That is as close as .NET gets from property injection.

I recommend aiming for constructor injection instead. Not having property injection should not cause you any problems. Often, our need for property injection stems from less-than-optimal design choices, whether from our design strategies or a framework we’re utilizing.

Next, we look at method injection.

Project – Registering the Demo Feature – Dependency Injection

Let’s explore registering the dependencies of the Demo Feature. That feature contains the following code:

namespace CompositionRoot.DemoFeature;
public class MyFeature
{
    private readonly IMyFeatureDependency _myFeatureDependency;
    public MyFeature(IMyFeatureDependency myFeatureDependency)
    {
        _myFeatureDependency = myFeatureDependency;
    }
    public void Operation()
    {
        // use _myFeatureDependency
    }
}
public interface IMyFeatureDependency { }
public class MyFeatureDependency : IMyFeatureDependency { }

As we can see, there is nothing complex but two empty classes and an interface. Remember that we are exploring the registration of dependencies, not what to do with them or what they can do—yet.Now, we want the container to serve an instance of the MyFeatureDependency class when a dependency requests the IMyFeatureDependency interface as the MyFeature class does. We want a singleton lifetime.To achieve this, in the Program.cs file, we can write the following code:

builder.Services.AddSingleton<MyFeature>();
builder.Services.AddSingleton<IMyFeatureDependency, MyFeatureDependency>();

We can also chain the two method calls instead:

builder.Services
    .AddSingleton<MyFeature>()
    .AddSingleton<IMyFeatureDependency, MyFeatureDependency>()
;

However, this is not yet elegant. What we want to achieve is this:

builder.Services.AddDemoFeature();

To build that registration method, we can write the following extension method:

using CompositionRoot.DemoFeature;
namespace Microsoft.Extensions.DependencyInjection;
public static class DemoFeatureExtensions
{
    public static IServiceCollection AddDemoFeature(this IServiceCollection services)
    {
        return services
            .AddSingleton<MyFeature>()
            .AddSingleton<IMyFeatureDependency, MyFeatureDependency>()
        ;
    }
}

As highlighted, the registration is the same but uses the services parameter, which is the extended type, instead of the builder.Services (builder does not exist in that class, yet the services parameter is the same object as the builder.Services property).If you are unfamiliar with extension methods, they come in handy for extending existing classes, like we just did. Besides having a static method inside a static class, the this keyword next to the first parameter determines whether it is an extension method.For example, we can build sophisticated libraries that are easy to use with a set of extension methods. Think System.Linq for such a system.Now that we learned the basics of dependency injection, there is one last thing to cover before revisiting the Strategy design pattern.

Using external IoC containers

ASP.NET Core provides an extensible built-in IoC container out of the box. It is not the most powerful IoC container because it lacks some advanced features, but it does the job for most applications.Rest assured; we can change it to another one if need be. You might also want to do that if you are used to another IoC container and want to stick to it.Here’s the strategy I recommend:

  1. Use the built-in container, as per Microsoft’s recommendation.
  2. When you can’t achieve something with it, look at your design and see if you can redesign your feature to work with the built-in container and simplify your design.
  3. If it is impossible to achieve your goal, see if extending the default container using an existing library or coding the feature yourself is possible.
  4. If it is still impossible, explore swapping it for another IoC container.

Assuming the container supports it, it is super simple to swap. The third-party container must implement the IServiceProviderFactory<TContainerBuilder> interface. Then, in the Program.cs file, we must register that factory using the UseServiceProviderFactory<TContainerBuilder> method like this:

var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory<ContainerBuilder>(new ContainerBuilderFactory());

In this case, the ContainerBuilder and ContainerBuilderFactory classes are just wrappers around ASP.NET Core, but your third-party container of choice should provide you with those types. I suggest you visit their documentation to know more.Once that factory is registered, we can configure the container using the ConfigureContainer<TContainerBuilder> method and register our dependencies as usual, like this:

builder.Host.ConfigureContainer<ContainerBuilder>((context, builder) =>
{
    builder.Services.AddSingleton<Dependency1>();
    builder.Services.AddSingleton<Dependency2>();
    builder.Services.AddSingleton<Dependency3>();
});

That’s the only difference; the rest of the Program.cs file remains the same.As I sense you don’t feel like implementing your own IoC container, multiple third-party integrations already exist. Here is a non-exhaustive list taken from the official documentation:

  • Autofac
  • DryIoc
  • Grace
  • LightInject
  • Lamar
  • Stashbox
  • Simple Injector

On top of replacing the container entirely, some libraries extend the default container and add functionalities to it. We explore this option in Chapter 11, Structural Patterns.Now that we have covered most of the theory, we revisit the Strategy pattern as the primary tool to compose our applications and add flexibility to our systems.

Registering our dependencies – Dependency Injection

In ASP.NET Core, we register our dependencies in the Program.cs file, which represents the composition root. Since the minimal hosting model, the WebApplicationBuilder exposes the Services property we use to add our dependencies to the container. Afterward, .NET creates the container when it builds the WebApplication instance.Next is a minimal Program.cs file depicting this concept:

var builder = WebApplication.CreateBuilder(args);
// Register dependencies
var app = builder.Build();
// The IoC container is now available
app.Run();

Then, we use the builder.Services property to register our dependencies in that IServiceCollection implementation. Here’s an example of registering some dependencies:

builder.Services.AddSingleton<Dependency1>();
builder.Services.AddSingleton<Dependency2>();
builder.Services.AddSingleton<Dependency3>();

The preceding code registers the dependencies using the singleton lifetime, so we get the same instance each time we request one.

Remember to compose the program in the composition root. That removes the need for those pesky new keywords spread around your code base and all the tight coupling that come with them. Moreover, it centralizes the application’s composition into that location, creating the plan to assemble the LEGO® blocks.

As you may be thinking right now, that can lead to a lot of registration statements in a single location, and you are correct; maintaining such a composition root would be a challenge in almost any application. To address this concern, we introduce an elegant way to encapsulate the registration code next, ensuring it remains manageable.

Registering your features elegantly

As we’ve just discovered, while we should register dependencies in the composition root, we can also arrange our registration code in a structured manner. For example, we can break down our application’s composition into several methods or classes and invoke them from our composition root. Another strategy could be to use an auto-discovery system to automate the registration of certain services.

The critical part is to centralize the program composition in one place.

A common pattern in ASP.NET Core is having special methods like Add[Feature name]. These methods register their dependencies, letting us add a group of dependencies with just one method call. This pattern is convenient for breaking down program composition into smaller, easier-to-handle parts, like individual features. This also makes the composition root more readable.

A feature is the correct size as long as it stays cohesive. If your feature becomes too big, does too many things, or starts to share dependencies with other features, it may be time for a redesign before losing control over it. That’s usually a good indicator of undesired coupling.

To implement this pattern, we use extension methods, making it trivial. Here’s a guide:

  1. Create a static class named [subject]Extensions in the Microsoft.Extensions.DependencyInjection namespace.
  2. Create an extension method that returns the IServiceCollection interface, which allows method calls to be chained.

According to Microsoft’s recommendation, we should create the class in the same namespace as the element we extend. In our case, the IServiceCollection interface lives in the Microsoft.Extensions.DependencyInjection namespace.

Of course, this is not mandatory, and we can adapt this process to our needs. For example, we can define the class in another namespace if we want consumers to add a using statement implicitly. We can also return something else when the registration process can continue beyond that first method, like a builder interface.

Builder interfaces are used to configure more complex features, like ASP.NET Core MVC. For example, the AddControllers extension method returns an IMvcBuilder interface that contains a PartManager property. Moreover, some extension methods target the IMvcBuilder interface, allowing further configuration of the feature by requiring its registration first; that is, you can’t configure IMvcBuilder before calling AddControllers. You can also design your features to leverage that pattern when needed.

Let’s explore a demo.

What’s an object’s lifetime? – Dependency Injection

When we create an instance manually, using the new keyword, we create a hold on that object; we know when we create it and when its life ends. That’s the lifetime of the object.Of course, using the new keyword leaves no chance to control these objects from the outside, enhance them, intercept them, or swap them for another implementation—as covered in the preceding Code smell – Control Freak section.

.NET object lifetime

With dependency injection, we need to forget about controlling objects and start to think about using dependencies, or more explicitly, depending on their interfaces. In ASP.NET Core, there are three possible lifetimes to choose from:

LifetimeDescription
TransientThe container creates a new instance every time.
ScopedThe container creates an instance per HTTP request and reuses it. In some rare cases, we can also create custom scopes.
SingletonThe container creates a single instance of that dependency and always reuses that unique object.

 Table 8.1: objects lifetime description

We can now manage our volatile dependencies using one of those three scopes. Here are some questions to help you choose:

  • Do I need a single instance of my dependency? Yes? Use the singleton lifetime.
  • Do I need a single instance of my dependency shared over an HTTP request? Yes? Use the scoped lifetime.
  • Do I need a new instance of my dependency every time? Yes? Use the transient lifetime.

A general approach to object lifetime is to design the components to be singletons. When impossible, we go for scoped. When scoped is also impossible, go for transient. This way, we maximize instance reuse, lower the overhead of creating objects, lower the memory cost of keeping those objects in memory, and lower the amount of garbage collection needed to remove unused instances.

For example, we can pick singleton mindlessly for stateless objects, which are the easiest to maintain and less likely to break.

For stateful objects, where multiple consumers use the same instance, we must ensure the object is thread-safe if the lifetime is singleton or scoped because multiple consumers could try to access it simultaneously.

One essential aspect to consider when choosing a lifetime is the consumers of stateful objects. For example, if we load data related to the current user, we must ensure that data do not leak to other users. To do so, we can define the lifetime of that object to scoped, which is limited to a single HTTP request. If we don’t want to reuse that state between multiple consumers, we can choose a transient lifetime to ensure every consumer gets their own instance.

How does that translate into code? .NET offers multiple extension methods to help us configure the lifetimes of our objects, like AddTransient, AddScoped, and AddSingleton, which explicitly state their lifetimes.

We use the built-in container throughout the book with many of its registration methods, so you should grow familiar with it very quickly. It has good discoverability, so you can explore the possibilities using IntelliSense while writing code or reading the documentation.

Next, we use those methods and explore how to register dependencies with the container.

Code smell – Control Freak – Dependency Injection

Control freak is a code smell and even an anti-pattern that forbids us from using the new keyword. Yes, using the new keyword is the code smell! The following code is wrong and can’t leverage DI:

namespace CompositionRoot.ControlFreak;
public class Consumer
{
    public void Do()
    {
        var dependency = new Dependency();
        dependency.Operation();
    }
}
public class Dependency
{
    public void Operation()
        => throw new NotImplementedException();
}

The highlighted line shows the anti-pattern in action. To enable the Consumer class to use dependency injection, we could update it like the following:

public class Consumer
{
    private readonly Dependency _dependency;
    public DIEnabledConsumer(Dependency dependency)
    {
        _dependency = dependency;
    }
    public void Do()
    {
        _dependency.Operation();
    }
}

The preceding code removes the new keyword and is now open for modification. The highlighted lines represent the constructor injection pattern we explore subsequently in this chapter.Nevertheless, do not ban the new keyword just yet. Instead, every time you use it, ask yourself whether the object you instantiated using the new keyword is a dependency that could be managed by the container and injected instead.To help with that, I borrowed two terms from Mark Seemann’s book Dependency Injection in .NET; the name Control Freak also comes from that book. He describes the following two categories of dependencies:

  • Stable dependencies
  • Volatile dependencies

Next is my take on defining them.

Stable dependencies

Stable dependencies should not break our application when a new version is released. They should use deterministic algorithms (input X should always produce output Y), and you should not expect to change them with something else in the future.

Most data structures devoided of behaviors, like Data Transfer Objects (DTOs), fall into this category. You can also consider the .NET BCL as stable dependencies.

We can still instantiate objects using the new keyword when they fall into this category because the dependencies are stable and unlikely to break anything if they change.Next, we look at their counterpart.

Volatile dependencies

Volatile dependencies can change at runtime, like extendable elements with contextual behaviors. They may also be likely to change for various reasons like new features development.

Most classes we create, such as data access and business logic code, are volatile dependencies.

The primary way to break the tight coupling between classes is to rely on interfaces and DI and no longer instantiate those volatile dependencies using the new keyword. Volatile dependencies are why dependency injection is key to building flexible, testable, and maintainable software.

Conclusion

To conclude this interlude: don’t be a control freak anymore; those days are behind you!

When in doubt, inject the dependency instead of using the new keyword.

Next, we explore the available lifetimes we can attribute to our volatile dependencies.

Object lifetime

Now that we understand we should no longer use the new keyword, we need a way to create those classes. From now on, the IoC container will play that role and manage object instantiation and their lifetime for us.

Striving for adaptability – Dependency Injection

To achieve a high degree of flexibility with DI, we can apply the following formula, driven by the SOLID principles:Object A should not know about object B that it is using. Instead, A should use an interface, I, implemented by B, and B should be resolved and injected at runtime.Let’s decompose this:

  • Object A should depend on interface I instead of concrete type B.
  • Instance B, injected into A, should be resolved by the IoC container at runtime.
  • A should not be aware of the existence of B.
  • A should not control the lifetime of B.

We can also inject objects directly without passing by an interface. It all depends on what we inject, in what context, and our requirements. We tackle many use cases throughout the book to help you understand DI.

Next, we translate this equation into an analogy that helps explain the reasons to use a container.

Understanding the use of the IoC container

To better understand the use of the IoC container and to create an image around the previous adaptability concept, let’s start with a LEGO® analogy where IoC is the equivalent of drawing a plan to build a LEGO®castle:

  1. We draw the plan
  2. We gather the blocks
  3. We press the start button on a hypothetical robot builder
  4. The robot assembles the blocks by following our plan
  5. The castle is complete

By following this logic, we can create a new 4×4 block with a unicorn painted on its side (concrete type), update the plan (composition root), and then press the restart button to rebuild the castle with that new block inserted into it, replacing the old one without affecting the structural integrity of the castle (program). As long as we respect the 4×4 block contract (interface), everything is updatable without impacting the rest of the castle, leading to great flexibility.Following that idea, if we need to manage every single LEGO® block one by one, it would quickly become incredibly complex! Therefore, managing all dependencies by hand in a project would be super tedious and error-prone, even in the smallest program. This situation is where an IoC container (the hypothetical robot builder) comes into play.

The role of an IoC container

An IoC container manages objects for us. We configure it, and then, when we ask for a service, the container resolves and injects it. On top of that, the container manages the lifetime of dependencies, leaving our classes to do only one thing, the job we designed them to do. No more need to think about their dependencies!The bottom line is that an IoC container is a DI framework that does the auto-wiring for us. We can conceptualize Dependency Injection as follows:

  1. The consumer of a dependency states its needs about one or more dependencies (contracts).
  2. The IoC container injects that dependency (implementation) upon creating the consumer, fulfilling its needs at runtime.

Next, we explore an code smell that applying Dependency Injection helps us avoid.

What is dependency injection? – Dependency Injection

Before you begin: Join our book community on Discord

Give your feedback straight to the author himself and chat to other early readers on our Discord server (find the “architecting-aspnet-core-apps-3e” channel under EARLY ACCESS SUBSCRIPTION).

https://packt.link/EarlyAccess

This chapter explores the ASP.NET Core Dependency Injection (DI) system, how to leverage it efficiently, and its limits and capabilities.We learn to compose objects using DI and delve into the Inversion of Control (IoC) principle. As we traverse the landscape of the built-in DI container, we explore its features and potential uses.Beyond practical examples, we lay down the conceptual foundation of Dependency Injection to understand its purpose, its benefits, and the problems it solves and to lay down the ground for the rest of the book as we rely heavily on DI.We then return to the first three Gang of Four (GoF) design patterns we encountered, but this time, through the lens of Dependency Injection. By refactoring these patterns using DI, we gain a more holistic understanding of how this powerful design tool influences the structure and flexibility of our software.Dependency Injection is a cornerstone in your path toward mastering modern application design and its transformative role in developing efficient, adaptable, testable, and maintainable software.In this chapter, we cover the following topics:

  • What is dependency injection?
  • Revisiting the Strategy pattern
  • Understanding guard clauses
  • Revisiting the Singleton pattern
  • Understanding the Service Locator pattern
  • Revisiting the Factory pattern

What is dependency injection?

DI is a way to apply the Inversion of Control (IoC) principle. IoC is a broader version of the dependency inversion principle (the D in SOLID).The idea behind DI is to move the creation of dependencies from the objects themselves to the composition root. That way, we can delegate the management of dependencies to an IoC container, which does the heavy lifting.

An IoC container and a DI container are the same thing—they’re just different words people use. I use both interchangeably in real life, but I stick to IoC container in the book because it seems more accurate than DI container.

IoC is the concept (the principle), while DI is a way of inverting the flow of control (applying IoC). For example, you apply the IoC principle (inverting the flow) by injecting dependencies at runtime (doing DI) using a container. Feel free to use any or both.

Next, we define the composition root.

The composition root

A critical concept behind DI is the composition root. The composition root is where we tell the container about our dependencies and their expected lifetime: where we compose our dependency trees. The composition root should be as close to the program’s starting point as possible, so from ASP.NET Core 6 onward, the composition root is in the Program.cs file. In the previous versions, it was in the Program or Startup classes.Next, we explore how to leverage DI to create highly adaptable systems.