Action 方法的执行

Posted 空城守望城空

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Action 方法的执行相关的知识,希望对你有一定的参考价值。

  在 Aps.net mvc 应用中对请求的处理最终都是转换为对某个 Controller 中的某个 Action 方法的调用,因此,要对一个请求进行处理,第一步,需要根据请求解析出对应的 ControllerAction 的名称,这是 Asp.net mvc 中的路由 的职责所在,第二步,需要根据第一步解析出来的内容定位对请求进行处理的 Action 方法所属的 Controller 类型,定位的过程称为 Asp.net mvc 中 Controller 的激活,第三步,就是根据请求的的内容到第二步定位的 Controller 中查找最终用来处理请求的 Action 方法并执行。
  要执行一个方法,首先要根据方法签名从多个重载(如果有)中选出最为匹配的一个,然后对方法的参数进行绑定,Action 方法的执行亦是如此。
  这一篇主要介绍一下 Action 方法执行过程的一些关键的组件。

关键组件

ActionInvoker

  在 Asp.net mvc 中的 Controller 类型是指那些直接或间接的实现了 IController 接口的类型,在该接口中仅定义了一个 void Execute(RequestContext requestContext) 方法,在通过 Controller 的激活过程成功创建 Controller 类型的实例后,后续的 Action 方法的执行便是由该方法来执行的,该方法是一个同步的方法,但默认情况下,创建 Controller 实例后的后续操作是异步实现的,这些异步的操作是通过 IAsyncController 接口来实现的,该接口继承自 IController,定义如下:

    public interface IAsyncController : IController
    {
        IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state);
        void EndExecute(IAsyncResult asyncResult);
    }
}

  Action 方法的执行是通过 IActionInvoker 来实现的,该接口中只定义了一个 bool InvokeAction(ControllerContext controllerContext, string actionName) 方法,该接口同样具有一个异步的版本 IAsyncActionInvoker,该接口继承自 IActionInvoker,其定义如下:

    public interface IAsyncActionInvoker : IActionInvoker
    {
        IAsyncResult BeginInvokeAction(ControllerContext controllerContext, string actionName, AsyncCallback callback, object state);
        bool EndInvokeAction(IAsyncResult asyncResult);
    }
}

  具体使用同步还是异步的版本来执行 Action,如果 Controller 执行的是同步的 Execute(RequestContext requestContext) 方法,则便使用同步的 IActionInvoker,否则使用的是异步的 IAsyncActionInvoker

ActionInvoker 的创建

  ActionInvoker 实例的创建是通过 Controller 类的 IActionInvoker CreateActionInvoker() 方法来创建的, IActionInvoker 的创建也是使用了 IOC 和工厂模式的思想,首先判断内部 IOC 容器(IDependencyResolver 类型)是否存在 IAsyncActionInvokerFactory类型的绑定,如果存在则创建该类型的实例,然后调用其 CreateInstance() 创建 IActionInvoker 类型的实例并返回,否则,判断 IOC 容器内是否存在 IActionInvokerFactory 类型的绑定,如果存在则创建该类型的实例,然后调用其 CreateInstance() 方法创建 IActionInvoker 类型的实例并返回。如果不存在,则判断 IOC 容器内是否存在 IAsyncActionInvoker 类型的绑定,如果存在,则创建实例并返回,否则判断是否存在 IActionInvoker 类型的绑定,如果存在,则创建实例并返回,如果不存在上述的任何类型的绑定,则创建一个 AsyncControllerActionInvoker 类型的实例并返回。
  

ControllerDescriptor

  该类从名字上就可以看出其是对 Controller 类型的一个描述,是一个抽象类,其定义(主要成员)如下:

    public abstract class ControllerDescriptor : ICustomAttributeProvider, IUniquelyIdentifiable
    {
	    //根据 ControllerContext 和 Action的名称获取指定的 Action的描述信息
        public abstract ActionDescriptor FindAction(ControllerContext controllerContext, string actionName)
		
		//获取指定Controller 下定义的所有的有效的 Action的信息
        public abstract ActionDescriptor[] GetCanonicalActions();
		
		//ICustomAttributeProvider 接口方法,获取指定Controller类型上使用的所有特性(Attribute) 的信息,参数 inherit 表示是否进行递归查找
        public virtual object[] GetCustomAttributes(bool inherit);
        //同属 ICustomAttributeProvider 接口方法,返回指定类型标识的自定义属性数组
        public virtual object[] GetCustomAttributes(Type attributeType, bool inherit);
		
		//获取 Controller 类型上使用的过滤器属性
        public virtual IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache);
		
		//ICustomAttributeProvider接口方法,判断指定类型的自定义属性是否存在
        public virtual bool IsDefined(Type attributeType, bool inherit);
      
	    //控制器的名称
        public virtual string ControllerName;
		//控制器的类型
        public abstract Type ControllerType { get; }
		//IuniquelyIdentifiable接口成员
        public virtual string UniqueId;//当前类型的唯一标识,由控制器名称、控制器类型以及当前类的类型生成
}

  上面的 GetCanonicalActions() 方法用来获取有效的 Action 方法,那么什么是有效的 Action 方法?一个规范的 Action 方法应满足如下的要求:

  • 所在类型必须直接或间接的继承自抽象类 ControllerBase
  • 必须是一个公共的实例方法
  • 必须是一个可调用的方法(BindingFlags.InvokedMethod),例如,构造函数便不是有效的 Action 方法
  • 方法不具有泛型参数
  • 方法不具有 out 和 ref 参数

  ControllerDescriptor 是抽象类型,其具有两个子类,ReflectedControllerDescriptorReflectedAsyncControllerDescriptor . 子类内部对父类中的方法进行重写,从名称上来看,一个是同步的,一个是异步的,要说清楚这两个类最大的不同,需要说明一个类 ActionSelectorBase,这是一个抽象类,该类的主要成员如下示:

//属性部分
public MethodInfo[] ActionMethods { get; private set; }//当前Controller下所有Action方法集合

public HashSet<MethodInfo> StandardRouteMethods { get; private set; }//所有 Action 方法的 HashSet集合

public MethodInfo[] AliasedMethods{get;}//所有具有别名的Action方法的集合
 
public ILookup<string, MethodInfo> NonAliasedMethods{get;private set;};//所有没有别名的方法的集合

private StandardRouteActionMethodCache CreateStandardRouteCache()//Action 方法缓存,该类是该类内部的一个私有类,内部仅具有两个属性,分别表示无别名的 Action 方法的集合和具有别名方法的集合。

//方法部分
protected void Initialize(Type controllerType);//负责对上述的属性进行初始化

protected abstract bool IsValidActionMethod(MethodInfo methodInfo);//判断给定方法是否为有效的 Action方法,基本的判定标准即上面说的那些

//根据给定的 Action 名称获取对应的ActionMethod,在该方法中首先从缓存的具有别名的方法集合和无别名的方法集合中筛选中具有指定名称的方法,然后改根据请求的方法(Post、Get等)对上一步得到的集合进行筛选,移除不满足的项
protected List<MethodInfo> FindActionMethods(ControllerContext controllerContext, string actionName);

//获取指定 Method 的 ActionName,如果方法没有别名则直接返回它本身的名称,如果方法有 ActionNameSelector 属性修饰,则返回其Name属性
public string GetActionName(MethodInfo methodInfo)

//根据指定的 ActionName 获取对应的 ActionMethod,内部调用 FindActionMethods,如果不存在,返回 null,具有多个时抛出 AmbiguousMatchException 异常
public MethodInfo FindActionMethod(ControllerContext controllerContext, string actionName)

  该类具有两个子类:ActionMethodSelectorAsyncActionMethodSelector,前者处理一些普通的同步方法相关的操作,后者处理一些异步方法相关的操作,这里的异步方法包括两种,一种是具有 Aysnc 前缀或具有 Completed 后缀的方法,此类方法主要是为了保持 Asp.net Mvc 的向后的兼容性,是 Asp.net mvc 3 之前实现异步的方式,另外一种是返回类型为 Task<ActionResult> 的方法。是 Asp.net mvc 3 之后提倡的异步 Action 实现方式。
  在 ControllerDescriptor 的两个子类中均有一个 ActionMethodSelectorBase 类型的变量 _selector,不同的是 ReflectedControllerDescriptor 的该变量是 ActionMethodSelector 类型的,而 ReflectedAsyncControllerDescriptor 的该变量是 AsyncMethodSelector 类型的。
  在 ReflectedControllerDescriptorReflectedAsyncController 中均对基类中的 ActionDescriptor FindAction(ControllerContext context,string actionName) 进行重写,前者内部调用 _selector 的同名方法返回一个与 actionName 匹配的 MethodInfo,最终返回的是上一步的 MethodInfoactionName 等作为参数初始化的一个 ReflectedActionDescriptor。对于后者,调用 _selector 的同名方法返回的是一个如下所示的委托

    delegate ActionDescriptor ActionDescriptorCreator(string actionName, ControllerDescriptor controllerDescriptor);}

  针对上述Action 方法的两种不同的异步实现方法,返回的是不同的结果,对于具有 Async 前缀Completed 后缀的方法,返回的是 ReflectedAsyncActionDescriptor 类型的实例,对于返回类型为 Task<ActionResult> 的 Action 方法,返回的是 TaskAsyncActionDescriptor 类型的实例。

ActionDescriptor

  顾名思义,这个类用于描述 Action 相关的一些信息,该类是一个抽象类,其定义(主要成员)如下所示:

public abstract class ActionDescriptor : ICustomAttributeProvider, IUniquelyIdentifiable
{
 /*****************************属性部分*********************************/
 //当前 Action 方法的名称
 public abstract string ActionName { get; }
 
 //当前 Action 所在的 Controller相关的 ControllerDescriptor
 public abstract ControllerDescriptor ControllerDescriptor { get; }
 
 //Asp.net mvc 自定义的基于 Dictinary 的线程安全的缓存集合,后面会介绍
 internal ActionMethodDispatcherCache DispatcherCache{get{..};set{..}
 
  public virtual string UniqueId{};//IUniquelyIdentifiable 接口成员,当前类型实例的唯一标识
  由当前类型、ControllerDescriptor、ActionName三者计算产生

/***************************** 方法部分 ***********************************/

/ICustomAttributeProvider 接口方法,功能不在赘述,但这里并没有提供具体的实现,前两个返回的均为空的 object[]
public virtual object[] GetCustomAttributes(bool inherit);

public virtual object[] GetCustomAttributes(Type attributeType, bool inherit);

public virtual bool IsDefined(Type attributeType, bool inherit);

//其它方法
public abstract object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters);//ActionDescriptor 类型的核心方法,Action 方法的返回的 ActionResult 便是由该方法的返回结果处理转换产生的

//根据跟定的 ParameterInfo 去 parameters中寻找指定的参数值,key 为 parameterInfo.Name,查找失败、参数类型不匹配、查找值为空但参数不能为空时抛出异常
internal static object ExtractParameterFromDictionary(ParameterInfo parameterInfo, IDictionary<string, object> parameters, MethodInfo methodInfo);

//作用同上面的方法,但在出现上述抛出异常的情况时会使用默认值替代而不是抛出异常,这里的默认值有两种,一种是直接在方法参数后使用等号赋值的,如Say(string name = "tom"),另外一种是在 model 类的属性上使用 DefaultValueAttribute 设置的默认值,前者具有更高的优先级
internal static object ExtractParameterOrDefaultFromDictionary(ParameterInfo parameterInfo, IDictionary<string, object> parameters);

//获取所有定义在当前 Action 上的过滤器Filters
public virtual IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache);

//获取所有的参数信息
public abstract ParameterDescriptor[] GetParameters();

  下面说一下 ActionMethodDispatcherCache ** 这个类型,该类型派生自 ReaderWriterCache<MethodInfo, ActionMethodDispatcher>,其中的ReaderWriterCache<TKey,TValue> 是一个 Asp.net mvc 自定义的一个基于 Dictinary 的线程安全的集合,ActionDescriptor** 可以看作是与当前 Action 方法相关的 MethodInfo 的封装,在找到请求所对应的 Action 方法时正常的思路是使用反射的方式来调用对应的方法,但是对 Action 方法的访问是相当的频繁的,大量使用反射会严重的影响程序的性能,因此,Asp.net mvc 内部并没有这么实现,而是将对应的 Action 方法转换为一棵表达式树,然后将表达式树编译为委托,最终执行委托的方式来实现的。ActionMethodDispatcher 类最重要的功能就是根据传入的 MethodInfo 编译产生一个委托,最终产生的委托类型为 object ActionExecutor(ControllerBase controller,object[] parameters)。上面所说的 ActionDescriptor 的核心方法 object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters) 的返回结果便是内部调用该类型的委托产生的。

ActionDescriptor 的子类

  ActionDescriptor 本身为抽象类,不能直接实例化使用,其类型具有两个直接子类:ReflectedActionDescriptorAsyncActionDescriptor,后者亦是一个抽象类,其具有两个子类型:ReflectedAsyncActionDescriptorTaskAsyncActionDescriptor。这里主要对 ReflectedActionDescriptorTaskAsyncActionDescriptor 进行说明。

  • ReflectedActionDescriptor
      该类主要看其 object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters) 方法的实现,其实现如下所示:
public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters)
        {
            if (controllerContext == null)
            {
                throw new ArgumentNullException("controllerContext");
            }
            if (parameters == null)
            {
                throw new ArgumentNullException("parameters");
            }

            // Performance sensitive so avoid Linq or delegates.
            ParameterInfo[] parameterInfos = MethodInfo.GetParameters();
            object[] parametersArray = new object[parameterInfos.Length];
            for (int i = 0; i < parameterInfos.Length; i++)
            {
                ParameterInfo parameterInfo = parameterInfos[i];
                object parameter = ExtractParameterFromDictionary(parameterInfo, parameters, MethodInfo);
                parametersArray[i] = parameter;
            }

            ActionMethodDispatcher dispatcher = DispatcherCache.GetDispatcher(MethodInfo);
            object actionReturnValue = dispatcher.Execute(controllerContext.Controller, parametersArray);
            return actionReturnValue;
        }

  参数中的 ControllerContext 可以看作是 ControllerRouteDataRequestContext 三者的封装,跟前面所说的一样,根据传入的 MethodInfo 实例获取 ActionMethodDispatcher 类型实例,调用其 Execute 方法并 返回执行结果。

  • TaskAsyncActionDescriptor
      在说这个类型之前先看一下其直接基类:AsyncActionDescriptor,其定义如下:
public abstract class AsyncActionDescriptor : ActionDescriptor
    {
        public abstract IAsyncResult BeginExecute(ControllerContext controllerContext, IDictionary<string, object> parameters, AsyncCallback callback, object state);

        public abstract object EndExecute(IAsyncResult asyncResult);

        public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters);//这里直接抛出无效操作的异常

        internal static AsyncManager GetAsyncManager(ControllerBase controller)
        {
            IAsyncManagerContainer helperContainer = controller as IAsyncManagerContainer;
            if (helperContainer == null)
            {
                throw Error.AsyncCommon_ControllerMustImplementIAsyncManagerContainer(controller.GetType());
            }

            return helperContainer.AsyncManager;
        }
    }

  前两个方法没有什么好说的,标准的异步操作方法,重点说一下最后一个方法,AsyncManager GetAysncManager(ControllerBase controller),该方法返回一个 AsyncManager类型,该类型主要功能有两个,第一,在异步操作和回调操作之间传递参数,第二个是向系统发送异步操作开始和结束的通知。该类的定义如下:

//属性部分
public event EventHandler Finished;//异步操作完成时的回调事件

public OperationCounter OutstandingOperations { get; private set; }//当前执行的异步操作的计数器

public IDictionary<string, object> Parameters { get; private set; }//异步操作与回调操作之间参数传递的集合

public int Timeout{get;set;}//异步操作的超时时间,单位为毫秒,默认值为 45*1000

//方法部分
public virtual void Finish();//异步操作完成时的回调函数

public virtual void Sync(Action action);异步操作的执行函数,内部调用 SynchronizationContext 实例的同名方法

  OutstandingOperations 属性是整个异步操作的核心属性,在异步操作开始和结束,应分别调用该类型的 Increment 方法和 Decrement 方法增加和减少计数操作(内部执行原子运算),每次在调用上述的两个方法时,都会检验当前的计数值,如果计数值为0,则表示异步操作已完成,此时,该类型的 Completed 方法便会表调用,响应的 AsyncManager 类型的 Finish 方法也会被调用。抽象类 Controller 实现了 IAsyncManagerContainer 接口,该接口中仅定义了一个 AsyncManager 类型的属性。因此,对一步操作超时进行设置的两个属性 :AsyncTimeoutAttributeNoAsyncTimeoutAttribute,其中前者继承自 ActionFilter,重写了过滤器的 void OnExecuting(ActionExecutingContext context) 方法,后者继承自前者,相当于将超时时间 duration 设置为 -1,表示永不超时。在重写的 void OnExecuting(ActionExecutingContext context) 便是对 Controller 的 AsyncManager 实例的 TimeOut 属性进行设置。

ParameterDescriptor

  顾名思义,该类型是对 Action 方法的参数进行描述的,是一个抽象类,同样的实现了 ICustomAttributeProvider 接口,该类的定义如下示:

public abstract class ParameterDescriptor : ICustomAttributeProvider
    {
    	//参数所属的 Action 的 ActionDescriptor
        public abstract ActionDescriptor ActionDescriptor { get; }

        //参数绑定信息,例如参数值的获取路径(Querstring、Form表单等)、参数是否需要进行绑定,是否具有某些前缀等信息
        public virtual ParameterBindingInfo BindingInfo{get;}

        //参数默认值
        public virtual object DefaultValue}{get;}
        
        //参数名称
        public abstract string ParameterName { get; }

        //参数类型
        public abstract Type ParameterType { get; }

        //以下为 ICustomAttributeProvider 接口方法
        public virtual object[] GetCustomAttributes(bool inherit);
        
        public virtual object[] GetCustomAttributes(Type attributeType, bool inherit);
       
        public virtual bool IsDefined(Type attributeType, bool inherit);
    }

  该类型仅有一个实现类:ReflectedParameterDescriptor,在该类中对基类中的方法或属性进行重写或实现,增加了一个 ParameterInfo 类型的属性,表示当前参数的一些信息。
  至此,Action 执行过程中一些关键的组件介绍完毕。

以上是关于Action 方法的执行的主要内容,如果未能解决你的问题,请参考以下文章

PHP 返回部分代码文档

创建自己的代码片段(CodeSnippet)

如何通过代码设置片段标签?

片段中的 startActivityForResult

Android代码片段

Android开发常用代码片段