你的位置:首页 > ASP.net教程

[ASP.net教程].NET 用 Unity 依赖注入概述注册和解析类型(一)


本文内容

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

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

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

下载 MyDemo and DIwithUnitySample

下载 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 会话或应用程序中持久容器。

一个真实的例子


咋看上去,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>>>())));


参考资料


  • Unity Application Block 1.2 - October 2008,该链接的内容已经过期,不再更新,但还是有一定参考价值。关于最新的 Unity 信息在 Unity Application Block site

 

下载 MyDemo and DIwithUnitySample

下载 Unity3

下载 Unity bootstrapper for ASP.NET MVC

下载 Unity bootstrapper for ASP.NET WebApi