Michael Stum
Michael Stum

Reputation: 180914

Route that matches another route, ignoring HttpMethodConstraint?

I have an ASP.net MVC 3 Site with routes like this:

routes.MapRoute("Get", "endpoint/{id}",
    new { Controller = "Foo", action = "GetFoo" },
    new { httpMethod = new HttpMethodConstraint("GET") });

routes.MapRoute("Post", "endpoint/{id}",
    new { Controller = "Foo", action = "NewFoo" },
    new { httpMethod = new HttpMethodConstraint("POST") });

routes.MapRoute("BadFoo", "endpoint/{id}",
    new { Controller = "Error", action = "MethodNotAllowed" });

routes.MapRoute("NotFound", "", 
    new { controller = "Error", action = "NotFound" });

So in a Nutshell, I have a Route that matches on certain HTTP Verbs like GET and POST but on other HTTP Verbs like PUT and DELETE it should return a specific error.

My default Route is a 404.

If I remove the "BadFoo" route, then a PUT against endpoint/{id} returns a 404 because none of the other routes match, so it goes to my NotFound route.

The thing is, I have a ton of routes like Get and Post where I have an HttpMethodConstraint and where I would have to create a route like the BadFoo route just to catch a correct match on the route string but not on the Method, which blows up my routing unnecessarily.

How could I setup routing with only the Get, Post and NotFound routes while still differentiating between a HTTP 404 not found (=invalid URL) and HTTP 405 Method not allowed (=valid URL, wrong HTTP Method)?

Upvotes: 4

Views: 1061

Answers (1)

Max Toro
Max Toro

Reputation: 28608

Instead of using route constraint you can delegate the HTTP method validation to the MVC runtime, using a custom ControllerActionInvoker and ActionMethodSelector attributes like [HttpGet], [HttpPost], etc.:

using System;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcApplication6.Controllers {

   public class HomeController : Controller {

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

      [HttpGet]
      public ActionResult Index() {
         return Content("GET");
      }

      [HttpPost]
      public ActionResult Index(string foo) {
         return Content("POST");
      }
   }

   class CustomActionInvoker : ControllerActionInvoker {

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

         // Find action, use selector attributes
         var action = base.FindAction(controllerContext, controllerDescriptor, actionName);

         if (action == null) {

            // Find action, ignore selector attributes
            var action2 = controllerDescriptor
               .GetCanonicalActions()
               .FirstOrDefault(a => a.ActionName.Equals(actionName, StringComparison.OrdinalIgnoreCase));

            if (action2 != null) {
               // Action found, Method Not Allowed ?
               throw new HttpException(405, "Method Not Allowed");
            }
         }

         return action;
      }
   }
}

Note my last comment 'Action found, Method Not Allowed ?', I wrote this as a question because there can be ActionMethodSelector attributes that are not related with HTTP method validation...

Upvotes: 4

Related Questions