[Day12] 談談Controller幾個重要成員

前言

上篇得知MVC預設透過DefaultControllerFactory反射方式動態建立Controller物件

本篇會分享我們常用到Controller基礎類別和相關物件.

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

此篇同步發布在筆者Blog [Day12] 談談Controller幾個重要成員

ControllerBase(Controller基礎類別)

ControllerBase具有如下幾個重要的屬性

  1. TempData:將設置資料存於Session中,生命週期除了當下請求, 導頁後仍可續存.
  2. ViewBag:儲存Controllerview傳遞資料或變數 (型別dynamic)
  3. ViewData:儲存Controllerview傳遞資料或變數 (型別ViewDataDictionary)

雖說ViewBagViewData看起來使用不同的物件,但從程式碼了解到其實ViewBag也是使用ViewData引用.

public abstract class ControllerBase : IController
{   
    public ControllerContext ControllerContext { get; set; }
    public TempDataDictionary TempData
    {
        get
        {
            if (ControllerContext != null && ControllerContext.IsChildAction)
            {
                return ControllerContext.ParentActionViewContext.TempData;
            }
            if (_tempDataDictionary == null)
            {
                _tempDataDictionary = new TempDataDictionary();
            }
            return _tempDataDictionary;
        }
        set { _tempDataDictionary = value; }
    }
    public dynamic ViewBag
    {
        get
        {
            if (_dynamicViewDataDictionary == null)
            {
                _dynamicViewDataDictionary = new DynamicViewDataDictionary(() => ViewData);
            }
            return _dynamicViewDataDictionary;
        }
    }
    public ViewDataDictionary ViewData { get; set; }
}

SessionStateTempDataProvider 控制儲存TempData

上面說到TempData字典集合生命週期除了當下請求, 導頁後仍可續存.原因是在SessionStateTempDataProvider將資料存在Session

controllerContext.HttpContext.Session["__ControllerTempData"]

可以透過上面程式碼取得當前的TempData字典集合物件.

ControllerBase Excute方法

ControllerBase這個類別繼承IController,前篇說到在HttpHandler ProcessRequest方法會透過反射找到一個符合Http請求IController介面物件.
並呼叫其Execute方法

Execute做了幾件事情.

  • 初始化ControllerContext物件,對於RequestContext簡易封裝.
  • ExecuteCore呼叫Hock方法(ExecuteCore是一個抽象方法提供繼承他的物件實做,預設是Controller類別)
protected virtual void Execute(RequestContext requestContext)
{
    if (requestContext == null)
    {
        throw new ArgumentNullException("requestContext");
    }
    if (requestContext.HttpContext == null)
    {
        throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext");
    }

    VerifyExecuteCalledOnce();
    Initialize(requestContext);

    using (ScopeStorage.CreateTransientScope())
    {
        ExecuteCore();
    }
}

void IController.Execute(RequestContext requestContext)
{
    Execute(requestContext);
}

Controller

在專案中Controller是我們預設使用繼承控制器類別,此類別中定義了很多的輔助方法和屬性讓撰寫控制器變得簡單。
Controller類別除了直接繼承ControllerBase之外,Controller還顯式實現IControllerIAsyncController介面,跟ASP.NET MVC 四大篩選器(IAuthorizationFilter,IActionFilter、IResultFilter,IExceptionFilter)的4個介面。

public abstract class Controller : 
	ControllerBase, 
	IActionFilter, 
	IAuthenticationFilter, 
	IAuthorizationFilter,
	IDisposable, 
	IExceptionFilter,
	IResultFilter,
	IAsyncController, 
	IAsyncManagerContainer
{
}

ExecuteCore

Controller重載實做ExecuteCore方法.

主要透過GetActionName(RouteData)取得執行的Action名稱,並透過ActionInvoker取得要Invoker的ActionInvoker.

  • PossiblyLoadTempData:建立載入TempData
  • PossiblySaveTempData:儲存TempData的資料
protected override void ExecuteCore()
{
    // If code in this method needs to be updated, please also check the BeginExecuteCore() and
    // EndExecuteCore() methods of AsyncController to see if that code also must be updated.
    PossiblyLoadTempData();
    try
    {
        string actionName = GetActionName(RouteData);
        if (!ActionInvoker.InvokeAction(ControllerContext, actionName))
        {
            HandleUnknownAction(actionName);
        }
    }
    finally
    {
        PossiblySaveTempData();
    }
}

ControllerContext

第一次初始話ControllerContext利用建構子

public ControllerContext(RequestContext requestContext, ControllerBase controller) 

ControllerBase.Initialize方法對於ControllerContext初始化,這個上下文資料封裝了許多此次請求的資料.

protected virtual void Initialize(RequestContext requestContext)
{
    ControllerContext = new ControllerContext(requestContext, this);
}

後面對於繼承ControllerContextContext傳入第一次初始化ControllerContext物件,
在建構子函數把傳入ControllerContextRequestContext資料填入繼承ControllerContext物件中

下面是MVC有繼承ControllerContext類別

  • AuthorizationContext
  • ExceptionContext
  • AuthenticationChallengeContext
  • ResultExecutedContext
  • ViewContext
  • ResultExecutingContext

ControllerContext

在原始碼中可以看到ControllerContext(ControllerContext controllerContext)很巧妙把自身類別當作建構子方法參數傳入.

Controller = controllerContext.Controller;
RequestContext = controllerContext.RequestContext;

主要是要把RequestContext值給填充,之後就可以利用RequestContext取得理面一些資料.

public class ControllerContext
{
	internal const string ParentActionViewContextToken = "ParentActionViewContext";
	private HttpContextBase _httpContext;
	private RequestContext _requestContext;
	private RouteData _routeData;

	// parameterless constructor used for mocking
	public ControllerContext()
	{
	}

	protected ControllerContext(ControllerContext controllerContext)
	{
		if (controllerContext == null)
		{
			throw new ArgumentNullException("controllerContext");
		}

		Controller = controllerContext.Controller;
		RequestContext = controllerContext.RequestContext;
	}

	public ControllerContext(HttpContextBase httpContext, RouteData routeData, ControllerBase controller)
		: this(new RequestContext(httpContext, routeData), controller)
	{
	}

	public ControllerContext(RequestContext requestContext, ControllerBase controller)
	{
		if (requestContext == null)
		{
			throw new ArgumentNullException("requestContext");
		}
		if (controller == null)
		{
			throw new ArgumentNullException("controller");
		}

		RequestContext = requestContext;
		Controller = controller;
	}
	//....
}

AuthorizationContext

我們看一下Authorizationfilter用到的參數AuthorizationContext.

InvokeAuthorizationFilters方法將AuthorizationContext初始化

AuthorizationContext context = new AuthorizationContext(controllerContext, actionDescriptor);

其中傳入參數controllerContext是第一次透過ControllerBase.Initialize初始化Context.

public class AuthorizationContext : ControllerContext
{
	// parameterless constructor used for mocking
	public AuthorizationContext()
	{
	}

	public AuthorizationContext(ControllerContext controllerContext)
		: base(controllerContext)
	{
	}

	public AuthorizationContext(ControllerContext controllerContext, ActionDescriptor actionDescriptor) : base(controllerContext)
	{
		if (actionDescriptor == null)
		{
			throw new ArgumentNullException("actionDescriptor");
		}

		ActionDescriptor = actionDescriptor;
	}

	public virtual ActionDescriptor ActionDescriptor { get; set; }

	public ActionResult Result { get; set; }
}

之後再呼叫base(controllerContext)利用ControllerContext建構子把資料填充.

小結:

Asp.net MVC對於為了方便我們使用控制器所以對於Controller進行許多資料封裝,讓我們只要繼承Controller就可以方便使用許多屬性.

下圖是Controller核心類別關係圖.Controller類別左右兩側有本次沒介紹到類別(之後會介紹到)

relationship_pic.PNG

當我看到ControllerContext的設計時讓我驚艷的,因為他把MVC用到Context都關聯綁定到一個類別中.

因為在商業邏輯中會有許多Model類別,且這些類別資料存在一定的相關性,我覺得這個設計可以使用可以大大改善資料傳遞上的麻煩,讓程式寫起來更安全,簡單

之後我會把上面的UML圖慢慢畫出來,一步一步揭開Asp.net MVC面紗.


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