首页 > 学院 > 开发设计 > 正文

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

2019-11-14 16:03:58
字体:
来源:转载
供稿:网友

本文内容

  • 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>>>())));

参考资料


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

 

下载 MyDemo and DIwithUnitySample

下载 MyDemo and DIwithUnitySample v2(补充)

下载 Unity3

下载 Unity bootstrapper for ASP.NET MVC

下载 Unity bootstrapper for ASP.NET WebApi

 

Unity xml 配置文件(2)


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表