0

I was searching for an answer to this question, and found this question, which is indeed very similar. However the solutions(s) posted there don't seem to be working for me... I wonder if it has to do with the question's age.

Given the following URL:

/my/items/6

I want HTTP PUT requests for this URL to be handled by one action method, and HTTP DELETE requests to be handled by another action method. Below are the routes I defined (note these are based in an area, so context is an AreaRegistrationContext instance, if that matters):

context.MapRoute(null,
    "my/items/{id}",
    new { area = "AreaName", controller = "ControllerName", action = "Replace" },
    new
    {
        httpMethod = new HttpMethodConstraint("POST", "PUT"),
    }
);

context.MapRoute(null,
    "my/items/{id}",
    new { area = "AreaName", controller = "ControllerName", action = "Destroy" },
    new
    {
        httpMethod = new HttpMethodConstraint("POST", "DELETE"),
    }
);

URL generation works fine with both of these routes, however there are problems when routing incoming requests. Only the first-declared route correctly maps to its respective action.

I dug into the HttpMethodConstraint source code and discovered that it does not care about the "X-HTTP-Method-Override" parameter, only HttpContext.Request.HttpMethod.

I was able to solve this problem with the following custom route constraint class:

public class HttpMethodOverrideConstraint : HttpMethodConstraint
{
    public HttpMethodOverrideConstraint(params string[] allowedMethods) 
        : base(allowedMethods) { }

    protected override bool Match(HttpContextBase httpContext, Route route, 
        string parameterName, RouteValueDictionary values, 
        RouteDirection routeDirection)
    {
        var methodOverride = httpContext.Request
            .Unvalidated().Form["X-HTTP-Method-Override"];

        if (methodOverride == null)
            return base.Match(httpContext, route, parameterName, 
                values, routeDirection);

        return 
            AllowedMethods.Any(m => 
                string.Equals(m, httpContext.Request.HttpMethod, 
                    StringComparison.OrdinalIgnoreCase))
            &&
            AllowedMethods.Any(m => 
                string.Equals(m, methodOverride, 
                    StringComparison.OrdinalIgnoreCase))
        ;
    }
}

...and these route definitions:

context.MapRoute(null,
    "my/items/{id}",
    new { area = "AreaName", controller = "ControllerName", action = "Replace" },
    new
    {
        httpMethod = new HttpMethodOverrideConstraint("POST", "PUT"),
    }
);

context.MapRoute(null,
    "my/items/{id}",
    new { area = "AreaName", controller = "ControllerName", action = "Destroy" },
    new
    {
        httpMethod = new HttpMethodOverrideConstraint("POST", "DELETE"),
    }
);

My question: is it really necessary to have a custom route constraint to accomplish this? Or is there any way to make it work out-of-the-box with standard MVC & routing classes?

Community
  • 1
  • 1
danludwig
  • 46,965
  • 25
  • 159
  • 237
  • possible duplicate of [REST Routes & Verbs - Handling routes without a parameter](http://stackoverflow.com/questions/10543685/rest-routes-verbs-handling-routes-without-a-parameter) – Matthew Whited Jun 06 '12 at 18:50

1 Answers1

0

Action filters are your friend...

HttpDeleteAttribute, HttpPutAttribute, HttpPostAttribute, HttpGetAttribute

Matthew Whited
  • 22,160
  • 4
  • 52
  • 69