Decorators in .NET Core with Dependency Injection

What is a Decorator?

Feel free to skip this section if you’re already familiar with the Decorator pattern

The Decorator Pattern allows you to add functionality to an implementation of an interface by wrapping it in another implementation. e.g.

interface IService {
  string GetValue();
}

class DbService {
  public string GetValue() => "value from DB";
}

class LoggingService {
  private readonly IService _concreteService;
  private readonly ILogger _logger;

  public LoggingService(IService concreteService, ILogger<LoggingService> logger) {
    _concreteService = concreteService;
    _logger = logger;
  }

  public string GetValue() {
    _logger.LogInformation("Getting value");
    var value = _concreteService.GetValue();
    _logger.LogInformation("Retrieved {0}", value);
    return value;
  }
}

We have a concrete implementation of an IService interface (DbService) that returns a string value. LoggingService then decorates that implementation by wrapping invocation of the concrete instance and logging entry and exit.

This pattern is very useful when you want to augment either your own or framework types with some new behaviour such as logging or exception handling.

Decorators and Dependency Injection

The dependency injection framework in .NET Core is pretty good but out-of-the-box it doesn’t support this pattern very well.

If I register 2 copies of IService then any class attempting to consume IService will receive the one that was registered last. If they try to consume an IEnumerable then they will receive all of them. This is perfectly sensible behaviour but in this case it makes life harder.

Worse, if an implementation of IService depends on an instance of IService (and is the last one registered) then we will get a StackOverflowException when we resolve it!

Ideally we would like to be able to use this pattern with dependency injection without having to put too many constraints on either the decorator or the concrete implementation.

Specifically:

  • the wrapped implementation should be able to be registered in the DI container against the same interface as the decorator. This avoids adding any requirements to the code we are decorating
  • the decorator should depend on an instance of the interface it implements. This allows decorators to be applied to other decorators or implementations and makes the decorator easily testable
  • any dependencies of either the concrete implementation or the decorator should be injected automatically

So how can we achieve all this?

Approach #1: Reference a Concrete Type

Have the decorator explicitly depend on a concrete implementation of the wrapped class.

class Decorator : IService {
  public Decorator(ConcreteService service) {}
}

public static void ConfigureServices(IServiceCollection services) {
  services.Replace(ServiceDescriptor.Scoped<IService, Decorator>());
}
  • ✅ Easy
  • ❌ Can only decorate one implementation
  • ❌ Cannot inject a mock for unit tests
  • ❌ Requires the decorated implementation to be registered against it’s concrete type – not the interface
  • ❌ Cannot take service lifetime (scoped, transient or singleton) from wrapped service

Overall, it’s not great.

Approach #2: Use a Factory Registration

Use a factory method to pass the explicit concrete type to the decorator constructor.

class Decorator : IService {
  public Decorator(IService concreteService) {}
}

public static void ConfigureServices(IServiceCollection services) {
  services.Replace(ServiceDescriptor.Scoped<IService>(s =>
    new Decorator(s.GetRequiredService<ConcreteService>())
  ));
}
  • ✅ Decorator can construct on an interface
  • ❌ Still requires decorated implementation to be registered against it’s concrete type
  • ❌ Forces you to manually maintain other dependencies for Decorator
  • ❌ Cannot take service lifetime from wrapped service

This solution is relatively neat until your Decorator class starts to gain other dependencies.

class Decorator : IService {
  public Decorator(
    IService concreteService,
    IOtherService1 anotherDependency,
    IOtherService2 yetAnotherDependency) {}
}

public static void ConfigureServices(IServiceCollection services) {
  services.Replace(ServiceDescriptor.Scoped<IService>(s =>
    new Decorator(
      s.GetRequiredService<ConcreteService>(),
      s.GetRequiresService<IOtherService1>(),
      s.GetRequiredService<IOtherService2>())
  ));
}

It’s not the end of the world but it is a bit of a headache having to manually update constructor calls when you change a signature.

This approach also fails our requirement that the wrapped implementation is not explicitly specified.

Approach #3: Reusing the Existing Service Descriptor

IServiceCollection is largely a wrapper around a list of ServiceDescriptor objects so we can go grab the existing registration and use that in our factory.

class Decorator : IService {
  public Decorator(
    IService concreteService,
    IOtherService1 anotherDependency) {}
}

public static void ConfigureServices(IServiceCollection services) {
  var wrappedServiceDescriptor = services.First(s => s.ServiceType == typeof(IService));

  services.Replace(ServiceDescriptor.Describe(
    typeof(IService),
    s => new Decorator(
      s.CreateInstance(wrappedServiceDescriptor),
      s.GetRequiresService<IOtherService1>()),
    wrappedServiceDescriptor.Lifetime
  ));
}

public static object CreateInstance(this IServiceProvider services, ServiceDescriptor descriptor)
{
  if (descriptor.ImplementationInstance != null)
    return descriptor.ImplementationInstance;

  if (descriptor.ImplementationFactory != null)
    return descriptor.ImplementationFactory(services);

  return ActivatorUtilities.GetServiceOrCreateInstance(services, descriptor.ImplementationType);
}

Here we use the existing service descriptor to resolve an instance that we can pass into our constructor, as well as copying the defined service lifetime. We’re using the CreateInstance helper method to create an instance of the wrapped type from the service descriptor.

  • ✅ Decorator can construct on an interface
  • ✅ No longer requires the decorated implementation to be registered agaist it’s concrete type
  • ✅ Copies service lifetime from wrapped service
  • ❌ Still forces you to manually maintain other dependencies for Decorator

Closer, but we still have the pain point of managing dependencies when Decorator changes in the future.

Approach #4: Use an Intermediate Scope

Use the dependency injection framework to instantiate the decorator from the services registered so far.

//Decorator class as above

public static void ConfigureServices(IServiceCollection services) {
  var wrappedServiceDescriptor = services.First(s => s.ServiceType == typeof(IService));

  // add the decorator as a concrete type (with the same lifetime as the wrapped service)
  services.Add(ServiceDescriptor.Describe(typeof(Decorator), typeof(Decorator), wrappedServiceDescriptor.Lifetime);

  // build a new service provider that contains the decorator
  // as a concrete type and the wrapped service as the interface
  // (as well as anything that has already been registered)
  var intermediateProvider = services.BuildServiceProvider();

  // replace the interface registration by resolving the
  // decorator type from the intermediate provider
  services.Replace(ServiceDescriptor.Describe(
    typeof(IService),
    s => intermediateProvider.GetRequiredService<Decorator>(),
    wrappedServiceDescriptor.Lifetime
  ));
}
  • ✅ Decorator can construct on an interface
  • ✅ Can register decorated implementation against interface
  • ✅ Automatically handles dependency changes
  • ❌ Can cause scope issues with other dependencies

This version is pretty good but it does have a subtle problem: the intermediate scope will only contain services that have been registered at the time this code is called. This means that if either Decorator or the wrapped service have other dependencies that are registered later then they won’t be available.

This is possibly not the worst problem in the world but it could lead to a nasty category of bug that would be very difficult to track down. Can we do better?

Going to the Source

By this point we can define our ideal solution as something like:

var existingService = services.First(s => s.ServiceType == typeof(IService));
services.Replace(ServiceDescriptor.Describe(
  typeof(IService),
  s => s.GetRequiredService<Decorator>(/* somehow pass in existingService here */),
  existingService.Lifetime)
);

i.e. we want to use the existing service registration but only while resolving the decorator. Anything else resolving IService from the container should get the decorator.

While researching this problem, the closest analogue that came to mind was the way in which HttpClientFactory injects specific HttpClient instances into resolved classes. After a few minutes digging through the source (open source FTW!) I found their implementation including a few useful utilities that can help us out here.

ActivatorUtilities & ObjectFactory

The ActivatorUtilities.CreateFactory helper method creates a factory function for a concrete type:

var objectFactory = ActivatorUtilities.CreateFactory(
  typeof(Decorator), 
  new[] { typeof(IService) });

The second parameter is an array of the types that we can explicitly provide to the created objectFactory function. In this case, we will provide an explicitly-specified instance of IService but every other constructor parameter should be sourced from a service provider.

We invoke the objectFactory by passing in a service provider as well as an array of concrete instances of the types specified above.

ServiceDescriptor wrappedDescriptor;
IServiceProvider services;
var instanceOfDecorator = objectFactory(
  services, 
  new [] { services.CreateInstance(wrappedDescriptor) });

These 2 methods together give us exactly what we need:

  • ✅ Decorator can construct on an interface
  • ✅ Wrapped implementation can be registered against the interface
  • ✅ Decorator scope is taken from wrapped service
  • ✅ Dependencies in wrapped implementation are injected automatically
  • ✅ Dependencies in decorator are injected automatically

Here’s our final implementation, converted into a generic extension method on the IServiceCollection:

public static class ServiceCollectionExtensions {

  public static void Decorate<TInterface, TDecorator>(this IServiceCollection services)
    where TInterface : class
    where TDecorator : class, TInterface
  {
    // grab the existing registration
    var wrappedDescriptor = services.FirstOrDefault(
      s => s.ServiceType == typeof(TInterface));

    // check it's valid
    if (wrappedDescriptor == null) 
      throw new InvalidOperationException($"{typeof(TInterface).Name} is not registered");

    // create the object factory for our decorator type,
    // specifying that we will supply TInterface explicitly
    var objectFactory = ActivatorUtilities.CreateFactory(
      typeof(TDecorator), 
      new[] { typeof(TInterface) });

    // replace the existing registration with one
    // that passes an instance of the existing registration
    // to the object factory for the decorator
    services.Replace(ServiceDescriptor.Describe(
      typeof(TInterface),
      s => (TInterface)objectFactory(s, new[] { s.CreateInstance(wrappedDescriptor) }),
      wrappedDescriptor.Lifetime)
    );
  }

  private static object CreateInstance(this IServiceProvider services, ServiceDescriptor descriptor)
  {
    if (descriptor.ImplementationInstance != null)
      return descriptor.ImplementationInstance;

    if (descriptor.ImplementationFactory != null)
      return descriptor.ImplementationFactory(services);

    return ActivatorUtilities.GetServiceOrCreateInstance(services, descriptor.ImplementationType);
  }
}

Our sample usage is now nice and easy. We can even stack more than one decorator with each one wrapping the previously-registered type!

public static void ConfigureServices(IServiceCollection services) {
  services.AddScoped<IService, DbService>();
  services.Decorate<IService, LoggingService>();
  services.Decorate<IService, ExceptionHandlingService>(); //etc.

  // IService resolves to:
  //  an ExceptionHandlingService
  //    wrapping a LoggingService
  //      wrapping a DbService
}

I’m not sure why this was such a complicated thing to achieve with the dependency injection framework – it is certainly easier in, for example, Autofac – but it goes to show the great advantage of open source: you can almost always find a way!

Advertisements

Can a 4-day work week work?

5 days of work in 4 days: can it work for both a developer and their company?

Background

I have been working as a professional software developer for over 13 years. In that time a lot has changed about the way I work: sometimes in an office, sometimes remote; sometimes in the centre of London and sometimes in the sticks. One thing has always been constant though: 9-5 (ish), 5 days a week.

Ok, sure, that’s what it said on the contract and in practice there’s plenty of flexibility in those working hours but it’s always been 5 days, and in my current role that’s worked out as 4 days in London and 1 day remote each week.

I live a fair distance outside London so when I come into town for my (nominally) 8h working day I’m spending a solid FOUR hours commuting on bikes, trains and tubes. Most of my non-weekend free time is a desperate attempt to get enough sleep.

This is not fun.

Proposal

Working remotely definitely solves some of these problems. I’m a great believer in remote working and when done right it can be incredibly effective, but I wanted to know if I could go one better.

Instead of shaving a couple of hours from the end of a couple of days, could I claw back an entire day?

I had already agreed to working remotely on Wednesdays (it breaks the week up nicely) but I was still coming into London 4 times every week. If I’m already spending 4h travelling, wouldn’t it make more sense to spend more time working and reduce my (hours worked)/(total hours) ratio?

Here’s my proposal:

Day Place Hours
Monday Office 10h
Tuesday Office 10h
Wednesday Remote 10h
Thursday Office 10h
Friday “On Call”

Through the week I would would work long days on Mon-Thu and then have Friday effectively free. I didn’t want to cause blocks for my team so I planned to be available on email, chat etc. but not actively working.

Overall this should mean that I’m working more-or-less the same hours every week. We’re in an industry where “hours worked” really shouldn’t be the metric by which we judge people but I’ve tried to make it tally up anyway.

Why this is a great idea for me

On the face of it this seems like a pretty big win! Who wouldn’t love extra time off??

The most important thing for me is seeing my kids for more than the half hour before bedtime that I currently get. It’s rarely their best time (they’re young) and weekends are always busy so it will be nice to have some one-on-one time with them. Maybe get a bit of that “work life balance” I’ve heard so much about.

Beyond that I’m looking forward to a bit more sleep and a little less time on trains!

Why this is a great idea for the company

If this works out then there should be a bunch of upsides for my company as well as my family.

Besides the correlation between “happy employee” and “hard-working employee” I’m hoping to come back from the longer weekend full of ideas and energy. You know how you go away on holiday and have enough headspace to come up with the really great ideas? That. But every week.

Another bonus is that I will have great availability on Mon-Thu. We have a lot of people working offset hours so some people do 8:00-16:30 and some 10:00-18:30, with both ends of that spectrum in my team. With my new working pattern I’ll be able to work with both the early risers and the night owls equally.

Finally, I am hoping that my regular semi-absence will encourage my team to act more autonomously. It’s harder to delegate decisions to someone senior if they’re not around!

Why this might be a disaster

Ok, I’ll admit there are a few things that could go wrong with this plan. I might be exhausted every day and not get any work done. I might be so exhausted by the end of the week that I spend my Fridays as a zombie. I might even be tempted to work on Friday as well if we’re under pressure.

Beyond various flavours of exhaustion, it may turn out that my absence on a Friday is a real problem for the team. It could be a blocker, even when I’m on email. It might turn out that it’s harder to book meetings or client visits.

The only way to find out if these problems are real or imagined is to try it out!

The plan

I had a chat with my boss and they’re happy for me to give this a try. I was a little surprised – I thought this was a fairly unusual request – but it’s nice to see a company that doesn’t just pay lip service to employee work/life balance.

We agreed that I would trial the new working pattern for a few weeks and observe the improvements (or otherwise). There isn’t really a good metric for “developer output” so the results are going to be largely subjective but I’ll be focussing on:

  • personal energy levels (work & home)
  • feedback from my team
  • feedback from other teams and departments
  • a fuzzy “quality of life” feeling

It’s not particularly scientific but I suspect it will become obvious quite quickly if it’s not working.

If nothing else, it’s going to be interesting finding out!