[Day13] Asp.net MVC如何實現IOC解析器

前言

IOC依賴反轉是oop重要程式設計思想。(Ioc—Inversion of Control 控制反轉)

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

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

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

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生命週期和物件建立.

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

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

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類別中

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

如下面程式碼

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容器就可輕易替換使用容器?

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

DependencyResolver 揭密

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介面有兩個方法

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

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

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委派解析器.
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中有哪些實際的應用.


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