Applications based on the Prism Library are composite applications that potentially consist of many loosely coupled types and services. They need to interact to contribute content and receive notifications based on user actions. Because they are loosely coupled, they need a way to interact and communicate with one another to deliver the required business functionality. To tie together these various pieces, applications based on the Prism Library rely on a dependency injection container.
基于Prism库的应用程序是由多个松耦合的类型和服务组成。组件需要和用户动作产生的内容和通知交互。因为组件式松耦合的,组件需要和其他组件交互。要把这些连接起来,Prism使用了依赖注入容器。
Dependency injection containers reduce the dependency coupling between objects by providing a facility to instantiate instances of classes and manage their lifetime based on the configuration of the container. During the objects creation, the container injects any dependencies that the object requires into it. If those dependencies have not yet been created, the container creates and resolves their dependencies first. In some cases, the container itself is resolved as a dependency. For example, when using the Unity Application Block (Unity) as the container, modules have the container injected, so they can register their views and services with that container.
依赖注入容器减少对象间的耦合关系,容器通过配置管理对象的实例化,生命周期。对象创建期间,容器注入对象所需的依赖项。如果这些依赖性没被创建,容器就先创建和解析依赖项。一些情况下,容器本身作为一个依赖性被解析。例如,使用Unity作为容器,模块需要注入容器,这样模块才能注册其视图和服务到容器中。
There are several advantages of using a container:
使用容器的诸般好处:
- A container removes the need for a component to locate its dependencies or manage their lifetimes.
- A container allows swapping of implemented dependencies without affecting the component.
- A container facilitates testability by allowing dependencies to be mocked.
- A container increases maintainability by allowing new components to be easily added to the system.
In the context of an application based on the Prism Library, there are specific advantages to a container:
在基于上述Prism库的应用程序的上下文中,容器有一些特别的优势:
- A container injects module dependencies into the module when it is loaded.
- A container is used for registering and resolving view models and views.
- A container can create the view models and injects the view.
- A container injects the composition services, such as the region manager and the event aggregator.
- A container is used for registering module-specific services, which are services that have module-specific functionality.
Note: Some samples in the Prism guidance rely on the Unity Application Block (Unity) as the container. Other code samples, for example the Modularity QuickStarts, use Managed Extensibility Framework (MEF). The Prism Library itself is not container-specific, and you can use its services and patterns with other containers, such as Castle Windsor, StructureMap, and Spring.NET.
注意:一些示例使用的Untiy,还有一些示例使用的MEF。Prism本身不是必须需要某容器的。你可以用其他容器,例如Castle Windsor, StructureMap, 和Spring.NET。
Key Decision: Choosing a Dependency Injection Container 关键决定:选择一个依赖注入容器
The Prism Library provides two options for dependency injection containers: Unity or MEF. Prism is extensible, thereby allowing other containers to be used instead with a little bit of work. Both Unity and MEF provide the same basic functionality for dependency injection, even though they work very differently. Some of the capabilities provided by both containers include the following:
Prism提供了两个选择:Unity或是MEF。Prism是可扩展的,因此花费一点点工作量也可以使用其他容器。尽管工作方式不同,Untiy和MEF都提供了依赖注入的基本功能:
- They both register types with the container. 都可以利用容器注册类型
- They both register instances with the container. 都可以利用容器注册实例
- They both imperatively create instances of registered types. 都可以马上创建出注册的类型的实例。
- They both inject instances of registered types into constructors. 都可以注入注册类型的实例到构造器。
- They both inject instances of registered types into properties.都可以注入注册类型的实例到属性。
- They both have declarative attributes for marking types and dependencies that need to be managed.都需要声明特性以标记类型和需要管理的依赖项。
- They both resolve dependencies in an object graph. 都在一个对象图中解析依赖项。
Unity provides several capabilities that MEF does not:
Untiy独有功能:
- It resolves concrete types without registration. 可以解析没注册的类型。
- It resolves open generics. 可以解析开泛型。
- It uses interception to capture calls to objects and add additional functionality to the target object.可以利用截断类获取调用对象并添加额外的功能到调用对象上。
MEF provides several capabilities that Unity does not:
MEF独有功能:
- It discovers assemblies in a directory.在路径中发现程序集。
- It uses XAP file download and assembly discovery.使用XAP文件下载并发现程序。
- It recomposes properties and collections as new types are discovered.重新组合属性和集合作为新对象被发现。
- It automatically exports derived types.自动导出继承类型。
- It is deployed with the .NET Framework.它可以同 .NET Framework一同部署。
The containers have differences in capabilities and work differently, but the Prism Library will work with either container and provide similar functionality. When considering which container to use, keep in mind the preceding capabilities and determine which fits your scenario better.
容器拥有不同的功能和工作方式。但Prism库可以和它们一同工作并提供类似的功能。当考虑要用那个容器时,考虑它的能力适不适合你的方案。
Considerations for Using the Container 使用容器的考虑
You should consider the following before using containers:
在使用容器之前你该考虑以下内容:
- Consider whether it is appropriate to register and resolve components using the container:
- Consider whether the performance impact of registering with the container and resolving instances from it is acceptable in your scenario. For example, if you need to create 10,000 polygons to draw a surface within the local scope of a rendering method, the cost of resolving all of those polygon instances through the container might have a significant performance cost because of the container's use of reflection for creating each entity.
- If there are many or deep dependencies, the cost of creation can increase significantly.
- If the component does not have any dependencies or is not a dependency for other types, it may not make sense to put it in the container.
- If the component has a single set of dependencies that are integral to the type and will never change, it may not make sense to put it in the container.
- Consider whether a component's lifetime should be registered as a singleton or instance:
- If the component is a global service that acts as a resource manager for a single resource, such as a logging service, you may want to register it as a singleton.
- If the component provides shared state to multiple consumers, you may want to register it as a singleton.
- If the object that is being injected needs to have a new instance of it injected each time a dependent object needs one, register it as a non-singleton. For example, each view probably needs a new instance of a view model.
- Consider whether you want to configure the container through code or configuration:
- If you want to centrally manage all the different services, configure the container through configuration.
- If you want to conditionally register specific services, configure the container through code.
- If you have module-level services, consider configuring the container through code so that those services are registered only if the module is loaded.
- 考虑是否适合使用容器注册和解析组件:
- 考虑容器注册和解析的性能是否符合你的方案。例如,如果你需要在一个呈现方法里创建10000个多边形,解析多边形肯定严重影响性能,因为容器会为每个多边形创建一个实体。
- 如果依赖项太多,或是有深层次的依赖关系,创建对象的花销会明显提高
- 如果组件没有任何依赖项,也没必要使用容器。
- 如果组件有一个依赖项就集成在类型里并且不会有什么改变,也没必要将其弄进容器里。
- 考虑组件是否应该被注册成单例:
- 如果组件是一个全局服务,是一个单一资源的管理器,例如日志服务,你需要将其注册为单例。
- 如果组件提供共享状态给多个用户,你需要将其注册为单例。
- 如果对象每次都需要一个新实例作为依赖对象,那注册它为一个非单例。例如,每个视图都需要一个新实例的视图模型。
- 考虑你配置容器使用代码还是配置文件:
- 如果你想集中管理所有不同的服务,那么使用配置文件。
- 如果你想根据条件进行注册特别的服务,那么使用代码配置。
- 如果你有个模块级别的服务,考虑用代码配置,以便只有模块加载后才注册这个服务。
Note:注意 |
---|
Some containers, such as MEF, cannot be configured via a configuration file and must be configured via code. 例如MEF的容器不能陪在一个配置文件,必须使用代码进行配置。 |
Core Scenarios 核心方案
Containers are used for two primary purposes, namely registering and resolving.
容器的使用主要有两个目的,即注册和解析。
Registering 注册
Before you can inject dependencies into an object, the types of the dependencies need to be registered with the container. Registering a type typically involves passing the container an interface and a concrete type that implements that interface. There are primarily two means for registering types and objects: through code or through configuration. The specific means vary from container to container.
在你可以注册依赖项到一个对象之前,此对象类型的依赖项需要被先注册到容器里。注册一个类型通常涉及传递给容器一个接口和一个实现此接口的具体类型。主要有两种方法用于注册类型和对象:通过代码或通过配置文件。具体的方法时多样的。
Typically, there are two ways of registering types and objects in the container through code:
通常,通过代码有两种方式注册类型和对象到容器中。
- You can register a type or a mapping with the container. At the appropriate time, the container will build an instance of the type you specify.
- You can register an existing object instance in the container as a singleton. The container will return a reference to the existing object.
Registering Types with the Unity Container Unity容器注册类型
During initialization, a type can register other types, such as views and services. Registration allows their dependencies to be provided through the container and allows them to be accessed from other types. To do this, the type will need to have the container injected into the module constructor. The following code shows how the OrderModule type in the Commanding QuickStart registers a type.
在初始化期间,类型可以注册其他类型,例如视图和服务。注册允许通过容器来提供它们的依赖项,并允许它们可以从其它类型的存取。为做到这点,类型将需要将容器注入到模块构造器。以下代码显示OrderModule 类型(来自命令快速入门)
// OrderModule.cs public class OrderModule : IModule { public void Initialize() { this.container.RegisterType<IOrdersRepository, OrdersRepository>(new ContainerControlledLifetimeManager()); ... } ... }
Depending on which container you use, registration can also be performed outside the code through configuration. For an example of this, see Registering Modules using a Configuration File in Modular Application Development.
这取决于您所使用的容器,注册页可以用配置文件,想看这个例子,请看利用配置文件注册模块(来自模块化应用程序开发)
Note:注意 |
---|
The advantage of registering in code, compared to configuration, is that the registration happens only if the module loads. 相比于在配置中注册,代码注册的好处是只有在模块加载了才注册模块需要的类型。 |
Registering Types with MEF MEF注册类型
MEF uses an attribute-based system for registering types with the container. As a result, adding type registration to the container is simple: it requires the addition of the [Export] attribute to a type as shown in the following code example.
MEF使用基于属性的系统来注册类型到容器。其结果是,添加类型到容器很简单:只需要[Export] 特性到需要的类型,如下代码所示。
[Export(typeof(ILoggerFacade))] public class CallbackLogger: ILoggerFacade { }
Another option when using MEF is to create an instance of a class and register that particular instance with the container. The QuickStartBootstrapper in the Modularity with MEF QuickStart shows an example of this in the ConfigureContainer method, as shown here.
另一种选择是当使用MEF创建一个类的实例并注册器到容器。代码如下(来自MEF模块化快速入门):
protected override void ConfigureContainer() { base.ConfigureContainer(); // Because we created the CallbackLogger and it needs to // be used immediately, we compose it to satisfy any imports it has. this.Container.ComposeExportedValue<CallbackLogger>(this.callbackLogger); }
Note:注意: |
---|
When using MEF as your container, it is recommended that you use attributes to register types. 当使用MEF作为你的容器是,推荐使用特性去注册类型。 |
Resolving 解析
After a type is registered, it can be resolved or injected as a dependency. When a type is being resolved, and the container needs to create a new instance, it injects the dependencies into these instances.
在一个类型注册之后,它可以被解析或作为依赖注入。当一个类型被解析,容器需要创建一个新实例,先将依赖项注入到次实例。
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 resolve a concrete type that has not been registered.
一些容器,包括Unity,允许你在不注册它的情况下解析一个具体类。 - If the type has been registered as a singleton, the container returns the singleton instance. If this is the first time the type was called for, the container creates it and holds on to 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 with MEF are singletons and the container holds a reference to the object. In Unity, new instances of objects are returned by default, and the container does not maintain a reference to the object.
默认情况下,MEF类型注册为单例,容器保持此单例的引用。在Untiy中,默认情况类型注册不为单例。
Resolving Instances with Unity Unity解析实例
The following code example from the Commanding QuickStart shows where the OrdersEditorView and OrdersToolBar views are resolved from the container to associate them to the corresponding regions.
下面的代码示例展示了命令快速入门中OrdersEditorView 和OrdersToolBar 视图从容器解析的地方,和怎样将他们联系到区域里。
// OrderModule.cs public class OrderModule : IModule { public void Initialize() { this.container.RegisterType<IOrdersRepository, OrdersRepository>(new ContainerControlledLifetimeManager()); // Show the Orders Editor view in the shell's main region. this.regionManager.RegisterViewWithRegion("MainRegion", () => this.container.Resolve<OrdersEditorView>()); // Show the Orders Toolbar view in the shell's toolbar region. this.regionManager.RegisterViewWithRegion("GlobalCommandsRegion", () => this.container.Resolve<OrdersToolBar>()); } ... }
The OrdersEditorViewModel constructor contains the following dependencies (the orders repository and the orders command proxy), which are injected when it is resolved.
OrdersEditorViewModel 的构造器包含下面的依赖项(订单存储库和订单命令代理),这些都是在其被解析时注入的。
// OrdersEditorViewModel.cs public OrdersEditorViewModel(IOrdersRepository ordersRepository, OrdersCommandProxy commandProxy) { this.ordersRepository = ordersRepository; this.commandProxy = commandProxy; // Create dummy order data. this.PopulateOrders(); // Initialize a CollectionView for the underlying Orders collection. this.Orders = new ListCollectionView( _orders ); // Track the current selection. this.Orders.CurrentChanged += SelectedOrderChanged; this.Orders.MoveCurrentTo(null); }
In addition to the constructor injection shown in the preceding code, Unity also allows for property injection. Any properties that have a [Dependency] attribute applied are automatically resolved and injected when the object is resolved.
除了上面代码展示的构造函数注入,Unity也可以属性注入。任何属性只要有[Dependency] 特性,当对象被解析时可以自动解析并注入。
Resolving Instances with MEF MEF解析实例
The following code example shows how the Bootstrapper in the Modularity with MEF QuickStart obtains an instance of the shell. Instead of requesting a concrete type, the code could request an instance of an interface.
下面代码示例展示Bootstrapper 怎样获得一个壳实例(来自MEF模块快速入门)。不是具体类型的,代码可以需要一个接口的实例(下面不是一个具体类型吗?)。
protected override DependencyObject CreateShell() { return this.Container.GetExportedValue<Shell>(); }
In any class that is resolved by MEF, you can also use constructor injection, as shown in the following code example from ModuleA in the Modularity with MEF QuickStart, which has an ILoggerFacadeand an IModuleTracker injected.
任何MEF解析的类,你也可以使用构造函数注入,如下代码所示(来自MEF模块化入门),ILoggerFacade和 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); }
Another option is to use property injection, as shown in the ModuleTracker class from the Modularity with MEF QuickStart, which has an instance of the ILoggerFacade injected.
另一个选择是利用属性入门,如下ModuleTracker 所示(来自MEF快速入门),ILoggerFacade 属性被注入。
[Export(typeof(IModuleTracker))] public class ModuleTracker : IModuleTracker { [Import] private ILoggerFacade Logger; }
Using Dependency Injection Containers and Services in Prism 在Prism中使用依赖注入容器和服务
Dependency injection containers, often referred to as just "containers," are used to satisfy dependencies between components; satisfying these dependencies typically involves registration and resolution. The Prism Library provides support for the Unity container and for MEF, but it is not container-specific. Because the library accesses the container through the IServiceLocator interface, the container can be replaced. To do this, your container must implement the IServiceLocator interface. Usually, if you are replacing the container, you will also need to provide your own container-specific bootstrapper. The IServiceLocator interface is defined in the Common Service Locator Library. This is an open source effort to provide an abstraction over IoC (Inversion of Control) containers, such as dependency injection containers, and service locators. The objective of using this library is to leverage IoC and Service Location without tying to a specific implementation.
依赖注入容器,通常简称“容器”,用于满足组件之间的依赖性;满足这些依赖关系通常涉及注册和解析。Prism提供Untiy和MEF支持,但并不是非它俩不可。因为Prism通过IServiceLocator 接口访问容器,所有容器是可以被替换的。未做到这点,你的容器必须实现IServiceLocator 接口。通常,若果你替换容器,你也要提供对应的引导器。IServiceLocator 接口定义在公共服务定位器库。这是一个提供抽象的IoC(控制反转)容器的开源代码,例如依赖组人容器和服务定义器。它的目标是使类库使用控制反转和服务定位模式却不依赖于特别的实现。
The Prism Library provides the UnityServiceLocatorAdapter and the MefServiceLocatorAdapter. Both adapters implement the ISeviceLocator interface by extending the ServiceLocatorImplBasetype. The following illustration shows the class hierarchy.
Prism库提供UnityServiceLocatorAdapter 和MefServiceLocatorAdapter。这俩适配器都实现了ISeviceLocator 接口并扩展ServiceLocatorImplBaset类型。下图展示了类的继承关系。
Although the Prism Library does not reference or rely on a specific container, it is typical for an application to rely on a specific container. This means that it is reasonable for a specific application to refer to the container, but the Prism Library does not refer to the container directly. For example, the Stock Trader RI and several of the QuickStarts included with Prism rely on Unity as the container. Other samples and QuickStarts rely on MEF.
尽管Prism不依赖于特别的容器,但一个程序中只能使用一种容器。(然后是一堆废话,意思是示例中使用了某一容器,不代表Prism就非得用这个容器,也可以使用其他容器,Prism就是这么牛逼,写书的你就这么喜欢玩逻辑游戏吗)。
IServiceLocator
The following code shows the IServiceLocator interface.
下面代码展示了IServiceLocator 接口。
public interface IServiceLocator : IServiceProvider { object GetInstance(Type serviceType); object GetInstance(Type serviceType, string key); IEnumerable<object> GetAllInstances(Type serviceType); TService GetInstance<TService>(); TService GetInstance<TService>(string key); IEnumerable<TService> GetAllInstances<TService>(); }
The Service Locator is extended in the Prism Library with the extension methods shown in the following code. You can see that IServiceLocator is used only for resolving, meaning it is used to obtain an instance; it is not used for registration.
Prism中扩展了服务定位器,扩展方法的嗲吗如下所示。你可以看到IServiceLocator 用来解析,这意味着它是用来获取示例的;不是用来注册的。
// ServiceLocatorExtensions public static class ServiceLocatorExtensions { public static object TryResolve(this IServiceLocator locator, Type type) { try { return locator.GetInstance(type); } catch (ActivationException) { return null; } } public static T TryResolve<T>(this IServiceLocator locator) where T: class { return locator.TryResolve(typeof(T)) as T; } }
The TryResolve extension method—which the Unity container does not support—returns an instance of the type to be resolved if it has been registered; otherwise, it returns null.
TryResolve 扩展方法——Unity 容器不支持——返回一个已经注册的型的实例;否则它返回null。
The ModuleInitializer uses IServiceLocator for resolving the module during module loading, as shown in the following code examples.
ModuleInitializer 会用IServiceLocator 在模块加载期间来解析模块,如下代码所示。
// ModuleInitializer.cs - Initialize() IModule moduleInstance = null; try { moduleInstance = this.CreateModule(moduleInfo); moduleInstance.Initialize(); } ...
// ModuleInitializer.cs - CreateModule() protected virtual IModule CreateModule(string typeName) { Type moduleType = Type.GetType(typeName); if (moduleType == null) { throw new ModuleInitializeException(string.Format(CultureInfo.CurrentCulture, Properties.Resources.FailedToGetType, typeName)); } return (IModule)this.serviceLocator.GetInstance(moduleType); }
Considerations for Using IServiceLocator 使用IServiceLocator注意事项
IServiceLocator is not meant to be the general-purpose container. Containers have different semantics of usage, which often drives the decision for why that container is chosen. Bearing this in mind, the Stock Trader RI uses the dependency injection container directly instead of using the IServiceLocator. This is the recommend approach for your application development.
IServiceLocator 不是意味着它就是通用容器。容器有不同的用法语义,以决定为什么他被选中。牢记这一点,股票操盘程序使用依赖注入容器直接代替使用IServiceLocator。这是一个开发程序的推荐方式。
In the following situations, it may be appropriate for you to use the IServiceLocator:
在以下情况下,你该考虑使用IServiceLocator:
- You are an independent software vendor (ISV) designing a third-party service that needs to support multiple containers.
- You are designing a service to be used in an organization where they use multiple containers.
More Information 更多信息
For information related to containers, see the following: 有关容器的更多信息,请看以下:
- Unity Application Block on MSDN. MSDN上的Unity Application Block
- Unity community site on CodePlex. CodePlex上的Unity community site
- Managed Extensibility Framework Overview on MSDN. MSDN上的Managed Extensibility Framework Overview
- MEF community site on CodePlex. CodePlex上的MEF community site
- Inversion of Control containers and the Dependency Injection pattern on Martin Fowler's website. Martin Fowler的站点上的文章Inversion of Control containers and the Dependency Injection pattern
- Design Patterns: Dependency Injection in MSDN Magazine. MSDN杂志上的Design Patterns: Dependency Injection
- Loosen Up: Tame Your Software Dependencies for More Flexible Apps in MSDN Magazine. MSDN杂志上的Loosen Up: Tame Your Software Dependencies for More Flexible Apps
- Castle Project
- StructureMap
- Spring.NET