Control Freak controllers – Dependency Injection

This first version of the code showcases the lack of flexibility that creating dependencies using the new keyword brings when the time to update the application arises. Here’s the initial controller that leverages an in-memory collection:

using Microsoft.AspNetCore.Mvc;
using Strategy.Services;
namespace Strategy.Controllers;
[Route(“travel/[controller]”)]
[ApiController]
public class ControlFreakLocationsController : ControllerBase
{
    [HttpGet]
    public async Task<IEnumerable<LocationSummary>> GetAsync(CancellationToken cancellationToken)
    {
        var locationService = new InMemoryLocationService();
        var locations = await locationService
            .FetchAllAsync(cancellationToken);
        return locations
            .Select(l => new LocationSummary(l.Id, l.Name));
    }
}

Executing this code works and returns the LocationSummary equivalent of the Location objects returned by the FetchAllAsync method of the InMemoryLocationService class. However, changing the InMemoryLocationService to a SqlLocationService is impossible without changing the code like this:

public class ControlFreakUpdatedLocationsController : ControllerBase
{
    [HttpGet]
    public async Task<IEnumerable<LocationSummary>> GetAsync(CancellationToken cancellationToken)
    {
        var database = new NotImplementedDatabase();
        var locationService = new SqlLocationService(database);
        var locations = await locationService.FetchAllAsync(cancellationToken);
        return locations.Select(l => new LocationSummary(l.Id, l.Name));
    }
}

The changes are highlighted in the two code blocks. We could also create an if statement to load one or the other conditionally, but exporting this to a whole system makes a lot of duplication.Advantages:

  • It is easy to understand the code and what objects the controller uses.

Disadvantages:

  • The controller is tightly coupled with its dependencies, leading to a lack of flexibility.
  • Going from InMemoryLocationService to SqlLocationService requires updating the code.

Let’s improve on that design next with the next controller pair.

Injecting an implementation in the controllers

This second version of the codebase improves flexibility by leveraging dependency injection. In the following controller, we inject the InMemoryLocationService class in its constructor:

using Microsoft.AspNetCore.Mvc;
using Strategy.Services;
namespace Strategy.Controllers;
[Route(“travel/[controller]”)]
[ApiController]
public class InjectImplementationLocationsController : ControllerBase
{
    private readonly InMemoryLocationService _locationService;
    public InjectImplementationLocationsController(
        InMemoryLocationService locationService)
    {
        _locationService = locationService;
    }
    [HttpGet]
    public async Task<IEnumerable<LocationSummary>> GetAsync(CancellationToken cancellationToken)
    {
        var locations = await _locationService.FetchAllAsync(cancellationToken);
        return locations.Select(l => new LocationSummary(l.Id, l.Name));
    }
}

Assuming the InMemoryLocationService class is registered with the container, running this code would yield the same result as the Control Freak version and return the in-memory cities.

To register a class with the container, we can do the following:

builder.Services.AddSingleton<InMemoryLocationService>();

Unfortunately, to change that service for the SqlLocationService, we need to change the code again. This time, however, we must only change the constructor injection code like this:

public class InjectImplementationUpdatedLocationsController : ControllerBase
{
    private readonly SqlLocationService _locationService;
    public InjectImplementationUpdatedLocationsController(SqlLocationService locationService)
    {
        _locationService = locationService;
    }
    // …
}

This is yet another not ideal outcome.Advantages:

  • It is easy to understand the code and what objects the controller uses.
  • Using constructor injection allows changing the dependency in one place, and all the methods get it (assuming we have more than one method).
  • We can inject subclasses without changing the code.

Disadvantages:

  • The controller is tightly coupled with its dependencies, leading to a lack of flexibility.
  • Going from InMemoryLocationService to SqlLocationService requires updating the code.

We are getting there but still have a last step to make that controller flexible.

Leave a Reply

Your email address will not be published. Required fields are marked *



         


          Terms of Use | Accessibility Privacy