一、你知道IOC與DI嗎?
1、IOC(Inversion of Control )——控制反轉
即依賴對象不在被依賴模塊的類中直接通過new來獲取
先看看下面這段代碼的問題~
public class SqlServerDal { public void Delete() { Console.WriteLine("刪除表中某個訂單信息!"); } } public class Order { private readonly SqlServerDal dal = new SqlServerDal(); public void Delete() { dal.Delete(); }
} using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DIPTest { class Program { static void Main(string[] args) { Order order = new Order(); order.Delete(); Console.Read(); } }
}
關於以上例子的說明:
(1)在Order類中,它依賴於具體的對象SqlServerDal,違反了依賴倒置的原則,即不論是高層還是底層,都應該依賴於抽象而不應該依賴於具體
(2)如果需求有變:數據訪問層換為OracleDal,那么這個時候,就要修改Order類的代碼;如果數據訪問層再次換為MySqlDal,那么還要繼續修改Order類的代碼......如果無休止的變下去,將會是一個噩夢,而且你不但要修改 Order里邊的代碼,可能你還要修改Product、Users等類里邊的代碼,因為它們也可能跟Order類是同樣的情況
怎么辦呢?IOC啊~
那如何IOC啊?使用DI啊~
2、DI(Dependency Injection)——依賴注入
DI是IoC的一種實現方式,就是將依賴對象的創建和綁定轉移到被依賴對象類的外面來實現
依賴注入分為:構造函數注入、屬性注入和接口注入
(1)構造函數注入
首先,我們為數據訪問類SqlServerDal定義一個抽象接口IDataAccess,並在IDataAccess接口中聲明GetAll方法:
public interface IDataAccess { void Delete(); }
然后在SqlServerDal類中,實現IDataAccess接口:
public class SqlServerDal:IDataAccess { public void Delete() { Console.WriteLine("刪除表中某個訂單信息!"); } }
接下來,我們還需要修改Order類:
public class Order { private IDataAccess da; //構造函數注入 public Order(IDataAccess ida) { da = ida;
} public void Delete() { da.Delete(); } }
下面是控制台程序調用的代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Text;
namespace IOCDemo
{ class Program { static void Main(string[] args) { SqlServerDal dal = new SqlServerDal();//在Order類外部創建依賴對象 Order order = new Order(dal);//通過構造函數注入依賴 order.Delete(); Console.Read(); } }
}
(2)屬性注入
屬性注入就是通過屬性來傳遞依賴。因此,我們首先需要在依賴類Order中定義一個屬性:
public class Order { private IDataAccess _da;
//屬性,接受依賴 public IDataAccess da { set { _da = value; } get { return _da; } } public void Delete() {
_da.Delete(); } }
下面是控制台程序調用的代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Text;
namespace IOCDemo
{ class Program { static void Main(string[] args) { AccessDal dal = new AccessDal();//在外部創建依賴對象 Order order = new Order(); order.da = dal;//給屬性賦值 order.Delete(); Console.Read(); } }
}
(3)接口注入
相比構造函數注入和屬性注入,用起來沒有它們方便。首先定義一個接口,包含一個設置依賴的方法。
public interface IDependent { void SetDependence(IDataAccess ida);//設置依賴項 }
用依賴類實現這個接口:
public class Order : IDependent { private IDataAccess _ida;
public void SetDependence(IDataAccess ida) { _ida = ida; } public void Delete() { _ida.Delete(); } }
下面是控制台程序調用的代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace IOCDemo { class Program { static void Main(string[] args) { AccessDal dal = new AccessDal();//在Order外部創建依賴對象 Order order = new Order(); order.SetDependence(dal);//傳遞依賴 order.Delete(); Console.Read(); } }
}
3、IoC容器
前面所有的栗子中,我們都是通過手動的方式來創建依賴對象,並將引用傳遞給被依賴模塊。比如:
SqlServerDal dal = new SqlServerDal();//在Order外部創建依賴對象 Order order = new Order(dal);//通過構造函數注入依賴
對於大型項目來說,相互依賴的組件比較多。如果還用手動的方式,自己來創建和注入依賴的話,顯然效率很低,而且往往還會出現不可控的場面。因此,IoC容器就誕生了。IoC容器實際上是一個DI框架,它能簡化我們的工作量。它包含以下幾個功能:
- 動態創建、注入依賴對象。
- 管理對象生命周期。
- 映射依賴關系。
本篇我們使用微軟框架組給提供的Unity來實現依賴注入,它是最流行的IOC容器之一
二、Unity的使用
1、Unity是個什么東東?
Unit是微軟patterns& practices組用C#實現的輕量級、可擴展的依賴注入容器,我們可以通過代碼或者XML配置文件的形式來配置對象與對象之間的關系,在運行時直接調用Unity容器即可獲取我們所需的對象,以便建立松散耦合的應用程序。
對於小型項目:用代碼的方式實現即可
對於中大型項目:使用配置文件比較好
2、Unity入門
您可以訪問http://unity.codeplex.com/releases得到最新版本的Unity,也可以直接在Nuget中獲取到最新版本的Unity,或者下載微軟的企業庫,然后在項目中添加Microsoft.Practices.Unity.dll和Microsoft.Practices.Unity.Configuration.dll的引用
這里用到的最重要的東東就是IUnityContainer 接口,它本身定義了很多方法,當然還有一些擴展方法,具體的接口定義這里就不說了,我們會經常用到IUnityContainer 接口的RegisterInstance、RegisterType、Resolve等方法。
這里我舉個栗子,首先定義如下接口,並用兩個類來進行實現:
/// <summary> /// 班級接口 /// </summary> public interface IClass { string ClassName { get; set; } void ShowInfo(); } /// <summary> /// 計科班 /// </summary> public class CbClass : IClass { public string ClassName { get; set; } public void ShowInfo() { Console.WriteLine("計科班:{0}", ClassName); } } /// <summary> /// 電商班 /// </summary> public class EcClass : IClass { public string ClassName { get; set; } public void ShowInfo() { Console.WriteLine("電商班:{0}", ClassName); } }
(1)用編程方式實現注入
使用Unity來管理對象與對象之間的關系可以分為以下幾步:
A、創建一個UnityContainer對象
B、通過UnityContainer對象的RegisterType方法來注冊對象與對象之間的關系
C、通過UnityContainer對象的Resolve方法來獲取指定對象關聯的對象
注入代碼如下:
public static void ContainerCodeTest() { IUnityContainer container = new UnityContainer(); //默認注冊(無命名),如果后面還有默認注冊會覆蓋前面的 container.RegisterType<IClass, CbClass>(); //命名注冊 container.RegisterType<IClass, EcClass>("ec"); //解析默認對象 IClass cbClass = container.Resolve<IClass>(); cbClass.ShowInfo(); //指定命名解析對象 IClass ecClass = container.Resolve<IClass>("ec"); ecClass.ShowInfo(); //獲取容器中所有IClass的注冊的已命名對象 IEnumerable<IClass> classList = container.ResolveAll<IClass>(); foreach (var item in classList) { item.ShowInfo(); } }
(2)配置文件方式
通過配置文件配置Unity信息需要有以下幾個步驟:
A、在配置文件<configSections> 配置節下注冊名為unity的section
B、在<configuration> 配置節下添加Unity配置信息
C、在代碼中讀取配置信息,並將配置載入到UnityContainer中
配置文件內容如下:
<?xml version="1.0" encoding="utf-8"?> <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> <!--定義類型別名--> <aliases> <add alias="IClass" type="ConsoleApplication1.UnityDemo.IClass,ConsoleApplication1" /> <add alias="CbClass" type="ConsoleApplication1.UnityDemo.CbClass,ConsoleApplication1" /> <add alias="EcClass" type="ConsoleApplication1.UnityDemo.EcClass,ConsoleApplication1" /> </aliases> <!--容器--> <container name="FirstClass"> <!--映射關系--> <register type="IClass" mapTo="CbClass"></register> <register type="IClass" mapTo="EcClass" name="ec"></register> </container> </unity> </configuration>
注入代碼如下:
public static void ContainerConfiguration() { IUnityContainer container = new UnityContainer();//獲取指定名稱的配置節
UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity"); container.LoadConfiguration(section, "FirstClass");//獲取特定配置節下已命名的配置節<container name="FirstClass">下的配置信息
IClass classInfo = container.Resolve<IClass>("ec"); classInfo. ShowInfo(); }
注意:
如果系統比較龐大,那么對象之間的依賴關系可能就會很復雜,最終導致配置文件變得很大,所以我們需要將Unity的配置信息從App.config或web.config中分離出來到某一個單獨的配置文件中,比如Unity.config,然后將其作為參數傳遞給下面的方法,依然可以實現依賴注入:
public static void ContainerConfigurationFromFile(string configFile) { //根據文件名獲取指定config文件 var fileMap = new ExeConfigurationFileMap { ExeConfigFilename = configFile }; //從config文件中讀取配置信息 Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); var unitySection = (UnityConfigurationSection)configuration.GetSection("unity"); var container = new UnityContainer().LoadConfiguration(unitySection, "FirstClass"); IClass classInfo = container.Resolve<IClass>("ec"); classInfo.ShowInfo(); }
3、使用Unity為已存在的對象注冊關系
在日常開發的過程中我們有時候會自己創建好一個對象,但是你又想對這個已經創建好的對象的生命周期進行管理,這個時候你可以使用Unity提供的RegisterInstance方法(有很多重載),由於RegisterInstance是對已存在的實例進行注冊,所以無法通過配置文件來進行配置。
代碼示例如下:
public static void RegisterInstance() { IClass myClass = new MyClass(); IClass yourClass = new YourClass(); //為myClass實例注冊默認實例 container.RegisterInstance<IClass>(myClass); //為yourClass實例注冊命名實例,同RegisterType container.RegisterInstance<IClass>("yourInstance", yourClass); container.Resolve<IClass>().ShowInfo(); container.Resolve<IClass>("yourInstance").ShowInfo(); }
這段代碼很簡單,就是使用RegisterInstance方法將已存在的實例myClass、yourClass等注冊到UnityContainer中,默認情況下其實用的是ContainerControlledLifetimeManager,這個生命周期是由UnityContainer來進行管理,UnityContainer會維護一個對象實例的強引用,當你將已存在的實例注冊到UnityContainer后,每次通過Resolve方法獲取對象都是同一對象,也就是單件實例(singleton instance),具體有關生命周期相關信息在下面進行介紹。
注意是單實例哦~
4、Unity中生命周期管理
我們在系統中引入Unity主要就是想通過Unity來解除對象之間的依賴關系,方便我們根據配置調用到所需的對象,而Unity默認情況下會自動幫我們維護好這些對象的生命周期,可能Unity自動維護的生命周期並不總是我們想要的,這時我們就要根據具體的需求來更改這些對象的生命周期,下面就簡單介紹一下Unity中內置的兩個常用生命周期管理器,其他的生命周期管理器如果需要可以自己上網查看其詳細說明。
(1)TransientLifetimeManager,瞬態生命周期,默認情況下,在使用RegisterType進行對象關系注冊時如果沒有指定生命周期管理器則默認使用這個生命周期管理器,這個生命周期管理器就如同其名字一樣,當使用這種管理器的時候,每次通過Resolve或ResolveAll調用對象的時候都會重新創建一個新的對象。
代碼如下:
public static void TransientLifetimeManagerCode() { //以下2種注冊效果是一樣的 container.RegisterType<IClass, MyClass>(); container.RegisterType<IClass, MyClass>(new TransientLifetimeManager()); Console.WriteLine("-------TransientLifetimeManager Begin------"); Console.WriteLine("第一次調用RegisterType注冊的對象HashCode:" + container.Resolve<IClass>().GetHashCode()); Console.WriteLine("第二次調用RegisterType注冊的對象HashCode:" + container.Resolve<IClass>().GetHashCode()); Console.WriteLine("-------TransientLifetimeManager End------"); }
如果是使用配置的方式,則需要在配置文件中注冊關系的時候在<register>配置節下新增<lifetime>既可(如果不新增則默認使用TransientLifetimeManager),如果想使用其他的生命周期管理器,則更改此配置節即可!
其中<lifetime>有3個參數:
- type,生命期周期管理器的類型,這邊可以選擇Unity內置的,也可以使用自定義的,其中內置的生命周期管理器會有智能提示
- typeConverter,生命周期管理器轉換類,用戶自定義一個生命周期管理器的時候所創建一個轉換器
- value,初始化生命周期管理器的值
如果用此生命周期管理器,則要在配置文件中新增的節點如下:
<register type="IClass" mapTo="MyClass"> <lifetime type="transient" /> </register>
注入代碼如下:
public static void TransientLifetimeManagerConfiguration() { //獲取指定名稱的配置節 UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity"); container.LoadConfiguration(section, "FirstClass"); Console.WriteLine("-------TransientLifetimeManager Begin------"); Console.WriteLine("第一次調用RegisterType注冊的對象HashCode:" + container.Resolve<IClass>("transient").GetHashCode()); Console.WriteLine("第二次調用RegisterType注冊的對象HashCode:" + container.Resolve<IClass>("transient").GetHashCode()); Console.WriteLine("-------TransientLifetimeManager End------"); }
以上無論是代碼還是配置的方式,運行之后都會發現實例的哈希碼是不一樣的,說明每次調用都是重新生成一個對象實例!
(2)ContainerControlledLifetimeManager,容器控制生命周期管理,這個生命周期管理器是RegisterInstance默認使用的生命周期管理器,也就是單件實例,UnityContainer會維護一個對象實例的強引用,每次調用的時候都會返回同一對象,示例代碼如下:
public static void ContainerControlledLifetimeManagerCode() { IClass myClass = new MyClass(); //以下2種注冊效果是一樣的 container.RegisterInstance<IClass>("ccl", myClass); container.RegisterInstance<IClass>("ccl", myClass, new ContainerControlledLifetimeManager()); container.RegisterType<IClass, MyClass>(new ContainerControlledLifetimeManager()); Console.WriteLine("-------ContainerControlledLifetimeManager Begin------"); Console.WriteLine("第一次調用RegisterType注冊的對象HashCode:" + container.Resolve<IClass>().GetHashCode()); Console.WriteLine("第二次調用RegisterType注冊的對象HashCode:" + container.Resolve<IClass>().GetHashCode()); Console.WriteLine("第一次調用RegisterInstance注冊的對象HashCode:" + container.Resolve<IClass>("ccl").GetHashCode()); Console.WriteLine("第二次調用RegisterInstance注冊的對象HashCode:" + container.Resolve<IClass>("ccl").GetHashCode()); Console.WriteLine("-------ContainerControlledLifetimeManager End------"); }
運行之后都會發現實例的哈希碼是一樣的,說明是單實例的
如果用此生命周期管理器,則要在配置文件中新增的節點如下:
<register type="IClass" mapTo="MyClass" name="ccl"> <lifetime type="singleton" /> </register>
注入代碼與上例類似,這里不再列出
三、ASP.NET MVC與Unity
說了這么多Unity,主要還是想將其用到ASP.NET MVC的IOC中,其實很簡單,大概就幾個步驟搞定:
1. 實現IDependencyResolver接口並通過DependencyResolver.SetResolver告知MVC,將部分類型實例解析工作交由IoC容器Unity來處理
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using Microsoft.Practices.Unity; namespace UnityOfMVC.IOC { public class UnityDependencyResolver : IDependencyResolver { IUnityContainer container; public UnityDependencyResolver(IUnityContainer container) { this.container = container; } public object GetService(Type serviceType) { if (!this.container.IsRegistered(serviceType)) { return null; } return container.Resolve(serviceType); } public IEnumerable<object> GetServices(Type serviceType) { return container.ResolveAll(serviceType); } } }
2、繼承DefaultControllerFactory,重載GetControllerInstance方法,實現自己的UnityControllerFactory類,並通過IoC容器將之注冊為IControllerFactory的實現
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; using System.Web.SessionState; namespace UnityOfMVC.IOC { public class UnityControllerFactory : DefaultControllerFactory { IUnityContainer container; public UnityControllerFactory(IUnityContainer container) { this.container = container; } protected override IController GetControllerInstance(RequestContext reqContext, Type controllerType) { return container.Resolve(controllerType) as IController; } } }
3、讓我們開始弄一下配置文件
<configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" /> </configSections> <unity> <containers> <container name="defaultContainer"> <register type="UnityOfMVC.Models.IStudentRepository, UnityOfMVC" mapTo="UnityOfMVC.Models.StudentRepository, UnityOfMVC"/> <register type="System.Web.Mvc.IControllerFactory, System.Web.Mvc" mapTo="UnityOfMVC.IOC.UnityControllerFactory, UnityOfMVC"/> </container> </containers> </unity>
4、用引導類Bootstrapper進行初始化工作
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Configuration; using System.Web.Mvc; using Microsoft.Practices.Unity; using Microsoft.Practices.Unity.Configuration; using UnityOfMVC.IOC; namespace UnityOfMVC.BootStrapper { public class Bootstrapper { public static IUnityContainer Init() { var container = BuildUnityContainer(); DependencyResolver.SetResolver(new UnityDependencyResolver(container)); return container; } private static IUnityContainer BuildUnityContainer() { var container = new UnityContainer(); UnityConfigurationSection configuration = (UnityConfigurationSection)ConfigurationManager.GetSection(UnityConfigurationSection.SectionName); configuration.Configure(container, "defaultContainer"); return container; } } }
5、在函數Application_Start() 中進行真正的初始化工作
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); AuthConfig.RegisterAuth();
BootStrapper.Bootstrapper.Init(); //就是這個東東
} }
6、現在在你的MVC程序中注入依賴代碼就ok了
(1)首先聲明一個Student學生類
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace UnityOfMVC.Models { public class Student { public int Id { get; set; } public string Name { get; set; } public string Graduation { get; set; } public string School { get; set; } public string Major { get; set; } } }
(2)然后聲明倉儲接口和其實現
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace UnityOfMVC.Models { public interface IStudentRepository { IEnumerable<Student> GetAll(); Student Get(int id); Student Add(Student item); bool Update(Student item); bool Delete(int id); } }

using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace UnityOfMVC.Models { public class StudentRepository : IStudentRepository { private List<Student> Articles = new List<Student>(); public StudentRepository() { //添加演示數據 Add(new Student { Id = 1, Name = "張三", Major = "軟件工程", Graduation = "2013年", School = "西安工業大學" }); Add(new Student { Id = 2, Name = "李四", Major = "計算機科學與技術", Graduation = "2013年", School = "西安工業大學" }); Add(new Student { Id = 3, Name = "王五", Major = "自動化", Graduation = "2013年", School = "西安工業大學" }); } /// <summary> /// 獲取全部文章 /// </summary> /// <returns></returns> public IEnumerable<Student> GetAll() { return Articles; } /// <summary> /// 通過ID獲取文章 /// </summary> /// <param name="id"></param> /// <returns></returns> public Student Get(int id) { return Articles.Find(p => p.Id == id); } /// <summary> /// 添加文章 /// </summary> /// <param name="item"></param> /// <returns></returns> public Student Add(Student item) { if (item == null) { throw new ArgumentNullException("item"); } Articles.Add(item); return item; } /// <summary> /// 更新文章 /// </summary> /// <param name="item"></param> /// <returns></returns> public bool Update(Student item) { if (item == null) { throw new ArgumentNullException("item"); } int index = Articles.FindIndex(p => p.Id == item.Id); if (index == -1) { return false; } Articles.RemoveAt(index); Articles.Add(item); return true; } /// <summary> /// 刪除文章 /// </summary> /// <param name="id"></param> /// <returns></returns> public bool Delete(int id) { Articles.RemoveAll(p => p.Id == id); return true; } } }
(3)最后添加控制器StudentController,並注入依賴代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using UnityOfMVC.Models; namespace UnityOfMVC.Controllers { public class StudentController : Controller { readonly IStudentRepository repository; //構造器注入 public StudentController(IStudentRepository repository) { this.repository = repository; } public ActionResult Index() { var data = repository.GetAll(); return View(data); } } }
(4)最后為控制器StudentController的Index方法添加視圖即可,這里不再詳述,運行效果如下: