When using Dependency Injection in a .NET Framework application, there is a popular NuGet package out there that developers are using. Autofac is the Dependency Injection package where you can include your dependencies on the start up of an application, and reference them at several stages of your application. Not only is it available in a .NET Framework package, it's also available for .NET Core.
In-built Dependency Injection Container
With .NET Core, there is an alternative to Autofac. The Microsoft.Extensions.DependencyInjection package is an inbuilt DI container for .NET Core. However, this is a lighter package of Autofac and doesn't include all the features. We are going to go through some of the features in this lighter package.
Registering Components
Inside the ConfigureServices method from the Startup class, you can add your components to Dependency Injection. You can add either a Scoped, Transient or Singleton lifetime to your component. Here is a brief description of what each lifetime is:
- Scoped - These are initalised every time a new DI scope is created.
- Transient - These are initalised every time the component is injected or requested
- Singleton - These are initalised for the lifetime of the application.
When registering a component in DI, you would typically create a class and mirror that class with an interface. The interface would be used as the reference type in DI. There is a major advantage of doing this which I will come onto later.
Here is an example of how you can register components in DI:
// Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ICategoryService, CategoryService>();
services.AddTransient<IEnquiryService, EnquiryService>();
services.AddSingleton<IImageService, ImageService>();
}
}
Creating a Dependency Injection Scope
Typically, a new scope will be called for every HTTP request in a ASP.NET Core application. Subsequently, that means if you have any scoped components set up, it will create a new instance every time without any additional programming. But, what if you are running a console application, or a windows service?
Well you can build your web host which will include your startup file. Then you can call the CreateScope method to create a new scope in DI. An example is below:
// Program.cs
public class Program
{
public static void Main(string[] args)
{
builder = WebHost.CreateDefaultBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.Build();
using (var scope = builder.Services.CreateScope())
{
var categoryService = (ICategoryService)scope.ServiceProvider.GetRequiredService(typeof(ICategoryService));
// ...
}
}
}
As you can see from the example above, we are referencing components that have been registered in DI. We are calling the GetRequiredService method which is included in the scope. We have a reference to the scope in our console application, that means we can use it. But how can we reference a component from DI in a ASP.NET Core application?
Using Dependency Injection Components in Constructors
Well, you can reference the type in the constructor. By default, this will resolve to however it's registered in DI. Here is an example:
// CategoryController.cs
public partial class CategoryController : Controller
{
protected readonly IImageService _imageService;
public CategoryController(
IImageService imageService
)
{
_imageService = imageService;
}
}
There is one major advantage and disadvantage of doing this way. Do you remember I said there was a major advantage of registering a DI type as an interface?
It's because of unit testing. If you have to call a class that has a load of DI parameters in the constructor, you have to create a reference for each of them. As interfaces don't have constructors, you only have to reference the interface. If you registered DI components as classes and used them in unit tests, you would have to get a reference for each parameter, and any other parameters in those constructors. It could be a lengthy job.
But what is the disadvantage of doing it this way? Well it comes if you are using it for multiple base classes. If you want to reference a new DI type in a base class constructor, you will have to update the base reference for all it's child classes. That could be very time consuming.
One way to get around this is to include the IServiceProvider type in your constructor. You can use this type to reference any type that is part of your DI container.
Conclusion
I hope I've managed to go through some of the features of the in-built dependency injection that comes with .NET Core. It certainly ticks the boxes if you are wanting to use the basic tools with DI. One of the things that seems to be missing is the ability to explicitly reference a DI component from anywhere in your application. Something I've relied on in the past with Autofac. I'm hoping this feature will be included soon.