In a previous post I covered registering generic types with Dependency Injection. This is one of the less common (and less documented) ways in which services could be registered with the Microsoft DI library. It turns out that you can do more with the DI available in the Microsoft.Extensions.DependencyInjection package than it may first appear.
Another “advanced” pattern that can be achieved is to register multiple concrete implementations for an interface. These can later be injected as an IEnumerable of that interface. In this post we’ll explore a quick example of how we can do that.
Let’s first discuss when and why you might want to do this. The example I have is based on a service I’ve been involved with previously. This service is responsible for reading messages from a Amazon SQS queue, enriching them and then saving them to ElasticSearch. Based on the property values in the message it conditionally enriches the data. Initially we only had a couple of possible enrichers, but over time we’ve added more.
The way we decided to implement this was to define an IEnricher interface. That interface looks a little like this:
There are two methods on the interface. The first is called CanEnrich and this will take the message object and determine if this enricher can do enrichment on the message. The second method, Enrich, then performs the actual enrichment of the message.
We can define zero or more implementations of this interface for the different enriching activities we may want to perform.
Here’s example of an enricher:
This enricher tries to lookup the city for any failed login messages coming through our queue. It can only do this if the IP Address is present.
And here’s another :
This enricher populates a DayOfWeek property, which is then used for aggregating in ElasticSearch. It can only do this if the incoming message contains a Date.
Both of are quite basic and contrived examples. The functionality isn’t that important here though.
The enrichers can now be registered with the ServiceCollection wherever that happens in your application:
It’s worth making it clear that the implementations will be added in the order they are registered. They will be returned in that same order when injected into calling code. Depending on your requirements, this may be useful and important. For this example we don’t really care what order we get them in.
To make use of these enrichers we can have them injected wherever we require them and DI is available. Since we’ve registered more than one instance, we ask the DI framework for an IEnumerable<IEnricher> which we can then enumerate over to access all implementations.
A simplified example of this in a caller would look like this:
Here I filter to only the enrichers which can enrich the message we are processing. Then I call the Enrich method on each one in turn.
Where this pattern proves particularly useful is if we imagine we now want to add another enricher. All we have to do is create a class which implements the interface and then ensure it is registered with DI. Now when our caller runs, that enricher will be included in the IEnumerable<IEnricher> which is injected. Our consuming code will make use of the new enricher without any further code changes.
The ASP.NET Core framework uses this same pattern in a number of places. One which I’ve covered in the past is the IHostedService interface. This allows you to define one or more “background” services to run whilst you application is alive. As with my enricher example, all you have to do is create a class implementing IHostedService and then register it with DI. When the application starts it will fire up any registered IHostedService instances in order.