Back

Decorating Dependency Injection in ASP.NET Core: A Practical Guide

Decorating Dependency Injection in ASP.NET Core: A Practical Guide

This blog covers the topic of decorator patterns in dependency injection in ASP.NET Core. Asleep yet? I assure you it’s not going to be boring, and I’ll try to avoid the fluff. If you’re seeking vast explanations and filler content, google “decorator pattern” and try not to fall asleep while reading the definition. We’ll dive into practical examples that you can follow along with. All code discussed will be readily available on GitHub. Get ready to take your understanding of dependency injection to the next level!

Motivation for decorating dependency injection

In a recent project, we used an external library that made use of dependency injection for easier customization of its default implementation. The default implementation was enough for our needs with just one minor tweak required. The addition of a parameter to an URL generated by the library. The challenge was that this modification was several layers deep in the call chain.

We had access to the source code as the library was developed internally, but adding specialized implementations seemed like overkill considering its widespread usage in multiple projects. On the other hand, simply copying and pasting the source code into our solution would solve the problem. But, this would result in a maintenance headache in the future. Any future bug fixes or new features would have to be manually copied over as well.


That’s where a colleague suggested and implemented decorator patterns for DI in ASP.NET. Few simple lines of code could achieve everything we wanted.

Example 1

We have a calculator endpoint with a “divide” method. A library performs the calculation for us, but we want to return 0 instead of throwing a divide by 0 exception.

Using the “Decorate” extension method, we can handle this DI modification with just a few lines of code:

builder.Services.AddScoped<ICalculator, Calculator>();
builder.Services.Decorate<ICalculator, MyCalculator>();

and MyCalculator implementation is

public class MyCalculator : ICalculator
{
  private ICalculator _calculator;
  public MyCalculator(ICalculator calculator)
  {
    _calculator = calculator;
  }

  public double Add(double left, double right)
    => _calculator.Add(left, right);

  public double Multiply(double left, double right)
    => _calculator.Multiply(left, right);

  public double Divide(double left, double right)
    => right == 0 ? 0 : _calculator.Divide(left, right);
}

The best part is everything else stays the same, ensuring true separation of concerns. For example, the associated controller remains unchanged.

public class CalculatorController : ControllerBase
{
  private ICalculator _calculator;
  public CalculatorController(ICalculator calculator)
  {
    _calculator = calculator;
  }

  [HttpGet("add")]
  public double Ad([FromQuery] double left, [FromQuery] double right)
    => _calculator.Add(left, right);

  [HttpGet("multiply")]
  public double Multiply([FromQuery] double left, [FromQuery] double right)
    => _calculator.Multiply(left, right);

  [HttpGet("divide")]
  public double Divide([FromQuery] double left, [FromQuery] double right)
    => _calculator.Divide(left, right);
}

The test that checks the behaviour looks like this and works as expected:

[TestMethod]
public async Task ReturnsZero_WhenDividingByZeroAsync()
{
  var response = await _client.GetAsync($"calculator/divide?left=1&right=0");
  var content = await response.Content.ReadFromJsonAsync<double>();

  response.IsSuccessStatusCode.Should().BeTrue();
  content.Should().Be(0);
}

High-level explanation

Whenever a request is made for an instance of ICalculator, an instance of MyCalculator will be returned, unless the request originated from MyCalculator. In this case, the original Calculator instance will be injected into MyCalculator. This allows us to only modify what needs to be changed while keeping everything else the same through the use of lambdas.

Why couldn’t we just create MyCalculator: IMyCalculator and inject both ICalculator and IMyCalculator? Legit question, but this approach would not have worked in our use case. Our library wanted ICalculator and would not settle for anything else.

Decorate extension method with MyCalculator and ICalculator

Example 2

Sometime later, I came across a common problem while writing tests. A part of the authentication layer needed to be bypassed for the tests to work, such as checking the token but not doing any housekeeping. The classic approach to this would be to have if-else blocks to handle specific behaviours based on the environment.

I don’t like this approach because the code becomes clumsy, the behaviour isn’t isolated to just tests, and if the environment is wrongly set up, it can bleed into production. And it’s just ugly.

Decorator pattern used for decorating dependency injections

As an example, let’s presume we have a health check endpoint with a ping method that returns pong. In our tests, we want the method to return OK. Not sure why we’d want that, but academic examples are hard, and real-life ones are too verbose to convey the idea, so just we’re just going to have to deal with it.

In our deployed code, we want to maintain the existing behaviour without using decorators. This allows us to use the original implementation without making any changes.

builder.Services.AddScoped<IHealthChecker, HealthChecker>();

And we have our decorated class.

public class MyHealthChecker : IHealthChecker
{
  private IHealthChecker _healthChecker;

  public MyHealthChecker(IHealthChecker healthChecker)
  {
    _healthChecker = healthChecker;
  }

  public string Ping()
  {
    if (_healthChecker.Ping() == "Pong")
      return "OK";
    return "NOT OK";
  }
}

And then in our tests, we can swap the implementation in the DI container just for the test.

[TestMethod]
[DataRow(false, "Pong")]
[DataRow(true, "OK")]
public async Task ReturnExpected_DependingOnDecoration(
  bool useDecorator, string expectedOutput)
{
  var factory = new WebApplicationFactory<Program>();
  if (useDecorator)
  {
    factory = factory.WithWebHostBuilder(builder =>
    {
      builder.ConfigureServices(services =>
      {
        services.Decorate<IHealthChecker, MyHealthChecker>();
      });
    });
  }

  var client = factory.CreateDefaultClient();

  var response = await client.GetAsync("/health");
  var content = await response.Content.ReadAsStringAsync();

  response.IsSuccessStatusCode.Should().BeTrue();
  content.Should().Be(expectedOutput);
}

If you clone the code and run the tests you can see both cases pass it. Not much more than that to say.

Decorator implementation

The implementation itself is not important, check out the code if you’re interested or want to use it, the main point is the possibilities that this pattern implementation gives you.

Conclusion

Even though this is an edge case sort of a pattern, the decorator pattern is a useful tool to have in your arsenal, especially in situations where you need to make modifications to an existing implementation without affecting its core functionality. It will save you from maintaining copy-pasted code and make your code more readable by eliminating environment checks. The usage is quite clear and self-explanatory, but I’d just suggest keeping the injection and decoration close to each other while setting up DI container because it’s easy to overlook them.

Another approach would be to create an extension method that combines injection and decoration, such as “AddScopedDecorated,” but I leave this choice up to you


Read more blog posts from Grumpy Old Git here.

Back
Do you have a project you need help with?
Get in Touch

By using this website, you agree to our use of cookies. We use cookies to provide you with a great experience and to help our website run effectively.

Accept