
Prism Developer's Guide - Part 3, Managing Dependencies Between Components
- Transfer
- Tutorial
Table of contents
Applications created using the Prism library are typically composite applications, potentially consisting of loosely coupled services and components. They should interact with each other so as to provide content to the user interface and receive notifications of user actions. Since they are loosely coupled, they need a way of interaction, without which the necessary functionality cannot be obtained.
To link all parts together, Prism applications rely on a DI container. DI containers reduce dependencies between objects by providing a way to instantiate classes and manage their lifetimes depending on the configuration of the container. When creating objects using a container, it injects the necessary dependencies into them. If dependencies have not yet been created, then the container at the beginning creates them and resolves their own dependencies. In some cases, the container itself is injected as a dependency. For example, when using Unity, a container is embedded in the modules so that they can register their views and services in it.
There are several advantages to using a container:
- The container eliminates the component’s need to locate its dependencies or to control their lifetime.
- The container allows you to replace implementations without affecting the components.
- The container facilitates testability, allowing you to embed fake dependencies in objects.
- The container simplifies maintenance by making it easy to add new components to the system.
In the context of an application based on the Prism library, there are certain advantages to using a container:
- The container resolves module dependencies when it is loaded.
- The container is used to register and create presentation models and views.
- A container can create view models and embed views.
- A container implements composite application services, such as a region manager, or an event aggregator.
- The container is used to register module-specific services with module-specific functionality.
Note
Some examples in the Prism manual use the Unity Application Block (Unity) container. Others, such as Modularity QuickStarts , use the Managed Extensibility Framework (MEF). The library of Prism itself is independent of the container used, and you can use its services and patterns with other containers, such as CastleWindsor, Autofac, Structuremap, Spring.NET, or any other.
Key decision: choosing a dependency injection container
The Prism library provides two default DI containers: Unity and MEF. Prism is extensible, so you can use other containers by writing a small amount of code to adapt them. Both Unity and MEF provide the same basic functionality needed to implement dependencies, even though they work very differently. Some of the features provided by both containers are:
- Both allow you to register types in a container.
- Both allow you to register instances in the container.
- Both allow you to force instances of registered types.
- Both inject instances of registered types into constructors.
- Both inject instances of registered types into properties.
- They both have declarative attributes for managing types and dependencies.
- They both resolve dependencies in the graph of objects.
Unity provides several features that are not in MEF:
- Allows specific types without registration.
- Allows open generalizations ( Generics ).
- May use method call interception to add additional functionality to the target ( Interception ).
MEF provides several features that are not available in Unity:
- It independently detects assemblies in the file system directory.
- Loads XAP files and looks for assemblies in them.
- Performs a recomposition of properties and collections when new types are discovered.
- Automatically exports derived types.
- Comes with the .NET Framework, starting with the fourth version.
Containers vary in capabilities and work differently, but the Prism library can work with any container, providing the same functionality. When considering which container to use, keep in mind your previous experience and determine which container is best suited for your application scripts.
Container Considerations
What to consider before using containers:
- Consider whether it is appropriate to register and resolve components using a container:
- Consider whether the performance impact when registering in the container and resolving instances is acceptable for your case. For example, if you have to create 10,000 polygons to draw something inside the draw method, then creating all the polygons through the container can lead to a significant performance loss.
The note.
Some containers are capable of resolving object instances almost as quickly as creating them through the new keyword. But, in any case, the resolution through the container of a large number of objects in the loop should be seriously justified. - If there are many deep dependencies, then the time required to resolve them can increase significantly.
- If a component has no dependencies or is itself not a dependency for other types, it may not make sense to use a container when creating it, or to put it in a container, respectively.
- If a component has a single set of dependencies that are an integral part of it and will never change, it may not make sense to use the container when creating it. Although, in this case, its testing can be complicated.
- Consider whether the performance impact when registering in the container and resolving instances is acceptable for your case. For example, if you have to create 10,000 polygons to draw something inside the draw method, then creating all the polygons through the container can lead to a significant performance loss.
- Consider whether the component should be registered as a singleton, or as an instance:
- If the component is a global service that acts as a single resource manager, such as a logging service, then you can register it as a singleton.
- If the component gives access to the general state to numerous consumers, then it can be registered as a singleton.
- If an object needs to create a new instance each time it is deployed, then it cannot be registered as a singleton. For example, each view probably needs a new instance of the view model.
- Consider whether you want to configure the container in code or through a configuration file:
- If you want to centrally manage all services, use the configuration file.
- If you want to register various services depending on any circumstances, configure the container in code.
- If you have module level services, configure the container through the code so that they are registered only when the module is loaded.
Note
Some containers, such as MEF, cannot be configured through the configuration file and must be configured in code.
Base scenarios
Containers are used for two main purposes, namely registration and permission.
registration
Before you can inject dependencies into an object, the types of dependencies must be registered in the container. Registering a type typically involves passing an interface to the container and the specific type that implements that interface. There are, first of all, two ways of registering types and objects: in code, or through a configuration file. Implementation details may vary depending on the container.
Typically, there are two ways to register types and objects in a container in code:
- You can register a type, or map one type to another. At the appropriate time, the container will create an instance of the type you specified.
- You can register an existing instance of an object as a singleton. The container will return a link to an existing object.
Registering types in UnityContainer
During initialization, a type may register other types, such as views and services. Registration allows you to resolve their dependencies by the container and become available to other types. To do this, you must embed the container in the module constructor. The following code shows how
OrderModule
from Commanding QuickStart registers the type of repository during initialization, like a singleton.public class OrderModule : IModule {
public void Initialize() {
this.container.RegisterType(new ContainerControlledLifetimeManager());
...
}
...
}
Depending on which container you are using, registration can also be done out of code through a configuration file. For an example, see “Registering Modules using a Configuration File” in Chapter 4, “ Modular Application Development .”
Registering types with an MEF container
To register types in a container, MEF uses an attribute-based system. As a result, it is quite easy to add type registration to the container: for this you need to add the attribute
[Export]
to the type that you want to register in the container, as shown in the following example.[Export(typeof(ILoggerFacade))]
public class CallbackLogger: ILoggerFacade {
...
}
Another use case for MEF might be to instantiate the class and register that particular instance in the container.
QuickStartBootstrapper
in Modularity for Silverlight with MEF, QuickStart shows an example of this in a method ConfigureContainer
.protected override void ConfigureContainer() {
base.ConfigureContainer();
// Поскольку мы создали CallbackLogger, и он должен использоваться сразу,
// мы проводим его композицию, чтобы удовлетворить любой импорт (зависимость), который он имеет.
this.Container.ComposeExportedValue(this.callbackLogger);
}
Note
When using MEF as a container, it is recommended that you use attributes for registering types.
Resolution
Once a type is registered, it can be resolved or implemented as a dependency. When a type is resolved, and the container must create a new instance of this type, it injects dependencies into this instance.
In general, when a type is resolved, one of three things happens:
- If the type has not been registered, the container throws an exception.
Note
Some containers, including Unity, allow you to allow a specific type that has not been registered. - If the type was registered as a singleton, the container returns an instance of the singleton. If this is the first call, the container can create an instance and save it for future calls.
- If the type has not been registered as a singleton, the container returns a new instance.
Note
By default, types registered in MEF are singleton, and the container stores references to objects. In Unity, by default, new instances of objects are returned, and the container does not store references to them.
Unity Resolution
The following code example of Commanding QuickStart shows how the representation
OrdersEditorView
and OrdersToolBar
resolved from the container to bind to their respective regions.public class OrderModule : IModule {
public void Initialize() {
this.container.RegisterType(new ContainerControlledLifetimeManager());
// Показываем представление Orders Editor в главном регионе оболочки.
this.regionManager.RegisterViewWithRegion("MainRegion",
() =>; this.container.Resolve());
// Показываем представление Orders Toolbar в регионе панели инструментов.
this.regionManager.RegisterViewWithRegion("GlobalCommandsRegion",
() => this.container.Resolve());
}
...
}
The designer
OrdersEditorPresentationModel
contains the following dependencies (order repository and proxy of the order command) that are introduced when it is resolved.public OrdersEditorPresentationModel(IOrdersRepository ordersRepository, OrdersCommandProxy commandProxy) {
this.ordersRepository = ordersRepository;
this.commandProxy = commandProxy;
// Создание фиктивных данных о заказе.
this.PopulateOrders();
// Инициализация CollectionView для основной коллекции заказов.
#if SILVERLIGHT
this.Orders = new PagedCollectionView( _orders );
#else
this.Orders = new ListCollectionView( _orders );
#endif
// Отслеживание текущего выбора.
this.Orders.CurrentChanged += SelectedOrderChanged;
this.Orders.MoveCurrentTo(null);
}
In addition to embedding in a constructor, as shown in the previous example, Unity can also embed dependencies in properties. Any properties to which the attribute is applied
[Dependency]
are automatically resolved and implemented, when the object is resolved. If the property is marked with an attribute OptionalDependency
, then if it is not possible to resolve the dependency, the property is assigned null
and an exception is not generated.Resolution of instances in MEF
The following code example shows how
Bootstrapper
in Modularity for Silverlight with MEF QuickStart gets an instance of a shell. Instead of requesting a specific type, the code could request an instance of the interface.protected override DependencyObject CreateShell() {
return this.Container.GetExportedValue();
}
In any class that allowed the MEF, you can also use injection in the constructor, as shown in the following code example from
ModuleA
in modularity for the Silverlight with the MEF the QuickStart , which are being introduced ILoggerFacade
and IModuleTracker
.[ImportingConstructor]
public ModuleA(ILoggerFacade logger, IModuleTracker moduleTracker) {
if (logger == null) {
throw new ArgumentNullException("logger");
}
if (moduleTracker == null) {
throw new ArgumentNullException("moduleTracker");
}
this.logger = logger;
this.moduleTracker = moduleTracker;
this.moduleTracker.RecordModuleConstructed(WellKnownModuleNames.ModuleA);
}
Alternatively, you can use property injection, as shown in the class
ModuleTracker
from Modularity for Silverlight with MEF QuickStart , which has an instance of it being deployed ILoggerFacade
.[Export(typeof(IModuleTracker))]
public class ModuleTracker : IModuleTracker {
// Из-за ограничений Silverlight/MEF, поле должно быть общедоступно.
[Import] public ILoggerFacade Logger;
}
Note
In Silverlight, imported properties and fields must be public.
Using Dependency Injection and Service Containers in Prism
Dependency injection containers are used to satisfy dependencies between components. Satisfying these dependencies typically involves registration and resolution. The Prism library provides support for Unity and MEF containers, but is independent of them. Since the library has access to the container through the interface
IServiceLocator
, the container can be easily replaced. To do this, you must implement an interface IServiceLocator
. Usually, if you replace the container, you will also need to write your own container-specific bootloader. The interface is IServiceLocator
defined in the Common Service Locator Library. This is an open source project for providing abstraction of IoC containers (Inversion of Control), such as dependency injection containers, and service locators. The purpose of using this library is to use IoC and Service Location, without providing a specific container implementation. The Prism library provides
UnityServiceLocatorAdapter
and MefServiceLocatorAdapter
. Both adapters implement the interface ISeviceLocator
, expanding the type ServiceLocatorImplBase
. The following illustration shows the class hierarchy. 
Although the Prism library does not reference or rely on a specific container, it is typical for an application to use a very specific DI container. This means that it is reasonable for the application to refer to a specific container, but the Prism library does not refer to the container directly. For example, an applicationStock Trader RI, and several of QuickStarts, use Unity as a container. Other examples and QuickStarts use MEF.
IServiceLocator
The following code shows the interface
IServiceLocator
and its methods.public interface IServiceLocator : IServiceProvider {
object GetInstance(Type serviceType);
object GetInstance(Type serviceType, string key);
IEnumerable