Is it possible to separate GETs and POSTs into separate API Controller types and accessing them using the same Route Prefix?
Here are my controllers:
[RoutePrefix("api/Books")]
public class BooksWriteController : EventStoreApiController
{
[Route("")]
public void Post([FromBody] CommandWrapper commandWrapper){...}
}
[RoutePrefix("api/Books")]
public class BooksReadController : MongoDbApiController
{
[Route("")]
public Book[] Get() {...}
[Route("{id:int}")]
public Book Get(int id) {...}
}
Web API (1.x-2.x) does not support multiple attribute routes with the same path on different controllers. The result is a 404, because all the route matches more than one controller and at that point Web API will consider the result ambiguous.
Note that MVC Core does support this scenario note: MVC Core serves as both MVC & Web API.
If you choose to use Web API 2.11 (or newer) you can create a route constraint for the http method per controller and use it instead of the built in Route Attribute. The sample below shows that you can use RoutePrefix or directly Routes (like kmacdonald's answer).
using System.Collections.Generic;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Routing;
public class BooksWriteController : ApiController
{
[PostRoute("api/Books")]
public void Post() { }
}
[RoutePrefix("api/books")]
public class BooksReadController : ApiController
{
[GetRoute]
public void Get() { }
[GetRoute("{id:int}")]
public void Get(int id) { }
}
These two classes simplify the use of the constraint route attribute
class GetRouteAttribute : MethodConstraintedRouteAttribute
{
public GetRouteAttribute(string template) : base(template ?? "", HttpMethod.Get) { }
}
class PostRouteAttribute : MethodConstraintedRouteAttribute
{
public PostRouteAttribute(string template) : base(template ?? "", HttpMethod.Post) { }
}
This class allows adding constraints to the route generated
class MethodConstraintedRouteAttribute : RouteFactoryAttribute
{
public MethodConstraintedRouteAttribute(string template, HttpMethod method)
: base(template)
{
Method = method;
}
public HttpMethod Method
{
get;
private set;
}
public override IDictionary<string, object> Constraints
{
get
{
var constraints = new HttpRouteValueDictionary();
constraints.Add("method", new MethodConstraint(Method));
return constraints;
}
}
}
This is just a standard route constraint, nit: you may want to cache the constraints object to reduce allocations.
class MethodConstraint : IHttpRouteConstraint
{
public HttpMethod Method { get; private set; }
public MethodConstraint(HttpMethod method)
{
Method = method;
}
public bool Match(HttpRequestMessage request,
IHttpRoute route,
string parameterName,
IDictionary<string, object> values,
HttpRouteDirection routeDirection)
{
return request.Method == Method;
}
}
You don't always need to specify a RoutePrefix
on your controller. you could just put the route directly on the web methods:
public class BooksWriteController : EventStoreApiController
{
[Route("api/Books")]
public void Post([FromBody] CommandWrapper commandWrapper){...}
}
public class BooksReadController : MongoDbApiController
{
[Route("api/Books")]
public TaskTypeInfo[] Get() {...}
[Route("api/Books/{id:int}")]
public TaskTypeInfo Get(int id) {...}
}
However, I would imagine that your RoutePrefix
would work fine on both controllers. I think the attribute RoutePrefix
is used in conjunction with the Route
attribute which actually defines the route. This means that as long as you don't have any conflicting routes (this is a biggie) you should be fine.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With