How to split controllers by route in Web Api 2 - asp.net-web-api2

I have a bunch of controllers and would like to segment them via route into two groups:
config.Routes.MapHttpRoute(
"r1",
"api/v1.0/route1/{controller}/{action}/{id}",
new {id = RouteParameter.Optional}
);
config.Routes.MapHttpRoute(
"r2",
"api/v1.0/route2/{controller}/{action}/{id}",
new { id = RouteParameter.Optional }
);
I thought I could do this with something like:
[RoutePrefix("api/v1.0/route1")]
public class MyController : ApiController
To make it go down ONLY route1, however I'm able to hit it via route2 also.
I also tried
[RoutePrefix("route1")]
public class MyController : ApiController
With the same result. How can I make MyController only go down route1? Any help is greatly appreciated as always.

You want
[RoutePrefix("api/v1.0)]
public class MyController : ApiController
and then on the method itself
[Route("route1")]
public async Task<HttpResponseMessage> RouteOne(object params)
{
...
}
[Route("route2")]
public async Task<HttpResponseMessage> RouteTwo(object params)
{
...
}
Agree with commenter who said to leave MapHttpRoute alone. Should look like this:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);

Related

Web API One action works while a nearly identical one doesn't?

Error Message
{
"Message": "No HTTP resource was found that matches the request URI 'https://localhost:44390/api/UserRoutes?effectiveDate=3/29/2019'.",
"MessageDetail": "No type was found that matches the controller named 'UserRoutes'."
}
Working Action
public class AdvanceOrderApiController : BaseApiController
{
[HttpGet, Route("api/AdvanceOrders")]
public AdvanceOrdersResult GetAdvanceOrdersForRouteDate(string route, DateTime effectiveDate)
{
...
}
}
// JavaScript Usage: route="0100" and effectiveDate="03/29/2019".
API.SendRequest("/api/AdvanceOrders", "GET", { route: route, effectiveDate: effectiveDate }, success, failure);
Not Working Action
public class UserApiController : BaseApiController
{
[HttpGet, Route("api/UserRoutes")]
public IEnumerable<string> GetUserRoutes(DateTime effectiveDate)
{
...
}
}
// JavaScript Usage: effectiveDate="03/29/2019"
API.SendRequest("/api/UserRoutes", "GET", { effectiveDate: effectiveDate }, success, failure);
WebApiConfig
Not sure that it's relevant since I'm just declaring the route for each action, but...
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
...
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
API.SendRequest
This function is just a wrapper around jQuery's $.ajax function, nothing fancy. If the code is necessary I'll present it, but it works for all my other API calls so I can't imagine it would be the source of the problem.
These actions are nearly identical, why does one work and the other doesn't?
Passing the date in as Igor said in the comments presented an error message that revealed that I had an Api controller in my Permissions area that had a route also named api/UserRoutes.
Once I changed the name of the route the problem resolved.
I just wish it could have just told me this error message from the start.

Attribute routing - every method, class or as needed?

Let's say you have an API controller. Some methods of this controller use the same routes:
[HttpPost] // /api/entities
public IHttpActionResult Add(Entity entity)
{
...
}
[HttpGet] // /api/entities
public IHttpActionResult FindAll()
{
...
}
[HttpGet] // /api/entities
public IHttpActionResult Find(String name)
{
...
}
[HttpGet] // /api/entities/id
public IHttpActionResult Find(Int32 id)
{
...
}
[HttpDelete] /api/entities/id
public IHttpActionResult Remove(Int32 id)
{
...
}
Do I apply RouteAttribute to all methods or only to two methods to cover for "api/entities" and "api/entities/id"? Or is it better to apply two RouteAttribute to the class itself?
If you have default routes specified in the configuration, that is:
routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
You don't have to apply routing attributes, don't forget that requests will be mapped to the actions not only by HTTP method, but by parameter type also, so there should be no problem.
Take a look at the "Action" section of the documentation.

Web api routing methods with different parameter names

This question could have been answered hundred times, but I couldnt find a proper resource. In a WebApi project (default project provided by VS) I have the ValuesController as below.
public string Get(int id)
{
return "value";
}
[HttpGet]
public string FindByName(string name)
{
return name;
}
[HttpGet]
public string FindById(int id)
{
return id.ToString();
}
In the WebApiConfig.cs, I have following route mapping.
config.Routes.MapHttpRoute(
name: "actionApiById",
routeTemplate: "api/{controller}/{action}/{Id}",
defaults: new { action = "FindById", Id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "actionApi",
routeTemplate: "api/{controller}/{action}/{name}",
defaults: new { name = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Now only the FindById() action is working when i try in the browser. Why does the rest of api calls return "No HTTP resource was found that matches the request"
How can I get all three methods working? without using AttributeRouting. Am I lack of basic concepts of web api? ( i think yes)
AS We all know REST is resource based and this identify the resource with the URL, so that not more than one method with same parameter will be allowed in the REST service but there is work around in MVC 5 Web Api method level routing.
Here is the example you can do that:
[HttpGet]
[Route("api/search/FindByName/{name}")]
FindByName(string name)
{
}
[HttpGet]
[Route("api/search/FindById/{name}")]
FindById(int searchId)
Note:"search" is the controller name.
Please let know if need more clarification.
In general you don't want to have a route per action like your sample suggests. As your app grows this will get quickly out of hand.
Also consider building your url space in a way that will look just RESTfull
So methods will be GetById, GetByName, and then pass the parameters in the query string to match the right action (BTW not sure what the difference in your case is between GetById and FindById if they are not really different consider just keeping one of them around).
You can stick with the default route and your request will look like:
/api/controller/345 or /api/controller?name=UserName or /api/controller?SearchId=345 (assuming search was indeed a different behavior)
Then the method signatures:
Get(int id)
{
}
[HttpGet]
FindByName(string name)
{
}
[HttpGet]
FindById(int searchId)
{
}
Your actionApiById Route also matches the actionApi route, As your id is integer try using constraint like this.
config.Routes.MapHttpRoute(
name: "actionApiById",
routeTemplate: "api/{controller}/{action}/{Id}",
defaults: new { action = "FindById", Id = RouteParameter.Optional }
constraints: new {Id = #"\d+" }
);

MVC 4.5 Web API Routing not working?

The 1st route works.
e.g. api/Shelves/SpaceTypes/1
The 2nd route doesn't work. I get multiple actions error.
e.g api/Shelves/1
Q) Why?
These are my routes:
config.Routes.MapHttpRoute(
"DefaultApiWithAction",
"api/{controller}/{action}/{id}"
);
config.Routes.MapHttpRoute(
"DefaultApiWithId",
"api/{controller}/{id}",
null,
new { id = #"\d+" }
);
This is my controller:
public HttpResponseMessage Get(int id)
{
...
}
[ActionName("SpaceTypes")]
public HttpResponseMessage GetSpaceTypes(int id)
{
...
}
For MVC 4.5 this is the only thing that works
There is currently a bug about this.
In order to get your routing to work so the following work
api/Shelves/ //Get All Shelves
api/SpaceTypes/1 //Get Shelf of id 1
api/Shelves/1/SpaceTypes/ //Get all space types for shelf 1
you need to do the following.
Change your routing over to. (Note the default action..)
config.Routes.MapHttpRoute(
name : "DefaultAPi",
routeTemplate : "api/{controller}/{id}/{action}",
defaults: new {id= RouteParameter.Optional,
action = "DefaultAction"}
);
In your controller change the base methods over to
[ActionName("DefaultAction")]
public string Get()
{
}
[ActionName("DefaultAction")]
public string Get(int id)
{
}
[ActionName("SpaceTypes")]
public string GetSpaceTypes(int id)
{
}
Now everything should work as expected..
Thanks to Kip Streithorst full this, for a full explanation
I had a similar issue and discovered i wasn't calling MapHttpAttributeRoutes method in my WebApiConfig...
hope it helps,
David
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
#Kristof is almost right. You should make your second route:
config.Routes.MapHttpRoute(
"DefaultApiWithId",
"api/{controller}/{id}",
new { action = "Get" },
new { id = #"\d+ }
);
This route does not know which action to bind to :
config.Routes.MapHttpRoute("DefaultApiWithId", "api/{controller}/{id}", null, new { id = #"\d+" });
Both of your methods are a valid candidate.
I'm not 100% clear what your setup is but in normal REST every resource has a controller, it seems like you have 1 controller with 2 resources.
To make it work in this setup you could force your second route to the get action like this :
config.Routes.MapHttpRoute("DefaultApiWithId", "api/{controller}/{id}", null, new { id = #"\d+", action="Get" });
Make sure in your project's Global.asx file, that you've added
WebApiConfig.Register(GlobalConfiguration.Configuration);
into the Application_Start function.

How to make {controller}/{id}/{action} work in MVC4?

I've tried everything but looks like I'm just not getting it at all. My WebApiConfig.cs looks like this:
config.Routes.MapHttpRoute(
"Default",
"api/{controller}/{id}",
new { id = RouteParameter.Optional });
config.Routes.MapHttpRoute(
"AccountVerification",
"api/{controller}/{id}/{action}",
null,
new { controller = "Account" });
And my controller looks like this:
public class AccountController : ApiController {
public HttpResponseMessage GetByKey(Guid accountID) {
...
}
[HttpGet]
[ActionName("Verify")]
public HttpResponseMessage VerifyAccount(Guid accountID) {
...
}
}
These methods should get a hit with the following URLs:
GET /api/account - WORKS
GET /api/account/00000000-0000-0000-000000000001 - WORKS
GET /api/account/00000000-0000-0000-000000000001/verify - DOESNT WORK
I've tried a lot of things; I am definitely doing something wrong here...please help.
First, if you want to test with fake Guids, as well as having optional Guid parameters, they must be Nullable parameters (fake guids will be deserialized as null) :
public class AccountController : ApiController
{
public HttpResponseMessage GetByKey(Guid? accountID)
{
throw new Exception("GetByKey " + (accountID.HasValue ? accountID.ToString() : "NULL"));
}
[System.Web.Http.HttpGet]
[System.Web.Http.ActionName("Verify")]
public HttpResponseMessage VerifyAccount(Guid? accountID)
{
throw new Exception("VerifyAccount "+(accountID.HasValue?accountID.ToString():"NULL"));
}
}
then, your mapping should :
use the most specific route first
use the correct parameters names
use the correct defaults for actions
config.Routes.MapHttpRoute(
"AccountVerification",
"api/{controller}/{accountID}/{action}"
);
config.Routes.MapHttpRoute(
"Default",
"api/{controller}/{accountID}",
defaults: new { Controller="Account", action = "GetByKey", accountID = RouteParameter.Optional }
);
GET /api/account/00000000-0000-0000-000000000001/verify is matching the first route in your routing collection. Therefore, it never inspects the second route to see if it matches. Make "api/{controller}/{id}/{action}" the first route in your collection and it should work correctly.