本文内容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 forasp.netMVC下载 Unity bootstrapper for ASP.NET WebApiUnity 概述UnityapplicationBlock(Unity)是一个轻量级的,可扩展的依赖注入容器,它支持构造函数注入,属性注入和方法调用注入。它为开发人员提供了以下优点:
提供简化的对象创建,特别是层级对象结构和依赖,简化应用程序代码;支持需求抽象;这可以让开发者在运行时或是配置文件指定依赖,简化横切关注点(crosscutting concerns)的管理;通过延迟组件配置到容器,增加了灵活性;具有服务定位器功能;这可以让客户存储或缓存容器。对 ASP.NET Web 应用程序特别有用,开发者可以在 ASP.NET 会话或应用程序中持久容器。环境Windows 7旗舰版 SP1Microsoft Visual Studio Ultimate 2013 Update 4一个真实的例子咋看上去,RegisterTypes 方法有点复杂;下面会详细讨论各种的类型注册;再讨论应用程序如何注册以在运行时需要时解析类型。这个例子也说明,在你应用程序的一个方法内如何完成所有类型的注册。
publicstaticvoidRegisterTypes(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>(newConfiguredRetryPolicyFactory())
.RegisterType<ISurveyAnswerContainerFactory, SurveyAnswerContainerFactory>(newContainerControlledLifetimeManager());
// 注册 table 类型
container
.RegisterType<IDataTable<SurveyRow>, DataTable<SurveyRow>>(newInjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.SurveysTableName))
.RegisterType<IDataTable<QuestionRow>, DataTable<QuestionRow>>(newInjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.QuestionsTableName));
// 注册 message queue 类型, 使用带泛型的 typeof
container
.RegisterType(
typeof(IMessageQueue<>),
typeof(MessageQueue<>),
newInjectionConstructor(storageAccountType, retryPolicyFactoryType,typeof(String)));
// 注册 blob 类型
container
.RegisterType<IBlobContainer<List<string>>,
EntitiesBlobContainer<List<string>>>(
newInjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.SurveyAnswersListsBlobName))
.RegisterType<IBlobContainer<Tenant>,
EntitiesBlobContainer<Tenant>>(
newInjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.TenantsBlobName))
.RegisterType<IBlobContainer<byte[]>,
FilesBlobContainer>(
newInjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.LogosBlobName,"image/jpeg"))
.RegisterType<IBlobContainer<SurveyAnswer>,
EntitiesBlobContainer<SurveyAnswer>>(
newInjectionConstructor(storageAccountType, retryPolicyFactoryType,typeof(String)));
// 注册 store 类型
container
.RegisterType<ISurveyStore, SurveyStore>()
.RegisterType<ITenantStore, TenantStore>()
.RegisterType<ISurveyAnswerStore, SurveyAnswerStore>(
newInjectionFactory((c, t, s) =>newSurveyAnswerStore(
container.Resolve<ITenantStore>(),
container.Resolve<ISurveyAnswerContainerFactory>(),
container.Resolve<IMessageQueue<SurveyAnswerStoredMessage>>(newParameterOverride("queueName", Constants.StandardAnswerQueueName)),
container.Resolve<IMessageQueue<SurveyAnswerStoredMessage>>(newParameterOverride("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类的具有三个参数的构造函数。
publicDataTable(StorageAccount account, IRetryPolicyFactory retryPolicyFactory,stringtableName)
: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>>(newInjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.SurveysTableName))
.RegisterType<IDataTable<QuestionRow>, DataTable<QuestionRow>>(newInjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.QuestionsTableName));
blob 类型也使用类似的方法:
container
.RegisterType<IBlobContainer<List<string>>,
EntitiesBlobContainer<List<string>>>(
newInjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.SurveyAnswersListsBlobName))
.RegisterType<IBlobContainer<Tenant>,
EntitiesBlobContainer<Tenant>>(
newInjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.TenantsBlobName))
.RegisterType<IBlobContainer<byte[]>,
FilesBlobContainer>(
newInjectionConstructor(storageAccountType, retryPolicyFactoryType, Constants.LogosBlobName,"image/jpeg"))
.RegisterType<IBlobContainer<SurveyAnswer>,
EntitiesBlobContainer<SurveyAnswer>>(
newInjectionConstructor(storageAccountType, retryPolicyFactoryType,typeof(String)));
这里的 blob,跟实际数据库中的 Binary Lob 无关。
除了构造函数注入外,Unity 也支持属性和方法注册。如果你使用属性注入,应该确保属性具有默认值。这个很容易忘记。
注册开放泛型下面代码段使用稍微不同的方法注册MessageQueue类型:它使用RegisterTypes方法的一个重载。
container
.RegisterType(
typeof(IMessageQueue<>),
typeof(MessageQueue<>),
newInjectionConstructor(storageAccountType, retryPolicyFactoryType,typeof(String)));
所谓“开放的泛型”,是泛型的尖括号里没有内容。
该方法使你用任何参数解析MessageQueue类型。下面代码段使用SurveyAnswerStoredMessage类型:
container.Resolve<IMessageQueue<SurveyAnswerStoredMessage>>(...);
参数覆盖本文最开始的代码中,InjectionConstructor构造函数的其中一个参数是typeof(string)。如下所示:
container
.RegisterType(
typeof(IMessageQueue<>),
typeof(MessageQueue<>),
newInjectionConstructor(storageAccountType, retryPolicyFactoryType,typeof(String)));
……
container
.RegisterType<IBlobContainer<SurveyAnswer>,
EntitiesBlobContainer<SurveyAnswer>>(
newInjectionConstructor(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 方法完成初始化工作。如下所示:
staticvoidMain(string[] args)
{
TextWriterTraceListener tr1 =newTextWriterTraceListener(System.Console.Out);
Debug.Listeners.Add(tr1);
using(var container =newUnityContainer())
{
Console.WriteLine("# Performing Registrations...");
ContainerBootstrapper.RegisterTypes(container);
Console.WriteLine("Container has {0} Registrations:",
container.Registrations.Count());
foreach(ContainerRegistration itemincontainer.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 配置或直接添加注册。
usingSystem;
usingMicrosoft.Practices.Unity;
usingMicrosoft.Practices.Unity.Configuration;
namespaceUnityBootstrapperForMVCDemo.App_Start
{
/// <summary>
/// Specifies the Unity configuration for the main container.
/// </summary>
publicclassUnityConfig
{
#regionUnity Container
privatestaticLazy<IUnityContainer> container =newLazy<IUnityContainer>(() =>
{
var container =newUnityContainer();
RegisterTypes(container);
returncontainer;
});
/// <summary>
/// Gets the configured Unity container.
/// </summary>
publicstaticIUnityContainer GetConfiguredContainer()
{
returncontainer.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>
publicstaticvoidRegisterTypes(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 类,该类从容器解析控制器。如果你需要为控制器类配置注入,那么你需要手动添加注册,或者向控制器类注入属性。
publicclassManagementController : Controller
{
privatereadonlyITenantStore tenantStore;
publicManagementController(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 容器。例子中,类型解析发生在一个工厂类,它在注册时确定一个构造函数的参数。下面的代码示例显示了这个工厂类。
publicclassSurveyAnswerContainerFactory : ISurveyAnswerContainerFactory
{
privatereadonlyIUnityContainer unityContainer;
publicSurveyAnswerContainerFactory(IUnityContainer unityContainer)
{
Trace.WriteLine(string.Format("Called constructor in SurveyAnswerContainerFactory"),"UNITY");
this.unityContainer = unityContainer;
}
publicIBlobContainer<SurveyAnswer> Create(stringtenant,stringsurveySlug)
{
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());
returnthis.unityContainer.Resolve<IBlobContainer<SurveyAnswer>>(
newParameterOverride("blobContainerName", blobContainerName));
}
}
在本例中,Resolve 方法使用一个参数覆盖,以对 blobContainerName 参数提供一个值,来构造 Entities-BlobContainer 类,该类已在容器注册,被注入到 SurveyAnswerContainerFactory 对象。
你前面看到应用程序如何在注入构造函数用 string 参数注册 IBlobContainer<Survey-Answer> 类型。如果没有参数覆盖,这个注册将失败,因为容器无法解析 string 类型。
你还可以在 ContainerBootstrapper 类看到参数覆盖的使用。本例中,参数覆盖提供 message queues 的创建。当已注册的 InjectionFactory 执行时,在解析时提供参数覆盖。
container
.RegisterType<ISurveyAnswerStore, SurveyAnswerStore>(
newInjectionFactory((c, t, s) =>newSurveyAnswerStore(
container.Resolve<ITenantStore>(),
container.Resolve<ISurveyAnswerContainerFactory>(),
container.Resolve<IMessageQueue<SurveyAnswerStoredMessage>>(newParameterOverride("queueName", Constants.StandardAnswerQueueName)),
container.Resolve<IMessageQueue<SurveyAnswerStoredMessage>>(newParameterOverride("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
Unityxml配置文件(2)