.NET 用 Unity 依赖注入——概述注册和解析类型(1)

本文内容

  • Unity 概述
  • 环境
  • 一个真实的例子
  • 类型注册(Type Registrations)
  • 解析类型(Resolving Types)

跳槽,新公司使用了 Unity,初步看了一下,公司的使用还是比较简单的,其实 Unity 本身的用法很多。另外,前段时间我翻译和实验了 Martin Fowler 的《Java 控制反转和依赖注入模式》,本文是 .NET 平台下的依赖注入。

Unity 涉及的内容和用法比较多,之后慢慢说,本文先大概介绍如何用 Unity 进行依赖注入,它基本可以分为两个操作:注册(RegisterType)和解析(Resolve),也就是说,先注册类型;然后解析类型,返回创建的对象。

下载 MyDemo and DIwithUnitySample

下载 MyDemo and DIwithUnitySample v2(补充)

下载 Unity 3

下载 Unity bootstrapper for ASP.NET MVC

下载 Unity bootstrapper for ASP.NET WebApi

Unity 概述


Unity Application Block(Unity)是一个轻量级的,可扩展的依赖注入容器,它支持构造函数注入,属性注入和方法调用注入。它为开发人员提供了以下优点:

  • 提供简化的对象创建,特别是层级对象结构和依赖,简化应用程序代码;
  • 支持需求抽象;这可以让开发者在运行时或是配置文件指定依赖,简化横切关注点(crosscutting concerns)的管理;
  • 通过延迟组件配置到容器,增加了灵活性;
  • 具有服务定位器功能;这可以让客户存储或缓存容器。对 ASP.NET Web 应用程序特别有用,开发者可以在 ASP.NET 会话或应用程序中持久容器。

环境


  • Windows 7 旗舰版 SP1
  • Microsoft Visual Studio Ultimate 2013 Update 4

一个真实的例子


咋看上去,RegisterTypes 方法有点复杂;下面会详细讨论各种的类型注册;再讨论应用程序如何注册以在运行时需要时解析类型。这个例子也说明,在你应用程序的一个方法内如何完成所有类型的注册。

public static void RegisterTypes(IUnityContainer container)
        {
            Trace.WriteLine(string.Format("Called RegisterTypes in ContainerBootstrapper"), "UNITY");
 
            var storageAccountType = typeof(StorageAccount);
            var retryPolicyFactoryType = typeof(IRetryPolicyFactory);
 
            // 实例注册
            StorageAccount account =
              ApplicationConfiguration.GetStorageAccount("DataConnectionString");
            container.RegisterInstance(account);
 
            // 注册工厂
            container
              .RegisterInstance<IRetryPolicyFactory>(new ConfiguredRetryPolicyFactory())
              .RegisterType<ISurveyAnswerContainerFactory, SurveyAnswerContainerFactory>(new ContainerControlledLifetimeManager());
 
            // 注册 table 类型
            container
              .RegisterType<IDataTable<SurveyRow>, DataTable<SurveyRow>>(new InjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.SurveysTableName))
              .RegisterType<IDataTable<QuestionRow>, DataTable<QuestionRow>>(new InjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.QuestionsTableName));
 
            // 注册 message queue 类型, 使用带泛型的 typeof
            container
              .RegisterType(
                  typeof(IMessageQueue<>),
                  typeof(MessageQueue<>),
                  new InjectionConstructor(storageAccountType, retryPolicyFactoryType, typeof(String)));
 
            // 注册 blob 类型
            container
              .RegisterType<IBlobContainer<List<string>>,
                EntitiesBlobContainer<List<string>>>(
                  new InjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.SurveyAnswersListsBlobName))
              .RegisterType<IBlobContainer<Tenant>,
                EntitiesBlobContainer<Tenant>>(
                  new InjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.TenantsBlobName))
              .RegisterType<IBlobContainer<byte[]>,
                FilesBlobContainer>(
                  new InjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.LogosBlobName, "image/jpeg"))
              .RegisterType<IBlobContainer<SurveyAnswer>,
                EntitiesBlobContainer<SurveyAnswer>>(
                  new InjectionConstructor(storageAccountType, retryPolicyFactoryType, typeof(String)));
 
            // 注册 store 类型
            container
              .RegisterType<ISurveyStore, SurveyStore>()
              .RegisterType<ITenantStore, TenantStore>()
              .RegisterType<ISurveyAnswerStore, SurveyAnswerStore>(
                new InjectionFactory((c, t, s) => new SurveyAnswerStore(
                  container.Resolve<ITenantStore>(),
                  container.Resolve<ISurveyAnswerContainerFactory>(),
                  container.Resolve<IMessageQueue<SurveyAnswerStoredMessage>>(new ParameterOverride("queueName", Constants.StandardAnswerQueueName)),
                  container.Resolve<IMessageQueue<SurveyAnswerStoredMessage>>(new ParameterOverride("queueName", Constants.PremiumAnswerQueueName)),
                  container.Resolve<IBlobContainer<List<String>>>())));
        }

类型注册(Type Registrations)


上面的代码列出了用 Unity 容器完成不同类型的注册。下面单独说明。

实例注册

最简单的类型注册就是实例注册,Unity 容器以单件实例来负责维护对象的引用。例如:

StorageAccount account =
              ApplicationConfiguration.GetStorageAccount("DataConnectionString");
            container.RegisterInstance(account);

StorageAccount 对象在注册时间就被创建,并且在容器中只有一个该对象的实例。这个单独的实例被容器中很多其他对象共享。

你也可以在 RegisterType 方法中使用 ContainerControlledLifetimeManager 类来创建单件实例,有容器维护对象的引用。

简单类型注册

最常见的类型注册是把一个接口类型映射到一个具体的类型。例如:

container.RegisterType<ISurveyStore, SurveyStore>();

接下来,你可以按如下代码解析 ISurveyStore 类型,容器将把任何所需的依赖注入到 SurveyStore 对象,并创建。

var surveyStore = container.Resolve<ISurveyStore>();

构造函数注入

下面的代码段说明 DataTable 类的具有三个参数的构造函数。

public DataTable(StorageAccount account, IRetryPolicyFactory retryPolicyFactory, string tableName)
    : base(retryPolicyFactory)
{
    Trace.WriteLine(string.Format("Called constructor in DataTable with account={0}, tableName={1}", account.ConnectionString, tableName), "UNITY");
    this.account = account;
    this.tableName = tableName;
}

在容器中注册 DataTable 类型会包含一个容器如何解析参数类型的 InjectionConstructor 定义:Storage-Account 和 RetryPolicyFactory 类型,以及表名。

container
             .RegisterType<IDataTable<SurveyRow>, DataTable<SurveyRow>>(new InjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.SurveysTableName))
             .RegisterType<IDataTable<QuestionRow>, DataTable<QuestionRow>>(new InjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.QuestionsTableName));

blob 类型也使用类似的方法:

container
              .RegisterType<IBlobContainer<List<string>>,
                EntitiesBlobContainer<List<string>>>(
                  new InjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.SurveyAnswersListsBlobName))
              .RegisterType<IBlobContainer<Tenant>,
                EntitiesBlobContainer<Tenant>>(
                  new InjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.TenantsBlobName))
              .RegisterType<IBlobContainer<byte[]>,
                FilesBlobContainer>(
                  new InjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.LogosBlobName, "image/jpeg"))
              .RegisterType<IBlobContainer<SurveyAnswer>,
                EntitiesBlobContainer<SurveyAnswer>>(
                  new InjectionConstructor(storageAccountType, retryPolicyFactoryType, typeof(String)));

这里的 blob,跟实际数据库中的 Binary Lob 无关。

除了构造函数注入外,Unity 也支持属性和方法注册。如果你使用属性注入,应该确保属性具有默认值。这个很容易忘记。

注册开放泛型

下面代码段使用稍微不同的方法注册 MessageQueue 类型:它使用 RegisterTypes 方法的一个重载。

container
             .RegisterType(
                 typeof(IMessageQueue<>),
                 typeof(MessageQueue<>),
                 new InjectionConstructor(storageAccountType, retryPolicyFactoryType, typeof(String)));

所谓“开放的泛型”,是泛型的尖括号里没有内容。

该方法使你用任何参数解析 MessageQueue 类型。下面代码段使用 SurveyAnswerStoredMessage 类型:

container.Resolve<IMessageQueue<SurveyAnswerStoredMessage>>(...);

参数覆盖

本文最开始的代码中,InjectionConstructor 构造函数的其中一个参数是 typeof(string)。如下所示:

container
              .RegisterType(
                  typeof(IMessageQueue<>),
                  typeof(MessageQueue<>),
                  new InjectionConstructor(storageAccountType, retryPolicyFactoryType, typeof(String)));
……
container
             .RegisterType<IBlobContainer<SurveyAnswer>,
               EntitiesBlobContainer<SurveyAnswer>>(
                 new InjectionConstructor(storageAccountType, retryPolicyFactoryType, typeof(String)));

容器不包括解决这种类型的注册。这提供了一个方便的方法来传递在注册时未知的参数值,容器通过 ParameterOverride 类型来创建实例。

解析类型(Resolving Types)


可以在三个地方完成注册:在初始化存储的一个单独的应用程序(a standalone application that initializes the storage),在 Web 应用程序的开始阶段(web application’s start-up phase),以及一个工厂类(factory class)。

简单解析

在简单的独立的应用程序中使用很简单:调用 RegisterTypes 方法完成注册,解析对象,然后调用它们的 Initialize 方法完成初始化工作。如下所示:

static void Main(string[] args)
{
    TextWriterTraceListener tr1 = new TextWriterTraceListener(System.Console.Out);
    Debug.Listeners.Add(tr1);
 
    using (var container = new UnityContainer())
    {
        Console.WriteLine("# Performing Registrations...");
        ContainerBootstrapper.RegisterTypes(container);
        Console.WriteLine("Container has {0} Registrations:",
              container.Registrations.Count());
        foreach (ContainerRegistration item in container.Registrations)
        {
            Console.WriteLine(item.GetMappingAsString());
        }
        Console.WriteLine();
        Console.WriteLine("# Performing type resolutions...");
        container.Resolve<ISurveyStore>().Initialize();
        container.Resolve<ISurveyAnswerStore>().Initialize();
        container.Resolve<ITenantStore>().Initialize();
 
        Console.WriteLine("Done");
        Console.ReadLine();
    }
}

Initialization 方法执行后,容器会被释放。

在一个 MVC 应用程序中解析

在 MVC 应用程序中的使用更要复杂点:应用程序配置容器,这样应用程序在启动时就会使用,之后,解析各种类型。记住,这是 ASP.NET MVC 应用程序;因此,容器必须能注入 MVC 控制器类。“Unity bootstrapper for ASP.NET MVC”NuGet package 简化了这些。当你将该包添加到你的项目后,会生成一个 UnityConfig 类,下面代码段说明该类的注册方法。你可以选择从你的应用程序文件加载 Unity 配置或直接添加注册。

using System;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
 
namespace UnityBootstrapperForMVCDemo.App_Start
{
    /// <summary>
    /// Specifies the Unity configuration for the main container.
    /// </summary>
    public class UnityConfig
    {
        #region Unity Container
        private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() =>
        {
            var container = new UnityContainer();
            RegisterTypes(container);
            return container;
        });
 
        /// <summary>
        /// Gets the configured Unity container.
        /// </summary>
        public static IUnityContainer GetConfiguredContainer()
        {
            return container.Value;
        }
        #endregion
 
        /// <summary>Registers the type mappings with the Unity container.</summary>
        /// <param name="container">The unity container to configure.</param>
        /// <remarks>There is no need to register concrete types such as controllers or API controllers (unless you want to 
        /// change the defaults), as Unity allows resolving a concrete type even if it was not previously registered.</remarks>
        public static void RegisterTypes(IUnityContainer container)
        {
            // NOTE: To load from web.config uncomment the line below. Make sure to add a Microsoft.Practices.Unity.Configuration to the using statements.
            // container.LoadConfiguration();
 
            // TODO: Register your types here
            // container.RegisterType<IProductRepository, ProductRepository>();
        }
    }
}

“Unity bootstrapper for ASP.NET MVC”提供一个 UnityDependencyResolver 类,该类从容器解析控制器。如果你需要为控制器类配置注入,那么你需要手动添加注册,或者向控制器类注入属性。

public class ManagementController : Controller
   {
       private readonly ITenantStore tenantStore;
 
       public ManagementController(ITenantStore tenantStore)
       {
           this.tenantStore = tenantStore;
       }
        ……
   }

在 MVC 和 WebAPI 应用程序中使用 Per Request Lifetime Manager

前面的例子展示了如何使用“Unity bootstrapper for ASP.NET MVC”NuGet package 在 MVC 应用程序中处理注册和解析控制器。该软件包还包括一个 PerRequestLifetime 管理器。该生命周期管理器使你可以创建一个已注册类型的实例,其行为就像一个 HTTP 请求范围内的单件。

如果您正在使用的ASP.NET Web API 项目,有一个“Unity bootstrapper for ASP.NET WebApi”NuGet软件包,会提供了等同的功能(搜索Unity3中的NuGet包管理器)。你可以在同一个项目中同时使用了“Unity bootstrapper for ASP.NET WebApi”和“Unity bootstrapper for ASP.NET MVC”,它们将共享同一个容器配置类。

用实时信息的解析

在设计时,你不会总知道你需要构造一个依赖的值。在下面例子中显示,用户提供一个应用程序必须在运行时必须创建的 blob 容器。例子中,类型解析发生在一个工厂类,它在注册时确定一个构造函数的参数。下面的代码示例显示了这个工厂类。

public class SurveyAnswerContainerFactory : ISurveyAnswerContainerFactory
{
    private readonly IUnityContainer unityContainer;
 
    public SurveyAnswerContainerFactory(IUnityContainer unityContainer)
    {
        Trace.WriteLine(string.Format("Called constructor in SurveyAnswerContainerFactory"), "UNITY");
 
        this.unityContainer = unityContainer;
    }
 
    public IBlobContainer<SurveyAnswer> Create(string tenant, string surveySlug)
    {
        Trace.WriteLine(string.Format("Called Create in SurveyAnswerContainerFactory with tenant={0}, surveySlug={1}", tenant, surveySlug), "UNITY");
 
        var blobContainerName = string.Format(
            CultureInfo.InvariantCulture,
            "surveyanswers-{0}-{1}",
            tenant.ToLowerInvariant(),
            surveySlug.ToLowerInvariant());
        return this.unityContainer.Resolve<IBlobContainer<SurveyAnswer>>(
            new ParameterOverride("blobContainerName", blobContainerName));
    }
}

在本例中,Resolve 方法使用一个参数覆盖,以对 blobContainerName 参数提供一个值,来构造 Entities-BlobContainer 类,该类已在容器注册,被注入到 SurveyAnswerContainerFactory 对象。

你前面看到应用程序如何在注入构造函数用 string 参数注册 IBlobContainer<Survey-Answer> 类型。如果没有参数覆盖,这个注册将失败,因为容器无法解析 string 类型。

你还可以在 ContainerBootstrapper 类看到参数覆盖的使用。本例中,参数覆盖提供 message queues 的创建。当已注册的 InjectionFactory 执行时,在解析时提供参数覆盖。

container
              .RegisterType<ISurveyAnswerStore, SurveyAnswerStore>(
                new InjectionFactory((c, t, s) => new SurveyAnswerStore(
                  container.Resolve<ITenantStore>(),
                  container.Resolve<ISurveyAnswerContainerFactory>(),
                  container.Resolve<IMessageQueue<SurveyAnswerStoredMessage>>(new ParameterOverride("queueName", Constants.StandardAnswerQueueName)),
                  container.Resolve<IMessageQueue<SurveyAnswerStoredMessage>>(new ParameterOverride("queueName", Constants.PremiumAnswerQueueName)),
                  container.Resolve<IBlobContainer<List<String>>>())));

参考资料


 

下载 MyDemo and DIwithUnitySample

下载 MyDemo and DIwithUnitySample v2(补充)

下载 Unity3

下载 Unity bootstrapper for ASP.NET MVC

下载 Unity bootstrapper for ASP.NET WebApi

 

Unity XML 配置文件(2)

你可能感兴趣的:(unity)