限制每个动作的HTTP动词

6
每个操作都限制可用的HTTP动词是一种好的做法吗?没有 [HttpGet][HttpPost][HttpPut][HttpDelete] 装饰每个操作,我的代码更简洁,但可能也不够健壮或安全。除非需要显式指定动词,例如有两个“Create”操作,其中 GET 版本返回一个新表单,POST 版本插入一个新记录,否则我在许多教程或示例代码中没有看到这样做。

也许一个更好的方法是反过来,创建自定义属性来拒绝你不想要的动词。从未尝试过,只是说一下 :-) - goenning
3个回答

3

个人而言,我尽量遵循RESTful规范并指定HTTP动词,但对于不修改服务器状态的GET操作,可以使用任何HTTP动词进行调用。


1

是的,我认为将操作限制在它所应处理的适当HTTP方法上是一个好的实践。这样可以避免错误请求进入系统,减少可能攻击的效果,提高代码文档化程度,强制执行RESTful设计等。

是的,使用[HttpGet][HttpPost]等属性会使您的代码更难阅读,特别是如果您还使用其他属性如[OutputCache][Authorize]等。

我使用一个自定义的IActionInvoker小技巧,而不是使用属性,我将HTTP方法添加到操作方法名称前缀中,例如:

public class AccountController : Controller {

   protected override IActionInvoker CreateActionInvoker() {
      return new HttpMethodPrefixedActionInvoker();
   }

   public ActionResult GetLogOn() {
      ...
   }

   public ActionResult PostLogOn(LogOnModel model, string returnUrl) {
      ...
   }

   public ActionResult GetLogOff() {
      ...
   }

   public ActionResult GetRegister() {
      ...
   }

   public ActionResult PostRegister(RegisterModel model) {
      ...
   }

   [Authorize]
   public ActionResult GetChangePassword() {
      ...
   }

   [Authorize]
   public ActionResult PostChangePassword(ChangePasswordModel model) {
      ...
   }

   public ActionResult GetChangePasswordSuccess() {
      ...
   }
}

请注意,这不会更改操作名称,它们仍然是LogOnLogOffRegister等。
以下是代码:
using System;
using System.Collections.Generic;
using System.Web.Mvc;

public class HttpMethodPrefixedActionInvoker : ControllerActionInvoker {

   protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName) {

      var request = controllerContext.HttpContext.Request;

      string httpMethod = request.GetHttpMethodOverride()
         ?? request.HttpMethod;

      // Implicit support for HEAD method. 
      // Decorate action with [HttpGet] if HEAD support is not wanted (e.g. action has side effects)

      if (String.Equals(httpMethod, "HEAD", StringComparison.OrdinalIgnoreCase))
         httpMethod = "GET";

      string httpMethodAndActionName = httpMethod + actionName;

      ActionDescriptor adescr = base.FindAction(controllerContext, controllerDescriptor, httpMethodAndActionName);

      if (adescr != null)
         adescr = new ActionDescriptorWrapper(adescr, actionName);

      return adescr;
   }

   class ActionDescriptorWrapper : ActionDescriptor {

      readonly ActionDescriptor wrapped;
      readonly string realActionName;

      public override string ActionName {
         get { return realActionName; }
      }

      public override ControllerDescriptor ControllerDescriptor {
         get { return wrapped.ControllerDescriptor; }
      }

      public override string UniqueId {
         get { return wrapped.UniqueId; }
      }

      public ActionDescriptorWrapper(ActionDescriptor wrapped, string realActionName) {

         this.wrapped = wrapped;
         this.realActionName = realActionName;
      }

      public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters) {
         return wrapped.Execute(controllerContext, parameters);
      }

      public override ParameterDescriptor[] GetParameters() {
         return wrapped.GetParameters();
      }

      public override object[] GetCustomAttributes(bool inherit) {
         return wrapped.GetCustomAttributes(inherit);
      }

      public override object[] GetCustomAttributes(Type attributeType, bool inherit) {
         return wrapped.GetCustomAttributes(attributeType, inherit);
      }

      public override bool Equals(object obj) {
         return wrapped.Equals(obj);
      }

      public override int GetHashCode() {
         return wrapped.GetHashCode();
      }

      public override ICollection<ActionSelector> GetSelectors() {
         return wrapped.GetSelectors();
      }

      public override bool IsDefined(Type attributeType, bool inherit) {
         return wrapped.IsDefined(attributeType, inherit);
      }

      public override string ToString() {
         return wrapped.ToString();
      }
   }
}

0

你不需要指定HttpGet,其他的都需要。


如果没有指定,这是否意味着默认情况下存在HttpGet? - J.W.
1
这不是“需要”的问题,而是“应该”的问题。如果一个操作用于更新某些内容(例如来自 AJAX POST 的内容),你不一定需要使用[HttpPost]标记,但似乎这是个好主意。 - MikeWyatt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接