Asp.net MVC如何實現IOC解析器 (第13天)

Agenda

前言

IOC依賴反轉是oop重要程式設計思想。

Ioc—Inversion of Control 控制反轉

控制反轉是一個設計思想 ,把對於某個物件的控制權移轉給第三方容器.

詳細資訊可以查看小弟另一篇文章 IOC(控制反轉),DI(依賴注入) 深入淺出~~

有沒有人會很好奇說為什麼只需要透過DependencyResolver.SetResolver方法我就可以直接使用AutoFac或其他IOC容器?

1
2
3
4
//....
// 建立相依解析器
IContainer container = new builder.Build();
DependencyResolver.SetResolver(container);

今天跟大家分享Asp.net MVC利用什麼設計技巧,讓外部IOC容器可以很方便融入系統中.

我有做一個可以針對於Asp.net MVC Debugger的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.

IOC介紹

控制反轉是一個設計思想,把對於某個物件建立,生命週期控制權移轉給第三方統一管理
在設計模組時建議依賴抽象,因為各個模組間不需要知道對方太多細節(實作),知道越多耦合越強。

A物件內部有使用到B物件 A,B物件中有依賴的成份
控制反轉是把原本AB控制權移交給第三方容器。
降低AB物件的耦合性,讓雙方都倚賴第三方容器。

上面說明太抽象嗎? 可以看一下下面這張圖.

img

最後對於使用者來說,我只需要認識這個第三方容器並跟這個容器取得我要A物件,至於A物件和其他物件關係使用者不用瞭解

IOC容器框架有很多種但基本上都有下面兩個功能

  1. 掌控物件生命週期
  2. 設定物件關係的註冊表(取用時會依照此註冊關係建立物件並自動注入相依物件)

程式碼介紹IOC by Autofac

我們依照此圖做一個簡單範例by Autofac

img

A物件會直接引用於BC物件這導致A掌控BC物件創建和銷毀

如下面程式碼,A物件需要掌控BC生命週期和物件建立.

1
2
3
4
public class A{
public B BObject {get;set;} = new B();
public C CObject {get;set;} = new C();
}

如果透過IOC容器我們就不用擔心物件如何建立和他所依賴BC物件,因為我們會在容器註表中指定他的關係,使用時只需要關注如何使用此物件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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>方法就可以跟容器來取想要的物件,至於引用的物件是如何注入或關係我們就不必關心.

AutoFac IOC容器 和 Asp.net mvc關係

如果Asp.net沒有搭配IOC容器(預設使用DefaultResolver)Asp.net MVC對於使用物件必須寫死在Controller類別中

無法使用建構子或屬性來決定使用哪個物件

如下面程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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物件呼叫無參數的建構子方法.

relationship_pic.PNG

因為Asp.net MVC建立Controller是透過Activator.CreateInstance方法,

如果我們想在建構子傳入參數或是想要統一管理注入的物件,就可以使用IOC容器來幫我完成


為什麼Asp.net MVC使用DependencyResolver.SetResolver方法替換成IOC容器就可輕易替換使用容器?

1
2
3
4
//....
// 建立相依解析器
IContainer container = new builder.Build();
DependencyResolver.SetResolver(container);

DependencyResolver 揭密

DependencyResolver.SetResolver提供一個替換_current欄位的機制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/// <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介面有兩個方法

  1. GetService返回一個物件
  2. GetServices返回一個物件集合

主要透過這GetService方法取得使用Controller物件

1
2
3
4
5
public interface IDependencyResolver
{
object GetService(Type serviceType);
IEnumerable<object> GetServices(Type serviceType);
}

MVC 裡IDependencyResolver

Asp.net MVC依賴DependencyResolver.Current來幫我們建立一個Controller物件

這邊介紹一下在MVC中三個IDependencyResolver解析器

  1. CacheDependencyResolver 快取解析器(利用ConcurrentDictionary是一個多執行緒安全的字典)
  2. DefaultDependencyResolver預設使用解析器(利用反射建立物件)
  3. DelegateBasedDependencyResolver委派解析器.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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,ControllerControllerFactory的關係如下圖

IOC_Asp.netMVC.png

下篇會介紹DependencyResolverAsp.net MVC中有哪些實際的應用.

此文作者:Daniel Shih(石頭)
此文地址https://isdaniel.github.io/ithelp-day13/
版權聲明:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW 許可協議。轉載請註明出處!


如果本文對您幫助很大,可街口支付斗內鼓勵石頭^^