📜 ⬆️ ⬇️

Prism Developer Guide - Part 3, managing dependencies between components

Table of contents
  1. Introduction
  2. Initializing Prism Applications
  3. Manage dependencies between components
  4. Modular Application Development
  5. Implementation of the MVVM pattern
  6. Advanced MVVM scripts
  7. Creating user interface
    1. User Interface Design Recommendations
  8. Navigation
    1. View-Based Navigation (View-Based Navigation)
  9. The interaction between loosely coupled components

Applications created with the Prism library are typically composite applications, potentially consisting of loosely coupled services and components. They must interact with each other so as to provide content to the user interface and receive notifications about user actions. Since they are loosely coupled, they need a way to interact, without which the necessary functionality cannot be obtained.

To link all the pieces together, Prism applications rely on the DI container. DI containers reduce dependencies between objects by providing a way to instantiate classes and control their lifetime depending on the configuration of the container. When creating objects using a container, it injects the necessary dependencies into them. If the dependencies have not yet been created, the container first creates them and resolves their own dependencies. In some cases, the container itself is implemented as a dependency. For example, when using Unity, a container is inserted into modules so that they can register their views and services in it.

There are several advantages to using a container:

In the context of an application based on the Prism library, there are certain advantages to using a container:


The 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 Prism library itself is independent of the container used, and you can use its services and patterns with other containers, such as Castle Windsor, Autofac, Structuremap, Spring.NET, or with any other.

Key solution: select dependency deployment 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 considering that they work very differently. Some of the features provided by both containers are:

Unity provides several features not found in MEF:

MEF provides several features not found in Unity:

Containers vary in capacity and work differently, but Prism 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 for your application scenarios.
')

Container Considerations


What should be considered before using containers:

The note
Some containers, such as MEF, cannot be configured through the configuration file and must be configured in code.

Basic scripts


Containers are used for two main purposes, namely, registration and authorization.

check in


Before you can embed dependencies in an object, the types of dependencies must be registered in the container. Type registration usually involves passing an interface to a container and a specific type that implements this interface. First of all, there are two ways of registering types and objects: in code, or through a configuration file. Implementation details may vary by container.

As a rule, there are two ways to register types and objects in a container in code:


Type Registration in UnityContainer

During initialization, a type can register other types, such as views and services. Registration allows you to resolve their dependencies container and become available to other types. To do this, you need to embed the container in the module's constructor. The following code shows how OrderModule from Commanding QuickStart registers a repository type during initialization as a singleton.

 public class OrderModule : IModule { public void Initialize() { this.container.RegisterType<IOrdersRepository, OrdersRepository>(new ContainerControlledLifetimeManager()); ... } ... } 

Depending on which container you are using, registration can also be performed out of code through the configuration file. For an example, see "Registering Modules using a Configuration File" in Chapter 4, " Modular Application Development ."

Type Registration with MEF Container

For registering types in a container, MEF uses an attribute-based system. As a result, it is quite easy to add a type registration to a container: this requires adding the [Export] attribute 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 could be to create an instance of a class and register that particular instance in a container. QuickStartBootstrapper in Modularity for Silverlight with MEF QuickStart shows an example of this in the ConfigureContainer method.

 protected override void ConfigureContainer() { base.ConfigureContainer(); //    CallbackLogger,     , //    ,     (),   . this.Container.ComposeExportedValue<CallbackLogger>(this.callbackLogger); } 

The note
When using MEF as a container, it is recommended that attributes be used to register types.

Resolution


After the type is registered, it can be enabled 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 that instance.

In general, when a type is allowed, one of three things happens:

Resolving instances in unity

The following code sample from Commanding QuickStart shows how the OrdersEditorView and OrdersToolBar resolved from the container to bind them to their respective regions.

 public class OrderModule : IModule { public void Initialize() { this.container.RegisterType<IOrdersRepository, OrdersRepository>(new ContainerControlledLifetimeManager()); //   Orders Editor    . this.regionManager.RegisterViewWithRegion("MainRegion", () =>; this.container.Resolve<OrdersEditorView>()); //   Orders Toolbar    . this.regionManager.RegisterViewWithRegion("GlobalCommandsRegion", () => this.container.Resolve<OrdersToolBar>()); } ... } 

The OrdersEditorPresentationModel constructor contains the following dependencies (order repository and order command proxy), which are entered 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 the constructor, as shown in the previous example, Unity can also embed dependencies in properties. Any properties to which the [Dependency] attribute is applied are automatically resolved and implemented when the object is resolved. If the property is marked with the OptionalDependency attribute, then if it is impossible to resolve the dependency, the property is assigned a null and no exception is thrown.

Resolving instances in MEF

The following code example shows how Bootstrapper in Modularity for Silverlight with MEF QuickStart gets a shell instance. Instead of requesting a specific type, the code might request an instance of the interface.

 protected override DependencyObject CreateShell() { return this.Container.GetExportedValue<Shell>(); } 

In any class that MEF resolves, you can also use injection into the constructor, as shown in the following code example from ModuleA in Modularity for Silverlight with MEF QuickStart , in which 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); } 

On the other hand, you can use property injection, as shown in the ModuleTracker class from Modularity for Silverlight with MEF QuickStart , which has an instance of ILoggerFacade being implemented.

 [Export(typeof(IModuleTracker))] public class ModuleTracker : IModuleTracker { // -  Silverlight/MEF,    . [Import] public ILoggerFacade Logger; } 

The note
In Silverlight, the properties and fields being imported should be publicly available.

Using dependency injection containers and services in Prism


Dependency injection containers are used to satisfy dependencies between components. Satisfying these dependencies usually includes registration and resolution. Prism provides support for Unity and MEF containers, but does not depend on them. Since the library has access to the container through the IServiceLocator interface, the container can be easily replaced. To do this, you must implement the IServiceLocator interface. Usually, if you replace a container, you will also need to write your own container-specific loader. The IServiceLocator interface IServiceLocator defined in the Common Service Locator Library . This is an open source project to provide an 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.

Prism library provides UnityServiceLocatorAdapter and MefServiceLocatorAdapter . Both adapters implement the ISeviceLocator interface, extending the type of ServiceLocatorImplBase . The following illustration shows the class hierarchy.

Implements Common Service Locator in Prism.

Although Prism 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 an application to refer to a specific container, but the Prism library does not reference the container directly. For example, the Stock Trader RI app and several of QuickStarts use Unity as the container. Other examples and QuickStarts use MEF.

IServiceLocator


The following code shows the IServiceLocator interface and its methods.

 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 extends the Prism library with extension methods, shown in the following code. You can see that IServiceLocator used only for permission, 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; } } 

The TryResolve extension TryResolve , which the Unity container does not support, returns an instance of the type that must be enabled if it was registered, otherwise it returns null .

ModuleInitializer uses the IServiceLocator to resolve module dependencies during its loading, as shown in the following code examples.

 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); } 

Considerations for using IServiceLocator


IServiceLocator not intended to be used as a general purpose container. Containers may have different usage semantics, which often affect the choice of container. Taking this into account, Stock Trader RI uses the dependency injection container directly instead of using the IServiceLocator . This is the recommended approach when developing applications.

In the following situations, the use of IServiceLocator is appropriate:

Additional Information


For information related to DI containers, see:

Source: https://habr.com/ru/post/176861/


All Articles