Help me, to help you — helping Microsoft DI framework to find dependencies.
This post is about dependency injection using Microsoft dependency injection framework. I use it a lot in my daily work and in personal projects.
When compared to its alternatives such as AutoFac
or Ninject
one of the missing features (as of now) is to “discover” the dependencies and register in the framework. For example, in Autofac
it can scan through the assemblies and register your classes as their implemented interfaces or as self.
So in this post let’s try to help Microsoft.DependencyInjection
framework to find our dependencies and register them as we would like to do.
Context
Consider you have a class and an interface which it implements.
public interface IEmailService
{
Task<bool> SendAsync();
}
public class EmailService : IEmailService
{
public Task<bool> SendAsync()
{
throw new System.NotImplementedException();
}
}
If this service to be injected we need to “explicitly” write the code to register it in the dependency injection framework. For example if we would like to inject as a Transient
dependency, we would need to write the code below.
// "services" is of type "IServiceCollection"
services.AddTransient<IEmailService, EmailService>();
Now imagine depending on your solution it can have 10, 15 or more different abstractions and you “will” need to write similar code such as above to register the dependency of each and everyone of them. What we are trying to do here is to help the Microsoft dependency injection framework to find our dependencies and to register them once and for all.
Approach
In here we are going to implement a custom attribute to provide information about how we would like to inject our dependency. Then we’ll implement an extension method so that it can be used with Microsoft.DependencyInjection
. You can do this in your application (API, Web, Console, etc..) itself or create a class library project. In either case make sure you have installed Microsoft.Extensions.DependencyInjection
nuget package.
Attributes
Simply, attributes are to provide additional information at runtime. Depending on how you define the attribute usage (class level, property level, etc..) you can apply the attribute in your code.
Extension Methods
Extension methods are there to add custom methods to existing types. They are great if you would like to extend the functionality of a given type customized to your requirement.
Let’s implement the attribute
The attribute can be used only in a Class
level and its properties are,
- LifeTime
This is of type ServiceLifeTime
which is the type which is already defined in the Microsoft.Extensions.DependencyInjection
framework.
- InjectAs
This is an enum which will declare “how” you would like to inject your type. I have defined only two ways to inject the type and you can define any other type as you would like to be injected depending on your use case.
Let’s implement the extension method
- Firstly it accepts a set of assemblies to scan for types.
- Then for each type it checks whether it has the custom attribute
InjectMeAttribute
- Once it finds the types which have the
InjectMeAttribute
(candidate types) then it will check how it needs to be registered in the dependency injection framework. - We use the capabilities of
Reflection
and build the required types to be registered in the framework. - At the core of the dependency injection framework every dependency is defined as a
ServiceDescriptor.
So we use it to construct the source and target types and then register them in the dependency injection framework.
Using the attribute
Considering the same example provided above now let’s decorate the EmailService
class with our attribute.
[InjectMe]
public class EmailService : IEmailService
{
public Task<bool> SendAsync()
{
throw new System.NotImplementedException();
}
}
Using the extension method
Let’s consider an ASP.NET Core Web API project to test this. In it’s ConfigureServices
method in Startup.cs
add the below method.
services.RegisterDependencies(typeof(Startup).Assembly);
You don’t need to define any dependencies “explicitly” as you did before for your classes anymore in the Startup
class.
Whenever a type needs to be injected you just need to decorate it with the InjectMe
attribute.
Thanks!