.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>>>())));
参考资料
- Unity Application Block 1.2 - October 2008,该链接的内容已经过期,不再更新,但还是有一定参考价值。关于最新的 Unity 信息在 Unity Application Block site
下载 MyDemo and DIwithUnitySample
下载 MyDemo and DIwithUnitySample v2(补充)
下载 Unity bootstrapper for ASP.NET MVC
下载 Unity bootstrapper for ASP.NET WebApi