[Day11] Asp.net MVC Controller是怎麼被建立

前言

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

relationship_pic.PNG

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

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

取得執行Controller

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

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方法组合来完成。

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名稱

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工廠.

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物件

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釋放使用資源
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物件並返回使用
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物件

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程式碼

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


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