The previous post introduced dependency injection (DI). This post will provide a practical approach to wire up DI cleanly.
The problem is where do we keep our dependency injection registration bootstrap/wiring code?
- We don’t want a giant central registration assembly or a monolithic global.asax class
- We don’t want to bleed DI into our implementation assemblies that requires DI references
- We don’t want to be locked into a specific DI framework
The solution is to componentise registrations and keep DI frameworks out of our implementation and interface libraries.
Download Source Code
What is Unity?
Unity is a DI container which facilitates building loosely coupled apps. It provides features such as object lifecycle management, registration by convention and instance/type interception.
What is MEF?
The Managed Extensibility Framework (MEF) provides DI container capabilities to build loosely coupled apps. It allows an app to discover dependencies implicitly with no configuration required by declaratively specifying the dependencies (known as imports) and what capabilities (known as exports) that are available.
MEF vs Unity
MEF and Unity provide similar capabilities but they both have strengths and weaknesses.
- Unity has greater DI capabilities – such as interception
- Unity is less invasive since MEF requires developers to sprinkle [Import] and [Export] attributes all throughout the code
- MEF has great discoverability features that are not available in Unity
The Winner: MEF for discovery + Unity for DI
Setup
The solution layout of the previous DI introduction post is shown below.
All of the bootstrapping code currently lives in the OrderApplication console assembly. The registration can quickly get out of hand with a large app with many components. The registration is also not discoverable which means we can’t just drop in new dlls to automatically replace existing functionality or add new functionality.
Clean Wiring
Let’s get started with clean wiring in 3 steps.
1. Move each components’ DI registration to its own assembly
// Orders.Bootstrap.dll
public class Component
{
private readonly IUnityContainer _container;
public Component(IUnityContainer container)
{
if (container == null) throw new ArgumentNullException("container");
_container = container;;
}
public void Register()
{
_container.RegisterType<IOrderRepository, OrderRepository>();
_container.RegisterType<IEmailService, EmailService>();
_container.RegisterType<IRenderingService, RazaorRenderingService>();
_container.RegisterType<IMailClient, SmtpMailClient>();
_container.RegisterType<IOrderService, OrderService>();
var baseTemplatePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
"EmailTemplates");
_container.RegisterType<ITemplateLocator, TemplateLocator>(
new InjectionConstructor(baseTemplatePath));
}
}
Solved! The Orders.Implementation and Orders.Interfaces assemblies are no longer coupled to a specific DI framework since it doesn’t know about DI.
2. Discover and bootstrap your registration
MEF is great at discovering assemblies so let’s create an interface that can be discovered by exporting the bootstrap interface.
// Bootstrap.Interfaces.dll
public interface IBootstrap
{
void Register();
}
Add the MEF export attribute to the bootstrap component created in step 1.
[Export(typeof(IBootstrap))]
public class Component : IBootstrap
{
private readonly IUnityContainer _container;
[ImportingConstructor]
public Component(IUnityContainer container)
{
if (container == null) throw new ArgumentNullException("container");
_container = container;;
}
public void Register()
{
// Registration code from step 1
}
}
The ResolvingFactory below will perform the discovery and Unity registration.
// Bootstrap.Implementation.dll
public class ResolvingFactory
{
[ImportMany(typeof(IBootstrap))]
private IEnumerable<IBootstrap> Bootstraps { get; set; }
private readonly IUnityContainer _container;
public ResolvingFactory()
{
_container = new UnityContainer();
Initialise();
}
private void Initialise()
{
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(GetBinPath()));
using (var mefContainer = new CompositionContainer(catalog))
{
mefContainer.ComposeExportedValue(_container);
mefContainer.SatisfyImportsOnce(this);
}
foreach (var bootstrap in Bootstraps)
{
bootstrap.Register();
}
}
public string GetBinPath()
{
var domainSetup = AppDomain.CurrentDomain.SetupInformation;
return !string.IsNullOrEmpty(domainSetup.PrivateBinPath) ?
domainSetup.PrivateBinPath : domainSetup.ApplicationBase;
}
public T Resolve<T>()
{
return _container.Resolve<T>();
}
}
The new project assembly layout is shown below.
3. Run the solution
Let’s run the new Unity + MEF solution.
// OrderApplication.dll
static void Main(string[] args)
{
var orderModel = new OrderModel()
{
Description = "Design Book",
Customer = new CustomerModel()
{
Email = "customer@yoursite.com",
Name = "Jay"
}
};
var factory = new ResolvingFactory();
var orderService = factory.Resolve<IOrderService>();
orderService.Create(orderModel);
}
Solved! Simply drop a new bootstrap dll in the app directory and it will be registered automatically.
Summary
DI code often becomes messy with large amounts of registrations.
This post shows how DI registration can be componentised to keep code clean, simple and discoverable.