🚫 Ad Blocker Detected

Please disable your AD blocker to continue using this site. Ads help us keep the content free! please press keyboard F5 to refresh page after disabled AD blocker

請關閉廣告攔截器以繼續使用本網站。廣告有助於我們保證內容免費。謝謝! 關閉後請按 F5 刷新頁面

0%

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 許可協議。轉載請註明出處!

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