🚫 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%

談談Controller幾個重要成員 (第12天)

Agenda

前言

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

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

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

ControllerBase(Controller基礎類別)

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

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

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

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

1
controllerContext.HttpContext.Session["__ControllerTempData"]

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

ControllerBase Excute方法

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

Execute做了幾件事情.

  • 初始化ControllerContext物件,對於RequestContext簡易封裝.
  • ExecuteCore呼叫Hock方法(ExecuteCore是一個抽象方法提供繼承他的物件實做,預設是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
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個介面。

1
2
3
4
5
6
7
8
9
10
11
12
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的資料
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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利用建構子

1
public ControllerContext(RequestContext requestContext, ControllerBase controller) 

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

1
2
3
4
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)很巧妙把自身類別當作建構子方法參數傳入.

1
2
3
Controller = controllerContext.Controller;
RequestContext = controllerContext.RequestContext;

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

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
39
40
41
42
43
44
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初始化

1
AuthorizationContext context = new AuthorizationContext(controllerContext, actionDescriptor);

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

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
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面紗.

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

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