WebAPI 2.0 Post and Delete Routes

What is going on

Web API 2.0 does not a allow a route to match on two different controllers. This is solved in MVC 6 (which is Web API combined framework).

What can I do about it

First like @woogy and you say, it is not a very common pattern, so most users should just not go here (or move to MVC 6 when it goes RTM).

The root cause is that the route actually matches, the verb defined an an IActionHttpMethodProvider does not constraint the route from matching, and it matches on multiple controllers thus failing.

You can however define a constraint on the route, and as a side effect get a more succinct API.

Let us get started

Define a verb constraint

This will constraint the route to only match the predefined verb, so it wouldn't match the other controller.

public class VerbConstraint : IHttpRouteConstraint
{
    private HttpMethod _method;

    public VerbConstraint(HttpMethod method)
    {
        _method = method;
    }

    public bool Match(HttpRequestMessage request,
                      IHttpRoute route,
                      string parameterName,
                      IDictionary<string, object> values,
                      HttpRouteDirection routeDirection)
    {
        // Note - we only want to constraint on the outgoing path
        if (routeDirection == HttpRouteDirection.UriGeneration || 
            request.Method == _method)        
        {
            return true;
        }

        return false;
    }
}

Define an abstract base class for a new attribute

public abstract class VerbRouteAttribute : RouteFactoryAttribute, IActionHttpMethodProvider
{
    private string _template;
    private HttpMethod _method;

    public VerbRouteAttribute(string template, string verb)
        : base(template)
    {
        _method = new HttpMethod(verb);
    }

    public Collection<HttpMethod> HttpMethods
    {
        get
        {
            var methods = new Collection<HttpMethod>();
            methods.Add(_method);

            return methods;
        }
    }

    public override IDictionary<string, object> Constraints
    {
        get
        {
            var constraints = new HttpRouteValueDictionary();
            constraints.Add("verb", new VerbConstraint(_method));
            return constraints;
        }
    }
}

This class merges 3 things 1. The route attribute with the route template 2. Applies a verb route constraint to the route 3. Specifies the action method selector, so the rest of the system (like help page) recognizes it just like the [HttpPost] / [HttpDelete]

Now let us define implementations

public class PostRouteAttribute : VerbRouteAttribute
{
    public PostRouteAttribute(string template) : base(template, "POST")
    {
    }
}

public class DeleteRouteAttribute : VerbRouteAttribute
{
    public DeleteRouteAttribute(string template) : base(template, "DELETE")
    {
    }
}

These as you can tell are pretty trivial, and just make the use of these attributes in your code a lot smoother.

Finally let us apply the new attributes (and remove the method attribute)

[AllowAnonymous]
public class TestController : ApiController
{
    [DeleteRoute("api/test")]
    public IHttpActionResult Endpoint1()
    {
        return this.Ok("endpoint1");
    }
}

[AllowAnonymous]
public class TestController2 : ApiController
{
    [PostRoute("api/test")]
    public IHttpActionResult Endpoint2()
    {
        return this.Ok("endpoint2");
    }
}