閱讀588 返回首頁    go 小米 go MIUI米柚


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

  上一篇:go  淺談嵌入式開發的發展方向
  下一篇:go  [ASP.NET MVC]通過對HtmlHelper擴展簡化“列表控件”的綁定