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方法,而里面调用了AcceptVerbsAttribute的IsValidForRequest方法。
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重写了父类ActionMethodSelectorAttribute的IsValidForRequest方法,
在重写中调用了AcceptVerbsAttribute的IsValidForRequest方法,
所以我们用反编译工具跟踪谁调用了它(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和其它标记的实现过程。