I've got a following two controllers:
[RoutePrefix("/some-resources")
class CreationController : ApiController
{
[HttpPost, Route]
public ... CreateResource(CreateData input)
{
// ...
}
}
[RoutePrefix("/some-resources")
class DisplayController : ApiController
{
[HttpGet, Route]
public ... ListAllResources()
{
// ...
}
[HttpGet, Route("{publicKey:guid}"]
public ... ShowSingleResource(Guid publicKey)
{
// ...
}
}
All three actions got in fact three different routes:
GET /some-resources
POST /some-resources
GET /some-resources/aaaaa-bbb-ccc-dddd
If I put them into single controller everything works just fine, however if I separate them (as shown above) WebApi throws following exception:
Multiple controller types were found that match the URL. This can
happen if attribute routes on multiple controllers match the requested
URL.
This message is quite obvious. It seems WebApi does not take HTTP method into account when looking for a right candidate for controller/action.
How could I achieve the expected behavior?
UPDATE: I've digged a little into Web API internals and I understand that's the way it works by default. My goal is to separate the code and logic - in real world case those controllers have different dependencies and are a bit more complex. For the sake of maintenance, testability, project organization etc. they should be different objects (SOLID and stuff).
I thought I could override some WebAPI services (IControllerSelector etc) however this seems to be a little bit risky and non-standard approach for this simple and - as I assumed - common case.
UPDATE
Based on your comments, updated question and the answer provided here
Multiple Controller Types with same Route prefix ASP.NET Web Api
Desired result can be achieved via custom route constraints for the HTTP method applied to controller actions.
On inspection of the default Http{Verb} attributes ie [HttpGet], [HttpPost] and the RouteAttribute, which by the way are sealed, I realized that their functionality can be combine into one class similar to how they are implemented in Asp.Net-Core.
The following is for GET and POST, but it shouldn't be difficult to create constraints for the other HTTP methods PUT, DELETE...etc to be applied to the controllers.
class HttpGetAttribute : MethodConstraintedRouteAttribute {
public HttpGetAttribute(string template) : base(template, HttpMethod.Get) { }
}
class HttpPostAttribute : MethodConstraintedRouteAttribute {
public HttpPostAttribute(string template) : base(template, HttpMethod.Post) { }
}
The important class is the route factory and the constraint itself. The framework already has base classes that take care of most of the route factory work and also a HttpMethodConstraint so it is just a matter of applying the desired routing functionality.
class MethodConstraintedRouteAttribute
: RouteFactoryAttribute, IActionHttpMethodProvider, IHttpRouteInfoProvider {
public MethodConstraintedRouteAttribute(string template, HttpMethod method)
: base(template) {
HttpMethods = new Collection<HttpMethod>(){
method
};
}
public Collection<HttpMethod> HttpMethods { get; private set; }
public override IDictionary<string, object> Constraints {
get {
var constraints = new HttpRouteValueDictionary();
constraints.Add("method", new HttpMethodConstraint(HttpMethods.ToArray()));
return constraints;
}
}
}
So given the following controller with the custom route constraints applied...
[RoutePrefix("api/some-resources")]
public class CreationController : ApiController {
[HttpPost("")]
public IHttpActionResult CreateResource(CreateData input) {
return Ok();
}
}
[RoutePrefix("api/some-resources")]
public class DisplayController : ApiController {
[HttpGet("")]
public IHttpActionResult ListAllResources() {
return Ok();
}
[HttpGet("{publicKey:guid}")]
public IHttpActionResult ShowSingleResource(Guid publicKey) {
return Ok();
}
}
Did an in-memory unit test to confirm functionality and it worked.
[TestClass]
public class WebApiRouteTests {
[TestMethod]
public async Task Multiple_controllers_with_same_URL_routes_but_different_HTTP_methods() {
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
var errorHandler = config.Services.GetExceptionHandler();
var handlerMock = new Mock<IExceptionHandler>();
handlerMock
.Setup(m => m.HandleAsync(It.IsAny<ExceptionHandlerContext>(), It.IsAny<System.Threading.CancellationToken>()))
.Callback<ExceptionHandlerContext, CancellationToken>((context, token) => {
var innerException = context.ExceptionContext.Exception;
Assert.Fail(innerException.Message);
});
config.Services.Replace(typeof(IExceptionHandler), handlerMock.Object);
using (var server = new HttpTestServer(config)) {
string url = "http://localhost/api/some-resources/";
var client = server.CreateClient();
client.BaseAddress = new Uri(url);
using (var response = await client.GetAsync("")) {
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
using (var response = await client.GetAsync("3D6BDC0A-B539-4EBF-83AD-2FF5E958AFC3")) {
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
using (var response = await client.PostAsJsonAsync("", new CreateData())) {
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
}
}
public class CreateData { }
}
ORIGINAL ANSWER
Referencing : Routing and Action Selection in ASP.NET Web API
That's because it uses the routes in the route table to find the controller first and then checks for Http{Verb} to select an action. which is why it works when they are all in the same controller. if it finds the same route to two different controllers it doesn't know when one to select, hence the error.
If the goal is simple code organization then take advantage of partial classes
ResourcesController.cs
[RoutePrefix("/some-resources")]
partial class ResourcesController : ApiController { }
ResourcesController_Creation.cs
partial class ResourcesController {
[HttpPost, Route]
public ... CreateResource(CreateData input) {
// ...
}
}
ResourcesController_Display.cs
partial class ResourcesController {
[HttpGet, Route]
public ... ListAllResources() {
// ...
}
[HttpGet, Route("{publicKey:guid}"]
public ... ShowSingleResource(Guid publicKey) {
// ...
}
}
Related
This question already has answers here:
Routing with multiple Get methods in ASP.NET Web API
(10 answers)
Closed 4 years ago.
I have web api 2.0 project
When i try to implement several method with same parameter, i have this error : Several actions found
namespace WebApi.Controllers
{
public class EventController : ApiController
{
[HttpGet]
public HttpResponseMessage GetTags(string token, int messageId)
{
return ApiCall<List<EntityTag>>.CallApi(token, ServicesMessage.GetTags(messageId));
}
[HttpGet]
public HttpResponseMessage Get(string token, int eventId)
{
return ApiCall<EntityEvent>.CallApi(token, ServicesEvent.Get(eventId));
}
}
}
Any idea?
Thanks
The WebAPI cannot distinquish between these two methods because they have the same parameter types and same HttpVerb, and use implicit routing (no route attribute on them).
I'm a big fan of explicit routing for controllers and methods, instead of depending on naming conventions - so I'd try adding a route attribute to the methods:
public class EventController : ApiController
{
[HttpGet]
[Route("gettags")]
public HttpResponseMessage GetTags(string token, int messageId)
{
return ApiCall<List<EntityTag>>.CallApi(token, ServicesMessage.GetTags(messageId));
}
[HttpGet]
[Route("get")]
public HttpResponseMessage Get(string token, int eventId)
{
return ApiCall<EntityEvent>.CallApi(token, ServicesEvent.Get(eventId));
}
}
... and add a RoutePrefix attribute on the controller itself, like so:
[RoutePrefix("/api/Event")]
public class EventController : ApiController
{
}
Then you should be able to call the methods with a GET request to these URLs:
/api/event/get?token=xxxxx&eventId=xxxx
and
/api/event/gettags?token=xxxxx&messageId=xxxx
Write this line in your webapiconfig.cs
config.Routes.MapHttpRoute("DefaultApiPost", "Api/{controller}", new { action = "Get" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
your Controller:
namespace WebApi.Controllers
{
public class EventController : ApiController
{
[HttpGet]
public HttpResponseMessage Tags(string token, int messageId)
{
return ApiCall<List<EntityTag>>.CallApi(token, ServicesMessage.GetTags(messageId));
}
[HttpGet]
public HttpResponseMessage Events(string token, int eventId)
{
return ApiCall<EntityEvent>.CallApi(token, ServicesEvent.Get(eventId));
}
}
}
Using this you can give the action names for every get request.
POST
when you want same things with post method just write below line in weapiconfig.cs
config.Routes.MapHttpRoute("DefaultApiPost", "Api/{controller}", new { action = "Post" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Post) });
And after that you can give a action name. no need to routing after using this.
When you want to call api than write follows:
api/Event/Tags //parameter as per your requirement
api/Event/Events //parameter as per your requirement
I need to build project, that implement REST API predefined by vendor application(which will consume it) - there is about thousand of REST-resources with some actions defined by different HTTP-Verb's(POST, GET, PUT, DELETE, etc..).
So, ideally, for each resource i should have single class like this:
public class SomethingController
{
public Something Post(string name, DateTime time)
{
// ...
}
public int PostStrange(string text)
{
// ...
}
public Something Put([FromBody]Something item)
{
// ...
}
public void Delete(int id)
{
// ...
}
}
In previous versions i can just call MapHttpRoute while registering routes, inherit classes like this from ApiController - and ASP.NET Web Api will do as i need... But in .NET Core i can't find anything like MapHttpRoute/ApiController.. Now there is routing and http-verb attributes, and i need to define everything explicitly for each class/method:
[Route("api/[controller]")]
public class SomethingController : Controller
{
[HttpPost]
public Something Post(string name, DateTime time)
{
// ...
}
[HttpPost("api/[controller]/strange")]
public int PostStrange(string text)
{
// ...
}
[HttpPut]
public Something Put([FromBody]Something item)
{
// ...
}
[HttpDelete]
public void Delete(int id)
{
// ...
}
}
Writing this attributes for each of thousands REST-resources is very boring and error prone...
Do i miss something here? Why in pretty new and modern ASP.NET Core that very common and important thing as building REST-Api made so over-complicated, compared to old ASP.NET?
There is nuget package Microsoft.AspNetCore.Mvc.WebApiCompatShim which main goal is to make migration from web api to core easier. It also provides a way to perform convention-based routing to actions you need. So, first install that package, then in startup:
public void ConfigureServices(IServiceCollection services) {
// add conventions here
services.AddMvc().AddWebApiConventions();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
app.UseMvc(routes => {
// map one global route
routes.MapWebApiRoute("WebApi", "api/{controller}");
});
}
After this small configuration you can inherit your controllers either from ApiController, which is added in package above for convenience of migration from web api, or native asp.net core Controller. Example of ApiController:
public class SomeController : ApiController {
// maps to GET /api/Some
// note - no routing attributes anywhere
public HttpResponseMessage Get() {
return new HttpResponseMessage(HttpStatusCode.OK);
}
// maps to POST /api/Some
public HttpResponseMessage Post() {
return new HttpResponseMessage(HttpStatusCode.OK);
}
}
Native asp.net core controller:
// mark with these attributes for it to work
[UseWebApiRoutes]
[UseWebApiActionConventions]
public class TestController : Controller {
// maps to GET /api/Test
// no routing attributes, but two "conventions" attributes
public IActionResult Get(string p) {
return new ObjectResult(new { Test = p });
}
}
You can also mark your base controller with these attributes:
[UseWebApiRoutes]
[UseWebApiActionConventions]
public class BaseController : Controller {
}
public class TestController : BaseController {
// maps to GET /api/Test
// no attributes
public IActionResult Get(string p) {
return new ObjectResult(new { Test = p });
}
}
If you are not migrating from web api - I'd suggest to use native Controller. ApiController has different structure (similar to asp.net web api ApiController), so there is not much reason to use it for anything other than its intended goal (migration from web api).
MapRoute is still there https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing
Attribute routing compliments MapRoute, not replaces it.
Apparently there are quite a few examples which drop the piece about Routing in order to simplify example. So just dig dipper.
I'm trying to implement both Attribute Routing and the VersionedRoute from RoutingConstaints Sample but when I use both on a controller, the versioned attribute no longer works.
What would I need to modify on the attribute to get it to play nice with Attribute Routing?
For code example download the sample project (or just look at the few files from the above link) and then modify the routes as such:
// When I use the RoutePrefix, VersionedRoute no longer works (Sending "Api-Version" through http header doesn't route correctly
// If I remove the RoutePrefix I can use VersionedRoute again
// What do I need to change in its code to be able to use both?
[VersionedRoute("api/Customers", 1)] // This route would be used as http://url/api/customers with a header of "api-version: 1"
[RoutePrefix("api/v1/Customers")] // This route would be used purely through url versioning of http://url/api/v1/Customers
public class CustomersV1Controller : ApiController {
/* Other stuff removed */
[VersionedRoute("api/Customer", 1)] // I'd rather not have to use this here at all and just use a single one on the class, but having both nor just one on either works right now.
[Route("")]
public IHttpActionResult Get()
{
return Json(_customers);
}
}
VersionedRoute Code
VersionConstraint Code
Edit: Please let me know if you need more information or even post ideas or things to try :)
Edit2: Here is an example of what I'm trying to do from Troy Hunt's Blog: http://www.troyhunt.com/2014/02/your-api-versioning-is-wrong-which-is.html
Edit3: Here is what I'd like to code to be as close to since it would reduce a lot of the overhead and magic strings.
[VersionedRoute("api/Customers", 1)] // This route would be used as http://url/api/customers with a header of "api-version: 1"
[RoutePrefix("api/v1/Customers")] // This route would be used purely through url versioning of http://url/api/v1/Customers
public class CustomersV1Controller : ApiController {
/* Other stuff removed */
[Route("")]
public IHttpActionResult Get()
{
// Removed
return Ok(customers);
}
[Route("{id:int}")]
public IHttpActionResult GetById(int id)
{
// Removed
return Ok(customer);
}
}
[VersionedRoute("api/Customers", 2)] // This route would be used as http://url/api/customers with a header of "api-version: 2"
[RoutePrefix("api/v2/Customers")] // This route would be used purely through url versioning of http://url/api/v2/Customers
public class CustomersV2Controller : ApiController {
/* Other stuff removed */
[Route("")]
public IHttpActionResult Get()
{
// Removed
return Ok(customersThatAreDifferentThanV1);
}
[Route("{id:int}")]
public IHttpActionResult GetById(int id)
{
// Removed
return Ok(customerThatIsDifferent);
}
}
Edit: Last bump, trying to only have to write the route version information once per route, at the controller attribute level and not per-action.
The Route and VersionedRoute attributes are working fine together, but your RoutePrefix attribute is also applied to your VersionedRoute (try accessing /api/v1/Customers/api/Customer - you'll get a response when the api-version header is set)
The following code would produce the desired behaviour with regards to the two URLs in your example returning the correct responses, but obviously this does not solve your problem of wanting one VersionedRoute and one RoutePrefix at the top of the class. Another approach would be needed for this. You can, however, have separate controllers for different api versions.
[RoutePrefix("api")]
public class CustomersV1Controller : ApiController
{
/* Other stuff removed */
[VersionedRoute("Customers", 1)]
[Route("v1/Customers")]
public IHttpActionResult Get()
{
return Json(_customers);
}
}
An improvement would be to create your own attribute instead of Route so you wouldn't need to prefix the version every time:
public class CustomVersionedRoute : Attribute, IHttpRouteInfoProvider
{
private readonly string _template;
public CustomVersionedRoute(string route, int version)
{
_template = string.Format("v{0}/{1}", version, route);
}
public string Name { get { return _template; } }
public string Template { get { return _template ; } }
public int Order { get; set; }
}
[RoutePrefix("api")]
public class CustomersV2Controller : ApiController
{
/* Other stuff removed */
[VersionedRoute("Customers", 2)]
[CustomVersionedRoute("Customers", 2)]
public IHttpActionResult Get()
{
return Json(_customers);
}
}
I have a case where I'm trying to create a whole bunch of Api controllers. I have created a base class controller that has all the basic GET, PUT, POST, DELETE actions as well as a few others that seem common for the project.
I then use T4 to script out each table in my db (the EF context, but none the less) as a controller, obviously inheriting from that parent controller (who ultimate inherits from ApiController. It all works great except for some routing issues.
Apparently you cannot define attributed routes in the base class and have the MapHttpAttributeRoutes find them.
Given this suedo code below is there any suggestion contrary to this belief and is there a "good" way to handle this? For now the thought is to ditch the base class and script each controller out in T4.
public class TestBaseController: ApiController {
// GET api/user
[Route("")]
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
[RoutePrefix("apiv2/user")]
public class UserController: TestBaseController
{
[Route("type")]
[HttpGet]
public string GetType()
{
return "my test type";
} ...
apiv2/user/ results in a 404, but apiv2/user/type returns the expected string.
By default attribute routes are not inherited, however with Web Api 2.2, we added an ability to do that:
Example:
public class BaseController : ApiController
{
[Route("{id:int}")]
public string Get(int id)
{
return "Success:" + id;
}
}
[RoutePrefix("api/values")]
public class ValuesController : BaseController
{
}
//---------------------------------------------------
config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());
public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
protected override IReadOnlyList<IDirectRouteFactory>
GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
{
return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>
(inherit: true);
}
}
I recently found the handy method .several() in Restangular.
Which allows me to fetch multiple single objects, very nice.
So I tried it out the following Restangular.several('accounts', 'a', 'b', 'c'); which generates this url: /api/accounts/a,b,c
Which doesn't sit very well with my ASP.NET Web Api controller.
First draft:
public class MyController : ApiController
{
public HttpResponseMessage GetAll() { ... }
public HttpResponseMessage GetSingle(string id) { ... }
public HttpResponseMessage GetMultiple(IEnumerable<string> ids) { ... }
}
This would be the ideal controller from my perspective. However it fails with an exception saying that MVC can't distinguish between the actinos :(
Second draft:
public class MyController : ApiController
{
public HttpResponseMessage GetAll() { ... }
public HttpResponseMessage GetSingle(string id) { ... }
}
Now this doesn't crash. But id is a,b,c.
Do I really need to manually split the string and work with it from there?
Also this wont work for int types. public HttpResponseMessage GetSingle(int id) { ... }
What is the proper way to implement .several() support in ASP.NET Web Api? I assume it's a standard REST call since it's in Restangular.
Attribute Routing with regex constraints can work here...
[Route("api/my")]
public HttpResponseMessage GetAll() {
// ...
}
[Route("api/my/{id:regex(^[^,]+$)}")]
public HttpResponseMessage GetSingle(string id)
{
// ...
}
[Route("api/my/{ids:regex(,)}")]
public HttpResponseMessage GetMultiple(string ids)
{
// strings
var idList = ids.Split(',');
//// ints
// var idList = ids.Split(',').Select(i => Convert.ToInt32(i)).ToList();
// ...
}