Recently we’ve been talking about Dependency Injection. Currently you should know how to use Autofac. Today we’ll learn about things that you may find useful in one of your project.
Modules
As you may know in big project you’ll have lots of services to inject. That will leave you with multiple lines of code that register interfaces into DI container. Adding something new or editing something that already exist will be hell, unless you order service registration. You can use comments, but let’s face it, we’re not here to multiply code smells. Not only you’d pollute code with comments, but additionally you’d create huge startup class.
Introducing modules. You can create smaller classes – modules, that will contain only part of registration. There group up similar services into one module and inject whole module. Then if you’d need to add something to module it’d be easier to find relevant place. Additionally splitting classes goes in pair with Single-Responsibility Principle.
Modules, How to?
It’s easy, there are only two steps and the rest stay the same as before. Let’s get to it. First you need to create module, make sure Autofac is added to your project:
public class RepositoryModule: Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<StudentRepository>().As<IStudentRepository>();
builder.RegisterType<CourseRepository>().As<ICourseRepository>();
}
}
As you can see it’s the same thing, but instead of registering everything in one, place we’re doing it in different classes, small enough to make your future changes easier. Last step is registering module:
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterModule(new RepositoryModule());
}
Configuration in json
There’s additional option that isn’t used too often, but is important to know about, because you never know when you may find use case for it.
It’s possible to configure DI container from settings file. You can put configuration in external file, so it’s possible to switch DI configuration at production without release. This solution has advantages and disadvantages. First of all you can make release without releasing, let’s say you want to change behavior of your application, you’ve got everything prepared, but IoC injects wrong service, you can switch configuration then restart application and from that moment your application will work different way.
To be perfectly honest, I couldn’t find good use for that feature yet. I’ve worked in project that did use it. It was use to support multiple versions of the same application in different environments. Deployment tool had every configuration and uploaded everything with switched config file. I can’t say it was best solution, everything could’ve be done inside code, but I guess they thought it’s better to keep only multiple configurations than C# code.
This solution has one big flaw. Normally compiler would check if your IoC configuration is correct. When you’ll use config file to setup IoC container compiler will not help you. You’ll know that configuration is wrong when your application will need service to be build. If something is wrong application will crash.
This solution is pretty dangerous, you can break you application without knowing about it. So every time you’ll make changes you’ll need to check if everything is all right, something that compiler would check for you. It’s for you to decide if you need it. Let’s find out how to do it. Here‘s whole documentation. I’ll cover the most relevant options.
{
"components": [
{
"type": "[namespace].[className], [dllName]",
"services": [
{ "type": "[namespace].[interfaceName], [dllName]" }
]
}
]
}
It is the simplest setup. In first type you’ll need to put service that you want to register. Class name with full namespace and dll name in which service is implemented after coma.
In “services” you’ll put interfaces that are implemented by service. Consider example below.
{
"components": [
{
"type": "Spike.Infrastructure.Repositories.StudentRepository, Spike.Infrastructure",
"services": [
{
"type": "Spike.Core.Interface.IStudentRepository, Spike.Core.Interface"
}
]
},
{
"type": "Spike.Infrastructure.Repositories.CourseRepository, Spike.Infrastructure",
"services": [
{
"type": "Spike.Core.Interface.ICourseRepository, Spike.Core.Interface"
}
]
}
]
}
Then you need to create module:
public class RepositoryModule : Module
{
private readonly string configPath;
public RepositoryModule(string configPath)
{
this.configPath = configPath;
}
protected override void Load(ContainerBuilder builder)
{
var autofacConfig = new ConfigurationBuilder();
autofacConfig.SetBasePath(Directory.GetCurrentDirectory());
autofacConfig.AddJsonFile(configPath);
var repositoryModule = new ConfigurationModule(autofacConfig.Build());
builder.RegisterModule(repositoryModule);
builder.RegisterType<DataLimiter<Student>>().As<IDataLimiter<Student>>();
}
}
Here only one thing is interesting. How to add json setting file to you application. This is how .Net Core 2.0 preview were adding settings.json, now it’s done behind the scene, but still you can use it to add files other than default setting files.
First we need to create Configuration builder, then set path to file we want to add. We want to add relative path so we need to set BasePath to our application directory. Then add file with AddJsonFile(string Path) method.
After than we’re creating Autofac module from file. autofacConfig.Build() will load file to memory.
It’s worth to mention that you can mix multiple ways to register services. As you can see, I’ve registered module, and then registered another service old way.
builder.RegisterModule(repositoryModule);
builder.RegisterType<DataLimiter<Student>>().As<IDataLimiter<Student>>();
Last thing to do is register our module. I’ve passed path to config file as param. So everything is configured inside appsettings.json, thanks to that we don’t have any configuration hardcoded in our application.
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterModule(new RepositoryModule(Configuration["AutofacConfig:RepositoryConfig"]));
builder.RegisterModule(new AutoMapperModule());
}