In this article I will try to describe all about using unity in our .NET projects and thus how to implement IoC (inversion of control) and DI (Dependency Injection).
Description of the Objects
Going straight to a class library project (.NET) which will we follow through out this article. In the project we have a Book class which is considered as our model class, a
BookService
class which will be considered as business layer class and a
BookRepository
class which will be considered as a data access layer class. Each of this class has their interface
as IBook
, IBookService
, and IBookRepository
.
Fig:1 Objects in the container
Here in this project we have all objects those will be contained in a container (i.e. Unity in our case). There are lots of containers out there in the web but we have chosen Unity because it is a moderate container in all respect. Find more about other containers here.
Next we create another .net mvc3 website project as our view layer. We have to reference our class-library project from this project because we are going to create our container in this mvc3 website project. Well there are many ways to install the Unity container but I would prefer to install it from Package Manager Console. In the Package Manager Console just type PM> Install-Package Unity. It will install unity in your project and will automatically add reference for Microsoft.Practices.Unity , Microsoft.Practices.Unity.Configuration and other necessary packages. Up to here you are ready with Unity and now you will have to use the Unity container.
Unity container can be configured in two ways - run-time configuration and design-time configuration.
Run-time configuration
Run-time configuration is easy, just put the following code in the Application_Start()
function of the web application’s
global.asax.cs file.
MvcApplication._myContainer = new UnityContainer();
MvcApplication._myContainer.RegisterType();
MvcApplication._myContainer.RegisterType();
MvcApplication._myContainer.RegisterType();
After instantiating the UnityContainer
you just need to register all the model objects (Book with IBook), Business layer objects (BookService
with
IBookService
) and repository objects (BookRepository
with
IBookRepository
). After registration you can use them by calling the resolve function anywhere in your application like bellow.
IBook _b = _cc.Resolve();
There are some certain advanced functions for DI with constructor, property of interface. You can get more about here.
Design-time-configuration
Here in this article we will more concentrate with the design-time-configuration because design-time-configuration has certain advantages over run-time-configuration. For example you can change the DAL layer of SQL server to Oracle, just by changing a bit in the configuration file and without recompiling a single line of code.
To configure unity at design time – put the following code in the <configuration>
element of the web.config file.
<configSections>
<section name="unity"
type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
Microsoft.Practices.Unity.Configuration"/>
</configSections>
<unity configSource="unity.config"/>
With this configuration we actually relocate our main container configuration in a separate file unity.config. Now lets see how we have configured all the objects of Model, BAL and DAL layers in this file.
<?xml version="1.0" encoding="utf-8"?>
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<typeAliases>
<!-- Models-->
<typeAlias alias="IBook" type="BusinessBackend.IBook, BusinessBackend" />
<typeAlias alias="Book" type="BusinessBackend.Book, BusinessBackend" />
<!-- Services -->
<typeAlias alias="IBookService" type="BusinessBackend.IBookService, BusinessBackend" />
<typeAlias alias="BookService" type="BusinessBackend.BookService, BusinessBackend" />
<!-- Repositories -->
<typeAlias alias="IBookRepository" type="BusinessBackend.IBookRepository, BusinessBackend" />
<typeAlias alias="BookRepository" type="BusinessBackend.BookRepository, BusinessBackend" />
</typeAliases>
<container>
<register type="IBook" mapTo="Book" />
<register type="IBookRepository" mapTo="BookRepository" name="SQLrepo" />
<register type="IBookService" mapTo="BookService" >
<constructor>
<param name="br" dependencyName="SQLrepo">
<!--<param name="br" dependencyType="BookRepository">-->
<!--<dependency type="BookRepository" />-->
<!--<dependency name="SQLrepo" />-->
</param>
</constructor>
</register>
</container>
</unity>
If you look in detail of the unity.config file you will be able to see that; in
<typeAliases>
section I just have given a short hand name of a particular class.
<typeAlias alias="[short hand name]" type="[namespace].[class], [assembly name]" />
Then in the <container>
section I have registered all the objects with its interface. Optionally you can give a name of a registration to use it further in the configuration file.
Like I have registered BookRepository
with IBookRepository
and named it “SQLrepo”.
<register type="[interface]" mapTo="[class]" name="[name of the registration]" />
Notice our BookService
class which have a parameterized constructor which takes an object of type IBookRepository.
public class BookService : IBookService
{
IBookRepository BookRepo;
public BookService(IBookRepository br)
{
BookRepo = br;
}
public IBook getBookById(int id)
{
return BookRepo.getBookById(id);
}
}
This is how we inject dependency on BookRepository
object of the
BookService
object. This means while creating a BookService
object you will have to provide a
BookRepository
object referenced by IBookRepository
. Unity will do this for us, if we properly configure the constructor of BookService with the proper dependency. To configure the constructor add a <constructor> element in the registration of BookService.
Then set the parameters with <param>
element.
You can set the dependencyName
attribute of the <param>
element to any named registration.
<param name="[name of the param]" dependencyName="[name of a registration]">
Or set the dependencyType
attribute of the <param>
element to the object type upon which the constructor is depending on.
<param name="[name of the param]" dependencyType="[type of the param]">
Or you can put a <dependency>
in the <param>
element and set the name attribute or type attribute.
<dependency type="[type of the param]" />
<dependency name="[name of a registration]" />
Up to now we are done with the configuration. Now we need to create a unity container in the code from this configuration. We will create a container in the
Application_Start()
by the following code.
var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
IUnityContainer container = new UnityContainer().LoadConfiguration(section);
Using the container
The best place to use this container is in a Controller-Factory of a MVC3 web application. Here is my Controller-Factory class
MyControllerFactory
which is derived from DefaultControllerFactory
.
public class MyControllerFactory: DefaultControllerFactory
{
IUnityContainer _container;
public MyControllerFactory(IUnityContainer c)
{
_container = c;
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
throw new System.Web.HttpException(404, "Page not found: " + requestContext.HttpContext.Request.Path);
if (!typeof(IController).IsAssignableFrom(controllerType))
throw new System.ArgumentException("Type does not subclass IController", "controllerType");
object[] parameters = null;
ConstructorInfo constructor = controllerType.GetConstructors().FirstOrDefault(c => c.GetParameters().Length > 0);
if (constructor != null)
{
ParameterInfo[] parametersInfo = constructor.GetParameters();
parameters = new object[parametersInfo.Length];
for (int i = 0; i < parametersInfo.Length; i++)
{
ParameterInfo p = parametersInfo[i];
if (!_container.IsRegistered(p.ParameterType))
throw new ApplicationException("Can't instanciate controller '" +
controllerType.Name + "', one of its parameters is unknown to the IoC Container");
parameters[i] = _container.Resolve(p.ParameterType);
}
}
try
{
return (IController)Activator.CreateInstance(controllerType, parameters);
}
catch (Exception ex)
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture,
"error creating controller", controllerType), ex);
}
}
}
Observe the constructor MyControllerFactory(IUnityContainer c)
is taking the container and preserves it to a local variable. Then, while creation of a new controller this preserved container is used to spawn the appropriate service-level-object (BAL object) to the controller through parameterized-constructor.
So each controller takes a service-level-object (BAL object) as constructor parameter. For example see the
HomeController
takes IBookService
as constructor parameter. Then this controller consumes this service-level-objects to get the book information.
public class HomeController : Controller
{
IBookService _bookSrv;
public HomeController(IBookService bs)
{
_bookSrv = bs;
}
public ActionResult Index()
{
IBookService _bks = _bookSrv;
ViewBag.Message = _bks.getBookById(2).BookName;
return View();
}
}
Extending the container with another Repository
At this point we want to insert another repository class which will handle Oracle database for example. So we add another class OracleBookRepository
implementing the same interface
IBookRepository
in our class library project.
Then we will have to register this new class in our unity.config also.
="1.0"="utf-8"
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<typeAliases>
<typeAlias alias="IBook" type="BusinessBackend.IBook, BusinessBackend" />
<typeAlias alias="Book" type="BusinessBackend.Book, BusinessBackend" />
<typeAlias alias="IBookService" type="BusinessBackend.IBookService, BusinessBackend" />
<typeAlias alias="BookService" type="BusinessBackend.BookService, BusinessBackend" />
<typeAlias alias="IBookRepository" type="BusinessBackend.IBookRepository, BusinessBackend" />
<typeAlias alias="BookRepository" type="BusinessBackend.BookRepository, BusinessBackend" />
<typeAlias alias="OracleBookRepository" type="BusinessBackend.OracleBookRepository, BusinessBackend" />
</typeAliases>
<container>
<register type="IBook" mapTo="Book" />
<register type="IBookRepository" mapTo="BookRepository" name="SQLrepo" />
<register type="IBookRepository" mapTo="OracleBookRepository" name="ORACLErepo" />
<register type="IBookService" mapTo="BookService" >
<constructor>
<param name="br" dependencyName="ORACLErepo">
</param>
</constructor>
</register>
</container>
</unity>
First we will have to make a type alias for the class OracleBookRepository
. Then register the type of
OracleBookRepository
with IBookRepository
. Next to use this new DAL object
OracleBookRepository
in our application we will have to edit the constructor of the
BookService
object so that it depends on OracleBookRepository
instead of BookRepository
. Observe the bold texts in the
unity.config above. Now that the new object OracleBookRepository
is added in our container.
The fun part with this is – if you want to go back to use BookRepository
instead of
OracleBookRepository
just change back the constructor of BookRepository
in the configuration file and no recompile is needed. This is the sweet part of decoupling with DI.
This is how you can create a Unity container and use it in the .NET MVC web application. Thus you also have implemented IoC when you have introduced Unity in your custom controller factory. You also have implemented DI while injecting
BookRepository
in the BookService
class. Now there are less dependency between the container and the consumer and so you can easily test the controllers with custom service objects in a test project.
Comments are appreciated.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.