Applications based on the prism class library may be complex applications composed of multiple loosely coupled types and services. They need to send content and receive notifications for interaction based on user actions, since they are loosely coupled, they need a way to interact and communicate with each other to deliver business functions. To combine these scattered modules, Prism-based applications use a dependency injection container, the dependency injection container provides instantiated class objects through container-based configuration and manages their declaration cycles to reduce dependency coupling between objects. When creating an object, the container injects all the Dependencies it needs into it. If these dependencies have not been created, the container will first create and parse the objects or dependencies required by the dependencies. In some cases, the container itself is resolved as part of the dependency. For example, when unity is used as the application container, the module is injected by the container, so they can inject their views and services into the container. The following are some advantages of using containers:
- When a component is removed from a container, You need to locate its dependent objects and manage the lifecycle tasks of these objects.
- The container implements the method of changing dependencies without affecting components.
- Containers can make testing easier by simulating dependency components.
- Containers increase the maintainability of applications by easily adding new components to the system.
In the use of prism applications, containers have some special advantages:
- The container injects the dependency of the module only when the module is loaded.
- Containers are used to register and parse view models and views.
- A container can create a module view and inject it into the view.
- Containers are injected into combined services, such as region management and event aggregators.
- The container is used to register the unique service of the module.
Note: Some prism examples Use unity as the application container, and some sample codes such as modularity quickstarts use MEF. The prism class library itself uses a dedicated container, you can use other containers, such as castle Windsor, structuremap, and spring.. net.
Key decision: select a dependency injection container
The prism Class Library provides two alternative dependency injection containers: unity and MEF. Prism can be expanded, so other custom containers can be used through a little code work. Although unity and MEF work very differently, they all provide the basic functions of dependency injection. The two containers share the same thing:
- They can all inject types in the container.
- They can all inject instances into the container.
- Both of them can create registered instances.
- They can all be injected in the constructor.
- They can all be injected into attributes.
- They can all manage the types and dependencies to be marked by declaring properties.
- They can all parse dependencies in an object chart.
Unique features of Unity:
- You can parse unregistered types.
- Public generic classes can be parsed
- Use a listener to capture the content called by the injected object and add additional functions to the target object.
Features specific to MEF:
- Find the assembly in the directory
- Download the xap file and discover the Assembly
- Reorganize attributes and sets when the heart type is found
- Automatic Export (discovery) subtype
- Dependent on. NET Framework
These containers have different capabilities and work in different ways, but the prism class library can work on these containers and provide similar functions. When you consider which container to use, remember the features described above and decide which container is more suitable for you.
Notes for using containers
Before using a container, consider the following:
- It is appropriate to consider whether to register/resolve components to the container.
- Consider whether the performance impact of registering to the container and parsing instance is acceptable in your scenario. For example, if you need to create 10000 polygon in the current perspective to draw a certain surface, the overhead of the container to unload so many polygon instances will cause a great performance burden, because it uses reflection to create each instance.
- If there are many dependencies or deep layers, it takes a lot of time to create them.
- If a component does not depend on any other component or is not dependent on any other component, it makes no sense to put it in the container.
- If a component has a separate set of dependencies that are complete and will not change, it is meaningless to put them in the container.
- Determines whether a component is registered as a singleton or an instance based on its lifecycle.
- If a global service plays a single resource management role, a component is registered as a singleton as a daily service.
- If a component is shared among multiple users, it is registered as a singleton.
- If a component requires an independent dependent instance for each injection, it cannot be registered as a singleton. For example, each view requires an instance of the view model.
- Whether you will use the code or the configuration file to configure the container
- If you want to centrally manage different services, use the configuration file to configure containers.
- If you want to register a specific service based on specific conditions, use the code to configure the container
- If you have module-level services, consider using code to configure containers, because these services will be registered during loading.
Note: Some containers, such as MEF, cannot be configured using the configuration file, but can only be configured using code.
Core SolutionContainers are mainly used for two purposes: Registration and resolution.
RegisterBefore you inject dependencies into an object, you must register the object dependency type with the container. Registering a type usually involves providing an interface to the container and a specific type that implements the interface. There are usually two main methods for registering types and objects: through code or configuration files. Of course, different containers have different methods. Generally, there are two methods to register a type and an object to the container through code:
- You can register a type or a diagram with the container. When appropriate, the container will create the instance of the class you specified.
- You can register an existing object in the container as a singleton. The container returns a reference to the existing object.
Use unity container registration type
During initialization, one type can register another type, just like views and services. After registration, you can provide the entire container with their dependencies, or access them through other types. To do this, the type needs to be injected into the container in the module constructor. The following code registers an ordermodule In the commanding Quickstart sample code.
public class OrderModule : IModule{ public void Initialize() { this.container.RegisterType<IOrdersRepository, OrdersRepository>(new ContainerControlledLifetimeManager()); ... } ...} |
Depending on the container you selected, registration can also be implemented outside the Code through the configuration file. For example, in chapter 4th "modular application development.", "registering modules using a configuration file"
Note:
Compared with the configuration file, the advantage of code registration is that the module is registered only when it is loaded.
Use MEF registration type
MEF uses the property-based method to register a type with the container. Therefore, it is very easy to add a type registration to the container: add an [Export] attribute to the type, as shown in the following code,
[Export(typeof(ILoggerFacade))]public class CallbackLogger: ILoggerFacade{} |
Another method for registering with MEF is to register a specific instance to a container, as shown in the following figure:
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 MEF is used as the container, the property registration type is recommended.
Analysis
After the type is registered, it can be parsed or registered as a dependency. When a type is parsed, the container needs to create a new instance and inject the dependency into the newly created object.
Generally, when a type is parsed, one of the following three situations occurs:
- If this type has not been registered, the container will throw an exception
-Note: Some containers, including unity, allow parsing of an unregistered specific type
- If the type is registered as Singleton, the container returns the singleton instance object. If this is the first call of this type, the container will create an instance object and save it for future use.
- If this type is not registered as a Singleton, the container returns a new instance
-Note: by default, the type in MEF is registered as Singleton mode, and the container retains the object reference. In unity, a new instance object is returned by default, and the container does not retain the reference of the object.
Parsing object instances in Unity
The following code is two views in the commanding Quickstart instance:OrderseditorviewAndOrderstoolbarHow is it resolved by the container and placed in its corresponding region.
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>()); } ...} |
OrderseditorpresentationmodeL The constructor contains the following dependencies (iordersrepository and orderscommandproxy). When they are parsed, they are injected.
public OrdersEditorPresentationModel( IOrdersRepository ordersRepository, OrdersCommandProxy commandProxy ){ this.ordersRepository = ordersRepository; this.commandProxy = commandProxy; // Create dummy order data. this.PopulateOrders(); // Initialize a CollectionView for the underlying Orders collection.#if SILVERLIGHT this.Orders = new PagedCollectionView( _orders );#else this.Orders = new ListCollectionView( _orders );#endif // Track the current selection. this.Orders.CurrentChanged += SelectedOrderChanged; this.Orders.MoveCurrentTo(null);} |
In the above Code, in addition to the injection in the constructor, unity can also be injected through attributes. Any attribute with [dependency] attribute features will be automatically parsed and injected when the object is parsed.
Parsing object instances in MEFThe following code is displayed in the modularity for Silverlight with MEF Quickstart instance:
BootstrapperHow to obtain a shell instance. Code can also request an interface instance, not just an actual instance.
protected override DependencyObject CreateShell(){ return this.Container.GetExportedValue<Shell>();} |
In any class parsed using MEF, you can also use constructor injection,
For example, modulea in instance modularity for Silverlight with MEF Quickstart,IloggerfacadeAndImoduletrackerIt is injected in the following code.
[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 method is to use property injection, such as in the modularity for Silverlight with MEF Quickstart instance.ModuletrackerClass injection into the instanceIloggerfacade.
[Export(typeof(IModuleTracker))]public class ModuleTracker : IModuleTracker{ // Due to Silverlight/MEF restrictions, this must be public. [Import] public ILoggerFacade Logger;} |
Note: In Silverlight,
ImportedAttribute and field must be public
Use dependency injection containers and services in PrismA dependency injection container, usually called a "container", is used to carry dependencies between components. It usually involves registration and resolution. Although prism provides unity and MEF resolution containers, Prism itself is not based on specific containers. Because prism uses
IservicelocatorThe interface accesses these containers, so the containers can be replaced. To achieve this, the selected container must implement the iservicelocator interface. Generally, if you want to replace the container, you need to provide the Bootstrap program for your specific container, the iservicelocator interface is defined in the common service locator class library. This is an open-source library and tries its best to improve the abstraction of a container using IOC (control reversal) ideas, just like dependency injection containers and service locators, the purpose of using this service is to use IOC and service positioning without depending on a specific implementation.
The prism Class Library providesUnityservicelocatoradapterAndMefservicelocatoradapter. Both classes are implemented through extensionServicelocatorimplbase implementsIsevicelocator interface. The following illustration shows the relationship between classes.
Although the prism class library does not depend on or implement any specific containers, applications usually depend on a specific container. This means that a specific application depends on a container, but it is reasonable that the prism class library does not directly relate to any container. For example, stock trader RI and some Quickstart use Prism and depend on the unity container, while other Quickstart and examples depend on MEF.
Iservicelocator
The following code is displayed:
Now
Iservicelocator Interface
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>();} |
In prism, the service locator is extended by some extension methods, as shown in the following code. You can find that
IservicelocatorIt is only used for parsing, that is, obtaining an instance, not for registration.
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; }} |
Extension Method
Tryresolve(Not supported by the Unity container) returns an instance of a registered class. Otherwise, null is returned.
ModuleinitializerUseIservicelocatorParse the module during module loading, as shown in the following code:
IModule moduleInstance = null;try{ moduleInstance = this.CreateModule(moduleInfo); moduleInstance.Initialize();}... |
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);} |
Usage
Iservicelocator
IservicelocatorIt is not a common container. Containers have different usage semantics, which is why a container is selected. With this in mind, stock trader RI uses unity instead
Iservicelocator,This is a recommended practice for your application development. In the following cases, use
Iservicelocator:
- You are an independent software developer (isV) who develops a third-party service suitable for multiple containers.
- You design a service in a system that uses multiple containers.
More informationFor more information about containers, see the following:
- "Unity Application Block" on msdn:
Http://www.msdn.com/unity
- Unity community site on codeplex:
Http://www.codeplex.com/unity
- "Managed extensibility framework overview" on msdn:
Http://msdn.microsoft.com/en-us/library/dd460648.aspx
- MEF community site on codeplex:
Http://mef.codeplex.com/
- "Inversion of control containers and the dependency injection pattern" on Martin Fowler's website:
Http://www.martinfowler.com/articles/injection.html
- "Design patterns: dependency injection" inMsdn magazine:
Http://msdn.microsoft.com/en-us/magazine/cc163739.aspx
- "Loosen up: tame your software dependencies for more flexible apps" inMsdn magazine:
Http://msdn.microsoft.com/en-us/magazine/cc337885.aspx
- Castle project:
Http://www.castleproject.org/container/index.html
- Structuremap:
Http://structuremap.sourceforge.net/Default.htm
- Spring. Net:
Http://www.springframework.net/
Prism4 document translation series --- Chapter 1 Manage dependencies between components