Reputation: 180914
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
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