MVC3使用Unity實現接口自動注冊
關于依賴注入
控制反轉(Inversion of Control,英文縮寫為IoC)是一個重要的面向對象編程的法則來削減計算機程序的耦合問題。應用控制反轉,對象在被創建的時候,由一個調控系統內所有對象的外界實體,將其所依賴的對象的引用,傳遞給它。也可以說,依賴被注入到對象中。所以,控制反轉是,關于一個對象如何獲取他所依賴的對象的引用,這個責任的反轉。
控制反轉還有一個名字叫做依賴注入(Dependency Injection),簡稱DI。相對而言對于.Net開發者來說聽到得更多的是依賴注入這個名詞。實現依賴注入的框架有很多,如Spring.Net,Unity,Ninject等等,基本上蘿卜白菜各有所愛,當然了更多的還是根本不尿依賴注入。
Unity(Unity Application Block)是微軟patterns & practices組用C#實現的輕量級,可擴展的依賴注入容器,支持構造函數注入、屬性注入、方法調用注入,開發者使用Unity可以很輕松的建立松散耦合的應用程序。關于Unity的常規使用方法可以參考 【ASP.Net MVC3 】使用Unity 實現依賴注入 ,本文主要探討下接口到實現類的自動映射注冊實現方案。
Unity容器接口和實現類的自動注冊
眾所周知我們使用依賴注入時要么使用配置文件建立接口和實現類的映射關系,要么使用代碼直接注冊依賴關系。這兩種方法都有一個弊端那就是當你新添加了一個接口和接口的實現后必須去修改配置文件或者代碼去注冊新的映射關系,當你改了接口、類名后還得再次去修改映射關系。實際應用中更多的是使用配置文件來實現映射關系的注冊,這對于Java來說就是天經地義的事,不過放到.Net來說完全不是那么回事,繁雜的配置文件,無盡的接口映射,重復的乏味的代碼簡直不可忍受,于是乎激發了實現全自動映射的念想。
原理:所謂的依賴注入無非就是根據配置文件或代碼完成接口和實現類的映射關系,然后使用反射實現的接口實例化而已。
目的:使用Unity實現ASP.Net MVC3的依賴注入,實現松耦合設計,但不使用配置文件,將開發人員從繁瑣的配置中解脫出來,將關注點放在業務領域設計實現上
思路:掃描應用程序域的的所有程序集,在Unity容器中完成實現了接口或繼承了抽象類的Class完成接口到Class的映射關系注冊
首先建立一個WebApp Mvc3項目,建立LazyRabbit.Domain和LazyRabbit.Infrastructure.Dependency類庫,使用NuGet添加對向WebApp和LazyRabbit.Infrastructure.Dependency添加Unity引用,NuGet使用請參考http://www.rzrgm.cn/lzrabbit/archive/2012/04/30/2476255.html
Install-Package Unity
完成后輸出類似如下結果:
每個程序包的所有者將相應程序包授權給您。Microsoft 不負責也不會授予對第三方程序包的任何許可。有些程序包可能包含受其他許可證控制的依賴項。請訪問程序包源(源) URL 以確定所有依賴項。 程序包管理器控制臺主機版本 2.0.30619.9119 鍵入“get-help NuGet”以查看所有可用的 NuGet 命令。 PM> Install-Package Unity 正在嘗試解析依賴項“CommonServiceLocator (≥ 1.0)”。 已成功安裝“CommonServiceLocator 1.0”。 您正在從 Microsoft patterns & practices 下載 Unity,有關此程序包的許可協議在 http://www.opensource.org/licenses/ms-pl 上提供。請檢查此程序包是否有其他依賴項,這些依賴項可能帶有各自的許可協議。您若使用程序包及依賴項,即構成您接受其許可協議。如果您不接受這些許可協議,請從您的設備中刪除相關組件。 已成功安裝“Unity 2.1.505.0”。 已成功將“CommonServiceLocator 1.0”添加到 WebApp。 已成功將“Unity 2.1.505.0”添加到 WebApp。
繼續向LazyRabbit.Infrastructure.Dependency項目添加System.Web.Mvc程序集引用,完成準備工作后來看我們的自動依賴注入實現核心類
在LazyRabbit.Infrastructure.Dependency項目添加DependencyContext類,完整代碼如下
DependencyContext
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Web; using System.Web.Mvc; using Microsoft.Practices.Unity; using System.Text; namespace LazyRabbit.Infrastructure.Dependency { /// <summary> /// Unity依賴注入輔助類 /// </summary> public sealed class DependencyContext { private static IUnityContainer _UnityContainer = new UnityContainer(); /// <summary> /// 獲取依賴注入容器(單例模式) /// </summary> public static IUnityContainer Current { get { return _UnityContainer; } } private DependencyContext() { } /// <summary> /// 獲取指定目錄及其子目錄的所有DLL文件路徑集合 /// </summary> /// <param name="assemblyDirectory"></param> /// <returns></returns> private static List<string> GetAssemblyFiles() { string assemblyDirectory = AppDomain.CurrentDomain.BaseDirectory; if (HttpContext.Current != null) { assemblyDirectory = Path.Combine(assemblyDirectory, "Bin"); } string[] notFiles = new string[] { "EntityFramework.dll", "ICSharpCode.SharpZipLib.dll", "Ionic.Zip.dll", "log4net.dll", "Microsoft.ApplicationBlocks.Data.dll", "Microsoft.Practices.ServiceLocation.dll", "Microsoft.Practices.Unity.Configuration.dll", "Microsoft.Practices.Unity.dll", "Newtonsoft.Json.dll" }; //獲取DLL文件 List<string> assemblyFiles = Directory.GetFiles(assemblyDirectory, "*.dll").Select(path => Path.GetFileName(path)).ToList(); //EXE可執行文件 assemblyFiles.AddRange(Directory.GetFiles(assemblyDirectory, "*.exe").Select(path => Path.GetFileName(path))); assemblyFiles = assemblyFiles.Where(f => !notFiles.Contains(f)).ToList(); return assemblyFiles; } /// <summary> /// 從當前應用程序域獲取已加載的程序集 /// </summary> /// <param name="assemblyFiles"></param> /// <returns></returns> public static List<Assembly> LoadAssembly() { List<string> assemblyFiles = GetAssemblyFiles(); //加載程序集不能使用Assembly.LoadFile()方法,該方法會導致DLL文件占用無法釋放,改為文件流加載方式 //return assemblyFiles.Select(assemblyFile => Assembly.Load(File.ReadAllBytes(assemblyFile))).ToList(); //放棄使用Assembly.Load方法加載程序集 //Assembly.Load方法返回的程序集和當前應用程序域運行的程序集是相互獨立 //當使用Load方法加載程序集Assembly1并加載類型T1時,然后從應用程序中的程序集Assembly1加載一個類型T1, //盡管這兩個類型看上去是完全相同的而且也確實是完全相同的,但這兩個T1類型卻不相同,因為這兩個都叫T1的類型分屬兩個不同的Assembly //只有一種情況下才會相同即你使用的這個類型有一個接口,并且這個接口定義在其它Assembly中 //因此為了程序的兼容性這里采取從當前應用程序域獲取程序集 return AppDomain.CurrentDomain.GetAssemblies().Where(assembly => assemblyFiles.Contains(assembly.ManifestModule.ScopeName)).ToList(); } /// <summary> /// 從程序集加載所有類(不包含接口、抽象類) /// </summary> /// <param name="assemblys"></param> /// <returns></returns> private static List<Type> GetClassTypes(List<Assembly> assemblys) { List<Type> types = new List<Type>(); assemblys.ForEach(assembly => { try { types.AddRange(assembly.GetTypes().Where(t => t.IsClass && !t.IsInterface && !t.IsAbstract)); } catch (ReflectionTypeLoadException ex) { //處理類型加載異常,一般為缺少引用的程序集導致 } }); return types; } /// <summary> /// 獲取類型的所有集成、實現的接口抽象類 /// </summary> /// <param name="classType"></param> /// <returns></returns> public static List<Type> GetBaseTypes(Type classType) { HashSet<string> ignoreInterface = new HashSet<string> { typeof(ISingleton).ToString(), typeof(IWeakReference).ToString() }; List<Type> baseTypes = classType.GetInterfaces().Where(t => !IsSystemNamespace(t.Namespace) && !ignoreInterface.Contains(t.FullName)).ToList(); GetAbstructTypes(classType, baseTypes); return baseTypes; } /// <summary> /// 獲取類型所有的抽象基類 /// </summary> /// <param name="classType"></param> /// <param name="abstructTypes"></param> public static void GetAbstructTypes(Type classType, List<Type> abstructTypes) { Type baseType = classType.BaseType; if (baseType != typeof(object) && baseType.IsAbstract && !IsSystemNamespace(baseType.Namespace)) { abstructTypes.Add(baseType); GetAbstructTypes(baseType, abstructTypes); } } /// <summary> /// 判斷接口或抽象類是否為系統的命名空間 /// </summary> /// <param name="ns"></param> /// <returns></returns> private static bool IsSystemNamespace(string ns) { //常用系統命名空間 HashSet<string> sysNamespace = new HashSet<string> { "Microsoft.Xml", "System", "System.Collections", "System.ComponentModel", "System.Configuration", "System.Data", "System.IO", "System.Runtime", "System.ServiceModel", "System.Text", "System.Web", "System.Xml" }; return sysNamespace.Contains(string.Join(".", ns.Split('.').Take(2))); } /// <summary> /// 從指定的類型集合中過濾出從指定類或接口派生的類 /// </summary> /// <typeparam name="T">基類或接口</typeparam> /// <param name="classTypes">類型集合</param> /// <returns></returns> private static List<Type> GetDerivedClass<T>(List<Type> classTypes) where T : class { return classTypes.AsParallel().Where(t => t.GetInterface(typeof(T).ToString()) != null).ToList(); } /// <summary> /// 注冊 /// </summary> /// <param name="types"></param> /// <param name="lifetimeManager"></param> private static void RegisterType<T>(List<Type> types) where T : LifetimeManager, new() { types.AsParallel().ForAll(classType => { List<Type> baseTypes = GetBaseTypes(classType).ToList(); foreach (Type baseType in baseTypes) { Current.RegisterType(baseType, classType, new T()); Current.RegisterType(baseType, classType, classType.FullName, new T()); } }); } /// <summary> /// 初始化依賴注入 /// 注冊所有實現了ISingleton和IWeakReference接口的類型到IUnityContainer容器 /// </summary> private static void Init() { //默認的情況下使用TransientLifetimeManager管理對象的生命周期,它不會在container中保存對象的引用,簡而言之每當調用Resolve或ResolveAll方法時都會實例化一個新的對象 //ContainerControlledLifetimeManager 單例模式,每次調用Resolve或ResolveAll方法都會調用同一個對象的引用 //ExternallyControlledLifetimeManager 弱引用 List<Assembly> assemblys = LoadAssembly(); List<Type> classTypes = GetClassTypes(assemblys); //所有接口默認注冊為弱引用 RegisterType<ExternallyControlledLifetimeManager>(classTypes); //先注冊單例,再注冊若引用,確保若同時實現了ISingleton和IWeakReference接口,則注冊為弱引用 //實現ISingleton接口的類型集合注冊為單例模式 List<Type> singletonTypeList = GetDerivedClass<ISingleton>(classTypes); //注冊單例 RegisterType<ContainerControlledLifetimeManager>(singletonTypeList); //實現IWeakReference接口的類型集合注冊為弱引用 List<Type> weakReferenceTypeList = GetDerivedClass<IWeakReference>(classTypes); //注冊弱引用 RegisterType<ExternallyControlledLifetimeManager>(weakReferenceTypeList); } /// <summary> /// 注冊依賴注入 /// </summary> public static void RegisterDependency() { Init(); DependencyResolver.SetResolver(new UnityDependencyResolver(Current)); } } }
具體說明下DependencyContext類
1.獲取bin目錄下的所有DLL文件,過濾掉不需要實現依賴注入的DLL
因為是自動掃描所有程序集,所以無法分辨哪些是我們自己的程序集,因此可以顯示的排除不需要的程序集,減少不必要的加載,以提高程序性能,這里我們只保留程序集的名稱,因為我們從當前應用程序域獲取程序集,所以我們只需要獲取要進行自動類型注冊的程序集名稱完成程序集過濾即可?! ?/p>
private static List<string> GetAssemblyFiles() { string assemblyDirectory = AppDomain.CurrentDomain.BaseDirectory; if (HttpContext.Current != null) { assemblyDirectory = Path.Combine(assemblyDirectory, "Bin"); } string[] notFiles = new string[] { "EntityFramework.dll", "ICSharpCode.SharpZipLib.dll", "Ionic.Zip.dll", "log4net.dll", "Microsoft.ApplicationBlocks.Data.dll", "Microsoft.Practices.ServiceLocation.dll", "Microsoft.Practices.Unity.Configuration.dll", "Microsoft.Practices.Unity.dll", "Newtonsoft.Json.dll" }; //獲取DLL文件 List<string> assemblyFiles = Directory.GetFiles(assemblyDirectory, "*.dll").Select(path => Path.GetFileName(path)).ToList(); //EXE可執行文件 assemblyFiles.AddRange(Directory.GetFiles(assemblyDirectory, "*.exe").Select(path => Path.GetFileName(path))); assemblyFiles = assemblyFiles.Where(f => !notFiles.Contains(f)).ToList(); return assemblyFiles; }
2.獲取當前應用程序域加載的程序集,根據上面找到的程序集名稱篩選我們要進行自動映射的程序集。
這里解釋下為什么要從應用程序域獲取程序集,而不是根據DLL文件路徑使用Assembly.Load方法加載。首先一般我們需要自動完成映射的程序集肯定是程序需要用到的,因此在應用程序域加載的程序集中肯定能找到;其次也是最重要的原因:使用Assembly.Load加載的程序集和應用程序域的程序集類型來自不同程序集,會導致相同的類型無法識別,我在注釋中也進行了說明。
public static List<Assembly> LoadAssembly() { List<string> assemblyFiles = GetAssemblyFiles(); //加載程序集不能使用Assembly.LoadFile()方法,該方法會導致DLL文件占用無法釋放,改為文件流加載方式 //return assemblyFiles.Select(assemblyFile => Assembly.Load(File.ReadAllBytes(assemblyFile))).ToList(); //放棄使用Assembly.Load方法加載程序集 //Assembly.Load方法返回的程序集和當前應用程序域運行的程序集是相互獨立 //當使用Load方法加載程序集Assembly1并加載類型T1時,然后從應用程序中的程序集Assembly1加載一個類型T1, //盡管這兩個類型看上去是完全相同的而且也確實是完全相同的,但這兩個T1類型卻不相同,因為這兩個都叫T1的類型分屬兩個不同的Assembly //只有一種情況下才會相同即你使用的這個類型有一個接口,并且這個接口定義在其它Assembly中 //因此為了程序的兼容性這里采取從當前應用程序域獲取程序集 return AppDomain.CurrentDomain.GetAssemblies().Where(assembly => assemblyFiles.Contains(assembly.ManifestModule.ScopeName)).ToList(); }
3.從程序集獲取所有類(不包含接口、抽象類),并為類的接口和抽象類注冊映射關系
從程序集中獲取所有的類,然后根據類實現的接口和集成的抽象類,完成接口、抽象類到實現類的類型映射,GetBaseTypes方法是根據類型獲取其所有的接口和抽象類,Current.RegisterType(baseType, classType, new T()),完成接口和實現類的映射,本例中默認將對象生命生命周期設置為弱引用,關于生命周期可以根據自己的需要設置。Current.RegisterType(baseType, classType, classType.FullName, new T())此方法又為每個接口注冊了一個帶Name的映射關系,之所以為每個映射關系都注冊兩個映射主要是為了兼容有些接口、抽象類有多個實現類,若只是用沒有Name的方法注冊,最后只能保留一個映射,而使用實現類的類名作為Name可以在需要時使用根據Name完成接口的解析。保留無Name的映射是為了使用方便,畢竟絕大多數接口都只有一個實現類,我們就不用在使用時額外加上Name了。這種機制帶來方便的同時也會有些性能的損失,看個人喜好和實際需要調整。
private static void RegisterType<T>(List<Type> types) where T : LifetimeManager, new() { types.AsParallel().ForAll(classType => { List<Type> baseTypes = GetBaseTypes(classType); foreach (Type baseType in baseTypes) { Current.RegisterType(baseType, classType, new T()); Current.RegisterType(baseType, classType, classType.FullName, new T()); } }); }
4.依賴注入的初始化調用
這里額外對ISingleton,IWeakReference接口進行了注冊,主要是為了保持靈活,默認注冊為弱引用,需要注冊為單例的可以繼承ISingletone接口,需要顯示若引用的繼承IWeakReference,這也就不會受默認生命周期改變影響了。
/// <summary> /// 初始化依賴注入 /// 注冊所有實現了ISingleton和IWeakReference接口的類型到IUnityContainer容器 /// </summary> private static void Init() { //默認的情況下使用TransientLifetimeManager管理對象的生命周期,它不會在container中保存對象的引用,簡而言之每當調用Resolve或ResolveAll方法時都會實例化一個新的對象 //ContainerControlledLifetimeManager 單例模式,每次調用Resolve或ResolveAll方法都會調用同一個對象的引用 //ExternallyControlledLifetimeManager 弱引用 List<Assembly> assemblys = LoadAssembly(); List<Type> classTypes = GetClassTypes(assemblys); //所有接口默認注冊為弱引用 RegisterType<ExternallyControlledLifetimeManager>(classTypes); //先注冊單例,再注冊若引用,確保若同時實現了ISingleton和IWeakReference接口,則注冊為弱引用 //實現ISingleton接口的類型集合注冊為單例模式 List<Type> singletonTypeList = GetDerivedClass<ISingleton>(classTypes); //注冊單例 RegisterType<ContainerControlledLifetimeManager>(singletonTypeList); //實現IWeakReference接口的類型集合注冊為弱引用 List<Type> weakReferenceTypeList = GetDerivedClass<IWeakReference>(classTypes); //注冊弱引用 RegisterType<ExternallyControlledLifetimeManager>(weakReferenceTypeList); }
到這里核心的的接口映射注冊就完成了,是不是很簡單,呵呵,下面來完成和MVC的關聯
Unity和MVC3的關聯
在LazyRabbit.Infrastructure.Dependency建立UnityDependencyResolver類實現System.Web.Mvc.IDependencyResolver接口的GetService和GetServices方法,具體代碼如下,很簡單不再解釋
UnityDependencyResolver
using System; using System.Collections.Generic; using System.Web.Mvc; using Microsoft.Practices.Unity; namespace LazyRabbit.Infrastructure.Dependency { public class UnityDependencyResolver : IDependencyResolver { public IUnityContainer _UnityContainer; /// <summary> /// /// </summary> /// <param name="unityContainer">依賴注入容器</param> public UnityDependencyResolver(IUnityContainer unityContainer) { _UnityContainer = unityContainer; } #region IDependencyResolver 成員 public object GetService(Type serviceType) { try { return _UnityContainer.Resolve(serviceType); } catch { /// 按微軟的要求,此方法,在沒有解析到任何對象的情況下,必須返回 null,必須這么做!?。。?/span> return null; } } public IEnumerable<object> GetServices(Type serviceType) { try { return _UnityContainer.ResolveAll(serviceType); } catch { /// 按微軟的要求,此方法,在沒有解析到任何對象的情況下,必須返回空集合,必須這么做!?。?! return new List<object>(); } } #endregion } }
現在我所需要的編碼已經全部完成了,只差最后一步,在Application_Start中添加代碼LazyRabbit.Infrastructure.Dependency.DependencyContext.RegisterDependency()完成依賴注入的初始化以及和MVC的關聯
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); LazyRabbit.Infrastructure.Dependency.DependencyContext.RegisterDependency(); }
RegisterDependency()方法代碼
public static void RegisterDependency() { Init(); DependencyResolver.SetResolver(new UnityDependencyResolver(Current)); }
具體應用依賴注入
至此全自動注冊依賴關系已經搞定,我們在WebApp建立IApplication接口和實現類Application,在LazyRabbit.Domain中建立IUserService接口和實現類UserService (LazyRabbit.Domain純粹是為了掩飾著玩建立的,呵呵),完成后我們在HomeController進行調用
public class HomeController : Controller { /// <summary> /// 這個不指定Name /// </summary> [Dependency] public IApplication Application { get; set; } /// <summary> /// 這個用Name指定實現類 /// </summary> [Dependency("LazyRabbit.Domain.UserService")] public IUserService UserService { get; set; } // // GET: /Home/ public ActionResult Index() { ViewBag.SiteInfo = Application.GetSiteInfo(); ViewBag.CurrentUser = UserService.GetCurrentUser(); return View(); } }
微軟Unity下載: http://www.microsoft.com/en-us/download/details.aspx?id=17866
Unity官網 http://unity.codeplex.com/
源碼下載:UnityDemo.rar
本例特點在于實現思路簡單,代碼通俗易懂,復用性強,可以把LazyRabbit.Infrastructure.Dependency類庫直接拿到項目使用,只需在Application_Start中調用下依賴注入初始化方法即可。這種設計方案可以大幅降低我們使用依賴注入的開發的工作量,設計上有足夠的靈活性,不需要對現有代碼做任何修改,即可輕松實現依賴注入,完全不用操心接口的注冊問題,只要定義了接口和實現類就能自動完成注冊。
注:此文章屬懶惰的肥兔原創,版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接
若您覺得這篇文章還不錯請點擊下右下角的推薦,有了您的支持才能激發作者更大的寫作熱情,非常感謝。
如有問題,可以通過lzrabbit@126.com聯系我。

浙公網安備 33010602011771號