GuidesDependency Injection Best Practices in an N-tier Modular Application

Dependency Injection Best Practices in an N-tier Modular Application

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Introduction

Microsoft Unity is one of the most popular tools to implement Dependency Injection (DI).

Note: If you are not familiar with Inversion of Control  (IoC) and DI, you can find more detail about them on Martin Flower’s blog here Dependency Injection. I don’t believe IoC is better explained anywhere else.

In a typical modern ASP.NET MVC Web Application, you will find a three tier layered architecture with units of isolation dependent on each other as illustrated in Figure 1.

A modern web three tier application
Figure 1: A modern web three tier application

A DI framework allows you to inject the dependencies, and in our web application to be precise, it allows you to inject the Data Layer contracts in Business Layer and the Business Layer contracts in presentation without having to create concrete instances of the implementation.

Problem

The DI container creates a dependency graph (Composite Root) between various registered objects. It requires that we register our concrete implementations with the DI. The host applications like ASP.Net MVC or WCF should refer to all the assemblies so that DI container can register all the dependencies during the application start phase. It means the host application will have direct access to various implementations of the business or data layer. This leads to few major issues as it is not only violating the architectural rules of layer separation but also it can lead to bad coding practices when a developer starts consuming the data layer or entity models directly in the presentation layer. This blurred layer separation can also make it hard for a compiler to check the architectural rules at compile time.

Further if the application lacks any capacity for discovering components on its own, then it must be explicitly told which components are available and should be loaded. This is typically accomplished by explicitly registering the available components in code or a configuration file. This can become a maintenance issue.

Let’s discuss this in detail with an example:-

Implementation details of Business and Data access layers
Figure 2: Implementation details of Business and Data access layers

In Figure 2, the home controller in the presentation layer depends on IUserDomain (a business layer interface) whose implementation UserDomain is internal to the business layer assembly. The UserDomain depends on IUserData whose implementation UserData is again internal to data access assembly.

The DI container is in the ASP.Net MVC application and it is in charge of instantiating all of the registered types but in order to do so it needs access to all internal concrete implementations of IUserDomain or IUserData. This would mean we need to make those implementations public and reference all of the assemblies in the host application.

Let’s Dive into the Code

I have created a sample ASP.Net MVC 4 application that uses Unity.Mvc4 to implement unity container and dependency resolver.

Visual Studio Solution design:

Sample ASP.Net MVC 4 Application
Figure 3: Sample ASP.Net MVC 4 Application

Common: UnityDemo.Common. It will have all the shared resources.

Domain: It is collection of modules. Each domain or module is fully capable of handling any business function. So it should know its data access layer (shared among domains or independent). Security in this case is one of the domain or module.

UnityDemo.Security: A business layer for security module or domain.

UnityDemo.Security.Data: Data access layer for security domain.

Web: It has ASP.Net MVC 4 application

Some Important Pieces of the Above Solution

IUserDomain: Business layer façade. The ‘Domain’ suffix means an independent business function/area. The entire business layer can be divided into multiple Domain layers, which may share or have their own data layer.

namespace UnityDemo.Common
{
public interface IUserDomain
{
IUser GetUser(int userId);
}
}

IUserData: Data layer façade.

namespace UnityDemo.Security.Data
{
public interface IUserData
{
User GetUser(int id);
}
}

UserData: Data layer façade concrete implementation.

namespace UnityDemo.Security.Data
{
public class UserData : IUserData
{
public User GetUser(int id)
{
//Just instantiate and pass..Ideally can come from a database thru EF
return new User
{
Id = id,
Name = "Manoj Kumar",
Age = 28
};
}
public IEnumerable<User> GetAllUsers()
{
return null;
}
}
}

UserDomain: Domain layer façade concrete implementation.

namespace UnityDemo.Security
{
using Data = UnityDemo.Security.Data;
public class UserDomain : IUserDomain
{
public readonly IUserData _data;
public UserDomain(IUserData data)
{
_data = data;
}
public IUser GetUser(int userId)
{
var user = _data.GetUser(userId);
return user.MapTo();
}
public IEnumerable<User> GetAllUsers()
{
return null;
}
}
}

HomeController: The default controller in ASP.Net MVC application. It depends on IUserDomain.

public class HomeController : Controller
{
private readonly IUserDomain _domain;
public HomeController(IUserDomain domain)
{
_domain = domain;
}
}

Bootstrapper: Bootstrapper registers all the concrete implementations in the web application.The Bootstrapper.Initialise() will be called from Application_Start () in Global.asax.

namespace UnityDemo.Web
{
public static class Bootstrapper
{
public static IUnityContainer Initialise()
{
var container = BuildUnityContainer();
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
return container;
}
private static IUnityContainer BuildUnityContainer()
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
}
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterType<IUserData, UserData>();
container.RegisterType<IUserDomain, UserDomain>();
}
}
}

Drawbacks with this Design:

Consequences of this direct access of concrete business and data layer types while registering to unity container in Bootstrapper:

container.RegisterType<IUserData, UserData>();

container.RegisterType<IUserDomain, UserDomain>();

1. There is no clear separation of layers. We can’t have truly independent modules.

2. The public implementations can be misused in the presentation layer (HomeController):

	public ActionResult UsersEdit()
	{
	//Started accessing domain directly
	var userDomain = new UserDomain(new UserData());
	var users = userDomain.GetAllUsers();
	//Or even bad: accessing data layer
	var data = new UserData();
	var users2 = data.GetAllUsers();
	//Or even worse: Using entity models instance directly
	using (var model = new SecurityModelContainer())
	{
	var users3 = from u in model.Users select u;
	}
	return View(users);
}

3. Direct references of business and data layer assemblies in the presentation layer to register concrete public types:

Ref in Web Problem
Figure 4: Ref in Web Problem

Let us now explore an approach to make this better.

Solution

If you are not using a DI container, you won’t need to reference data access assemblies of any module which might also contain Entity Framework (EF) models in an MVC application. You could have just referenced business layer assemblies. But this would mean that you lose some of the benefits of decoupling. We know that at the end of the day all assemblies would be included in the bin folder of MVC application, but the problem is the accessibility of concrete implementation of data or business implementations in the presentation layer. Let’s dive into a technique where we can solve this problem by keeping concrete implementations internal while still benefiting from DI.

We can leverage the power of MEF. The Managed Extensibility Framework or MEF is a library for creating lightweight, extensible applications. It allows implicit discovering of extensions via composition without any configuration. An MEF component, called a part, declaratively specifies both its dependencies (known as imports) and what capabilities (known as exports) it makes available. When a part is created, the MEF composition engine satisfies its imports with what is available from other parts.

This approach solves the problems of hard-coding the references or configuring them in a fragile config file. MEF allows applications to discover and examine parts by their metadata, without instantiating them or even loading their assemblies. As a result, there is no need to carefully specify when and how extensions should be loaded. For more detail on MEF read Managed Extensibility Framework. MEF is an integral part of the .NET Framework 4.

Let’s see how to implement MEF along with Unity to solve this problem:

We can define an interface IModule and its implementation ModuleInit in all the business or data layer assemblies. This interface will help to identify ModuleInit, and ModuleInit class will be responsible to register all internal implementations against defined interfaces. Let’s dive straight into the code:

We will need reference of System.ComponentModel.Composition assembly to implement MEF.

Define IModuleRegistrar interface (A wrapper around IUnityContainer for registration). Allows objects implementing IModule to register types in unity.

namespace UnityDemo.Common
{
public interface IModuleRegistrar
{
void RegisterType<TFrom, TTo>() where TTo : TFrom;
}
}

Define IModule interface: To identify modules/domain using reflection.

namespace UnityDemo.Common
{
public interface IModule
{
void Initialize(IModuleRegistrar registrar);
}
}

A wrapper to register all the internal type with unity:

internal class ModuleRegistrar : IModuleRegistrar
{
private readonly IUnityContainer _container;
public ModuleRegistrar(IUnityContainer container)
{
this._container = container; //Register interception behaviour if any
}
public void RegisterType<TFrom, TTo>() where TTo : TFrom
{
this._container.RegisterType<TFrom, TTo>();
}
}

Create ModuleInit Classes in domain and data (wherever we need to register types with DI container) and Export typeof IModule [MEF implementation]thru attributes:

To Register all the Internal types of data layer – UserData:

namespace UnityDemo.Security.Data
{
[Export(typeof(IModule))]
public class ModuleInit : IModule
{
public void Initialize(IModuleRegistrar registrar)
{
registrar.RegisterType<IUserData, UserData>();
}
}
}

To Register all the Internal types of business layer/Domain:

namespace UnityDemo.Security
{
[Export(typeof(IModule))]
public class ModuleInit : IModule
{
public void Initialize(IModuleRegistrar registrar)
{
registrar.RegisterType<IUserDomain, UserDomain>();
}
}
}

To invoke registration: Identify the ModuleInit classes and call Initialize:

For this, we can define a static class ModuleLoader in Common project and call it from Bootstrapper of the host application. This class will have a method called LoadContainer, which will find all the MEF parts using import definitions. All the ModuleInit classes have export defined for the type IModule. In this way we can find all the modules and register them by calling the Initialize() method of ModuleInit classes.

Find all ModuleInit and call initialize to register types in various modules [MEF implementation]:

namespace UnityDemo.Common
{
public static class ModuleLoader
{
public static void LoadContainer(IUnityContainer container, string path, string pattern)
{
var dirCat = new DirectoryCatalog(path, pattern);
var importDef = BuildImportDefinition();
try
{
using (var aggregateCatalog = new AggregateCatalog())
{
aggregateCatalog.Catalogs.Add(dirCat);
using (var componsitionContainer = new CompositionContainer(aggregateCatalog))
{
IEnumerable<Export> exports = componsitionContainer.GetExports(importDef);
IEnumerable<IModule> modules =
exports.Select(export => export.Value as IModule).Where(m => m != null);
var registrar = new ModuleRegistrar(container);
foreach (IModule module in modules)
{
module.Initialize(registrar);
}
}
}
}
catch (ReflectionTypeLoadException typeLoadException)
{
var builder = new StringBuilder();
foreach (Exception loaderException in typeLoadException.LoaderExceptions)
{
builder.AppendFormat("{0}n", loaderException.Message);
}
throw new TypeLoadException(builder.ToString(), typeLoadException);
}
}
private static ImportDefinition BuildImportDefinition()
{
return new ImportDefinition(
def => true, typeof(IModule).FullName, ImportCardinality.ZeroOrMore, false, false);
}
}

Call LoadContainer() of ModuleLoader from Bootstrapper in host application by passing assemblies path and file pattern (mostly project name’s prefix and .dll as suffix):

Register all the internal type with unity by invoking ModuleLoader:

public static void RegisterTypes(IUnityContainer container)
{
//Comment the old way
//container.RegisterType<IUserData, UserData>();
//container.RegisterType<IUserDomain, UserDomain>();
//Module initialization thru MEF
ModuleLoader.LoadContainer(container, ".bin", "UnityDemo.*.dll");
}

Advantage/Benefits

1. Automatic registration of type exposed by any module. If we need to change implementation of any interface consumed by MVC Web application or a business layer, we can just define a new assembly and add to the bin folder of the Web application.

2. This application can easily be extended by combining various modules, which can be registered to Unity by MEF extensibility support during runtime.

3. All implementations of module or data layer remain internal to respective assemblies. So, it can’t be misused (See the breaking code due to absence of domain and data): The concrete types are not accessible in controller now.

public ActionResult UsersEdit()
{
//Started accessing domain directly (Can't access now)
var userDomain = new UserDomain(new UserData());
var users = userDomain.GetAllUsers();
//Or even bad: accessing data layer (Can't access now)
var data = new UserData();
var users2 = data.GetAllUsers();
//Or even worse: Using entity models instance in UI layer (Can't access now)
using (var model = new SecurityModelContainer())
{
var users3 = from u in model.Users select u;
}
return View(users);
}

4. No reference of Data layer in MVC application: The business layer (UnityDemo.Security) is referenced only because we need all the assemblies in bin folder of MVC app, but it won’t harm as we have all implementations in business layer internal to that assembly.

Ref in Web Solved
Figure 5: Ref in Web Solved

 

Source Code

I have developed this sample application; you can download it here. You can also find it on github. This application is in ASP.Net MVC and demonstrates the technique discussed above. The source code is not perfect in all sense as its primary focus is to demo this MEF based unity container initialization.

Github source code location: https://github.com/manoj-kumar1/DI-Best-Practices-in-N-tier

To run this application, you would need Visual Studio 2010 with MVC 4 installed. Once you open the solution you can go to Manage Nuget Package on UnityDemo.Web project and click following button to restore the missing packages.

Restore Package
Figure 5: Restore Package

Conclusion

In this article, you have learned a different technique to configure DI (Dependency Injection) using unity container and MEF. This technique solves a couple of problems. First, it helps to keep the business or data layer implementations internal to respective assemblies and second, the architectural rules are not violated while utilizing the benefits of a DI container. This leads to much cleaner modular application which can be extended quite easily.

About the Author:

Manoj Kumar is a Principal Consultant with Neudesic with significant experience of 9 years in designing and developing enterprise scale solutions. He poses sound knowledge of SDLC, software architecture and design practices such as ATAM, architecture analysis, SOA/SOD, design patterns, distributed architecture, cloud computing, ESB like Neuron/BizTalk, and OOP/OOAD. He keeps special interest in technologies and looks for better possibilities in software developments. He blogs regularly on his popular technical blog and has also published articles on popular technical sites like CodeProject. His screencasts published on YouTube are quite popular. Most recently he has been actively working on building apps and evangelizing development practices on Windows 8 and Windows Phone 8.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories