I've been reading a lot about IoC and DI and I still haven't understood if it's a bad practice to make your Application IoC agnostic, but seems only logical to me that it should be IoC agnostic.
Using a 4 layer approach I've coded a test project just to try and apply these concepts and this is what a came up with:
Data Layer with EF 6.1.3
UnitOfWork:
using TestProject.Persistence.Contracts;
namespace TestProject.Core
{
public class UnitOfWork : IUnitOfWork
{
private readonly IContext _context;
public IRepo<Customer> Customers { get; set; }
public IRepo<MembershipType> MembershipsTypes { get; set; }
public IRepo<Movie> Movies { get; set; }
public IRepo<Genre> Genres { get; set; }
public UnitOfWork(
IContext context,
IRepo<Customer> customers,
IRepo<MembershipType> membershipsTypes,
IRepo<Movie> movies,
IRepo<Genre> genres)
{
_context = context;
Customers = customers;
MembershipsTypes = membershipsTypes;
Movies = movies;
Genres = genres;
}
public int Complete()
{
return _context.SaveChanges();
}
public void Dispose()
{
_context.Dispose();
GC.SuppressFinalize(this);
}
}
}
Repo(generic):
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using TestProject.Core.Contracts;
using TestProject.Core.Contracts.Repos;
using TestProject.Persistence.Contracts;
namespace TestProject.Persistence.Repos
{
public class Repo<TEntity> : IRepo<TEntity> where TEntity : class
{
protected readonly DbSet<TEntity> _entities;
public Repo(IContext context) => _entities = context.Set<TEntity>();
public void Add(TEntity entity) => _entities.Add(entity);
public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
{
return _entities.Where(predicate).ToList();
}
public TEntity Get(int id) => _entities.Find(id);
public IEnumerable<TEntity> GetAll() => _entities.ToList();
public void Remove(TEntity entity) => _entities.Remove(entity);
public TEntity SingleOrDefault(Expression<Func<TEntity, bool>> predicate)
{
return _entities.SingleOrDefault(predicate);
}
}
}
Core Layer (with Interfaces to be used)
IUnitOfWork:
using System;
using TestProject.Core.Contracts.Repos;
using TestProject.Core.Models;
namespace TestProject.Core.Contracts
{
public interface IUnitOfWork : IDisposable
{
IRepo<Customer> Customers { get; set; }
IRepo<Movie> Movies { get; set; }
IRepo<MembershipType> MembershipsTypes { get; set; }
IRepo<Genre> Genres { get; set; }
int Complete();
}
}
IoC Layer(using Autofac)
Builder:
using Autofac;
using Autofac.Integration.Mvc;
using System.Reflection;
using System.Web.Mvc;
namespace TestProject.IoC
{
public class Builder : ContainerBuilder
{
public static void Register(Assembly assembly)
{
var builder = new Builder();
builder.RegisterControllers(assembly);
builder.RegisterModule<WebModule>();
DependencyResolver.SetResolver(new AutofacDependencyResolver(builder.Build()));
}
}
}
WebModule:
using Autofac;
using TestProject.Core;
using TestProject.Core.Contracts;
using TestProject.Core.Contracts.Repos;
using TestProject.Persistence.Contexts;
using TestProject.Persistence.Contracts;
using TestProject.Persistence.Repos;
namespace TestProject.IoC
{
public class WebModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>();
builder.RegisterGeneric(typeof(Repo<>)).As(typeof(IRepo<>));
builder.RegisterType<TestProjectWebContext>().As<IContext>();
base.Load(builder);
}
}
}
And finally MVC WebApp
Global.asax:
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using TestProject.IoC;
namespace TestProject.Web
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
Builder.Register(typeof(MvcApplication).Assembly);
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
}
I ran it and it works.
I had to do a little of work around in order to reference the MVC assembly in the IoC Layer but it is still but all the components are still registered at composition root.
Does it matter that the actual class that handles the registration is not in the MVC project?
What is the best approach?
1 Answer 1
Do not tie up too many classes with DI if you want code to loosely coupled with Container.
Instead, Why not have your own registration & resolver object. This way you will have to change only three classes (in given example) when you wish to change the DI container.
public interface IContainerRegister
{
void Register<TInterface, TService>() where TService : class, TInterface;
// not required, I needed though for WCF
IResolver GetResolver();
}
public interface IResolver
{
TService GetService<TService>();
}
internal class Resolver : IResolver
{
// change to your container choice
private readonly IUnityContainer _diContainer;
public Resolver(IUnityContainer DIContainer)
{
_diContainer = DIContainer;
}
public TService GetService<TService>()
{
return _diContainer.Resolve<TService>();
}
}
internal class ContainerRegister : IContainerRegister
{
// change to your container choice
private readonly IUnityContainer _diContainer;
public ContainerRegister(IUnityContainer diContainer)
{
_diContainer = diContainer;
}
public void Register<TInterface, TService>() where TService : class, TInterface
{
// change according to your container choice
_diContainer.RegisterType<TInterface, TService>();
}
public IResolver GetResolver()
{
// change according to your container choice
return _diContainer.Resolve<IResolver>();
}
}
internal class BootStrap
{
// change to your container choice
private UnityContainer _container = null;
public IContainerRegister Init()
{
_container = new UnityContainer();
_container.LoadConfiguration();
_container.RegisterInstance<IUnityContainer>(_container);
_container.RegisterType<IResolver, Resolver>();
_container.RegisterType<IContainerRegister, ContainerRegister>();
// register your modules here using reflection
// resolve each modules and call register()
return _container.Resolve<IContainerRegister>();
}
}
public abstract class BaseModule {
public BaseModule(IContainerRegister register){
_register = register;
}
public abstract void Register();
}
Keep all these classes in one assembly and call bootstrap from starting of the application, and inject register class when you need to use modules.
This way you can keep all your code indepndent of DI, and only one assembly needs to be changed.
You may need to expose more methods for different types of registrations and resolution.