Introducing Dependency Injection
In typical development, a project starts with an entry point (an executable, a default.aspx page, and so on). You might develop your application as one giant project, but in most cases some level of modularity exists in that your application loads a number of assemblies that are part of the project. The main assembly knows what assemblies it needs and creates hard references to those pieces. At compile time, the main project knows about all the referenced assemblies, and the user interface consists of static controls. The application is in control of what code it needs and usually knows all the code it might use. This becomes a problem, however, because development takes place inside the main application project. As a monolithic application grows, build time and conflicting changes can slow down development.
Dependency injection aims to reverse this situation by providing instructions that set up dependencies at run time. Instead of the project controlling these dependencies, a piece of code called a container is responsible for injecting them.
But why is this important? For one thing, modularizing your code should make it easier to test. Being able to swap out a project's dependencies enables cleaner testing so that only the code to be tested can be the source of a test failing, instead of code somewhere in the nested chain of dependencies. Here's a concrete example. Imagine you have a component that other developers use to look up addresses for particular companies. Your component depends on a data access component that retrieves the data for you. When you test your component, you start by testing it against the database, and some of the tests fail. But because the schema and builds of the database are constantly changing, you don't know whether your tests are failing because of your own code or the data access code. With your component's hard dependency on the data access component, testing the application becomes unreliable and causes churn while you track down failures in your code or in other's code.
Your component might look something like this:
public class AddressComponent
{
DataAccessComponent data = new DataAccessComponent();
public AddressComponent()
{
}
...
}
Instead of a hard-wired component, you could accept an interface that represents your data access, as shown here:
public interface IDataAccess
{
...
}
public class AddressComponent
{
IDataAccess data;
public AddressComponent(IDataAccess da)
{
data = da;
}
...
}
Ordinarily, an interface is used so you can create a version that allows you to adjust your code. This approach is often called "mocking." Mocking means creating an implementation of the dependency that does not actually represent the real version. Literally, you're creating a mock implementation.
This approach is better because the dependency (IDataAccess) can be injected into the project during construction of the object. The implementation of the IDataAccess component will depend on the requirements (testing or real).
That's essentially how dependency injection works, but how is the injection handled? The job of the container is to handle creation of the types, which it does by allowing you to register types and then resolving them. For example, assume you have a concrete class that implements the IDataAccess interface. During start up of the application, you can tell the container to register the type. Anywhere else in your application where you need the type, you can ask the container to resolve the type, as shown here:
public void App_Startup()
{
container.RegisterType();
}
...
public void GetData()
{
IDataAccess acc = container.Resolve();
}
Depending on the situation (testing or production), you can swap out the implementation of IDataAccess simply by changing the registration. Additionally, the container can handle construction injection of dependencies. If an object that needs to be created by the container's constructor takes an interface that the container can resolve, it resolves the type and passes it to the constructor, as shown in
Figure 3.
public class AddressComponent : IAddressComponent
{
IDataAccess data;
public AddressComponent(IDataAccess da)
{
data = da;
}
}
...
public void App_Startup()
{
container.RegisterType();
container.RegisterType();
}
public void GetAddresses()
{
// When we ask the container to create the AddressComponent,
// it sees that a constructor takes a IDataAccess object
// so it automatically resolves that dependency
IAddressComponent addr = container.Resolve();
}
Notice that the AddressComponent's constructor takes an implementation of IDataAccess. When the constructor creates the AddressComponent class during resolution, it automatically creates the instance of IDataAccess and passes it to the AddressComponent.
When you register types with the container, you also tell the container to deal with the lifetime of the type in special ways. For example, if you are working with a logging component, you might want to treat it as a singleton so that every part of the application that needs logging does not get its own copy (which is the default behavior). To do this, you can supply an implementation of the abstract LifetimeManager class. Several lifetime managers are supported. ContainerControlledLifetimeManager is a singleton per process and PerThreadLifetimeManager is a singleton per thread. For ExternallyControlledLifetimeManager, the container holds a weak reference to the singleton. If the object is released externally, the container creates a new instance, otherwise it returns the live object contained in the weak reference.
You use the LifetimeManager class by specifying it when registering a type. Here's an example:
container.RegisterType( new ContainerControlledLifetimeManager());
In the CAL, the IoC container is based on the Unity framework from the patterns & practices group. I'll use the Unity container in the following examples, but there are also a number of open source alternatives to the Unity IoC container, such as Ninject, Spring.NET, Castle, and StructureMap. If you are familiar with and already using an IoC container other than Unity, you can supply your own container (although it takes a little more effort).
Startup Behavior
Ordinarily in a Silverlight application, the startup behavior is simply to create the main XAML page's class and assign it to the application's RootVisual property. In a composite application, this work is still required, but instead of creating the XAML page class, a composite application typically uses a bootstrapping class to handle startup behavior.
To start, you need a new class that derives from the UnityBootstrapper class. This class is in the Microsoft.Practices.Composite.UnityExtensions assembly. The bootstrapper contains overridable methods that handle different parts of startup behavior. Often, you will not override every startup method, only the ones necessary. The two methods you must override are CreateShell and GetModuleCatalog.
The CreateShell method is where the main XAML class is created. This is typically called the shell because it is the visual container for the application's components. My example includes a bootstrapper that creates a new instance of the Shell class and assigns it to RootVisual before returning this new Shell class, as shown here:
public class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
Shell theShell = new Shell();
App.Current.RootVisual = theShell;
return theShell;
}
protected override IModuleCatalog GetModuleCatalog()
{
...
}
}
The GetModuleCatalog method, which I'll explain in the next section, returns the list of modules to load.
Now that you have a bootstrapper class, you can use it in your Silverlight application's startup method. Usually, you create a new instance of the bootstrapper class and call its Run method, as shown in
Figure 4.
public partial class App : Application
{
public App()
{
this.Startup += this.Application_Startup;
this.Exit += this.Application_Exit;
this.UnhandledException += this.Application_UnhandledException;
InitializeComponent();
}
private void Application_Startup(object sender, StartupEventArgs e)
{
Bootstrapper boot = new Bootstrapper();
boot.Run();
}
...
}
The bootstrapper is also involved in registering types with the container that different parts of the application require. To accomplish this, you override the ConfigureContainer method of the bootstrapper. This gives you a chance to register any types that are going to be used by the rest of the application.
Figure 5 shows the code.
public class Bootstrapper : UnityBootstrapper
{
protected override void ConfigureContainer()
{
Container.RegisterType();
base.ConfigureContainer();
}
protected override DependencyObject CreateShell()
{
// Get the provider for the shell
IShellProvider shellProvider = Container.Resolve();
// Tell the provider to create the shell
UIElement theShell = shellProvider.CreateShell();
// Assign the shell to the root visual of our App
App.Current.RootVisual = theShell;
// Return the Shell
return theShell;
}
protected override IModuleCatalog GetModuleCatalog()
{
...
}
}
Here, the code registers an interface for a class that implements the IShellProvider interface, which is created in our example and is not part of the CAL framework. That way we can use it in our implementation of the CreateShell method. We can resolve the interface and then use it to create an instance of the shell so we can assign it to RootVisual and return it. This methodology may seem like extra work, but as you delve into how the CAL helps you build your application, it becomes clear how this bootstrapper is helping you.
EditModularity
In a typical .NET environment, the assembly is the main unit of work. This designation allows developers to work on their code separately from each other. In the CAL, each of these units of work is a module, and for the CAL to use a module, it needs a class that can communicate the module's startup behavior. This class also needs to supports the IModule interface. The IModule interface requires a single method called Initialize that allows the module to set itself up to be used in the rest of the application. The example includes a ServerLogger module that contains the logging capabilities for our application. The ServerLoggingModule class supports the IModule interface as shown here:
public class ServerLoggerModule : IModule
{
public void Initialize()
{
...
}
}
The problem is that we don't know what we want to initialize in our module. Since it's a ServerLogging module, it seems logical that we want to register a type that does logging for us. We want to use the container to register the type so that whoever needs the logging facility can simply use our implementation without knowing the exact type of logging it performs.
We get the container by creating a constructor that takes the IUnityContainer interface. If you remember the discussion of dependency injection, the container uses constructor injection to add types that it knows about. IUnityContainer represents the container in our application, so if we add that constructor, we can then save it and use it in our initialization like so:
public class ServerLoggerModule : IModule
{
IUnityContainer theContainer;
public ServerLoggerModule(IUnityContainer container)
{
theContainer = container;
}
public void Initialize()
{
theContainer.RegisterType(
new ContainerControlledLifetimeManager());
}
}
Once initialized, this module is responsible for the logging implementation for the application. But how does this module get loaded?
When using the CAL to compose an application, you need to create a ModuleCatalog that contains all the modules for the application. You create this catalog by overriding the bootstrapper's GetModuleCatalog call. In Silverlight, you can populate this catalog with code or with XAML.
With code, you create a new instance of the ModuleCatalog class and populate it with the modules. For example, look at this:
protected override IModuleCatalog GetModuleCatalog()
{
var logModule = new ModuleInfo()
{
ModuleName = "ServerLogger",
ModuleType = "ServerLogger.ServerLoggerModule, ServerLogger, Version = 1.0.0.0"
};
var catalog = new ModuleCatalog();
catalog.AddModule(logModule);
return catalog;
}
Here, I simply add a single module called ServerLogger, the type defined in the ModuleInfo's ModuleType property. In addition, you can specify dependencies between modules. Because some modules might depend on others, using dependencies helps the catalog know the order in which to bring in the dependencies. Using the ModuleInfo.DependsOn property, you can specify which named modules are required to load another module.
You can load the catalog directly from a XAML file, as shown here:
protected override IModuleCatalog GetModuleCatalog()
{
var catalog = ModuleCatalog.CreateFromXaml(new Uri("catalog.xaml",
UriKind.Relative));
return catalog;
}
The XAML file contains the same type information you can create with code. The benefit of using XAML is that you can change it on the fly. (Imagine retrieving the XAML file from a server or from another location based on which user logged on.) An example of a catalog.xaml file is shown in
Figure 6.