欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC是如何运行的(四)

程序员文章站 2022-06-21 22:37:09
Action的执行 作为Controller的基类ControllerBase,它的Execute方法主要作用在于执行目标Action方法。如果目标Action方法返回一个Act...
Action的执行

作为Controller的基类ControllerBase,它的Execute方法主要作用在于执行目标Action方法。如果目标Action方法返回一个ActionResult对象,它还需要执行该对象来对当前请求予以响应。在ASP.NET MVC框架中,两者的执行是通过一个叫做ActionInvoker的对象来完成的。

1.ActionInvoker

我们同样为ActionInvoker定义了一个借口IActionInvoker。如下所示。该接口定义了唯一的方法InvokeAction用于执行指定名称的Action方法,该方法的第一个参数是一个表示针对当前Controller上下文的ControllerContext对象。

 

public interface IActionInvoker
    {
        void InvokeAction(ControllerContext controllerContext, string actionName);
    }

ControllerContext表示对当前Controller和请求上下文的封装,这两个要素分别通过Controller和RequestContext属性来表示。

 

 

public class ControllerContext
    {
        public ControllerBase Controller { get; set; }
        public RequestContext RequestContext { get; set; }
    }

 

ControllerBase中表示ActionInvoker的同名属性在构造函数中被初始化。在Execute方法中,他通过作为方法参数的RequestContext对象创建一个ControllerContext对象,并通过包含在RequestContext中的RouteData得到目标Action对象,最后将这两者作为参数调用ActionInvoker的InvokeAction方法在ControllerBase的定义中可以看到,在构造函数中默认创建的ActionInvoker是一个类型为ControllerActionInvoker的对象。

 

public class ControllerActionInvoker : IActionInvoker
    {
        public IModelBinder ModelBinder { get; private set; }
        public ControllerActionInvoker()
        {
            this.ModelBinder = new DefaultModelBinder();
        }
 
        public void InvokeAction(ControllerContext controllerContext, string actionName)
        {
            //省略实现
        }
    }

 

InvokeAction方法的目的在于实现针对Action方法的执行,由于Action方法具有相应的参数,在执行Action方法之前必须进行参数的绑定。ASP.NET MVC将这个机制称为Model绑定,而这又涉及另一个名为ModelBinder的对象。

 

2.ModelBinder

我们为ModelBinder实现的接口IModelBinder提供了一个简单的定义,该接口具有唯一的BindModel方法,他根据ControllerContext、Model名称和类型得到一个作为参数的对象。

 

public interface IModelBinder
    {
        object BindModel(ControllerContext controllerContext, string modelName,Type modelType);
    }


 

通过前面给出的关于ControllerActionInvoker的定义可以看到,在构造函数中默认创建的ModelBinder是一个DefaultModelBinder对象。

如下代码所示,绑定到参数上的数据具有4个来源,即提交的表单、请求查询字符串、RouteData的Values和DataTokens属性,他们都是字典结构的数据集合。如果请求参数类型为字符串或者简单的值类型,我们可以直接根据参数名称和Key进行匹配。对于复杂类型,则先根据提供的数据类型采用反射的方式创建一个空对象,然后根据属性名与Key的匹配关系提供相应的数据并对属性进行赋值。

 

public class DefaultModelBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, string modelName, Type modelType)
        {
            if (modelType.IsValueType || typeof(string) == modelType)
            {
                object instance;
                if (GetValueTypeInstance(controllerContext, modelName, modelType, out instance))
                {
                    return instance;
                };
                return Activator.CreateInstance(modelType);
            }
 
            object modelInstance = Activator.CreateInstance(modelType);
            foreach (PropertyInfo property in modelType.GetProperties())
            {
                if (!property.CanWrite || (!property.PropertyType.IsValueType && property.PropertyType != typeof(string)))
                {
                    continue;
                }
                object propertyValue;
                if (GetValueTypeInstance(controllerContext, property.Name,
                    property.PropertyType, out propertyValue))
                {
                    property.SetValue(modelInstance, propertyValue, null);
                }
            }
            return modelInstance;
        }
 
        private bool GetValueTypeInstance(ControllerContext controllerContext, string modelName, Type modelType, out object value)
        {
            Dictionary dataSource = new Dictionary();
 
            //数据来源一:HttpContext.Current.Request.Form
            foreach (string key in HttpContext.Current.Request.Form)
            {
                if (dataSource.ContainsKey(key.ToLower()))
                {
                    continue;
                }
                dataSource.Add(key.ToLower(), HttpContext.Current.Request.Form[key]);
            }
 
            //数据来源二:HttpContext.Current.Request.QueryString
            foreach (string key in HttpContext.Current.Request.QueryString)
            {
                if (dataSource.ContainsKey(key.ToLower()))
                {
                    continue;
                }
                dataSource.Add(key.ToLower(), HttpContext.Current.Request.QueryString[key]);
            }
 
            //数据来源三:ControllerContext.RequestContext.RouteData.Values
            foreach (var item in controllerContext.RequestContext.RouteData.Values)
            {
                if (dataSource.ContainsKey(item.Key.ToLower()))
                {
                    continue;
                }
                dataSource.Add(item.Key.ToLower(), controllerContext.RequestContext.RouteData.Values[item.Key]);
            }
 
            //数据来源四:ControllerContext.RequestContext.RouteData.DataTokens
            foreach (var item in controllerContext.RequestContext.RouteData.DataTokens)
            {
                if (dataSource.ContainsKey(item.Key.ToLower()))
                {
                    continue;
                }
                dataSource.Add(item.Key.ToLower(), controllerContext.RequestContext.RouteData.DataTokens[item.Key]);
            }
 
            if (dataSource.TryGetValue(modelName.ToLower(), out value))
            {
                value = Convert.ChangeType(value, modelType);
                return true;
            }
            return false;
        }
    }


 

3.ControllerActionInvoker

实现了IActionInvoker接口的ControllerActionInvoker是默认使用的ActionInvoker。在实现的InvokerAction方法中,我们根据Action的名称得到用于描述对应方法的MethodInfo对象,进而得到描述所有参数的ParameterInfo列表。针对每个ParameterInfo对象,我们借助ModelBinder对象采用Model绑定的方式从当前请求中获取源数据并生成相应的参数对象。

 

public class ControllerActionInvoker : IActionInvoker
    {
        public IModelBinder ModelBinder { get; private set; }
        public ControllerActionInvoker()
        {
            this.ModelBinder = new DefaultModelBinder();
        }
 
        public void InvokeAction(ControllerContext controllerContext, string actionName)
        {
            MethodInfo methodInfo = controllerContext.Controller.GetType().GetMethods().First(m => string.Compare(actionName, m.Name, true) == 0);
            List parameters = new List();
            foreach (ParameterInfo parameter in methodInfo.GetParameters())
            {
                parameters.Add(this.ModelBinder.BindModel(controllerContext, parameter.Name, parameter.ParameterType));
            }
            ActionExecutor executor = new ActionExecutor(methodInfo);
            ActionResult actionResult = (ActionResult)executor.Execute(controllerContext.Controller, parameters.ToArray());
            actionResult.ExecuteResult(controllerContext);
        }
    }


 

接下来我们创建一个类型为ActionExecutor的对象,并将激活Controller对象和通过Model绑定生成的参数列表作为输入参数调用这个ActionExecutor对象的Execute方法,目标Action方法最终得到执行。

4.ActionExecutor

目标Action方法的执行最终是由ActionExecute来完成的,那么它具体采用怎样的方法执行策略呢?虽然ActionExecute是根据描述目标Action方法的MethodInfo来创建的,,它完全可以采用反射的方式来执行此方法。但是为了获得更高的性能,他并没有这么做。目标Action方法的执行最终是采用“表达式树”的方式来完成的。

我们可以利用表达式树将一段代码表示成一种树状的数据结构,这个表达式可以被编译成可执行代码。基于表达式树对目标Action方法的执行实现在ActionExecute的Execute方法中。如下所示,我们根据描述被执行Action方法的MethodInfo对象来创建ActionExecute对象,并在静态方法CreateExecute中根据这个MethodInfo对象来构建用于执行目标方法的表达式树的表达式树并对其进行编译生成一个Func类型的委托对象。目标Action方法的执行最终由此委托对象来完成。

 

internal class ActionExecutor
    {
        private static Dictionary> executors = new Dictionary>();
        private static object syncHelper = new object();
        public MethodInfo MethodInfo { get; private set; }
 
        public ActionExecutor(MethodInfo methodInfo)
        {
            this.MethodInfo = methodInfo;
        }
 
        public object Execute(object target, object[] arguments)
        {
            Func executor;
            if (!executors.TryGetValue(this.MethodInfo, out executor))
            {
                lock (syncHelper)
                {
                    if (!executors.TryGetValue(this.MethodInfo, out executor))
                    {
                        executor = CreateExecutor(this.MethodInfo);
                        executors[this.MethodInfo] = executor;
                    }
                }
            }
            return executor(target, arguments);
        }
 
        private static Func CreateExecutor(MethodInfo methodInfo)
        {
            ParameterExpression target = Expression.Parameter(typeof(object), "target");
            ParameterExpression arguments = Expression.Parameter(typeof(object[]), "arguments");
 
            List parameters = new List();
            ParameterInfo[] paramInfos = methodInfo.GetParameters();
            for (int i = 0; i < paramInfos.Length; i++)
            {
                ParameterInfo paramInfo = paramInfos[i];
                BinaryExpression getElementByIndex = Expression.ArrayIndex(arguments, Expression.Constant(i));
                UnaryExpression convertToParameterType = Expression.Convert(getElementByIndex, paramInfo.ParameterType);
                parameters.Add(convertToParameterType);
            }
 
            UnaryExpression instanceCast = Expression.Convert(target, methodInfo.ReflectedType);
            MethodCallExpression methodCall = methodCall = Expression.Call(instanceCast, methodInfo, parameters);
            UnaryExpression convertToObjectType = Expression.Convert(methodCall, typeof(object));
            return Expression.Lambda>(convertToObjectType, target, arguments).Compile();
        }
    }


 

ActionExecute对象的Execute方法执行之后返回的对象代表执行目标Action方法方法返回的值,假设这个返回值总是一个ActionResult对象,我们会直接将其转换成ActionResult类型并调用其ExecuteResult方法对请求作最终的响应。

5.ActionResult

我们为具体的ActionResult定义了一个ActionResult抽象基类。该抽象基类具有一个参数类型为ControllerContext的抽象方法ExecuteResult,我们最终对请求的实现就实现在该方法中。

 

public abstract class ActionResult
    {
        public abstract void ExecuteResult(ControllerContext context);
    }

 

在之前创建的例子中,Action方法返回的是一个类型为RawContentResult的对象。顾名思义,RawContentResult旨在将我们写入的内容原封不动地呈现出来。如下面的代码片段所示,RawContentResult具有一个Action类型的只读属性Callback,我们利用它来写入需要呈现的内容。在实现的ExecuteResult方法中,我们对这个Action对象予以执行,而作为参数的正是当前HttpResponse的Output属性表示的TextWrite对象,毫无疑问通过Action对象写入的内容将作为响应返回到客户端。

 

public class RawContentResult : ActionResult
    {
        public Action Callback { get; private set; }
        public RawContentResult(Action action)
        {
            this.Callback = action;
        }
        public override void ExecuteResult(ControllerContext context)
        {
            this.Callback(context.RequestContext.HttpContext.Response.Output);
        }
    }