Posted on March 16, 2023
.Net Core also gives you a built-in dependency injection framework. It provides enough functionality, but there are certain disadvantages like resolving a service, lazy instantiation, etc.
Autofac is an addictive IoC container .NET. It manages the dependencies between classes so that applications stay easy to change as they grow in size and complexity. This is achieved by treating regular .NET classes as components.
What issue are we resolving? Let's see the sample code.
This code defines a RepositoryManager class that implements the IRepositoryManager interface. The RepositoryManager class has two private Lazy fields for IPingPongRepository and IUnitOfWork, respectively. The Lazy type allows for deferred initialization of the fields until they are actually accessed for the first time.
We already talked about Lazy initialization in another article Part 2 - Onion Architecture/Clean Architecture in aspnet Core
Did you see any issue with this code? Let's deep dive.
The problem with this code is that it tightly couples the RepositoryManager class with the concrete implementation of the PingPongRepository and UnitOfWork classes. This violates the principles of dependency inversion and makes it difficult to swap out the implementations of these classes with other implementations or mock objects during testing.
How to fix the problem above and leverage Lazy initialization by IOC implementation?
I'm trying to fix my own problem. Cheers!
The simple answer is using By using Autofac to manage the dependencies of the RepositoryManager class, we can take advantage of Autofac's powerful dependency injection capabilities, such as constructor injection, property injection, and more. This can make it easier to manage the dependencies of our application and promote more modular, testable code.
We already talked about Lazy initialization in another article Part 2 - Onion Architecture/Clean Architecture in aspnet Core
Configuring Autofac in .NET Core application
NuGet
PM> Install-Package Autofac
PM> Install-Package Autofac.Extensions.DependencyInjection
Add Autofac Modules i.e. PersistanceAutofacModule & PresentationAutofacModule
using Autofac;
using OnionDemo.Domain.Repositories;
using OnionDemo.Persistance.Repositories;
namespace OnionDemo.Presentation
{
public class PersistanceAutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<RepositoryManager>().As<IRepositoryManager>();
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>();
builder.RegisterType<PingPongRepository>().As<IPingPongRepository>();
//builder.Services.AddScoped<IRepositoryManager, RepositoryManager>();
// Other Lifetime
// Transient
//builder.RegisterType<PingPongRepository>().As<IPingPongRepository>()
// .InstancePerDependency();
//// Scoped
//builder.RegisterType<PingPongRepository>().As<IPingPongRepository>()
// .InstancePerLifetimeScope();
//builder.RegisterType<PingPongRepository>().As<IPingPongRepository>()
// .InstancePerRequest();
//// Singleton
//builder.RegisterType<PingPongRepository>().As<IPingPongRepository>()
// .SingleInstance();
// Scan an assembly for components
//builder.RegisterAssemblyTypes(typeof(AssemblyReference).Assembly)
// .Where(t => t.Name.EndsWith("Repository"))
// .AsImplementedInterfaces();
}
}
}
using Autofac;
using OnionDemo.Domain.Repositories;
using OnionDemo.Persistance.Repositories;
namespace OnionDemo.Presentation
{
public class PersistanceAutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<RepositoryManager>().As<IRepositoryManager>();
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>();
builder.RegisterType<PingPongRepository>().As<IPingPongRepository>();
//builder.Services.AddScoped<IRepositoryManager, RepositoryManager>();
// Other Lifetime
// Transient
//builder.RegisterType<PingPongRepository>().As<IPingPongRepository>()
// .InstancePerDependency();
//// Scoped
//builder.RegisterType<PingPongRepository>().As<IPingPongRepository>()
// .InstancePerLifetimeScope();
//builder.RegisterType<PingPongRepository>().As<IPingPongRepository>()
// .InstancePerRequest();
//// Singleton
//builder.RegisterType<PingPongRepository>().As<IPingPongRepository>()
// .SingleInstance();
// Scan an assembly for components
//builder.RegisterAssemblyTypes(typeof(AssemblyReference).Assembly)
// .Where(t => t.Name.EndsWith("Repository"))
// .AsImplementedInterfaces();
}
}
}
ASP.Net Core 6.0 Configuration (Program.cs)
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// Call ConfigureContainer on the Host sub property
builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
{
builder.RegisterModule(new PersistanceAutofacModule());
builder.RegisterModule(new PresentationAutofacModule());
});
Let's compare the new implementation of RepositoryManager.cs
namespace OnionDemo.Persistance.Repositories
{
public sealed class RepositoryManager : IRepositoryManager
{
private readonly IPingPongRepository _pingPongRepository;
private readonly IUnitOfWork _unitOfWork;
public RepositoryManager(IPingPongRepository pingPongRepository, IUnitOfWork unitOfWork)
{
_pingPongRepository = pingPongRepository;
_unitOfWork = unitOfWork;
}
public IPingPongRepository PingPongRepository => _pingPongRepository;
public IUnitOfWork UnitOfWork => _unitOfWork;
}
}
Here is the full sample code to download from Github
Here is recent commit change
What are the benefit of Autofac and the new way of code?
The main benefit of this implementation is that it promotes dependency injection and adheres to the SOLID principles, particularly the Dependency Inversion Principle (DIP) using Lazy initialization. By injecting the IPingPongRepository and IUnitOfWork dependencies into the RepositoryManager constructor, we can easily switch out the implementations of these dependencies at runtime without having to modify the RepositoryManager class itself.
This makes the code more modular, testable, and maintainable. For example, we could easily replace the IPingPongRepository implementation with a different implementation, such as a mock object for testing purposes, or a different implementation for a different database provider, without having to modify the RepositoryManager class.
Additionally, we can write more abstract code and less coupled it to specific implementation details by using interfaces instead of concrete types. This can make it easier to reason about and modify the code in the future.
Overall, this implementation promotes better software design practices and can lead to more maintainable and robust code.
Thanks for reading!
Posted on March 16, 2023
Anonymous User
April 16, 2024
thanks man, it was helpful
Anonymous User
October 9, 2023
thanks for sharing
Anonymous User
March 16, 2023
Great explanation