Asp.net MVC Controller是怎麼被建立 (第11天)

Agenda

前言

前篇介紹MVC使用HttpHandlerMvcHandler透過並MvcRouteHandler物件來返回.

relationship_pic.PNG

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

大家介紹如何取得Controller執行物件

取得執行Controller

ProcessRequest方法是透過ProcessRequestInit取得執行controller物件,讓我們看看是這個方法如何controller物件.

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
private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory)
{
HttpContext currentContext = HttpContext.Current;
if (currentContext != null)
{
bool? isRequestValidationEnabled = ValidationUtility.IsValidationEnabled(currentContext);
if (isRequestValidationEnabled == true)
{
ValidationUtility.EnableDynamicValidation(currentContext);
}
}

AddVersionHeader(httpContext);
RemoveOptionalRoutingParameters();

// Get the controller type
string controllerName = RequestContext.RouteData.GetRequiredString("controller");

// Instantiate the controller and call Execute
factory = ControllerBuilder.GetControllerFactory();
controller = factory.CreateController(RequestContext, controllerName);
if (controller == null)
{
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentCulture,
MvcResources.ControllerBuilder_FactoryReturnedNull,
factory.GetType(),
controllerName));
}
}

從上面程式碼可以得知我們執行Controller物件實現於IController介面,並會呼叫IController.Execute方法.

IController介面是同步的方式執行。為了支持非同步請求處理,IController介面非同步版本System.Web.Mvc.IAsyncController被定義出来。IAsyncController介面通過BeginExecute/EndExecute方法组合来完成。

1
2
3
4
5
6
7
8
9
10
public interface IController
{
void Execute(RequestContext requestContext);
}

public interface IAsyncController : IController
{
IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state);
void EndExecute(IAsyncResult asyncResult);
}

透過RouteData.GetRequiredString取得執行Controller名稱,經由RouteValueDictionary查找之前註冊Url樣板並解析此次要使用Controller名稱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public string GetRequiredString(string valueName)
{
object obj;
if (this.Values.TryGetValue(valueName, out obj))
{
string str = obj as string;
if (!string.IsNullOrEmpty(str))
return str;
}
throw new InvalidOperationException(string.Format((IFormatProvider) CultureInfo.CurrentUICulture, System.Web.SR.GetString("RouteData_RequiredValue"), new object[1]
{
(object) valueName
}));
}

ControllerBuilder

ControllerBuilder類別定義一個Current靜態只讀屬性現在返回ControllerBuilder物件是一個全域物件。SetControllerFactory方法重載用於註冊ControllerFactory類型或物件,而GetControllerFactory方法返回一個具體ControllerFactory物件。

我們透過GetControllerFactory取得返回Controller工廠.

1
2
3
4
5
6
7
8
9
public class ControllerBuilder
{
public IControllerFactory GetControllerFactory();
public void SetControllerFactory(Type controllerFactoryType);
public void SetControllerFactory(IControllerFactory controllerFactory);
IControllerFactory GetControllerFactory();
public HashSet<string> DefaultNamespaces { get; }
public static ControllerBuilder Current { get; }
}

GetControllerFactory透過private IResolver<IControllerFactory>取得要執行的ControllerFactory.

一般來說沒有設置就是使用DefaultControllerFactory工廠來取得Controller物件

1
2
3
4
5
6
7
8
9
10
11
12
public IControllerFactory GetControllerFactory()
{
return _serviceResolver.Current;
}

internal ControllerBuilder(IResolver<IControllerFactory> serviceResolver)
{
_serviceResolver = serviceResolver ?? new SingleServiceResolver<IControllerFactory>(
() => _factoryThunk(),
new DefaultControllerFactory { ControllerBuilder = this },
"ControllerBuilder.GetControllerFactory");
}

IControllerFactory介面

IControllerFactory介面有三個方法.

  1. CreateController取得Controller物件(工廠模式最重要方法)
  2. GetControllerSessionBehavior取得Session
    • Default:使用預設ASP.NET Session狀態行為。
    • Required:使用完全的讀和寫Session狀態行為。
    • ReadOnly:使用只讀Session狀態。
    • Disabled:不使用Session狀態。
  3. ReleaseController釋放使用資源
1
2
3
4
5
6
public interface IControllerFactory
{
IController CreateController(RequestContext requestContext, string controllerName);
SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName);
void ReleaseController(IController controller);
}

ControllerFactory(DefaultControllerFactory.cs)

既然知道透過哪個工廠來產生Controller我們繼續追工廠是如何產生Controller物件

  1. GetControllerType取得要執行Controller類型
  2. GetControllerInstance取得Controller物件並返回使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public virtual IController CreateController(RequestContext requestContext, string controllerName)
{
if (requestContext == null)
{
throw new ArgumentNullException("requestContext");
}

if (String.IsNullOrEmpty(controllerName) && !requestContext.RouteData.HasDirectRouteMatch())
{
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
}

Type controllerType = GetControllerType(requestContext, controllerName);
IController controller = GetControllerInstance(requestContext, controllerType);
return controller;
}

GetControllerInstance通過反射(系統不會對建立的Controller進行快取

使用IControllerActivator(預設DefaultControllerActivator) 來建立Controller物件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protected internal virtual IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
{
throw new HttpException(404,
String.Format(
CultureInfo.CurrentCulture,
MvcResources.DefaultControllerFactory_NoControllerFound,
requestContext.HttpContext.Request.Path));
}
if (!typeof(IController).IsAssignableFrom(controllerType))
{
throw new ArgumentException(
String.Format(
CultureInfo.CurrentCulture,
MvcResources.DefaultControllerFactory_TypeDoesNotSubclassControllerBase,
controllerType),
"controllerType");
}
//使用IControllerActivator(預設DefaultControllerActivator) 來建立Controller物件
return ControllerActivator.Create(requestContext, controllerType);
}

建立Controller的IControllerActivator

上面說GetControllerInstance會透過一個ControllerActivator,而ControllerActivator預設其實是DefaultControllerActivator類別幫助我們建立Controller物件透過Create方法.

以下是DefaultControllerActivator程式碼

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
private class DefaultControllerActivator : IControllerActivator
{
private Func<IDependencyResolver> _resolverThunk;

public DefaultControllerActivator()
: this(null)
{
}

public DefaultControllerActivator(IDependencyResolver resolver)
{
if (resolver == null)
{
_resolverThunk = () => DependencyResolver.Current;
}
else
{
_resolverThunk = () => resolver;
}
}

public IController Create(RequestContext requestContext, Type controllerType)
{
try
{
return (IController)(_resolverThunk().GetService(controllerType) ?? Activator.CreateInstance(controllerType));
}
catch (Exception ex)
{
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentCulture,
MvcResources.DefaultControllerFactory_ErrorCreatingController,
controllerType),
ex);
}
}
}

能看到這邊依賴一個IDependencyResolver,這裡先埋個小伏筆後面幾篇會為各位解答.

DefaultControllerActivator透過Activator.CreateInstance產生Controller物件,使用無建構子參數的Create方式

小結:

今天我們學到如何取得Controller執行物件

  1. 透過一個IControllerFactory工廠物件取得Controller執行物件,對於外部提供可替換點.
  2. 利用RouteData.GetRequiredString取得執行的Controller名稱
  3. DefaultControllerFactory透過反射方式動態建立物件.

工廠模式主要核心把如何使用物件跟如何建立物件中間解耦合,使用方不關心如何產生物件,只專注於此物件可執行的能力(介面)

下圖是本次介紹類別UML關係圖

mvchandler_uml.png

MvcHandlerMVC的核心類別,借由ControllerBuilder創件者來取得產生Controller的工廠(預設使用DefaultControllerFactory),並呼叫CreateController方法來產生一個Controller

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


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