Microsoft Unity






4.66/5 (28 votes)
An introductory look at Microsoft's Unity.
Introduction
This article provides an introductory look at Unity - Microsoft's Dependency Injection container.
What is Dependency Injection? And Why Is It A Good Idea?
Consider the classic situation of a business object that needs to be able to persist itself to a database. What is the best way to organise the code so that the business object can access the database?
The possibilities include:
- Hard code a connection string into the business object.
- Place the connection string in a configuration file and hard code the location of the configuration file into the object.
- Pass a locator interface to the business object in its constructor or via a property setter. The business objects asked the locator for access to the database.
- Pass a database interface to the business object in its constructor or via a property setter.
The first two options put hard-coded configuration information into the business object so are not recommended.
The third option is an example of the service locator pattern. If the business object uses the default service, the business object no longer contains any hard-coded configuration information. However some people regard the service locator pattern as an anti-pattern since (i) the business object may request a specific service by name, in which case the business object must know about its environment, and (ii) the .NET service locator can provide multiple service types so the business object's dependency on the database is hidden; the dependency is not apparent by looking at the object's method signatures - that makes refactoring difficult if one is not willing to trawl through the business object's source code.
The last option is an example of Dependency Injection. The database is a dependency of the business object, and the database is "injected" into the business object. Dependency Injection has the advantages that the business object is no longer concerned with any details about how to obtain a database object. Its sole purpose is to execute business logic. I.e. the design exhibits "Separation of Concerns" (components should do one thing) which is considered good programming practice. Also the business object's dependencies are declared in the constructor and so visible to the external world.
Where does the logic exist to create the database object and feed it to the business object? In a small application, the developer may simply craft some code that:
- looks up some configuration file for a connection string
- creates the database object, and
- creates the business object passing the database object to it in its constructor
The Unity framework replaces that hand crafted code with a configurable framework that uses declarative statements to define how objects should be created and initialized.
Inversion of Control
The developer's custom framework described in the previous section and Unity are both examples of the Inversion of Control (IoC) pattern. In both cases, the framework begins execution at start-up and business objects are instantiated by and used by the framework in response to events, i.e., the framework "controls" the business objects, which is the reverse of the "normal" flow of control.
Service Locator
A Unity container can be converted into a service locator by wrapping the container in a UnityServiceLocatorAdapter
. Despite its status as an anti-pattern amongst some commentators, Prism uses the IServiceLocator
interface so it is common to see code like the following in Prism/Unity applications.
IUnityContainer container;
...
container.RegisterInstance<IServiceLocator>(new UnityServiceLocatorAdapter(container));
Registering and Resolving Types
The following code snippet uses an IOptionPricer
interface obtained from the Unity framework to price an option.
The BinomialOptionPricer
object is defined and registered with the UnityContainer
so that whenever the container is asked for a IOptionPricer
interface, the Unity container creates and returns an instance of a BonomialOptionPricer
. (The IOptionPricer
interface is said to be resolved to the type BinomialOptionPricer
).
// This is the interface requested from Unity and used to price the option.
interface IOptionPricer
{
double Calculate(OptionType optionType, double S, double K, double T, double r, double vol);
}
// This is the object that will be actually returned by Unity
// (once registered) whenever an IOptionPricer is requested.
class BinomialOptionPricer : IOptionPricer
{
...
}
IUnityContainer container = new UnityContainer(); // Always use the IUnityContainer
// rather than the underlying container.
container.RegisterType<IOptionPricer, BinomialOptionPricer>(); // Registered
// the default implementation for IOptionPricer.
...
// Create a pricer () - request a IOptionPricer
IOptionPricer pricer = container.Resolve<IOptionPricer>(); // Returns an instance
// of BinomialOptionPricer.
// Use the pricer to price an option.
double PV = price->Calculate(OptionType.Call, S, K, T, r, vol);
Singletons
A Unity container can be configured using the IUnityContainer.RegisterInstance()
method so that the same object (a singleton) is returned whenever a request is made for a specific interface.
IOptionPricer pricer = new BinomialOptionPricer(); // This is always
//returned when an IOptionPricer is requested.
container.RegisterInstance<IOptionPricer>(pricer);
Smart Injection
The code snippet below instantiates a OptionPriceFrm
object. Note that the OptionPriceFrm
constructor takes two parameters and so requires valid IRateProvider
and IOptionPricer
interfaces. Traditional code would require that objects be created as parameters before the form can be created, as below.
public class OptionPricerFrm
{
public OptionPricerFrm(IRatesProvider provider,
IOptionPricer pricer) // constructor takes parameter
{
...
}
...
}
IRatesProvider provider = new RatesProvider(...); // create parameter 1.
IOptionPricer pricer = new OptionPricer(...); // create parameter 2.
var frm = new OptionPricerFrm(provider, pricer); // previously constructed objects passed to ctor.
Unity in contrast does not require us to provide parameter values for the constructor; it will recursively resolve any interfaces needed as parameters by the constructor. The Unity code to create the form would look like:
// Unity figures out what parameters are needed to pass to the ctor and creates them.
var frm = container.Resolve<OptionPricerFrm>();
The Unity version of the code is much easier to refactor. (For example, if the OptionPriceFrm
object was altered so that its constructor took an additional interface, none of the code to resolve IOptionPricer
interfaces would need to be changed).
Unity also provides facilities to override the constructor parameters values, and to initialise resolved objects using properties. The reader is referred to Unity documentation (and the source code) for further details.
Named Registration
The same interface may refer to different underlying object types in different parts of an application. For example, a trading application may use an IOptionPricer
interface to price options. A trader may want to use a non-standard model to hedge his positions since he feels that it more accurately reflects market prices, but use an approved model to calculate limits, charges and to report Profit and Loss to the Middle Office.
The following code shows two separate registrations for the IOptionPricer
interface; the parameter supplied to the RegisterType
method is the name of that registration.
IUnityContainer container = new UnityContainer(); // Always use the
// IUnityContainer rather than the underlying container.
container.RegisterType<IOptionPricer,
ApprovedOptionPricer>("std"); // Registered the approved option pricer.
container.RegisterType<IOptionPricer,
BinomialOptionPricer>("traders"); // Registered the non-standard binomial pricer.
The name of the registration to use to resolve the type is passed to the Resolve
method as a parameter, as below.
IUnityContainer container = new UnityContainer(); // Always use the
// IUnityContainer rather than the underlying container.
var approvedPricer = container.Resolve<IOptionPricer>
("std"); // Registered the approved option pricer.
var tradersPricer = container.Resolve<IOptionPricer>
("traders"); // Registered the non-standard binomial pricer.
If no names is specified, the registration is referred to as the default registration.
Note that the registration name can be changed dynamically, even if the resolved objects have constructors that take a different number of parameters.
Using the Unity Configuration File
If we rely on the previous coding approach and want to use a different implementation class for IOptionPricer
, we need to recompile the application.
A Unity container can be initialised from the app.config (or web.config) file. In the example below, the first part of the type
and mapTo
strings is the type name, the second part is the name of the assembly in which the object or interface resides. The section says there is a single Unity container named "main
" with two mappings from the OptionPricersInterfaces.IOptionPricer
interface - one to the QuantLib.OptionPricer
object in the QuantLib
assembly, and another to the TraderModels.BinomialOptionPricer
object in the TraderModels
assembly.
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<containers>
<container name="main">
<type name="Traders Screen"
type="OptionPricersInterfaces.IOptionPricer, OptionPricersInterfaces"
mapTo="TraderModels.BinomialOptionPricer, TradersModels"/>
<type name="Profit and Loss Report"
type="OptionPricersInterfaces.IOptionPricer, OptionPricersInterfaces"
mapTo="QuantLib.OptionPricer, QuantLib"/>
</container>
</containers>
</unity>
The code to initialise the Unity container from the configuration file is shown below:
IUnityContainer container = new UnityContainer();
// Use configuration file to register types.
UnityConfigurationSection configSection =
(UnityConfigurationSection)ConfigurationManager.GetSection("unity");
configSection.Configure(container, "main");
Modules
Microsoft provides a number of mechanisms to assist with the registration of interfaces and implementations including modules and the Unity bootstrapper. Modules are more low-level than the bootstrapper, which uses modules internally. Applications tend to use the UnityBootstrapper
, however it is useful to know the steps involved if using modules directly (shown below). Note that modules are part of Prism, not Unity.
// This is used by the ModuleManager so cannot be omitted.
container.RegisterInstance<IServiceLocator>(new UnityServiceLocatorAdapter(container));
// This is used by the ModuleManager so cannot be omitted.
TextLogger logger = new TextLogger(); // Default logger
container.RegisterInstance<ILoggerFacade>(logger);
//
// Setup the module catalog. Catalog specific logic goes here.
//
IModuleCatalog catalog =
new ModuleCatalog(); // Create the catalog - may be a derived type.
container.RegisterInstance<IModuleCatalog>
(catalog); // Singleton so IModuleManager can find it.
...
container.RegisterType<IModuleInitializer, ModuleInitializer>();
container.RegisterType<IModuleManager, ModuleManager>();
IModuleManager manager = container.Resolve<IModuleManager>();
manager.Run();
In every case, a ModuleCatalog
or derived object must be setup so that it will be populated with ModuleInfo
. There are several ModuleCatalog
derived classes including ConfigurationModuleCatalog
which populates the catalog from a configuration file, and DirectoryModuleCatalog
which populates the catalog by scanning a directory for modules. Custom ModuleCatalog
objects (which implement IModuleCatalog
) are also possible. Once the catalog has been setup, the ModuleManager.Run
loads and registers the specified types with Unity.
DirectoryModuleCatalog
The DirectoryModuleCatalog
specifies a directory to scan at startup. The ModuleManager
(reflection-only) loads any assemblies it finds and checks for objects that implement IModule
. If one is found, an instance of the class (Registration
in the example below) is created. The constructor (or IUnityContainer
property) will be passed a IUnityContainer
interface since it is needed to do the registration. Once the object is created, the IModule.Initialise()
method is called which registers the interfaces and implementations with Unity.
public class Registration : IModule
{
IUnityContainer container = null;
public Registration(IUnityContainer container)
{
this.container = container;
}
public void Initialize()
{
container.RegisterType<IOptionPricer, OptionPricer>("std");
container.RegisterType<IOptionPricer,
BinomialTreeOptionPricer>("binomial");
container.RegisterType<IRatesProvider, RatesProvider>("std");
}
}
ConfigurationModuleCatalog
The ConfigurationModuleCatalog
which initialises Unity from the application (or web) configuration file is particularly simple to setup.
ConfigurationModuleCatalog catalog = new ConfigurationModuleCatalog();
container.RegisterInstance<imodulecatalog>(catalog);
UnityBootstrapper
Microsoft provides the UnityBootstrapper
class to simplify the coding of Prism applications that use Unity. It does a lot more than just initialise the Unity IoC container. The author thinks PrismBootstrapperForUnity
would be a better name - the following processing occurs in the UnityBootstrapper.Run
method:
- Creates a
ILoggerFacade
implementation. - Creates a
ModuleCatalog
viaCreateModuleCatalog
. - Creates a Unity container.
- Configures the default region adapter mappings.
- Configures default region behaviours
- Registers Prism framework exceptions.
- Creates the shell via
CreateShell
. - Initialises the shell.
- Initialise the modules and registers interfaces and implementations with Unity.
The UnityBootstrapper
is typically used by overloading the CreateModuleCatalog()
and CreateShell()
methods.
class OptionPricerBootstrapper : UnityBootstrapper
{
protected override IModuleCatalog CreateModuleCatalog() // previously GetModuleCatalog()
{
string path = @".\Modules";
if (!Directory.Exists(path))
throw new Exception(path + " does not exist.");
return (new DirectoryModuleCatalog() { ModulePath = path });
}
protected override System.Windows.DependencyObject CreateShell()
{
MainWindow wnd = new MainWindow(Container);
wnd.Show();
return wnd;
}
}
Bootstrapper().Run();
The CreateModuleCatalog()
is typically used to create a DirectoryModuleCatalog
that points to the location of the self-registering modules (as above), but could just as easily return a ConfigurationModuleCatalog
or custom ModuleCatalog
.
The CreateShell()
method expects the shell (main window) is created and returned by this method (the StartupUri
in App.xml should be removed). Please refer to Prism documentation for the role of the shell in Prism (composite) applications.
Sample Code
The sample code consists of several projects:
OptionPricerInterfaces
- This contains definitions of theIOptionPricer
,IRateProvider
andIOptionPricer
interfaces used by all the other projects.OptionPricers
- Contains implementations forIOptionPricer
,IRateProvider
andIOptionPricer
used by all the applications.BareBones
- A very simple Unity application to demonstate the basic idea of registration and type resolution. It is a command line application.BareBones2
- Demonstrates some slightly more complex examples of the use of Unity. Also a command line application.OptionPricerApp
- A Winforms application that displays an option pricing dialog and registers and instantiates theOptionPricer
object using Unity in code.OptionPricerApp2
- A Winforms application that displays an option pricing dialog and uses Unity to resolveOptionPricerFrm
,OptionPricer
andRatesProvider
objects. TheOptionPricer
interfaces/implementations are registered using a configuration file.OptionPricerApp3
- A Winforms application that displays an option pricing dialog and uses aDirectoryModuleCatalog
to initialise the Unity container by scanning a directory for modules.OptionPricerApp4
- A WPF application that uses the PrismUnityBootstrapper
.
Potential Issues with Unity
Large configuration files can become cumbersome to maintain - it can become difficult to spot errors. (The author has wasted quite a few hours this way). It is always very tempting to overuse "new" technology. There is no reason not to use Unity throughout your application, but it may be better to only move registration declarations to a configuration file if there is a real likelihood that the registered types will change. The author is aware that painful release procedures and internal IT development policies in large corporation may mean that advice is not realistic.
It may makes sense that each Use Case should have its own named interface registrations to avoid the situation where a change to one part of an application results in unexpected changes elsewhere.
Conclusion
Unity is a great, lightweight container that supports Dependency Injection. There is no obvious reason not to use it no matter what technology you are using, whether it is WPF, Winforms or something else.
History
No updates yet.