IOC
依賴反轉是oop
重要程式設計思想。
Ioc—Inversion of Control
控制反轉
控制反轉是一個設計思想 ,把對於某個物件的控制權移轉給第三方容器.
詳細資訊可以查看小弟另一篇文章 IOC(控制反轉),DI(依賴注入) 深入淺出~~
有沒有人會很好奇說為什麼只需要透過
DependencyResolver.SetResolver
方法我就可以直接使用AutoFac
或其他IOC容器?
//....
// 建立相依解析器
IContainer container = new builder.Build();
DependencyResolver.SetResolver(container);
今天跟大家分享Asp.net MVC
利用什麼設計技巧,讓外部IOC
容器可以很方便融入系統中.
我有做一個可以針對於Asp.net MVC Debugger的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.
控制反轉是一個設計思想,把對於某個物件建立,生命週期控制權移轉給第三方統一管理
在設計模組時建議依賴抽象,因為各個模組間不需要知道對方太多細節(實作),知道越多耦合越強。
A
物件內部有使用到B
物件 A
,B
物件中有依賴的成份
控制反轉是把原本A
對B
控制權移交給第三方容器。
降低A
對B
物件的耦合性,讓雙方都倚賴第三方容器。
上面說明太抽象嗎? 可以看一下下面這張圖.
最後對於使用者來說,我只需要認識這個第三方容器並跟這個容器取得我要A物件,至於A物件和其他物件關係使用者不用瞭解
IOC容器框架有很多種但基本上都有下面兩個功能
我們依照此圖做一個簡單範例by Autofac
A
物件會直接引用於B
和C
物件這導致A
掌控B
和C
物件創建和銷毀
如下面程式碼,A物件需要掌控B
和C
生命週期和物件建立.
public class A{
public B BObject {get;set;} = new B();
public C CObject {get;set;} = new C();
}
如果透過IOC
容器我們就不用擔心物件如何建立和他所依賴B
和C
物件,因為我們會在容器註表中指定他的關係,使用時只需要關注如何使用此物件.
public class A{
public B BObject {get;private set;}
public C CObject {get;private set;}
public A(B b,C c){
BObject = b;
CObject = c;
}
}
//autofac property injection
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<B>();
builder.RegisterType<C>();
builder.RegisterType<A>().PropertiesAutowired();
IContainer container = builder.Build();
var a = container.Resolve<A>();
這個程式碼是利用Autofac
框架,比起上面多了一段註冊程式碼.主要告訴容器物件之間關係和如何掌控物件生命週期.
上面例子最後只需要利用container.Resolve<T>
方法就可以跟容器來取想要的物件,至於引用的物件是如何注入或關係我們就不必關心.
如果Asp.net
沒有搭配IOC容器(預設使用DefaultResolver
)Asp.net MVC
對於使用物件必須寫死在Controller
類別中
無法使用建構子或屬性來決定使用哪個物件
如下面程式碼
public class HomeController : Controller
{
IUserService userService;
public HomeController(IUserService userService){
if(userService == null)
userService = new UserService();
}
public ActionResult Index()
{
return View();
}
//....
如果在建構子使用參數會丟錯誤,在[Day11] Asp.net MVC Controller是怎麼被建立談到建立
Controller
物件透過DefaultControllerActivator
預設使用反射建立Controller
物件呼叫無參數的建構子方法.
因為
Asp.net MVC
建立Controller
是透過Activator.CreateInstance
方法,
如果我們想在建構子傳入參數或是想要統一管理注入的物件,就可以使用IOC
容器來幫我完成
為什麼
Asp.net MVC
使用DependencyResolver.SetResolver
方法替換成IOC
容器就可輕易替換使用容器?
//....
// 建立相依解析器
IContainer container = new builder.Build();
DependencyResolver.SetResolver(container);
DependencyResolver.SetResolver
提供一個替換_current
欄位的機制
/// <summary>
/// 可將第三方IOC容器設置
/// </summary>
/// <param name="resolver"></param>
public static void SetResolver(IDependencyResolver resolver)
{
_instance.InnerSetResolver(resolver);
}
public static void SetResolver(object commonServiceLocator)
{
_instance.InnerSetResolver(commonServiceLocator);
}
public void InnerSetResolver(IDependencyResolver resolver)
{
if (resolver == null)
{
throw new ArgumentNullException("resolver");
}
_current = resolver;
_currentCache = new CacheDependencyResolver(_current);
}
Asp.net MVC
提供一個介面 IDependencyResolver
讓第三方容器實現並擴充.IDependencyResolver
介面有兩個方法
GetService
返回一個物件GetServices
返回一個物件集合主要透過這GetService
方法取得使用Controller
物件
public interface IDependencyResolver
{
object GetService(Type serviceType);
IEnumerable<object> GetServices(Type serviceType);
}
Asp.net MVC
依賴DependencyResolver.Current
來幫我們建立一個Controller
物件
這邊介紹一下在MVC
中三個IDependencyResolver
解析器
CacheDependencyResolver
快取解析器(利用ConcurrentDictionary
是一個多執行緒安全的字典)DefaultDependencyResolver
預設使用解析器(利用反射建立物件)DelegateBasedDependencyResolver
委派解析器.prprivate sealed class CacheDependencyResolver : IDependencyResolver
{
//ConcurrentDictionary 是一個多執行緒 安全的Dictionary
private readonly ConcurrentDictionary<Type, object> _cache = new ConcurrentDictionary<Type, object>();
private readonly ConcurrentDictionary<Type, IEnumerable<object>> _cacheMultiple = new ConcurrentDictionary<Type, IEnumerable<object>>();
private readonly Func<Type, object> _getServiceDelegate;
private readonly Func<Type, IEnumerable<object>> _getServicesDelegate;
private readonly IDependencyResolver _resolver;
public CacheDependencyResolver(IDependencyResolver resolver)
{
_resolver = resolver;
_getServiceDelegate = _resolver.GetService;
_getServicesDelegate = _resolver.GetServices;
}
public object GetService(Type serviceType)
{
return _cache.GetOrAdd(serviceType, _getServiceDelegate);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return _cacheMultiple.GetOrAdd(serviceType, _getServicesDelegate);
}
}
private class DefaultDependencyResolver : IDependencyResolver
{
public object GetService(Type serviceType)
{
// Since attempting to create an instance of an interface or an abstract type results in an exception, immediately return null
// to improve performance and the debugging experience with first-chance exceptions enabled.
if (serviceType.IsInterface || serviceType.IsAbstract)
{
return null;
}
try
{
return Activator.CreateInstance(serviceType);
}
catch
{
return null;
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
return Enumerable.Empty<object>();
}
}
建立Controller
預設使用DefaultDependencyResolver
這個解析器
第三方IOC
容器利用DependencyResolver.SetResolver
方法把DefaultDependencyResolver
替換掉使用他們自己實現的解析器提供物件
不是透過DefaultDependencyResolver
反射來建立物件喔~
我們了解為什麼Asp.net MVC
可透過DependencyResolver.SetResolver
替換成IOC
容器注入控制器物件.
如果要建立客製化的解析器可以實現IDependencyResolver
介面並使用DependencyResolver.SetResolver
替換DefaultDependencyResolver
預設解析器
DependencyResolver
,Controller
和ControllerFactory
的關係如下圖
下篇會介紹DependencyResolver
在Asp.net MVC
中有哪些實際的應用.