ASP.NET MVC中的[HttpPost]是什么呢?

[HttpPost]是什么呢?


在MVC开发中经常会看到[HttpPost][HttpGet],也许我们知道它是干什么用的但可能不知道它是怎样实现的,为了知道它的实现过程,我和大家一起一探究竟!

在Visual Studio中,随便找来一段代码,

使用F12定位它,发现是个位于System.Web.Mvc中的一个类HttpPostAttribute
它继承于ActionMethodSelectorAttribute
现在我们打开反编译工具打开Bin目录中的System.Web.Mvc.dll
找到查看HttpPostAttribute的代码,

工具:

  • JetBrains DotPeek

  • .NET Reflector

  • ILSpy

开始分析


HttpVerbs是什么
首先说一下,下面会用到:它是一个枚举

HttpVerbs.cs

public enum HttpVerbs
{
    Get = 1,
    Post = 2,
    Put = 4,
    Delete = 8,
    Head = 16,
    Patch = 32,
    Options = 64,
}

HttpPostAttribute.cs

using System.Reflection;

namespace System.Web.Mvc
{
    //  定义了一个标记
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public sealed class HttpPostAttribute : ActionMethodSelectorAttribute
    {
    //  创建AcceptVerbsAttribute对象并传入了HttpVerbs.Post,
    private static readonly AcceptVerbsAttribute _innerAttribute = new AcceptVerbsAttribute(HttpVerbs.Post);
    //  此处是重写父类的IsValidForRequest方法,但只是调用了AcceptVerbsAttribute的IsValidForRequest方法
    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
    {
      return HttpPostAttribute._innerAttribute.IsValidForRequest(controllerContext, methodInfo);
    }
  }
}

override重写了父类的IsValidForRequest方法,而里面调用了AcceptVerbsAttributeIsValidForRequest方法。

AcceptVerbsAttribute是什么?

AcceptVerbsAttribute.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Web.Mvc.Properties;

namespace System.Web.Mvc
{
    //  定义了一个标记
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    
    //  继承于 ActionMethodSelectorAttribute
    public sealed class AcceptVerbsAttribute : ActionMethodSelectorAttribute
    {
        //  声明了一个集合型的Verbs
        public ICollection<string> Verbs
        {
            get;
            private set;
        }
        
        //  第一个构造函数
        //  尾部直接调用了自己类中的EnumToArray函数传入了前面参数verbs
        public AcceptVerbsAttribute(HttpVerbs verbs) : this(AcceptVerbsAttribute.EnumToArray(verbs))
        {
        }
        
        //  第二个构造函数
        //  参数是无限大的string数组verbs
        //  它和上个构造方法不同的是它指定了某个或多个Verbs,而不是默认的全部(GET/POST/PUT/DELETE等)
        public AcceptVerbsAttribute(params string[] verbs)
        {
            if (verbs == null || verbs.Length == 0)
            {
                throw new ArgumentException(MvcResources.Common_NullOrEmpty, "verbs");
            }
            //  创建一个只可读的集合,并将参数verbs带入
            this.Verbs = new ReadOnlyCollection<string>(verbs);
        }

        //  如果verbs没有match,就将match加入到verbList中
        private static void AddEntryToList(HttpVerbs verbs, HttpVerbs match, List<string> verbList, string entryText)
        {
            if ((verbs & match) != (HttpVerbs)0)
            {
                verbList.Add(entryText);
            }
        }
        
        //  将verbs填入N个HttpMethod并返回个数组
        //  这段代码干的事就是将GET,POST,PUT之类的添加到默认的Verbs之中,
        
        internal static string[] EnumToArray(HttpVerbs verbs)
        {
            List<string> list = new List<string>();
            AcceptVerbsAttribute.AddEntryToList(verbs, HttpVerbs.Get, list, "GET");
            AcceptVerbsAttribute.AddEntryToList(verbs, HttpVerbs.Post, list, "POST");
            AcceptVerbsAttribute.AddEntryToList(verbs, HttpVerbs.Put, list, "PUT");
            AcceptVerbsAttribute.AddEntryToList(verbs, HttpVerbs.Delete, list, "DELETE");
            AcceptVerbsAttribute.AddEntryToList(verbs, HttpVerbs.Head, list, "HEAD");
            AcceptVerbsAttribute.AddEntryToList(verbs, HttpVerbs.Patch, list, "PATCH");
            AcceptVerbsAttribute.AddEntryToList(verbs, HttpVerbs.Options, list, "OPTIONS");
            return list.ToArray();
        }

        //  主要的方法!
        //  该方法重写了父类IsValidForRequest方法
        public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
        {
            if (controllerContext == null)
            {
                throw new ArgumentNullException("controllerContext");
            }
            string httpMethodOverride = controllerContext.HttpContext.Request.GetHttpMethodOverride();
            //  看看Verbs中是否包含从HttpContext获取到的HttpMethod,返回bool
            return this.Verbs.Contains(httpMethodOverride, StringComparer.OrdinalIgnoreCase);
        }
    }
}

我们再来看它的最重要的方法:

public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
    {
      if (controllerContext == null)
        throw new ArgumentNullException("controllerContext");
      return this.Verbs.Contains<string>(controllerContext.HttpContext.Request.GetHttpMethodOverride(), (IEqualityComparer<string>) StringComparer.OrdinalIgnoreCase);
    }

这也就是可以被子类被重写的方法了!

它将你HTTP请求中的方法GET/POST/PUT等带入到Verbs集合中看看是否存在,return的是bool类型代表有没有。

当然它自己还继承了一个类:

public sealed class AcceptVerbsAttribute : ActionMethodSelectorAttribute

发现这个类也继承于ActionMethodSelectorAttribute
你或许想起来了HttpPostAttribute也继承了ActionMethodSelectorAttribute

ActionMethodSelectorAttribute.cs

using System.Reflection;

namespace System.Web.Mvc
{
    //  注解标记
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public abstract class ActionMethodSelectorAttribute : Attribute
    {
        //  虚方法
        public abstract bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo);
    }
}

提供了一个虚方法IsValidForRequest,可让子类去实现。


回到现在


我们的HttpPostAttribute调用了它的AcceptVerbsAttribute中的IsValidForRequest

public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
    if (controllerContext == null)
        throw new ArgumentNullException("controllerContext");
        
    return this.Verbs.Contains<string>(controllerContext.HttpContext.Request.GetHttpMethodOverride(), (IEqualityComparer<string>) StringComparer.OrdinalIgnoreCase);
}

创建该类AcceptVerbsAttribute的方法是用了第二种构造函数

public AcceptVerbsAttribute(params string[] verbs)

而类中的IsValidForRequest方法是判断HttpMethod是否存在与HttpContext中,有就返回true,没有返回false。


问题来了


你也许会问我这个HttpPostAttribute到底在哪里被调用了,为什么我挂上[HttpPost]之后它就能只允许Post访问呢?

我们知道HttpPostAttribute重写了父类ActionMethodSelectorAttributeIsValidForRequest方法,
在重写中调用了AcceptVerbsAttributeIsValidForRequest方法,
所以我们用反编译工具跟踪谁调用了它(IsValidForRequest)即可!

根据反编译结果,得到类ActionMethodSelectorBase中的调用了它

protected static bool IsValidMethodSelector(ReadOnlyCollection<ActionMethodSelectorAttribute> attributes, ControllerContext controllerContext, MethodInfo method)
{
    int count = attributes.Count;
    for (int index = 0; index < count; ++index)
    {
        //  注意看这里:
        if (!attributes[index].IsValidForRequest(controllerContext, method))
        return false;
    }
    return true;
}

再深的层次我就不跟踪了,但是大家要明白一个道理,
有Attribute标记的地方就一定会有Reflection反射,反射可以获取到方法附加的Attribute,并且保存到MethodInfo中,在MVC中的Controller、Action相关的类中,都会用到反射来获取Attribute标记。

在我们这次分析中,只要继承于ActionMethodSelectorAttribute的标记类中实现IsValidForRequest都会经历在ActionMethodSelectorBase调用。

所以这也就是HttpPost、HttpGet和其它标记的实现过程。

posted @ 2018-01-04 12:01  KJXY  阅读(573)  评论(0编辑  收藏  举报