Wildcard parameter in WebAPI routing matching where it shouldn't - c#

I have a small ASP.NET WebAPI app and the only route I've set up is the following:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{*furtherpath}"
);
I have the following controller:
public class FoldersController : ApiController {
public string GetThis(DateTime queryStringDate) {
return "abc";
}
public bool GetThat(string furtherpath) {
return "xyz";
}
}
I get a 500 Internal Server Error when I try to make this request, because it matches both of those method actions:
GET http://[server]/api/folders?queryStringDate=2015-02-11%2000:00:00
Now I would've thought this would unambiguously match GetThis because the requested URL doesn't contain the slash at the end which would separate {controller} and {*furtherpath}, and furtherpath is not marked as an optional parameter. Why is this request ambiguous to WebAPI and how can I tell WebAPI that the lack of a slash after folders means that this request should match GetThis?

I think the problem is that both methods match the route. furtherpath is seen as a parameter which is simply not used by the GetThis method.
You would really benefit from using attribute routing here. Use this to register your routes:
config.MapAttributeRoutes();
And then decorate your methods with the routes:
public class FoldersController : ApiController
{
[HttpGet]
[Route("api/folders/")]
public string GetThis(DateTime queryStringDate)
{
return "abc";
}
[HttpGet]
[Route("api/folders/{furtherpath}")]
public bool GetThat(string furtherpath)
{
return "xyz";
}
}
This will give you much more granular control over your routes.
The link provided by #luca-ghersi is very helpful as well.

If you take out the "furtherpath" from route config, the route will be guided by your variables name.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/");
GET http://[server]/api/folders?queryStringDate=2015-02-11%2000:00:00
GET http://[server]/api/folders?furtherpath=2015-02-11%2000:00:00

Related

Same method signature for two diff methods in web api

it might have duplicate but i didn't find right solution,
My web api,
public class SampleController : ApiController
{
public string Get(int id)
{
return "value";
}
public string hello(int id)
{
return "value";
}
}
my webapiconfig,
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
my problem is
When i call http://localhost:1234/api/Sample/5 it's hitting Get(int id) but how can i call method 2 i.e hello(int id) ?? what needs to be changed and what's the best way to handle these kind of scenarios ??
TLDR:
If you want to reference individual actions in your Web API then change your routing to this:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then you can access your action like this: localhost/api/{controller}/{action}/. Look here for further information, especially "Routing by Action Name".
Orig:
You seem to expect the same behaviour as with MVC Controllers. The Standard-Routing for MVC-Controller is this:
routeTemplate: "{controller}/{action}/{id}"
This corresponds to the name of the controller, the method which is to be used and some form of input. ApiControllers Route differently:
routeTemplate: "staticPart/{controller}/{id}"
As you can see there is only a reference to the individual controller and the input, as well as the "staticPart" which normally is something like /api/
The Idea is that you use a RESTful approach, connecting methods with different types of http methods (eg. DELETE, GET, POST, PUSH and PUT)
The Get Method in your example is a special because through the name "Get" you have told the compiler that this method corresponds with HTTP-GET.
So to get to your question: Either you change your Routing to that of MVC-Controller. So that you reference individual actions in your requests or you use different HTTP-Methods. Or you set routes indivdually as shown by MaxB
You can find an official overview on Web API routing here There you'll find examples on all possibilities.
I'm not sure if I got your problem right, but if I did:
You should specify the route for the function not in its name, but in some other way. From my little experience with the topic, that's how I do it:
[HttpGet]
[Route("SystemInfo")] // That's the name of the route you will call
public IHttpActionResult SystemInfo()
{
return Ok();
}
Consider checking this out.
So, considering your question, it would be like so:
[Route("Get")]
public string Get(int id)
{
return "value";
}
[Route("hello")]
public string hello(int id)
{
return "value";
}

No HTTP resource was found that matches the request URI - and - No action was found on the controller that matches the request

I am trying to build out a new endpoint in API app that already has a lot of other endpoints working just fine. Not sure what I'm doing wrong. I am getting two errors:
Message: No HTTP resource was found that matches the request URI 'http://localhost:62342/api/VoiceMailStatus
and
MessageDetail: No action was found on the controller 'VoiceMailStatus' that matches the request.
Here's the controller:
public class VoiceMailStatusController : ControllerBase
{
[HttpPost]
[Route("api/VoiceMailStatus")]
public string VoiceMailStatus(string var)
{
...
}
}
And here's the route:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I'm using PostMan:
There are a LOT of threads here about both of these error messages. I've read many of them, but have yet to find a solution. One of them said to change this:
public string VoiceMailStatus(string var)
to this:
public string VoiceMailStatus(string var = "")
And while that did get the error to go away and I was able to get inside of the method while in debug, var was always just an empty string.
EDIT: GOT IT WORKING
In addition to adding [FromBody] as per Andrii Litvinov's answer, I also had to do one more thing. What had been this:
public string VoiceMailStatus(string var)
{
...
}
Is now this:
public string VoiceMailStatus([FromBody] VMStatus request)
{
...
}
And then VMStatus is just a small little class with a single string property:
public class VMStatus
{
public string var { get; set; }
}
Most likely you need to apply FromBody attribute to your parameter:
public string VoiceMailStatus([FromBody] string var)
If that does not help try to rename parameter to something else, e.g.: var1, because var is reserved work in C# and could cause some binding issues, but I doubt that's the case.
You probably want to add this to your controller since you have api in the route config:
[RoutePrefix("API/VoiceMailStatus")]
and then add this to your action:
[Route("VoiceMailStatus", Name = "VoiceMailStatus")]
This should tie the action to the url localhost/api/voicemailstatus/voicemailstatus

HttpControllerSelector not working with Attribute Routing

I'm using a custom HTTP controller selector to version my API.
config.Services.Replace(typeof(IHttpControllerSelector), new NamespaceSelector(config));
Below is my controller with actions:
[RoutePrefix("api/v1/messages")]
public class MessagesController : ApiController
{
[Route("unreadall")] // api/v1/messages/unreadall
public IEnumerable<long> UnreadAll()
{
// Return value;
}
[Route("{type}/unreadall")] // api/v1/messages/{type}/unreadall
public IEnumerable<long> UnreadAll(string type)
{
// Return value;
}
[Route("unreadnext")] // api/v1/messages/unreadnext
public long UnreadNext()
{
// Return value;
}
[Route("{type/}unreadnext")] // api/v1/messages/{type}/unreadnext
public long UnreadNext(string type)
{
// Return value;
}
[Route("{id:long}/markasread")] // api/v1/messages/123/markasread
[HttpPut]
public string MarkAsRead(long id)
{
// Return value;
}
[Route("{id:long}")] // Default Action
public string Get(long id) // api/v1/messages/123
{
// Return value;
}
[Route("")] // Default Action
[HttpPost]
public long Post(string message) // api/v1/messages
{
// Return value;
}
}
Below is my route config:
config.Routes.MapHttpRoute(
name: "DefaultApi1",
routeTemplate: "api/{version}/{controller}/{id}/{action}"
);
config.Routes.MapHttpRoute(
name: "DefaultApi2",
routeTemplate: "api/{version}/{controller}/{action}"
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{version}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
When I test my routes, the following work.
/api/v1/messages/unreadall
/api/v1/messages/unreadnext
/api/v1/messages/123/markasread
But the below routes, also point to the same actions.
/api/v1/messages/type/unreadall
/api/v1/messages/type/unreadnext
And I get errors for the rest of my routes.
/api/v1/messages/123
Error:
{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost:59411/api/v1/messages/123'.",
"MessageDetail": "No action was found on the controller 'MessagesController' that matches the name '123'."
}
POST: /api/v1/messages
Error:
{
"Message": "The requested resource does not support http method 'POST'."
}
Can someone please tell what I'm doing wrong with my route configuration ? or can someone please post working route configuration for my scenarios above ?
Appreciate your help !
Cheers,
What you are getting it's the expected behavior: the routes defined in route config works, while the attribute routes does not.
This happens because request.GetRouteData() is not taking into account attribute routes. This makes sense of course because there is no specific controller that a route points to as attributed routes are related to methods, not controllers.
When you use attribute routing, all the route attributes get added to a common route without a name. This is a special route that is an instance of an internal class called RouteCollectionRoute. This route has a collection of sub-routes that you can query for that includes all the attribute routes. But if you just want the selected route for your call, you can simple ask for it using the RouteData.Values:
var routeData = request.GetRouteData();
var subroutes = (IEnumerable<IHttpRouteData>)routeData.Values["MS_SubRoutes"];
var route = subroutes.First().Route;
Source: http://wildermuth.com/2013/11/12/Web_API_2_s_Attribute_Routing_Looking_Deeper

ASP.NET WebApi - Two Controllers With Similar Action Methods

I have a scenario where I have 2 different controllers that each have multiple Get methods. I have the methods decorated with the ActionName attribute, but the Routing isn't working as I think it should.
ContactController
public ContactModel GetContactByID(string id)
{
...
}
[ActionName("username")]
public ContactModel GetContactByUserName(string text)
{
...
}
PaymentController
public PaymentModel Get(Guid id)
{
...
}
[HttpGet, ActionName("sale")]
public PaymentActivityModel Sale(Guid id)
{
...
}
Routes
config.Routes.MapHttpRoute(
"PaymentControllerActionId",
"api/client/{clientId}/{controller}/{action}/{id}",
defaults: null
);
config.Routes.MapHttpRoute(
"ContactControllerActionText",
"api/client/{clientId}/{controller}/{action}/{text}",
defaults: null
);
config.Routes.MapHttpRoute(
"ClientControllerId",
"api/client/{clientId}/{controller}/{id}",
new { id = RouteParameter.Optional }
);
When I navigate to a Payment, it works fine. But when I navigate to a Contact, I receive:
No HTTP resource was found that matches the request URI, .../api/client/.../contact/username/exampleUserName
No action was found on the controller 'Contact' that matches the request.
I was under the impression that the parameter name would be matched up with the action name (text vs. id).
Is the problem that one uses a Guid id and one uses a string id?
This is a client-facing API, and I have a client-friendly ContactID that is a string. Whereas the PaymentID is a Guid.
First I want to point out your routes don't make a lot of sense.
This route says it is for PaymentControllerActionId, however you left the routing open to use any controller instead of specifying it is only for the Payment Controller.
config.Routes.MapHttpRoute(
"PaymentControllerActionId",
"api/client/{clientId}/{controller}/{action}/{id}",
defaults: null
);
To answer your question. You likely need to specify {text} as an optional RouteParameter. Otherwise the framework usually expects that you have defined a custom routing constraint (and you should have a routing constraint on {clientId} IMO).
Also, if one of your actions expects a GUID. Put it as a Guid argument type, and WebAPI will match it correctly. Alternatively, since both functions are expecting either a name or a guid, you could just have only 1 function that takes a string, and in your db do a select statement matching the GUID or the NAME and return the contact. Much less code, easier to read, makes more sense.
If you are using Web API 2, then you could use attribute routing for this purpose.
In the below example, the controllers use a mix of conventional + attribute routing, but you can change this to only go by attribute routing if you need.
Example:
Route Configuration
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
"ClientControllerId",
"api/client/{clientId}/{controller}/{id}",
new { id = RouteParameter.Optional }
);
ContactController
[RoutePrefix("api/client/{clientId}/Contact")]
public class ContactController
{
// this uses conventional route
public ContactModel GetContactByID(string id)
{
...
}
[Route("username/{userName}")]
public ContactModel GetContactByUserName(string userName)
{
...
}
}
PaymentController
[RoutePrefix("api/client/{clientId}/Payment")]
public class PaymentController
{
// this uses conventional route
public PaymentModel Get(Guid id)
{
...
}
[HttpGet, Route("sale/{id}")]
public PaymentActivityModel Sale(Guid id)
{
...
}
}

Multiple actions were found that match the request in Web Api

I keep getting this error when I try to have 2 "Get" methods
Multiple actions were found that match the request: webapi
I been looking around at the other similar questions about this on stack but I don't get it.
I have 2 different names and using the "HttpGet" attribute
[HttpGet]
public HttpResponseMessage Summary(MyVm vm)
{
return null;
}
[HttpGet]
public HttpResponseMessage FullDetails()
{
return null;
}
Your route map is probably something like this in WebApiConfig.cs:
routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
But in order to have multiple actions with the same http method you need to provide webapi with more information via the route like so:
routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional });
Notice that the routeTemplate now includes an action. Lots more info here: http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
Update:
Alright, now that I think I understand what you are after here is another take at this:
Perhaps you don't need the action url parameter and should describe the contents that you are after in another way. Since you are saying that the methods are returning data from the same entity then just let the parameters do the describing for you.
For example your two methods could be turned into:
public HttpResponseMessage Get()
{
return null;
}
public HttpResponseMessage Get(MyVm vm)
{
return null;
}
What kind of data are you passing in the MyVm object? If you are able to just pass variables through the URI, I would suggest going that route. Otherwise, you'll need to send the object in the body of the request and that isn't very HTTP of you when doing a GET (it works though, just use [FromBody] infront of MyVm).
Hopefully this illustrates that you can have multiple GET methods in a single controller without using the action name or even the [HttpGet] attribute.
Update as of Web API 2.
With this API config in your WebApiConfig.cs file:
public static void Register(HttpConfiguration config)
{
//// Web API routes
config.MapHttpAttributeRoutes(); //Don't miss this
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = System.Web.Http.RouteParameter.Optional }
);
}
You can route our controller like this:
[Route("api/ControllerName/Summary")]
[HttpGet]
public HttpResponseMessage Summary(MyVm vm)
{
return null;
}
[Route("api/ControllerName/FullDetails")]
[HttpGet]
public HttpResponseMessage FullDetails()
{
return null;
}
Where ControllerName is the name of your controller (without "controller"). This will allow you to get each action with the route detailed above.
For further reading: http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
In Web API (by default) methods are chosen based on a combination of HTTP method and route values.
MyVm looks like a complex object, read by formatter from the body so you have two identical methods in terms of route data (since neither of them has any parameters from the route) - which makes it impossible for the dispatcher (IHttpActionSelector) to match the appropriate one.
You need to differ them by either querystring or route parameter to resolve ambiguity.
After a lot of searching the web and trying to find the most suitable form for routing map
if have found the following
config.Routes.MapHttpRoute("DefaultApiWithId", "Api/{controller}/{id}", new { id =RouteParameter.Optional }, new { id = #"\d+" });
config.Routes.MapHttpRoute("DefaultApiWithAction", "Api/{controller}/{action}");
These mapping applying to both action name mapping and basic http convention (GET,POST,PUT,DELETE)
This is the answer for everyone who knows everything is correct and has checked 50 times.....
Make sure you are not repeatedly looking at RouteConfig.cs.
The file you want to edit is named WebApiConfig.cs
Also, it should probably look exactly like this:
using System.Web.Http;
namespace My.Epic.Website
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
// api/Country/WithStates
config.Routes.MapHttpRoute(
name: "ControllerAndActionOnly",
routeTemplate: "api/{controller}/{action}",
defaults: new { },
constraints: new { action = #"^[a-zA-Z]+([\s][a-zA-Z]+)*$" });
config.Routes.MapHttpRoute(
name: "DefaultActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
I could have saved myself about 3 hours.
It might be possible that your webmethods are being resolved to the same url. Have a look at the following link :-
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
So, you might need to add your methodname to your routing table.
Without using actions the options would be:
move one of the methods to a different controller, so that they don't clash.
use just one method that takes the param, and if it's null call the other method from your code.
This solution worked for me.
Please place Route2 first in WebApiConfig. Also Add HttpGet and HttpPost before each method and include controller name and method name in the url.
WebApiConfig =>
config.Routes.MapHttpRoute(
name: "MapByAction",
routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional });
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
Controller =>
public class ValuesController : ApiController
{
[HttpPost]
public string GetCustomer([FromBody] RequestModel req)
{
return "Customer";
}
[HttpPost]
public string GetCustomerList([FromBody] RequestModel req)
{
return "Customer List";
}
}
Url =>
http://localhost:7050/api/Values/GetCustomer
http://localhost:7050/api/Values/GetCustomerList
I found that that when I have two Get methods, one parameterless and one with a complex type as a parameter that I got the same error. I solved this by adding a dummy parameter of type int, named Id, as my first parameter, followed by my complex type parameter. I then added the complex type parameter to the route template. The following worked for me.
First get:
public IEnumerable<SearchItem> Get()
{
...
}
Second get:
public IEnumerable<SearchItem> Get(int id, [FromUri] List<string> layers)
{
...
}
WebApiConfig:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{layers}",
defaults: new { id = RouteParameter.Optional, layers RouteParameter.Optional }
);
It is possible due to using MVC controller instead of Web API controller.
Check the namespace in Web API controller it should be as following
using System.Net;
using System.Net.Http;
using System.Web.Http;
If the namespace are as following then it is give above error in web api controller method calling
using System.Web;
using System.Web.Mvc;
Please check you have two methods which has the different name and same parameters.
If so please delete any of the method and try.
I've stumbled upon this problem while trying to augment my WebAPI controllers with extra actions.
Assume you would have
public IEnumerable<string> Get()
{
return this.Repository.GetAll();
}
[HttpGet]
public void ReSeed()
{
// Your custom action here
}
There are now two methods that satisfy the request for /api/controller which triggers the problem described by TS.
I didn't want to add "dummy" parameters to my additional actions so I looked into default actions and came up with:
[ActionName("builtin")]
public IEnumerable<string> Get()
{
return this.Repository.GetAll();
}
for the first method in combination with the "dual" route binding:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "builtin", id = RouteParameter.Optional },
constraints: new { id = #"\d+" });
config.Routes.MapHttpRoute(
name: "CustomActionApi",
routeTemplate: "api/{controller}/{action}");
Note that even though there is no "action" parameter in the first route template apparently you can still configure a default action allowing us to separate the routing of the "normal" WebAPI calls and the calls to the extra action.
In my Case Everything was right
1) Web Config was configured properly
2) Route prefix and Route attributes were proper
Still i was getting the error. In my Case "Route" attribute (by pressing F12) was point to System.Web.MVc but not System.Web.Http which caused the issue.
You can add [Route("api/[controller]/[action]")] to your controller class.
[Route("api/[controller]/[action]")]
[ApiController]
public class MySuperController : ControllerBase
{
...
}
I know it is an old question, but sometimes, when you use service resources like from AngularJS to connect to WebAPI, make sure you are using the correct route, other wise this error happens.
Make sure you do NOT decorate your Controller methods for the default GET|PUT|POST|DELETE actions with [HttpPost/Put/Get/Delete] attribute. I had added this attibute to my vanilla Post controller action and it caused a 404.
Hope this helps someone as it can be very frustrating and bring progress to a halt.
For example => TestController
[HttpGet]
public string TestMethod(int arg0)
{
return "";
}
[HttpGet]
public string TestMethod2(string arg0)
{
return "";
}
[HttpGet]
public string TestMethod3(int arg0,string arg1)
{
return "";
}
If you can only change WebApiConfig.cs file.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/",
defaults: null
);
Thats it :)
And Result :
Have you tried like:
[HttpGet("Summary")]
public HttpResponseMessage Summary(MyVm vm)
{
return null;
}
[HttpGet("FullDetails")]
public HttpResponseMessage FullDetails()
{
return null;
}

Categories