家人身體不太好,好幾天沒在園子里發帖了。
新項目還是要用MVC3,team 計划使用 Unity。看了一下網上的資料,都是比較老的了,官網也沒什么好的指引。MVC也在更新,Unity也在更新。花了1天半時間去MSDN,P&P查資料,整理了一下分享給大家。言歸正傳:
什么是Unity?
Unity是一個輕量級的可擴展的依賴注入容器,支持構造函數,屬性和方法調用注入。Unity可以處理那些從事基於組件的軟件工程的開發人員所面對的問題。構建一個成功應用程序的關鍵是實現非常松散的耦合設計。松散耦合的應用程序更靈活,更易於維護。這樣的程序也更容易在開發期間進行測試。你可以模擬對象,具有較強的具體依賴關系的墊片(輕量級模擬實現),如數據庫連接,網絡連接,ERP連接,和豐富的用戶界面組件。例如,處理客戶信息的對象可能依賴於其他對象訪問的數據存儲,驗證信息,並檢查該用戶是否被授權執行更新。依賴注入技術,可確保客戶類正確實例化和填充所有這些對象,尤其是在依賴可能是抽象的 。
如何得到Unity?
您可以訪問http://unity.codeplex.com/releases得到最新版本的Unity現在。當然,如果您在您的visual studio 中安裝了Nuget 包管理器,你可以直接在Nuget中獲取到最新版本的Unity。
API
UnityContainer.RegisterType<ITFrom,TTO>();
UnityContainer.RegisterType< ITFrom, TTO >();
UnityContainer.RegisterType< ITFrom, TTO >("keyName");
IEnumerable<T> databases = UnityContainer.ResolveAll<T>();
IT instance = UnityContainer.Resolve<IT>();
T instance = UnityContainer.Resolve<T>("keyName");
UnitContainer.RegisterInstance<T>("keyName",new T());
UnityContainer.BuildUp(existingInstance);
IUnityContainer childContainer1 = parentContainer.CreateChildContainer();
代碼舉例
在開始之前我們要先做一些准備工作。首先創建一個控制台應用程序。使用Nuget 添加Unity到當前項目中。我們可以發現,dll引用中多了3個dll:Microsoft.Practices.ServiceLocation, Microsoft.Practices.Unity和Microsoft.Practices.Configuation。
示例1:根據接口依賴創建類
上邊簡單介紹了Unity的API。如果在沒有注冊的情況下Resolve一個類型會發生什么呢?
假設我們需要對日志進行處理。我們先聲明一個接口ILogger:
public interface ILogger
{
void Write(string log);
}
我們可以有多種方法實現這個接口,我們假設希望寫日志到文件中:
public class FileLogger:ILogger
{
#region ILogger Members
public void Write(string log)
{
Console.WriteLine("Write log in file.");
}
#endregion
}
我們在實際生活中對數據庫的選擇也是多種多樣的。我們創建一個數據庫的基類:
public class Database
{
}
創建一個派生類:
public class CustomerDatabase : Database
{
private ILogger _logger;
public CustomerDatabase(ILogger logger)
{
_logger = logger;
}
}
注意它的構造器的參數是ILogger類型。首先我們要創建一個Unity 容器:
UnityContainer container = new UnityContainer();
接下來我們需要在容器中注冊一種類型,它是一個類型的映射,接口類型是ILogger,我希望返回的類型是FileLogger:
container.RegisterType<ILogger, FileLogger>();
然后我們使用Resolve 方法:
Database database = container.Resolve<CustomerDatabase>();
經過調試我們可以發現,如果在容器中沒有注冊的類型。執行Resolv方法后,Unity嘗試創建該類型,會執行該類的構造器。最后database變量的類型就是CustomerDatabase,而且它的私有字段ILogger的當前實例也為FileLogger。
示例2:類型映射
我們希望返回一個日志類的實例,無論它是哪個實現類。我們可以直接在Resolve的類型中指定類型為接口ILogger:
UnityContainer container = new UnityContainer();
container.RegisterType<ILogger, FileLogger>();
ILogger logger = container.Resolve<ILogger>();
每一次 container都會給我們返回一個新的logger實例。
示例3:單例模式的注冊
如果我們想告訴Unity,我們想控制生命周期,我們想用單例模式。RegisterType方法包含一個重載,將使用LifetimeManager。每次我們在想獲得到database實例時,unity總是會返回第一次我創建的CustomerDatabase。
UnityContainer container = new UnityContainer();
container.RegisterType<Database, CustomerDatabase>
(new ContainerControlledLifetimeManager());
示例4:注冊時附帶key
在我們向容器里注冊時,可以附帶一個string 類型的Key值。
UnityContainer container = new UnityContainer();
container.RegisterType<Database, SQLDatabase>("SQL");
container.RegisterType<Database, ORACLEDatabase>("ORACLE");
IEnumerable<Database> databases = container.ResolveAll<Database>();
Database database = container.Resolve<Database>("SQL");
我們分別向容器中注冊了名為“SQL“和”ORACLE“的Database。當我們使用ResolverAll方法是。容器會返回容器中所有類型為Database的類。
這時我們如果僅僅想回去SQL的實例,我們可以使用container.Resolve<Database>("SQL");
示例5:注冊已經存在的實例
通過下邊方式我們可以在container中注冊一個實例:
UnityContainer container = new UnityContainer();
container.RegisterInstance<Database>(new SQLDatabase());
container.RegisterInstance<Database>("Oracle", new ORACLEDatabase());
Database database = container.Resolve<Database>();
Database oracleDatabase = container.Resolve<Database>("Oracle");
看起來和上邊的方式沒什么不同。重要的是,當我們使用RegisterInstance方法時,Unity會注冊一個單例。
我們還有一種方法可以把已經存在的實例注入到容器中。
UnityContainer container = new UnityContainer();
container.RegisterType<ILogger, FileLogger>();
SQLDatabase existDatabase = new SQLDatabase();
container.BuildUp(existDatabase);
container.RegisterInstance<Database>(existDatabase);
Database database = container.Resolve<Database>();
就如上邊代碼中,我們已經存在一個database 是DB2Database。你希望Unity做的是把依賴注入到容器中。
我們用BuildUp方法告訴Unity我們的想法。這時候Unity回去DB2Database 類,如果他發現了[Dependency]這個特性。他就自動的把我們前邊注冊的FileLogger注入到DB2Database的Logger字段中。
以下是DB2Database類:
public class DB2Database:Database
{
[Dependency]
public ILogger Logger { get; set; }
}
使用配置文件來實現關系映射
我們也可以再web.config里配置 文件的依賴關系映射。首先我打開web.config文件。按照如下結構添加section。這里我只是簡單的映射了ILogger 接口和FileLogger。並且指定了生命周期是單例。
<configuration>
<configSections>
<section name="unity"
type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
Microsoft.Practices.Unity.Configuration"/>
</configSections>
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<container name="containerOne">
<types>
<type type="UnityDemo_ConsoleApplication.ILogger" mapTo="UnityDemo_ConsoleApplication.FileLogger"
lifeTime="Singleton"/>
</types>
</container>
</unity>
...
...
</configuration>
如果你想更詳細的了解元素和屬性的使用,可以看以下Unity 在xml中配置的結構圖:
更詳細了解,請參見:
http://msdn.microsoft.com/en-us/library/ff647848.aspx
http://msdn.microsoft.com/zh-cn/library/dd203230.aspx
如何讀取配置 並加載
Unity同樣支持我們在配置文件里寫設定映射關系。
首先我們要引入命名空間: Microsoft.Practices.Unity.Configuration;
在Unity2.0以上版本,已經廢棄了以前的方法。現在我們有2種方式可以讀取配置。
第一種,我們使用configurationManager:
引用命名空間:System.Configuration
IUnityContainer myContainer = new UnityContainer();
myContainer.LoadConfiguration("containerOne ");
UnityConfigurationSection section
= (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Configure(myContainer, "containerOne");
第二種,我們可以直接用容器讀取配置信息:
IUnityContainer myContainer = new UnityContainer();
myContainer.LoadConfiguration("containerName");
通過 Injection API 指定依賴
假設我們有一個類GenericDatabase:
public class GenericDatabase:Database
{
private string _connectionString;
public ILogger Logger { get; set; }
public GenericDatabase(string connectionString)
{
_connectionString = connectionString;
}
}
在這里我們要通過Injection API 來為這個類注入connectionString 和 Logger。
首先我們還和前邊的一樣注冊映射關系:
IUnityContainer container = new UnityContainer();
container.RegisterType<ILogger, FileLogger>();
container.RegisterType<Database, GenericDatabase>();
然后通過Injection API 為GenericDatabase注入ConnectionStrings和Logger:
container.Configure<InjectedMembers>()
.ConfigureInjectionFor<GenericDatabase>(
new InjectionConstructor(
ConfigurationManager.ConnectionStrings["ConnectionStrings"] == null
? "defaultConnectionString" : ConfigurationManager.ConnectionStrings["ConnectionStrings"].ConnectionString),
new InjectionProperty("Logger")
);
Database database = container.Resolve<Database>();
這樣我們最后獲得的database 就包含了connection 和 Logger。
嵌套式容器
容器是可以嵌套的,獲取實例時遵循的規則是,如果子容器里不包含需要的對象,則會去父容器獲取。如果有,則從自己里獲取。
一旦父容器銷毀,子容器也隨之銷毀。
UnityContainer parentContainer = new UnityContainer();
IUnityContainer childContainer1 = parentContainer.CreateChildContainer();
IUnityContainer childContainer2 = parentContainer.CreateChildContainer();
parentContainer.RegisterType<ILogger, FileLogger>(new ContainerControlledLifetimeManager());
childContainer1.RegisterType<ILogger, EventLogger>(new ContainerControlledLifetimeManager());
//應該從parentContainer得到FileLogger
ILogger logger = childContainer2.Resolve<ILogger>();
logger.Write("Test");
//應該從自己本身得到eventLogger
ILogger logger2 = childContainer1.Resolve<ILogger>();
在MVC 中使用Unity注入Controller
在MVC2中我們會寫一個controlleFactory 繼承自DefaultControllerFactory。
並且override GetControllerInstance()這個方法。
MVC3對於依賴注入提供更好的支持。我們可以使用- IDependencyResolver 和 IControllerActivator 來實現對controller的注入。
具體實現如下:
創建一個MVC3項目。
我們要實現MVC3中新提供 的兩個接口:IDependencyResolver和IControllerActivator
IDependencyResolver公開兩個方法 - GetService的GetServices.The GetService方法解決了單獨注冊的服務,支持任意對象的創建,GetServices解決注冊多個服務。IDependencyResolver接口的實現應該委托給底層的依賴注入容器提供注冊服務請求的類型。當有沒有注冊的服務請求的類型,ASP.NET MVC框架預計這個接口的實現返回GetService為空,並從GetServices返回空集合。讓我們以統一提供依賴注入工作IDependencyResolver intreface派生創建一個自定義的依賴解析器類。
我們定義一個類名為UnityDependencyResolver:
public class UnityDependencyResolver : IDependencyResolver
{
IUnityContainer container;
public UnityDependencyResolver(IUnityContainer container)
{
this.container = container;
}
public object GetService(Type serviceType)
{
try
{
return container.Resolve(serviceType);
}
catch
{
return null;
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
try
{
return container.ResolveAll(serviceType);
}
catch
{
return new List<object>();
}
}
}
實現兩個方法GetService和GetServices。使用Unity容器返回我們需要的Service或者ojbect。
實現兩個方法GetService和GetServices。使用Unity容器返回我們需要的Service或者ojbect。
ASP.NET MVC 3已經推出了一個新的接口IControllerActivator,讓您激活與自定義的行為控制器,並且可以使用依賴注入.讓我們創建一個派生自IControllerActivator 接口的一個自定義的控制器
IController IControllerActivator.Create( System.Web.Routing.RequestContext requestContext,
Type controllerType)
{
return DependencyResolver.Current
.GetService(controllerType) as IController;
}
DependencyResolver.Current.GetService會執行我們自己定義的UnityDependencyResolver中的方法。
定義好這兩個類,我們找到Global.asax.cs,並在其中中添加一個私有方法GetUnityContainer():
private IUnityContainer GetUnityContainer()
{
//Create UnityContainer
IUnityContainer container = new UnityContainer()
.RegisterType<IControllerActivator, CustomControllerActivator>()
.RegisterType<ILogger, FlatFileLogger>();
return container;
}
這個方法定義了一個新的容器。並且注冊了映射關系。我們要返回的container中包含:CustomControllerActivator和FlatFileLogger。
IUnityContainer container = GetUnityContainer();
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
上邊的都做好了。我們在Application_Start方法中添加如下代碼:
protected void Application_Start()
{
...
IUnityContainer container = GetUnityContainer();
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
}
首先我們通過GetUnityContainer方法獲得container,並且設置當前的Resolver是我們自己實現的UnityDependencyResolver。
在Controller中我們只需要添加一個[Dependency]特性,就可以很方便的獲取到我們注入的Logger。
public class HomeController : Controller
{
[Dependency]
public ILogger Logger { get; set; }
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
Logger.GetType();
return View();
}
public ActionResult About()
{
return View();
}
}
我們可以使用Logger.GetType()查看到,我們當前的Logger就是我們之前注冊的FlatFileLogger。
參考資料
http://www.cnblogs.com/Terrylee/archive/2008/02/21/unity-application-block-part1.html
http://msdn.microsoft.com/zh-cn/library/ff663144.aspx
http://www.martinfowler.com/articles/injection.html
http://www.pnpguidance.net/Screencast/UnityDependencyInjectionIoCScreencast.aspx
http://msdn.microsoft.com/en-us/library/ff660878(v=pandp.20).aspx
http://msdn.microsoft.com/zh-cn/library/dd203182.aspx