zoukankan      html  css  js  c++  java
  • [转]Inject Some Life into Your Applications—Getting to Know the Unity Application Block

     
    Inject Some Life into Your Applications—Getting to Know the Unity Application Block

    Cc816062.practices(en-us,MSDN.10).png

    patterns & practices Developer Center
    Alex Homer, Microsoft Corporation
    August 2008

    Object-oriented programming is dead—instead, the future for building complex service-based applications is aspect-oriented programming. …Okay, so this wild claim, aimed mainly at grabbing your attention early on, is perhaps not totally justified. Dependency injection, the major design pattern implemented by the Unity Application Block (also known as Unity), is an object-oriented programming technique that has nothing directly to do with aspect-oriented programming.

    However, that earlier wild statement does contain more than a modicum of truth, because the way we build applications is changing quite dramatically. Unity can help you to implement aspect-oriented programming techniques, as well as many other techniques for implementing plug-in architectures, service location facilities, and removal of direct object dependencies. These techniques are available now, have a proven track record, and are already in use in many best-practice applications and scenarios.

    This article presents an introduction to the Unity Application Block and describes how it helps you to implement some of the most useful object creation and lifetime management patterns. It uses a simple ASP.NET example application that you can download and experiment with to see Unity in action.

    Contents

    Design Patterns Can Be Your Friends
    Be Concerned About Your Concerns
    How the patterns & practices Team Builds Objects
    Making It Easier for Everyone
    What Is Unity?
    Using Unity in ASP.NET—a Simple Example
    Conclusion

    Design Patterns Can Be Your Friends

    For some years, I have been talking with architects and developers at conferences and user groups about how they can implement the many and various software design patterns. What becomes clear as you look at the common (and some of the less common) patterns is that the majority of them aim to better manage dependencies. This could be the dependencies between objects, between commands and actions, between event publishers and subscribers, and between application artifacts of all types.

    There is a whole set of related design patterns that provide useful guidance on managing dependencies between artifacts of all types, and which help developers to construct loosely coupled systems that have minimum dependencies. These patterns, aimed mainly at the creation and use of objects that implement crosscutting concerns, include Factory, Builder, Lifetime, Inversion of Control (IoC), and Dependency Injection.

    Be Concerned About Your Concerns

    Did you notice another, perhaps unfamiliar, word that crept into the previous paragraph? Aspect-oriented programming is all about "concerns", and how you manage them successfully. Instead of thinking of an application as a series of separate objects, you consider each function of the application as a concern (hence the opening statement that object-oriented programming is dead!). Components that relate directly to a specific application, such as code that accesses a customer file, code that generates and displays a particular view, or components that implement a specialized data access layer, are "core concerns."

    Aspect-oriented programming comes into its own because it considers all other types of components and code as "crosscutting concerns." These features are generic or semi-generic to many applications, and they usually include familiar functions, such as validation, authorization, caching, structured exception handling, logging, and performance monitoring. Aspect-oriented programming techniques aim to help you more efficiently manage these crosscutting concerns.

    Unity and the Dependency Injection pattern are not directly connected with aspect-oriented programming, but Unity does provide some very useful techniques for managing crosscutting concerns. Using Unity makes it much easier to manage the dependencies between your application's core code and the objects that implement validation, authorization, caching, structured exception handling, logging, and performance monitoring.

    How the patterns & practices Team Builds Objects

    The patterns & practices team at Microsoft have been using a generic object factory named ObjectBuilder for several years as the basis for much of the software they produce. ObjectBuilder allows applications to generate instances of specific concrete classes based on requests for an interface or a base class type. As it generates the object instance, ObjectBuilder can inject instances of dependent objects and run code to prepare the new object at various stages of the instantiation process.

    However, while ObjectBuilder is undoubtedly a powerful tool, it can be complicated to program because it requires you to construct strategy chains, set policies, and provision a locator and a build plan. For this reason, it has never been fully documented and has very little direct use outside of the team of specialist developers on the patterns & practices team.

    Making It Easier for Everyone

    The Unity Application Block (Unity) makes it easy for developers to take advantage of ObjectBuilder (now in version 2.0) and implement inversion of control, service location, and dependency injection capabilities in their applications—and with the minimum of code and effort. Unity has been developed especially for use with Microsoft .NET-connected applications. The following are some examples of how Unity can be used:

    • It can be used as a stand-alone dependency injection mechanism.
    • It can be used for seamless integration with other patterns & practices software factories and tools, such as Enterprise Library.
    • It can be used with any other .NET-connected application, including Windows Forms, Web Forms (ASP.NET), and Console applications.
    • It can be used with Windows services, such as Windows Communication Foundation (WCF) Web services.
    • It can be used with presentation technologies, such as Windows Presentation Foundation (WPF)–based applications and Silverlight.

    What Is Unity?

    Basically, Unity is just a container that stores registrations and mappings between types and can instantiate the appropriate concrete types on demand. Or, to be more precise, it is a lightweight, extensible dependency injection container with support for nested containers and facilities for constructor, property, and method call injection. Unity is available as a stand-alone download and is also included with Enterprise Library 4.0. You can use Unity with Enterprise Library to create both Enterprise Library objects and your own custom business objects, or you can use it without installing Enterprise Library.

    To take advantage of the capabilities of Unity, you register types and mappings with the container to specify the dependencies between interfaces, base classes, and concrete object types. You can create these registrations and mappings using configuration (stored in a file, a database, or almost anywhere else you want), or you can generate them at run time in code. You can also specify injection of objects into your own classes using attributes that indicate the properties or methods that require injection of dependent objects and have objects specified in the parameters of class constructors automatically injected.

    In addition, as if this was not enough, you can take advantage of the prebuilt container extensions that support extra features, such as the Event Broker extension that implements an attribute-driven publish/subscribe mechanism you can use in your applications. You can even build your own custom container extensions, although that does mean you must understand how to program ObjectBuilder!

    In the remainder of this article, I will show you a simple example of using Unity in an ASP.NET Web application. You can see Unity used in a Windows Forms application, including with the Event Broker container extension, by running the QuickStart example applications included in the Unity download.

    Cc816062.note(en-us,MSDN.10).gifNote:
    For a full description of the Unity Application Block, see Introduction to Unity on MSDN. To download Unity as a stand-alone product, see Unity Application Block on MSDN. For information about Enterprise Library, and to download the latest version, see Enterprise Library on MSDN.

    Using Unity in ASP.NET—a Simple Example

    As an example of the features Unity provides, this example allows you to easily experiment with registering default mappings, using named mappings, and using constructor injection. It also demonstrates how you can store the container in a Web Forms application and read configuration from an XML format configuration file.

    Figure 1 shows the simple interface, a single ASP.NET page containing buttons to activate each of the features that the application demonstrates. When you first run the application, it creates and populates a new Unity container instance, and then it stores it in the ASP.NET Application object. A label at the bottom of the page indicates the results of each process the example carries out.

    Cc816062.64dfcaa6-689e-4e70-a746-5bfc9f6ab5bf(en-us,MSDN.10).png

    Figure 1
    The example application when first opened, showing that it created and populated a new Unity container


    Cc816062.note(en-us,MSDN.10).gifNote:
    You can download the example application from http://www.codeplex.com/unity/Wiki/View.aspx?title=Code%20samples. Copy the example application to a computer that has Unity installed in the standard location, and then open the project in Visual Studio 2005, Visual Studio 2008, or Visual Studio Express Edition. Alternatively, you can install the files into a folder in your WWWRoot folder, and then run it under Internet Information Services (IIS).


    The following sections describe each feature of the example application in detail and explain how you can use them to implement dependency injection, inversion of control, and service location functionality in your own applications:

    Creating and Populating a Unity Container

    The example application initially loads its configuration data from an XML file. In fact, the configuration is stored in the normal Web.config file, in a section named <unity> that the configuration handler built into Unity can read. Therefore, the configuration file must also specify this configuration handler in the same way that all custom configuration section handlers work, by using the <configSections> section. The following extract from the Web.config file shows the declaration of the configuration handler and the <unity> section that contains the information this handler will read.

                <configSections>
                <section name="unity"
                type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
                Microsoft.Practices.Unity.Configuration, Version=1.0.0.0,
                Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
                </configSections>
                <unity>
                <typeAliases>
                <!-- Lifetime manager types -->
                <typeAlias alias="singleton"
                type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager,
                Microsoft.Practices.Unity" />
                <typeAlias alias="external"
                type="Microsoft.Practices.Unity.ExternallyControlledLifetimeManager,
                Microsoft.Practices.Unity" />
                <!-- Custom object types -->
                <typeAlias alias="IMyInterface"
                type="MyObjects.IMyInterface, MyObjects" />
                <typeAlias alias="MyRealObject"
                type="MyObjects.MyRealObject, MyObjects" />
                <typeAlias alias="MyOtherObject"
                type="MyObjects.MyOtherObject, MyObjects" />
                <typeAlias alias="ILogger"
                type="MyObjects.ILogger, MyObjects" />
                <typeAlias alias="MyLogger"
                type="MyObjects.MyLogger, MyObjects" />
                <typeAlias alias="MyFastLogger"
                type="MyObjects.MyFastLogger, MyObjects" />
                </typeAliases>
                <containers>
                <container name="containerOne">
                <types>
                <!-- Default (un-named) mapping for IMyInterface to MyRealObject-->
                <type type="IMyInterface" mapTo="MyRealObject" />
                <!-- Default (un-named) mapping for ILogger to MyLogger -->
                <type type="ILogger" mapTo="MyLogger">
                <lifetime type="singleton" />
                </type>
                <!-- Named mapping for ILogger to MyLogger -->
                <type type="ILogger" mapTo="MyLogger" name="StandardLogger">
                <lifetime type="singleton" />
                </type>
                <!-- Named mapping for ILogger to MyFastLogger -->
                <type type="ILogger" mapTo="MyFastLogger" name="SuperFastLogger">
                <lifetime type="external" />
                </type>
                </types>
                </container>
                </containers>
                </unity>
                

    Using Type Aliases

    The XML-based configuration of a Unity container, such as from a file, means that you often have to specify class types (interfaces, base classes, concrete classes, and lifetime manager types), which may require the assembly name, version, culture, and public key in addition to the type name. One extremely useful feature of the Unity configuration handler is that it allows you to define type aliases and then refer to an object using the alias instead of the fully qualified type name. As an example, this extract from the configuration file defines aliases that allow the remaining sections of the configuration to refer to the two standard Unity lifetime manager types as just "singleton" and "external" instead of specifying the complete type name every time.

                <typeAliases>
                <!-- Lifetime manager types -->
                <typeAlias alias="singleton"
                type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager,
                Microsoft.Practices.Unity" />
                <typeAlias alias="external"
                type="Microsoft.Practices.Unity.ExternallyControlledLifetimeManager,
                Microsoft.Practices.Unity" />
                <!-- Custom object types -->
                <typeAlias alias="IMyInterface"
                type="MyObjects.IMyInterface, MyObjects" />
                <typeAlias alias="MyRealObject"
                type="MyObjects.MyRealObject, MyObjects" />
                ...
                </typeAliases>
                

    You can also see two examples of aliases for custom components in the application. The two types shown here, MyObjects.IMyInterface and MyObjects.MyRealObject, are implemented as code files in the MyObjects project of the solution and are compiled into an assembly named MyObjects. The type aliases allow us to refer to these objects elsewhere in the configuration using just "IMyInterface" and "MyRealObject".

    Registering Types and Mappings

    The remaining part of the Unity configuration is a <containers> section where you can define one or more <container> elements. Each <container> element is a named set of configurations that you can used to configure one or more instances of a UnityContainer at run time. You cannot nest the containers in the configuration, though you can use each <container> element to populate any number of containers within a hierarchy that you create at run time. The next extract from the configuration file shows the configuration of the single container used in the example application.

                <containers>
                <container name="containerOne">
                <types>
                <!-- Default (un-named) mapping for IMyInterface to MyRealObject-->
                <type type="IMyInterface" mapTo="MyRealObject" />
                <!-- Default (un-named) mapping for ILogger to MyLogger -->
                <type type="ILogger" mapTo="MyLogger">
                <lifetime type="singleton" />
                </type>
                <!-- Named mapping for ILogger to MyLogger-->
                <type type="ILogger" mapTo="MyLogger" name="StandardLogger">
                <lifetime type="singleton" />
                </type>
                <!-- Named mapping for ILogger to MyFastLogger-->
                <type type="ILogger" mapTo="MyFastLogger" name="SuperFastLogger">
                <lifetime type="external" />
                </type>
                </types>
                </container>
                </containers>
                

    This example uses only the <types> section to register type mappings. You can also include sections that define registration for existing objects (instances) and for custom container extensions and their configuration details.

    The example defines four type mappings:

    • The first mapping is a default (unnamed) registration for the IMyInterface interface and specifies that requests for an object of type IMyInterface should resolve and return an instance of the class MyRealObject (this is the reason dependency injection techniques are often referred to as "interface programming"). The following is an example of this type of mapping.
                      <type type="IMyInterface" mapTo="MyRealObject" />
                      
    • The second mapping is a default (unnamed) registration for the ILogger interface and specifies that requests for an object of type ILogger should resolve and return an instance of the class MyLogger. In addition, the container should ensure that only a single instance of this object will exist during the lifetime of the container. The following is an example of this type of mapping.
                      <type type="ILogger" mapTo="MyLogger">
                      <lifetime type="singleton" />
                      </type>
                      
    • The third and fourth mappings specify as the key the same ILogger interface type as the previous mapping. However, these two mappings also specify a name for the registration, and they map to different target types. This means that code can request an instance of a class that implements the ILogger interface and specify the mapping name. The container will resolve the request using the mapping that has the specified name or raise an exception if there is no mapping with that name. The following are examples of these types of mapping.
                      <type type="ILogger" mapTo="MyLogger" name="StandardLogger">
                      <lifetime type="singleton" />
                      </type>
                      <type type="ILogger" mapTo="MyFastLogger" name="SuperFastLogger">
                      <lifetime type="external" />
                      </type>
                      
    Cc816062.note(en-us,MSDN.10).gifNote:
    Registration and mapping names are case-sensitive. For example, "SuperfastLogger" and "SuperFastLogger" are two different mapping names. If you use an empty string for the name, the container will resolve and return the result of the default (unnamed) mapping.

    Specifying Object Lifetimes

    Three of the mappings listed in the previous section include a <lifetime> element that defines the lifetime manager to use with that mapping. Unity uses instances of classes named "lifetime managers" to control the lifetime of objects that the container creates and (optionally) stores.

    If you do not specify a lifetime manager when you register a type mapping, as is the case in the first mapping in the example, Unity uses a transient lifetime manager. This means that the container will create a new instance of the target object each time for each request it resolves. It does not store a reference to the object.

    If you want the container to create a singleton instance of the target object, you specify the container-controll lifetime manager. The container will return the same instance of the target object for each request it resolves.

    The third option is to use the externally controlled lifetime manager. This lifetime manager allows you to register type mappings and existing objects with the container so that it will maintain only a weak reference to the objects. As with the container-controll lifetime manager, the container will return the same instance of the target object for each request it resolves. However, it does not hold onto a strong reference to the object after it creates it, which means that the garbage collector can dispose of the object if no other code is holding a strong reference to it. The externally controlled lifetime manager is really only useful when registering existing object instances, where another process or application created the instance and is responsible for disposing of it at the appropriate time. You just want to be able to get at the object through the container.

    The third and fourth mappings in the previous section demonstrate how you can register mappings with the container-controll lifetime manager and the externally controlled lifetime manager.

    Cc816062.note(en-us,MSDN.10).gifNote:
    When you register existing object instances with the container using the <instances> section of the Unity configuration or by executing the RegisterInstance method at run time, Unity uses the container-controlled lifetime manager by default because it assumes that you will register only existing objects when you require singleton behavior.

    Loading the Configuration Information

    Unity does not automatically load configuration data, even if you place it in the Web.config or App.config file. This is because you may want to populate more than one container instance from the data in the configuration, or you may need to create a hierarchy of nested containers and then choose which <container> sections in the configuration to load into each one. Although this behavior may change in future releases, the code required to load configuration data is relatively simple. It also makes it easy for you to store and retrieve the configuration data from elsewhere (such as a database, any XML file, a Web service, a custom text file, or practically any other location).

    To load the configuration data into a container, the first step is to create the container instance or obtain a reference to an existing container. After that, use the standard .NET configuration class methods to access and load the data, as shown here.

    Visual Basic
                Dim myContainer As IUnityContainer = New UnityContainer()
                Dim section As UnityConfigurationSection _
                = CType(ConfigurationManager.GetSection("unity"), UnityConfigurationSection)
                section.Containers("containerOne").Configure(myContainer)
                
                IUnityContainer myContainer = new UnityContainer();
                UnityConfigurationSection section
                = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
                section.Containers["containerOne"].Configure(myContainer);
                

    Cc816062.note(en-us,MSDN.10).gifNote:
    If you have only one container with no name defined in the configuration, you can load the configuration information by specifying the default container like this:
    section.Containers.Default .Configure(myContainer)

    Storing and Retrieving a Unity Container

    The example application stores the container in the ASP.NET Application object so that it can access it on each page load. This avoids recreating and populating the container every time. However, more importantly, it ensures that the objects defined as singletons are not recreated every time. The container is responsible for holding a reference to any objects that you register as singleton instances (using the container-controlled or the externally controlled lifetime manager), so a reference to the container must be retained to preserve this behavior.

    To avoid a possible race condition, the application loads the configuration into a new UnityContainer instance in the Application_Start event handler in the Global.asax file and sets it to null/Nothing in the Application_End event handler, as shown here.

    Visual Basic
                Sub Application_Start(sender as Object, e as EventArgs)
                ' Create and populate a new Unity container from configuration.
                Dim myContainer = New UnityContainer()
                Dim section As UnityConfigurationSection _
                = CType(ConfigurationManager.GetSection("unity"), UnityConfigurationSection)
                section.Containers("containerOne").Configure(myContainer)
                Application("MyContainer") = myContainer
                End Sub
                Sub Application_End(sender as Object, e as EventArgs)
                Application("MyContainer") = Nothing
                End Sub
                
                void Application_Start(object sender, EventArgs e)
                {
                // Create and populate a new Unity container from configuration.
                UnityContainer myContainer = new UnityContainer();
                UnityConfigurationSection section
                = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
                section.Containers["containerOne"].Configure(myContainer);
                Application["MyContainer"] = myContainer;
                }
                void Application_End(object sender, EventArgs e)
                {
                Application["MyContainer"] = null;
                }
                

    Then, in the Page_Load event, the code looks for an existing container in the Application. If it is not there, it reports an error as shown in the following code.

    Visual Basic
                Private myContainer As IUnityContainer = Nothing
                Private outMessage As String = String.Empty
                Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
                Handles Me.Load
                ' Get the existing populated Unity container from the Application object.
                Dim retrievedContainer As Object = Application("MyContainer")
                If retrievedContainer Is Nothing Then
                outMessage = "ERROR: Unity container not populated in Global.asax."
                Else
                ' Found an existing container, so cast it to the correct type.
                myContainer = DirectCast(retrievedContainer, IUnityContainer)
                outMessage = "Retrieved the populated Unity container from the Application object.<p />"
                End If
                lbl_Output.Text &= outMessage
                End Sub
                
                private IUnityContainer myContainer = null;
                private String outMessage = String.Empty;
                protected void Page_Load(object sender, EventArgs e)
                {
                // Get the existing populated Unity container from the Application object.
                Object retrievedContainer   = Application["MyContainer"];
                if (retrievedContainer == null)
                {
                outMessage = "ERROR: Unity container not populated in Global.asax.";
                }
                else
                {
                // Found an existing container, so cast it to the correct type.
                myContainer = retrievedContainer as IUnityContainer;
                outMessage = "Retrieved the populated Unity container from the Application object.<p />";
                }
                lbl_Output.Text += outMessage;
                }
                

    The result is that, when you reload the page by clicking any of the buttons, the message at the bottom of the page reads, "Retrieved the populated Unity container from the Application object."

    Of course, you are not limited to using the Application object to store the container. The decision of where to store it is the same as for any other object that you need to keep in scope between page postbacks. If the registrations and mappings are the same for every user of the application, you can use the Application object as demonstrated in the example. However, if the registrations and mappings are different for every user of the application, or for each group of users, you may choose to store the container in each user's Session object instead. Alternatively, you can implement a custom scenario using the ASP.NET Cache object or even write custom code to serialize the container yourself and store it in some other location.

    Resolving Objects

    After the container is configured, you are ready to use it to resolve dependencies. Unity, like most dependency injection mechanisms, can perform several types of dependency resolution and injection. For example, it can do the following:

    • It can resolve a concrete type that implements a specific interface in response to a request for that interface type and return an instance of the concrete type.
    • It can resolve a concrete type that inherits from a specific base class in response to a request for that base class type and return an instance of the concrete type.
    • It can resolve a concrete type that implements a specific interface or inherits from a specific base class and inject the resolved instance into a parameter of a class constructor (constructor injection).
    • It can resolve a concrete type that implements a specific interface or inherits from a specific base class and inject the resolved instance as the value of a property (property/setter injection).
    • It can resolve a concrete type that implements a specific interface or inherits from a specific base class and inject the resolved instance into a parameter of a method and then execute that method (method call injection).

    The example application uses Unity to resolve concrete types based on requests for an interface type and also demonstrates constructor injection. This section describes using the Resolve method of the container to resolve an interface type. The subsequent section Using Constructor Injection describes how you can use Unity to perform constructor injection.

    Figure 2 shows the result of clicking the first button in the example application. The code requests an object of type IMyInterface, and the Unity container resolves this to an instance of the concrete type MyRealObject.

    Cc816062.b85c2045-6f05-4b6c-bb84-65be3ed118e3(en-us,MSDN.10).png

    Figure 2
    Resolving a concrete type in response to a request for an interface type

    The code in the example application that runs when you click the first button executes the Resolve method of the container, specifying the type IMyInterface. It then calls the method of the dependent object to display a message indicating the object type.

    Visual Basic
                Protected Sub btn_GetInterface_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
                Handles btn_GetInterface.Click
                ' Resolve an instance of the class registered for IMyInterface.
                Dim myInstance As IMyInterface = myContainer.Resolve(Of IMyInterface)()
                ' Display the result of calling the class method.
                lbl_Output.Text &= myInstance.GetObjectStringResult()
                End Sub
                
                protected void btn_GetInterface_Click(object sender, EventArgs e)
                {
                // Resolve an instance of the class registered for IMyInterface.
                IMyInterface  myInstance = myContainer.Resolve<IMyInterface>();
                // Display the result of calling the class method.
                lbl_Output.Text += myInstance.GetObjectStringResult();
                }
                

    Cc816062.note(en-us,MSDN.10).gifNote:
    This example uses the generic overloads of the Unity container methods, such as the Resolve method shown in the previous code. However, all the methods of the container have non-generic overloads so that you can use Unity in languages that do not support generics.


    The container contains a default mapping for the IMyInterface type that specifies it should return an instance of the MyRealObject class.

                <!-- Default (un-named) mapping for IMyInterface to MyRealObject-->
                <type type="IMyInterface" mapTo="MyRealObject" />
                

    Therefore, the container returns an instance of the MyRealObject class. This class implements the IMyInterface interface by containing the single method named GetObjectStringResult, which returns a message indicating what type of object it is.

    Visual Basic
                Public Class MyRealObject
                Implements IMyInterface
                Public Function GetObjectStringResult() As String _
                Implements IMyInterface.GetObjectStringResult
                Return "Hi, I'm the Real Object!"
                End Function
                End Class
                
                public class MyRealObject : IMyInterface
                {
                public string GetObjectStringResult()
                {
                return "Hi, I'm the Real Object!";
                }
                }
                

    As you can see from this example of interface programming, code in an application (or a parent class) can use interfaces to define its dependencies instead of directly specifying the concrete types. At run time, the Unity container will return an instance of the appropriate concrete object type; the instance that will be returned depends on the configuration of the container. What is even more useful is that you can change the configuration of the container at run time—as you will see in the next section.

    Registering Type Mappings at Run Time

    A key that is a combination of the registered type and, optionally, a string name identifies each registration or mapping in the Unity container. If the name is an empty string (a default or unnamed registration), the key is simply the registered type.

    In the example application configuration, we registered a mapping using the IMyInterface type as the key, and mapped it to the MyRealObject type.

                <!-- Default (un-named) mapping for IMyInterface to MyRealObject-->
                <type type="IMyInterface" mapTo="MyRealObject" />
                

    However, if we register a new mapping using the same key, it will replace the existing mapping in the container. Unity provides two methods for dynamically registering types and objects in the container:

    • RegisterType. This method registers 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 optionally specify a name for the registration and specify how the container will manage the lifetime of the object.
    • RegisterInstance. This method generates a registration in the container for an existing object. The container will return a reference to the existing object. You can optionally specify a name for the registration and specify how the container will manage the lifetime of the object.

    When you click the second button in the example application, the code that executes uses the RegisterType method to replace the mapping for the IMyInterface type with a new mapping. It also displays a message at the bottom of the page to indicate this.

    Visual Basic
                Protected Sub btn_RegisterNew_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
                Handles btn_RegisterNew.Click
                ' Register a new default (un-named) mapping with the container.
                ' This replaces the existing mapping in the container for IMyInterface.
                myContainer.RegisterType(Of IMyInterface, MyOtherObject)()
                lbl_Output.Text &= "Registered a new mapping for IMyInterface to MyOtherObject.<p />"
                End Sub
                
                protected void btn_RegisterNew_Click(object sender, EventArgs e)
                {
                // Register a new default (un-named) mapping with the container.
                // This replaces the existing mapping in the container for IMyInterface.
                myContainer.RegisterType<IMyInterface, MyOtherObject>();
                lbl_Output.Text += "Registered a new mapping for IMyInterface to MyOtherObject.<p />";
                }
                

    If you now click the first button in the example application again, the Resolve method will return an instance of the newly registered type MyOtherObject instead. The MyOtherObject class also implements the IMyInterface interface and returns a different message when the code executes its GetObjectStringResult method. Figure 3 shows the result. If you want to repeat the process, you can click the last button on the page to reload the original configuration from Web.config.

    Cc816062.04a8d3f2-5f8c-4ee1-9e18-7e9ec61b4a11(en-us,MSDN.10).png

    Figure 3
    After registering a different mapping, the container resolves the new object type

    You can see how this capability can allow you to dynamically change the mappings, and therefore change the actual concrete object types that Unity will resolve, in response to the changing requirements of your applications.

    The ability to resolve concrete types based on a request for a base class or interface type is also useful for tasks such as service location. As discussed earlier in this article, you can register types as singletons, which is ideal for a service that contains no state, or read-only state, that several parts of an application will use. You register a mapping between the base class or interface type and the concrete service class, and then you simply call the Resolve method and specify that base class or interface type whenever you need to use the service. Unity will create an instance of the service on the first request, and then it returns the same instance in response to all subsequent requests.

    Using Named Registrations

    So far, the example has used only default (unnamed) mappings in the container. However, the configuration applied to the container also defines two named mappings for the ILogger interface.

                <!-- Named mapping for ILogger to StandardLogger-->
                <type type="ILogger" mapTo="MyLogger" name="StandardLogger">
                <lifetime type="singleton" />
                </type>
                <!-- Named mapping for ILogger to SuperFastLogger-->
                <type type="ILogger" mapTo="MyFastLogger" name="SuperFastLogger">
                <lifetime type="external" />
                </type>
                

    In this case, the keys for the two mappings differ only on the name. Code can request an instance of a class that implements the ILogger interface and specify in addition the name of the mapping that Unity should use.

    The example application contains a drop-down list where you can select either of the mapping names. When you click the associated button, code calls the Resolve method of the container that specifies the type ILogger and the name you select in the list, and then it calls the WriteMessage method of the resolved object instance to display a message indicating which object type was returned.

    Visual Basic
                Protected Sub btn_GetLogger_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
                Handles btn_GetLogger.Click
                ' Get the logger name selected in the list box. 
                ' This name specifies the type of logger required.
                Dim loggerType As String = lst_LoggerType.SelectedValue
                ' Resolve an instance of the appropriate class registered for ILogger 
                ' using the specified mapping name (may be empty for the default mapping).
                Dim myInstance As ILogger = myContainer.Resolve(Of ILogger)(loggerType)
                ' Get the logger to write a message and display the result.
                lbl_Output.Text &= myInstance.WriteMessage("HELLO UNITY!")
                End Sub
                
                protected void btn_GetLogger_Click(object sender, EventArgs e)
                {
                // Get the logger name selected in the list box. 
                // This name specifies the type of logger required.
                String loggerType = lst_LoggerType.SelectedValue;
                // Resolve an instance of the appropriate class registered for ILogger 
                // using the specified mapping name (may be empty for the default mapping).
                ILogger myInstance = myContainer.Resolve<ILogger>(loggerType);
                // Get the logger to write a message and display the result.
                lbl_Output.Text += myInstance.WriteMessage("HELLO UNITY!");
                }
                

    The two concrete implementations of the ILogger interface are very simple. They implement the WriteMessage method that takes a string value as a parameter and returns the string (in the real world they would, of course, write the log message to the appropriate target). The next listing shows the MyLogger class.

    Visual Basic
                Public Class MyLogger
                Implements ILogger
                Public Function WriteMessage(ByVal message As String) As String _
                Implements ILogger.WriteMessage
                ' Write to the log file here.
                Return "Hi, I'm the MyLogger, and this is the output I logged: '" & message & "'."
                End Function
                End Class
                
                public class MyLogger : ILogger
                {
                public string WriteMessage(string message)
                {
                // Write to the log file here.
                return "Hi, I'm the MyLogger, and this is the output I logged: '" + message + "'.";
                }
                }
                

    Figure 4 shows the result of clicking the third button in the example application after selecting the two logger names in the drop-down list. You can see that Unity resolved the two concrete types MyLogger and MyFastLogger based on the name of mapping selected in the drop-down list.

    Cc816062.a41751a8-8d93-4bf6-b901-a2c79efa8762(en-us,MSDN.10).png

    Figure 4
    Using named mapping to resolve the same type with different named mappings

    Although this example uses a simple drop-down list, you can imagine that the name you use to resolve an instance of the appropriate concrete type could come from almost anywhere. It is just a string value, so it could be stored in the <appSettings> section of a configuration file, retrieved from a database or Web service, selected by the user from a list, modified by Windows Group Policy for external management, and more.

    Using Constructor Injection

    Although I talk about dependency injection throughout this article, we have not actually done any injection, as such, so far. We have just been using Unity to resolve object types. Where Unity, and the dependency injection design pattern itself, really becomes useful is when the container generates instances of objects that have dependencies. It can automatically resolve the dependent object types required by the objects it creates, generate the appropriate concrete types, and then inject these concrete instances into the object it is creating.

    Figure 5 shows a schematic view of the dependency injection process that Unity can accomplish.

    Cc816062.9444d795-6e6e-4d23-ab27-77467e20123a(en-us,MSDN.10).png

    Figure 5
    A schematic view of the dependency injection process

    The example application demonstrates only constructor injection, but the process is similar for other types of injection. However, there are fundamental differences in the way you apply dependency injection using Unity. The following are the types of injection together with descriptions of how they are applied using Unity:

    • Constructor injection. This type of injection occurs automatically. When you create an instance of an object using the Unity container, it will automatically detect the constructor with the largest number of parameters and execute this, generating instances of each object defined in the constructor parameters. It resolves each parameter type through the container, applying any registrations or mappings for that type. If you want to specify a particular constructor for Unity to use, you can add the InjectionConstructor attribute to that constructor in the target class.
    • Property (setter) injection. This type of injection is optional. You can add the Dependency attribute to any property declarations that you want Unity to resolve through the container. Unity will resolve that property type and set the value of the property to an instance of the resolved type.
    • Method call injection. This type of injection is also optional. You can add the InjectionMethod attribute to any method declarations where you want Unity to resolve the method parameters through the container. Unity will resolve each parameter type and set the value of that parameter to an instance of the resolved type, and then it will execute the method. Method call injection is useful if you need to execute some type of initialization method within the target object.

    Cc816062.note(en-us,MSDN.10).gifNote:
    You can configure constructor, property (setter), and method call injection for a class in the configuration for a container, either by reading configuration from an XML source or by dynamically creating the appropriate configuration element types at run time.


    The example application contains a class named MyObjectWithInjectedLogger that contains a single constructor. This constructor takes as a parameter an instance of a class that implements the ILogger interface. The MyObjectWithInjectedLogger class itself implements the IMyInterface interface, and therefore contains the GetObjectStringResult method.

    The GetObjectStringResult method executes the WriteMessage method of the ILogger implementation to create a log message and then returns a message indicating the object type and appends to it the log message, as shown here.

    Visual Basic
                Public Class MyObjectWithInjectedLogger
                Implements IMyInterface
                Private theLogger As ILogger
                Public Sub New(ByVal log As ILogger)
                theLogger = log
                End Sub
                Public Function GetObjectStringResult() As String _
                Implements IMyInterface.GetObjectStringResult
                Dim output As String = theLogger.WriteMessage("SOME REALLY IMPORTANT MESSAGE")
                Return "Hi, I'm the 'MyObjectWithInjectedLogger' object, and I've got a " _
                & "reference to some type of Logger!<p />" & output
                End Function
                End Class
                
                public class MyObjectWithInjectedLogger : IMyInterface
                {
                private ILogger theLogger;
                public MyObjectWithInjectedLogger(ILogger log)
                {
                theLogger = log;
                }
                public string GetObjectStringResult()
                {
                string output = theLogger.WriteMessage("SOME REALLY IMPORTANT MESSAGE");
                return "Hi, I'm the 'MyObjectWithInjectedLogger' object, and I've got a "
                + "reference to some type of Logger!<p />" + output;
                }
                }
                

    The main ASP.NET page of the example application contains a button that resolves an instance of the MyObjectWithInjectedLogger class through the Unity container, and then it calls the GetObjectStringResult method.

    Visual Basic
                Protected Sub btn_ConstructorInjection_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
                Handles btn_ConstructorInjection.Click
                ' Resolve an instance of the concrete MyObjectWithInjectedLogger class. 
                ' This class contains a reference to ILogger in the constructor parameters.
                Dim myInstance As MyObjectWithInjectedLogger _
                = myContainer.Resolve(Of MyObjectWithInjectedLogger)()
                ' Get the injected logger to write a message and display the result.
                lbl_Output.Text &= myInstance.GetObjectStringResult()
                End Sub
                
                protected void btn_ConstructorInjection_Click(object sender, EventArgs e)
                {
                // Resolve an instance of the concrete MyObjectWithInjectedLogger class. 
                // This class contains a reference to ILogger in the constructor parameters.
                MyObjectWithInjectedLogger myInstance
                = myContainer.Resolve<MyObjectWithInjectedLogger>();
                // Get the injected logger to write a message and display the result.
                lbl_Output.Text += myInstance.GetObjectStringResult();
                }
                

    If you look back at the configuration of the container, you will see that there is no mention of the MyObjectWithInjectedLogger class. When Unity resolves a type that has no registrations or mappings in the container, it simply returns an instance of the type you specify. However, it passes the new instance through the dependency injection mechanism, which means that constructor, property, and method call injection will occur where there is an appropriate constructor or attributes.

    In the example application, this means that Unity will discover the single constructor in the MyObjectWithInjectedLogger class and find the ILogger type parameter. It will resolve this type through the container, which includes the following default mapping for the ILogger type.

                <!-- Default (un-named) mapping for ILogger to MyLogger -->
                <type type="ILogger" mapTo="MyLogger">
                <lifetime type="singleton" />
                </type>
                

    This means that Unity will either create a singleton instance of the MyLogger class or retrieve an existing instance (the mapping specifies the singleton lifetime manager), and then it will inject this into the constructor parameter. The result is that the MyObjectWithInjectedLogger class will contain a reference to an instance of the MyLogger class that it can use to generate the log message.


    Cc816062.note(en-us,MSDN.10).gifNote:
    Because the injected object is created though the Unity container, any dependencies it may have will also be resolved and the appropriate types injected into this object. This process continues until all dependencies are satisfied. However, you must be careful not to inadvertently create circular references, where two injected objects specify each other as parameters of their constructors, injected method parameters, or injected property values.


    Figure 6 shows the result of clicking this button. You can see that the code in the main ASP.NET page received from Unity is an instance of the MyObjectWithInjectedLogger class and that the GetObjectStringResult method of that class used the injected MyLogger instance to create the log message. At no point did we have to decide which type of logger to use, create an instance, or set the constructor parameter.

    Cc816062.fda9ee70-ddd4-4f08-a954-8fb71871c9d2(en-us,MSDN.10).png

    Figure 6
    Constructor injection automatically creates an appropriate logger instance, which the MyObjectWithInjectedLogger class uses to write a log message

    To see how useful this approach can be, edit the Web.config file by changing the mapping for the default ILogger registration to point to the MyFastLogger type, as shown here.

                <!-- Default (un-named) mapping for ILogger to MyLogger -->
                <type type="ILogger" mapTo="MyFastLogger">
                <lifetime type="singleton" />
                </type>
                

    Now, when you click the fourth button again, Unity will inject an instance of the MyFastLogger class into the constructor parameter of the MyObjectWithInjectedLogger class, which will then use this logger type instead, as shown in Figure 7.

    Cc816062.b26a2109-7da6-4b05-bddb-6b0d98b66e5e(en-us,MSDN.10).png

    Figure 7
    Changing the configuration of the container automatically changes the concrete type injected into the constructor parameter of the target class

    Conclusion

    Of all the standard design patterns, those that are proving to be most useful in architecting complex modern applications are the patterns associated with the creation and lifetime management of objects. As applications become larger, more distributed, and use standard components to manage crosscutting concerns, architects must find ways to reduce the dependencies between applications, objects, and components.

    The future for building these types of applications is the use of dependency injection techniques that decouple the components and services of an application, provide better reusability, and simplifying the design and code. The move toward aspect-oriented programming provides better management of crosscutting concerns such as validation, logging, caching, and instrumentation. It also aligns well with the techniques for implementing the Dependency Injection and the Inversion of Control (IoC) design patterns. In these patterns, objects take over control of the tasks that they must execute during initialization, and the order that these tasks will execute.

    The Unity Application Block provides the Unity container and associated mechanisms for managing container configuration. Unity can provide a full range of type resolution, object creation, and dependency injection functions. As you have seen in this example, Unity considerably reduces the code you need to write, while supporting interface-programming techniques that promote decoupling between objects.

    You can use Unity to create mappings between interfaces or base classes and the matching concrete types, manage the lifetime of these types, and inject instances into other objects. Dependency injection is useful in many situations and can support many other design pattern implementations. For example, you can use it to inject the controller or presenter when implementing the Model-View-Controller (MVC) or Model-View-Presenter (MVP) design pattern.

    In addition, you can use container extensions to expand the capabilities of Unity. A great example is the implementation of the Publish/Subscribe design pattern implemented by the EventBroker extension that is part of the QuickStarts included with the Unity Application Block.

    For more information about the Unity Application Block, see the following:

    Cc816062.practices(en-us,MSDN.10).png

  • 相关阅读:
    设计模式---工厂模式和抽象工厂模式
    设计模式---简单工厂模式
    设计模式---设计模式的分类及六大原则
    分布式---Raft算法
    分布式---Paxos算法
    分布式---CAP和BASE理论
    分布式---分布式事务
    分布式---分布式锁
    css
    react生命周期
  • 原文地址:https://www.cnblogs.com/think8848/p/1313038.html
Copyright © 2011-2022 走看看