如何通过命名约定约束ASP.NET MVC控制器Action的HTTP请求谓词
实现ASP.NET MVC Action按命名约定自动绑定HTTP谓词
当然可以实现!ASP.NET MVC的扩展性很强,我们可以通过自定义Action方法选择器来复刻Web API的命名约定机制,让Action根据名称前缀自动响应特定HTTP谓词,不用给每个Action手动加[HttpGet]/[HttpPost]这类特性。
核心思路
MVC的ActionMethodSelectorAttribute是抽象类,我们可以继承它并重写IsValidForRequest方法,在这个方法里根据Action名称的前缀判断允许的HTTP谓词,再和当前请求的方法做匹配。这样就能实现“命名约定自动约束”的效果。
具体实现步骤
1. 创建自定义的Action选择器特性
首先写一个继承自ActionMethodSelectorAttribute的类,定义我们的命名规则(你可以根据自己的业务需求调整前缀):
using System; using System.Linq; using System.Web.Mvc; public class HttpVerbByNamingConventionAttribute : ActionMethodSelectorAttribute { // 定义各HTTP谓词对应的前缀 private static readonly string[] _getPrefixes = { "Get", "Find", "Query" }; private static readonly string[] _postPrefixes = { "Post", "Save", "Create" }; private static readonly string[] _putPrefixes = { "Put", "Update" }; private static readonly string[] _deletePrefixes = { "Delete", "Remove" }; public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo) { // 先检查Action是否已经手动添加了HTTP谓词特性(优先使用手动配置) var hasHttpGet = methodInfo.GetCustomAttributes(typeof(HttpGetAttribute), false).Any(); var hasHttpPost = methodInfo.GetCustomAttributes(typeof(HttpPostAttribute), false).Any(); var hasHttpPut = methodInfo.GetCustomAttributes(typeof(HttpPutAttribute), false).Any(); var hasHttpDelete = methodInfo.GetCustomAttributes(typeof(HttpDeleteAttribute), false).Any(); if (hasHttpGet) return string.Equals(controllerContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase); if (hasHttpPost) return string.Equals(controllerContext.HttpContext.Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase); if (hasHttpPut) return string.Equals(controllerContext.HttpContext.Request.HttpMethod, "PUT", StringComparison.OrdinalIgnoreCase); if (hasHttpDelete) return string.Equals(controllerContext.HttpContext.Request.HttpMethod, "DELETE", StringComparison.OrdinalIgnoreCase); // 没有手动配置,应用命名约定规则 var actionName = methodInfo.Name; var requestMethod = controllerContext.HttpContext.Request.HttpMethod; // 匹配GET前缀 if (_getPrefixes.Any(prefix => actionName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))) { return string.Equals(requestMethod, "GET", StringComparison.OrdinalIgnoreCase); } // 匹配POST前缀 if (_postPrefixes.Any(prefix => actionName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))) { return string.Equals(requestMethod, "POST", StringComparison.OrdinalIgnoreCase); } // 匹配PUT前缀 if (_putPrefixes.Any(prefix => actionName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))) { return string.Equals(requestMethod, "PUT", StringComparison.OrdinalIgnoreCase); } // 匹配DELETE前缀 if (_deletePrefixes.Any(prefix => actionName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))) { return string.Equals(requestMethod, "DELETE", StringComparison.OrdinalIgnoreCase); } // 无匹配前缀时,默认允许所有请求方法(可根据需求改为return false拒绝) return true; } }
2. 应用特性到控制器
有两种方式让这个特性生效:
- 单个控制器生效:直接给目标控制器加特性
- 全局生效:创建一个基类控制器,所有业务控制器继承它,基类上加上特性(更高效)
方式一:单个控制器应用
[HttpVerbByNamingConvention] public class UserController : Controller { // 仅响应GET请求(匹配Find前缀) public ActionResult FindUser(int id) { // 业务逻辑... return View(); } // 仅响应POST请求(匹配Save前缀) public ActionResult SaveUser(UserModel model) { if (ModelState.IsValid) { // 保存逻辑... return RedirectToAction("FindUser", new { id = model.Id }); } return View(model); } // 仅响应PUT请求(匹配Update前缀) public ActionResult UpdateUser(UserModel model) { // 更新逻辑... return Json(new { success = true }); } // 仅响应DELETE请求(匹配Delete前缀) public ActionResult DeleteUser(int id) { // 删除逻辑... return Json(new { success = true }); } // 无匹配前缀,允许所有请求方法 public ActionResult Index() { return View(); } }
方式二:全局生效(基类控制器)
// 基类控制器添加特性 [HttpVerbByNamingConvention] public abstract class BaseController : Controller { } // 业务控制器继承基类即可自动生效 public class UserController : BaseController { // 这里的Action自动遵循命名约定 }
额外说明
- 你可以完全自定义前缀规则:比如添加
Patch前缀对应PATCH请求,或者调整现有前缀列表 - 如果某个Action需要打破命名约定,直接给它加
[HttpGet]/[HttpPost]等内置特性即可——因为我们的代码优先检查手动添加的特性,会覆盖命名约定 - 无匹配前缀的Action默认允许所有请求方法,如果你想默认拒绝,可以把最后一行的
return true改成return false
内容的提问来源于stack exchange,提问作者Ankush Jain
相关产品推荐
相关产品推荐

