The decorator pattern using .NET's dependency injection

profile
Tim Deschryver
timdeschryver.dev

The Problem link

Imagine you have a (globally used) interface (from your own, or from a 3rd party) in your codebase and you want to introduce common behavior that needs to apply to all instances. Let's say you want to add extra logging, build resiliency, or add some caching. How would you tackle this problem?

One way to achieve this is by creating a new (abstract) class that contains the new behavior, and then inherit from this class in all derived classes. This might seem the most straightforward thing to do, but there are a few problems that you'll encounter immediately or in the future:

While this implementation seemed simple at first, there are a few gotcha's to take into consideration. It even becomes impossible to maintain if you want to add the behavior to all inheritors of an interface.

The (Manual) Solution link

Instead, it's probably better to resort to the Decorator pattern, from the "Gang of Four" book. In this blog post, we'll look at how we can leverage the dependency injection (DI) container to implement the decorator pattern.

The decorator pattern is a structural design pattern that allows you to introduce some new behavior to an existing class, without affecting the original class. It achieves this by enclosing a class with one (or more) decorator classes, which delegate method calls to the linked class.

You can think of the decorator pattern as a clothing layering system. As a runner this resonates with me, especially in the colder months. When the temperature drops, I still wear the same clothes as with "normal" weather, but I add an extra layer on top to keep me warm. When it rains, I add a waterproof jacket to keep my dry. The base layer doesn't change through the colder months, but extra layers are added depending on the weather conditions.

format_quote

There’s no such thing as bad weather, only bad clothes - a Norwegian saying

The same philosophy applies to the decorator pattern. Extra layers are added to an existing implementation to add extra functionality to it.

Let's take a look at an example. In the example below we define a decorator that logs a message before and after the Get method of a repository (IRepository) is invoked.

The decorator RepositoryLoggerDecorator receives the "inner" repository IRepository<T>, which is the real instance (or a decorated instance), and wraps it with its own implementation. In its implementation, the messages are logged, and the decorated version invokes the inner method.

As you can see within the above snippet, the repositories implementation hasn't changed. If that were the case, multiple repositories would need to be updated. Or, if we went with inheritance, then all derived classes had to inject an ILogger instance to pass it to the parent class.

To decorate a repository with the RepositoryLoggerDecorator, configure the DI container to new up a decorated instance and pass it the required dependencies.

Now every time an IRepository<Customer> instance is requested, this will resolve to the decorated version. Voila, we achieved the decorator pattern with .NET's dependency injection.

The (Automatic) Solution by using Scrutor link

The above implementation has a downside. We have to resort to a factory function to manually instantiate the decorator. This leaks some implementation details, and we'll have to update the factory every time the constructor arguments change.

By bringing in Scrutor, we can make this simpler and less brittle. Although Scrutor is mostly known for its assembly scanning capabilities, it also includes an extension to decorate instances with ease.

To use Scrutor, first install the NuGet package with the following command (or via the UI).

Next, update the DI configuration to make use of the Decorate method of Scrutor instead of doing this manually by using a factory method. In the next snippet Decorate will decorate IRepository<Customer> using the RepositoryLoggerDecorator class.

Warning

Make sure to first register the derived classes to the DI container while using this pattern. Otherwise, this will result in an error that the required service (IRepository<Customer>) cannot be found.

We can make this even simpler by using the following overload for Decorate. This will decorate all generic instances of IRepository<T> using the RepositoryLoggerDecorator.

For example, in the next snippet both IRepository<Customer> and IRepository<Order> will be decorated.

Finally, to automatically register all IRepository<T> instances, we can make use of the scanning capabilities of Scrutor.

Note

To get to know how Scrutor implements this, I recommend Andrew Lock's article Adding decorated classes to the ASP.NET Core DI container using Scrutor.

Conclusion link

Using the decorator pattern it's possible to add extra functionality on top of an existing implementation without changing the original code. In this blog post, we've seen that the DI container of .NET allows us to configure specific instances with a decorator. To make this simpler we can bring in Scrutor to automate this configuration.

Feel free to update this blog post on GitHub, thanks in advance!

Join My Newsletter (WIP)

Join my weekly newsletter to receive my latest blog posts and bits, directly in your inbox.

Support me

I appreciate it if you would support me if have you enjoyed this post and found it useful, thank you in advance.

Buy Me a Coffee at ko-fi.com PayPal logo

Share this post