ASP.NET MVC是如何運行的(4): Action的執行
作為Controller基類ControllerBase的Execute方法的核心在於對Action方法的執行和作為方法返回的ActionResult的執行,兩者的執行是通過一個叫做ActionInvoker的組件來完成的。
我們同樣為ActionInvoker定義了一個接口IActionInvoker。如下麵的代碼片斷所示,該接口定義了一個唯一的方法InvokeAction用於執行指定名稱的Action方法,該方法的第一個參數是一個表示基於當前Controller上下文的ControllerContext對象。
1: public interface IActionInvoker
2: {
3: void InvokeAction(ControllerContext controllerContext, string actionName);
4: }
ControllerContext類型在真正的ASP.NET MVC框架中要負責一些,在這裏我們對它進行了簡化,僅僅將它表示成對當前Controller和請求上下文的封裝,而這兩個要素分別通過如下所示的Controller和RequestContext屬性表示。
1: public class ControllerContext
2: {
3: public ControllerBase Controller { get; set; }
4: public RequestContext RequestContext { get; set; }
5: }
ControllerBase中表示ActionInvoker的同名屬性在構造函數中被初始化。在Execute方法中,通過作為方法參數的RequestContext對象創建ControllerContext對象,並通過包含在RequestContext中的RouteData得到目標Action的名稱,然後將這兩者作為參數調用ActionInvoker的InvokeAction方法。
從前麵給出的關於ControllerBase的定義我們可以看到在構造函數中默認創建的ActionInvoker是一個類型為ControllerActionInvoker的對象。如下所示的代碼片斷反映了整個ControllerActionInvoker的定義,而InvokeAction方法的目的在於實現針對Action方法的執行。由於Action方法具有相應的參數,在執行Action方法之前必須進行參數的綁定。ASP.NET MVC將這個機製成為Model的綁定,而這又涉及到另一個重要的組件ModelBinder。
1: public class ControllerActionInvoker : IActionInvoker
2: {
3: public IModelBinder ModelBinder { get; private set; }
4: public ControllerActionInvoker()
5: {
6: this.ModelBinder = new DefaultModelBinder();
7: }
8: public void InvokeAction(ControllerContext controllerContext, string actionName)
9: {
10: MethodInfo method = controllerContext.Controller.GetType().GetMethods().First(m => string.Compare(actionName, m.Name, true) == 0);
11: List<object> parameters = new List<object>();
12: foreach (ParameterInfo parameter in method.GetParameters())
13: {
14: parameters.Add(this.ModelBinder.BindModel(controllerContext, parameter.Name, parameter.ParameterType));
15: }
16: ActionResult actionResult = method.Invoke(controllerContext.Controller, parameters.ToArray()) as ActionResult;
17: actionResult.ExecuteResult(controllerContext);
18: }
19: }
我們為ModelBinder提供了一個如下一個簡單的定義,這與在真正的ASP.NET MVC中的同名接口的定義不盡相同。該接口具有唯一的BindModel根據ControllerContext和Model名稱(在這裏實際上是參數名稱)和類型得到一個作為參數的對象。
1: public interface IModelBinder
2: {
3: object BindModel(ControllerContext controllerContext, string modelName, Type modelType);
4: }
通過前麵給出的關於ControllerActionInvoker的定義我們可以看到在構造函數中默認創建的ModelBinder對象是一個DefaultModelBinder對象。由於僅僅是對ASP.NET MVC的模擬,定義在自定義的DefaultModelBinder中的Model綁定邏輯比ASP.NET MVC中同名類型中實現的要簡單得多。
如下麵的代碼片斷所示,綁定到參數上的數據具有三個來源:HTTP-POST Form、RouteData和Values和DataTokens,它們都是字典結構的數據集合。如果參數類型為字符串或者簡單的值類型,我們直接根據參數名稱和Key進行匹配;對於複雜類型(比如之前例子中定義的包含Contrller和Action名稱的數據類型SimpleModel),則通過反射根據類型創建新的對象並根據屬性名稱與Key的匹配關係對相應的屬性進行賦值。
1: public class DefaultModelBinder : IModelBinder
2: {
3: public object BindModel(ControllerContext controllerContext, string modelName, Type modelType)
4: {
5: if (modelType.IsValueType || typeof(string) == modelType)
6: {
7: object instance;
8: if (GetValueTypeInstance(controllerContext, modelName, modelType, out instance))
9: {
10: return instance;
11: };
12: return Activator.CreateInstance(modelType);
13: }
14: object modelInstance = Activator.CreateInstance(modelType);
15: foreach (PropertyInfo property in modelType.GetProperties())
16: {
17: if (!property.CanWrite || (!property.PropertyType.IsValueType && property.PropertyType!= typeof(string)))
18: {
19: continue;
20: }
21: object propertyValue;
22: if (GetValueTypeInstance(controllerContext, property.Name, property.PropertyType, out propertyValue))
23: {
24: property.SetValue(modelInstance, propertyValue, null);
25: }
26: }
27: return modelInstance;
28: }
29: private bool GetValueTypeInstance(ControllerContext controllerContext, string modelName, Type modelType, out object value)
30: {
31: var form = HttpContext.Current.Request.Form;
32: string key;
33: if (null != form)
34: {
35: key = form.AllKeys.FirstOrDefault(k => string.Compare(k, modelName, true) == 0);
36: if (key != null)
37: {
38: value = Convert.ChangeType(form[key], modelType);
39: return true;
40: }
41: }
42:
43: key = controllerContext.RequestContext.RouteData.Values
44: .Where(item => string.Compare(item.Key, modelName, true) == 0)
45: .Select(item => item.Key).FirstOrDefault();
46: if (null != key)
47: {
48: value = Convert.ChangeType(controllerContext.RequestContext.RouteData.Values[key], modelType);
49: return true;
50: }
51:
52: key = controllerContext.RequestContext.RouteData.DataTokens
53: .Where(item => string.Compare(item.Key, modelName, true) == 0)
54: .Select(item => item.Key).FirstOrDefault();
55: if (null != key)
56: {
57: value = Convert.ChangeType(controllerContext.RequestContext.RouteData.DataTokens[key], modelType);
58: return true;
59: }
60: value = null;
61: return false;
62: }
63: }
在ControllerActionInvoker的InvokeAction方法中,我們直接將傳入的Action名稱作為方法名從Controller類型中得到表示Action操作的MethodInfo對象。然後遍曆MethodInfo的參數列表,對於每一個ParameterInfo對象,我們將它的Name和ParameterType屬性表示的參數名稱和類型連同創建ControllerContext作為參數調用ModelBinder的BindModel方法並得到對應的參數值。最後通過反射的方式傳入參數列表並執行MethodInfo。和真正的ASP.NET MVC一樣,定義在Contrller的Action方法返回一個ActionResult對象,我們通過指定它的Execute方法是先對請求的響應。
我們為具體的ActionResult定義了一個ActionResult抽象基類。如下麵的代碼片斷所示,該抽象類具有一個參數類型為ControllerContext的抽象方法ExecuteResult,我們最終對請求的響應就實現在這裏。
1: public abstract class ActionResult
2: {
3: public abstract void ExecuteResult(ControllerContext context);
4: }
在之前創建的例子中,Action方法返回的是一個類型為RawContentResult的對象。顧名思義,RawContentResult將初始化時指定的內容(字符串)原封不動地寫入針對當前請求的HTTP回複中,具體的實現如下所示。
1: public class RawContentResult: ActionResult
2: {
3: public string RawData { get; private set; }
4: public RawContentResult(string rawData)
5: {
6: RawData = rawData;
7: }
8: public override void ExecuteResult(ControllerContext context)
9: {
10: context.RequestContext.HttpContext.Response.Write(this.RawData);
11: }
12: }
ASP.NET MVC是如何運行的[1]: 建立在“偽”MVC框架上的Web應用
ASP.NET MVC是如何運行的[2]: URL路由
ASP.NET MVC是如何運行的[3]: Controller的激活
ASP.NET MVC是如何運行的[4]: Action的執行
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-26 12:04:29