跳槽,新公司使用了 Unity,初步看了一下,公司的使用還是比較簡單的,其實 Unity 本身的用法很多。另外,前段時間我翻譯和實驗了 Martin Fowler 的《java 控制反轉和依賴注入模式》,本文是 .NET 平臺下的依賴注入。
Unity 涉及的內容和用法比較多,之后慢慢說,本文先大概介紹如何用 Unity 進行依賴注入,它基本可以分為兩個操作:注冊(RegisterType)和解析(Resolve),也就是說,先注冊類型;然后解析類型,返回創建的對象。
Unity application Block(Unity)是一個輕量級的,可擴展的依賴注入容器,它支持構造函數注入,屬性注入和方法調用注入。它為開發人員提供了以下優點:
咋看上去,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>>>())));
}
上面的代碼列出了用 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 類型來創建實例。
可以在三個地方完成注冊:在初始化存儲的一個單獨的應用程序(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 應用程序中的使用更要復雜點:應用程序配置容器,這樣應用程序在啟動時就會使用,之后,解析各種類型。記住,這是 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;
}
……
}
前面的例子展示了如何使用“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
新聞熱點
疑難解答