即依賴對象不在被依賴模塊的類中直接經過new來獲取web
先看看下面這段代碼的問題~編程
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(); } }
}
關於以上例子的說明:app
(1)在Order類中,它依賴於具體的對象SqlServerDal,違反了依賴倒置的原則,即不管是高層仍是底層,都應該依賴於抽象而不該該依賴於具體框架
(2)若是需求有變:數據訪問層換爲OracleDal,那麼這個時候,就要修改Order類的代碼;若是數據訪問層再次換爲MySqlDal,那麼還要繼續修改Order類的代碼......若是無休止的變下去,將會是一個噩夢,並且你不但要修改 Order裏邊的代碼,可能你還要修改Product、Users等類裏邊的代碼,由於它們也可能跟Order類是一樣的狀況ide
怎麼辦呢?IOC啊~函數
那如何IOC啊?使用DI啊~ui
DI是IoC的一種實現方式,就是將依賴對象的建立和綁定轉移到被依賴對象類的外面來實現this
依賴注入分爲:構造函數注入、屬性注入和接口注入spa
(1)構造函數注入code
首先,咱們爲數據訪問類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(); } }
}
前面全部的栗子中,咱們都是經過手動的方式來建立依賴對象,並將引用傳遞給被依賴模塊。好比:
SqlServerDal dal = new SqlServerDal();//在Order外部建立依賴對象 Order order = new Order(dal);//經過構造函數注入依賴
對於大型項目來講,相互依賴的組件比較多。若是還用手動的方式,本身來建立和注入依賴的話,顯然效率很低,並且每每還會出現不可控的場面。所以,IoC容器就誕生了。IoC容器其實是一個DI框架,它能簡化咱們的工做量。它包含如下幾個功能:
本篇咱們使用微軟框架組給提供的Unity來實現依賴注入,它是最流行的IOC容器之一
Unit是微軟patterns& practices組用C#實現的輕量級、可擴展的依賴注入容器,咱們能夠經過代碼或者XML配置文件的形式來配置對象與對象之間的關係,在運行時直接調用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(); }
在平常開發的過程當中咱們有時候會本身建立好一個對象,可是你又想對這個已經建立好的對象的生命週期進行管理,這個時候你可使用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),具體有關生命週期相關信息在下面進行介紹。
注意是單實例哦~
咱們在系統中引入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個參數:
若是用今生命週期管理器,則要在配置文件中新增的節點以下:
<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>
注入代碼與上例相似,這裏再也不列出
說了這麼多Unity,主要仍是想將其用到ASP.NET MVC的IOC中,其實很簡單,大概就幾個步驟搞定:
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); } } }
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; } } }
<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>
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; } } }
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(); //就是這個東東
} }
(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方法添加視圖便可,這裏再也不詳述,運行效果以下: