Set WebApi 2 routes - c#

I'm fairly new to WepApi2 and creating restfull webservice, but I get the hang of most of it.
Recently, I started developing a new WebApi2 where I encounter that some of my queries required some unesacapable characters, specially the slash and inverted slash ones.
I read several tutorials and questions here about the matter, but none would suffice me, so I ended up to setting my api queries like this
http://host/controler/action/?param1=x&param2=y
Everything works perfect. I had to setup the ActionName and Route attributes to my action, no problem there, but once I tried the old way of query
http://host/controller/action/x/y
I would always get a No action was found on the controller 'Controller' that matches the request.
Here's my routes configuration
// Web API configuration and services
config.MapHttpAttributeRoutes();
// Web API routes
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{id}",
defaults: new { controller = "HelloWorld", id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "Config1",
routeTemplate: "{controller}/{action}/{param1}/{param2}/{param3}",
defaults: new { controller = "Controller", action = "action", param2 = RouteParameter.Optional, param3 = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "Config2",
routeTemplate: "{controller}/{param1}/{param2}/{param3}",
defaults: new { controller = "action", param3 = RouteParameter.Optional }
);
You may notice I have 2 configurations. Well, the first is to classify the action under a controller, as so, in the future, can add more controllers with specific actions, instead of having a huge list of controllers with an specific action, but the second approach is what the client wants.
So, is there a way to have both ways working together??
Edit: My bad, I forgot to show how my controller and actions where setup
public class ControllerController : WebApiController
{
[Route("Controller/action1/")]
[ActionName("action1")]
[HttpGet()]
public Object action1(string param1, string param2)
{
// do action actions code
return result;
}
As you can see, besides setting the route in the config, I'm also setting the action routing attribute, but I wasn't aware I could set more than 1 Route attribute. Will try that and report back!

You could try using attribute routing instead:
// in your startup configuration:
config.MapHttpAttributeRoutes();
// and your controller:
[RoutePrefix("foo")]
public class FooController
{
[HttpGet]
[Route("bar/{param1}/{param2}")]
[Route("bar")
public IHttpActionResult GetBar(string param1, string param2)
{
// ...
}
}

Related

How do I not generate the default endpoints when using attribute routing?

I'm using attribute routing in a Web API controller (.NET Framework 4.5.2 - don't ask, I'm trying to get approval for time to move everything forward).
I have applied a [RoutePrefix] attribute to my ApiController.
I have two controller actions, both HttpGets. Each has a [Route] attribute applied.
I'm using Swagger to auto-generate docs. In the docs for this controller I see three endpoints listed - two for my controller actions, and another HttpGet with the bare controller route.
That is what I have is this:
[RoutePrefix("api/test/Tickets")]
public class TestTicketsController : ApiController
{
[HttpGet, Route("")]
public HttpResponseMessage GetTickets()
{
....
}
[HttpGet, Route("since")]
public HttpResponseMessage GetTicketsSince(string since)
{
....
}
}
And In the generated Swagger docs I see three endpoints:
GET api/test/Tickets
GET api/test/Tickets/since
GET api/TestTickets
This third endpoint, api/TestTickets, seems to be derived from the class name of the controller, ignored my routing attributes. And when I call it, I get an HTTP 200 with an empty body, despite not having defined an action for it.
Where is this coming from? And how can I stop it from being generated?
===
It was suggested that I remove the [HttpGet, Route("")] attribute. If I do, I get an error:
Multiple operations with path 'api/TestTickets' and method 'GET'.
It was also suggested that I include my WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "swagger_root",
routeTemplate: "",
defaults: null,
constraints: null,
handler: new RedirectHandler((message => message.RequestUri.ToString()), "api/docs/index"));
var mediaType = new MediaTypeHeaderValue("application/json");
var formatter = new JsonMediaTypeFormatter();
formatter.SupportedMediaTypes.Clear();
formatter.SupportedMediaTypes.Add(mediaType);
config.Formatters.Clear();
config.Formatters.Add(formatter);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
If I comment-out the config.Routes.MapHttpRoute, the extra endpoint goes away.
Now it's just a matter of determining whether we have any controllers that expect this default endpoint to be there.
Thanks.
abdulg pointed out in a comment, rather than in an answer - default routes are configured in WebApiConfig.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);

Web API Multiple Actions were found that match this request, if called without parameter

I've got a problem using ASP.NET WebAPI Controller. What i try to build is a controller which contains two actions that can be called by a http-get. What i have so far:
[HttpGet]
public IList<Model> Get()
{
...
}
[HttpGet]
public IList<Model> GetAllWorkshops()
{
...
}
I also edited my RouteConfig.cs to the following:
routes.MapHttpRoute(
name: "API Default With ID",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{action}"
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id =UrlParameter.Optional }
);
The strange behaviour is that if i call : http://localhost:1536/Api/Workshop/Get or localhost:1536/Api/Workshop/GetAllWorkshops, i get the exception that multiple actions were found that match this request. But if i call localhost:1536/Api/Workshop/Get/1 or localhost:1536/Api/Workshop/GetAllWorkshops/1 it was works perfectly. So i think that the route with the id works. But why do the other two requests throw these exceptions?
I tried almost every imaginable route. It would be great if you find my mistakes!
Thank you very much!
Because route named "API Default With ID" has id as optional, so /api/workshop/get matched both the first and second route, hence the error.
To fix this error, you can remove second route because it is just a subset of the first route template.
However, it is better to use new Attribute Routing introduced in Web API 2, it is by far more maintainable and easy to understand than using old-style mapping.
If you use attribute mapping, your class will look like:
[RoutePrefix("api/workshop")]
public class WorkshopController : ApiController {
[HttpGet]
[Route("Get")]
public IList<Model> Get()
{
...
}
[HttpGet]
[Route("GetAllWorkshops")]
public IList<Model> GetAllWorkshops()
{
...
}
}
Very clear and concise.

Multiple actions were found that match the request when using actions in Route config

I'm currently building an API using Web API 2.2
I have the RESTful part of it working but now I need one non-RESTful controller:
public class PremisesController : ApiController
{
private PremiseService _service;
public PremisesController()
{
_service = new PremiseService();
}
[HttpGet]
public IHttpActionResult Premise(string id)
{
id = id.Replace(" ", String.Empty).ToUpper();
List<Premise> premises = _service.GetPremisesForPostcode(id);
return Ok(premises);
}
[HttpGet]
public IHttpActionResult Building(string id)
{
double premise = Convert.ToDouble(id);
Building building = _service.GetBuildingsForPremise(premise);
return Ok(building);
}
}
The routing config is as follows:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Im getting the error that it can't distinguish between the two methods when I initiate a GET action:
Multiple actions were found that match the request
So my question is Do I need to specify the Route attribute on top of each method and if yes, why? Doesn't the second route (ActionApi) deals with that situation?
EDIT:
I just tested you're code and it works the way it is... maybe just it is unclear.
/api/Premises/Premise/8 --> will take you to your first action
/api/Premises/Building/8 --> will take you to your second action
/api/Premises/8 --> will cause error because the routing will go to the first rule api/{controller}/{id} with a GET request, then he can't distinguish which of the actions you want because they both match the first route: (api/Premises/{id})
You could also use the RoutePrefix attribute on your controller.
[RoutePrefix("api/premises")]
public class PremisesController : ApiController
That combined with the route attribute would mean you shouldn't get multiple actions with the same route

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;
}

Multiple actions were found that match the request Web API?

I am using web API and i am new in this. I am stuck in a routing problem. I have a controller with following actions :
// GET api/Ceremony
public IEnumerable<Ceremony> GetCeremonies()
{
return db.Ceremonies.AsEnumerable();
}
// GET api/Ceremony/5
public Ceremony GetCeremony(int id)
{
Ceremony ceremony = db.Ceremonies.Find(id);
return ceremony;
}
public IEnumerable<Ceremony> GetFilteredCeremonies(Search filter)
{
return filter.Ceremonies();
}
The problem occure when i added the action GetFilteredCeremonies to my controller. After adding this when i make an ajax call to GetCeremonies action then it return an Exception with following message :
"Message":"An error has occurred.","ExceptionMessage":"Multiple actions were
found that match the request
FYI: The parameter Search is the Model class which contains properties and a function name Ceremonies.
EDIT
Route:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
If you're not requirement bound to use REST services that use api/{controller}/{id} route and attempt to resolve action based on method GET/POST/DELETE/PUT, you can modify your route to classic MVC route to api/{controller}/{action}/{id} and it will solve your problems.
The problem here is your 2 Get methods will resolve to api/Ceremony and MVC does not allow parameter overloading. A quick workaround (not necessarily the preferred approach) for this sort of problem is to make your id parameter nullable e.g.
// GET api/Ceremony
public IEnumerable<Ceremony> GetCeremonies(int? id)
{
if (id.HasValue)
{
Ceremony ceremony = db.Ceremonies.Find(id);
return ceremony;
}
else
{
return db.Ceremonies.AsEnumerable();
}
}
However, you would then be returning a list of ceremonies when with 1 item when your trying to query for a single ceremony - if you could live with that then it may be the solution for you.
The recommended solution is to map your paths appropriately to the correct actions e.g.
context.Routes.MapHttpRoute(
name: "GetAllCeremonies",
routeTemplate: "api/{controller}",
defaults: new { action = "GetCeremonies" }
);
context.Routes.MapHttpRoute(
name: "GetSingleCeremony",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "GetCeremony", id = UrlParameter.Optional }
);
Luckily nowadays with WEB API2 you can use Attribute Routing. Microsoft has gone open source on a big scale and then a wizard named Tim McCall contributed it from the community. So since somewhere end 2013, early 2014 you can add attributes like [Route("myroute")] on your WEB API methods. See below code example.
Still - as I just found out - you have to make sure to use System.Web.Http.Route and NOT System.Web.Mvc.Route. Otherwise you'll still get the error message Multiple actions were found that match the request.
using System.Web.Http;
...
[Route("getceremonies")]
[HttpGet]
// GET api/Ceremony
public IEnumerable<Ceremony> GetCeremonies()
{
return db.Ceremonies.AsEnumerable();
}
[Route("getceremony")]
[HttpGet]
// GET api/Ceremony/5
public Ceremony GetCeremony(int id)
{
Ceremony ceremony = db.Ceremonies.Find(id);
return ceremony;
}
[Route("getfilteredceremonies")]
[HttpGet]
public IEnumerable<Ceremony> GetFilteredCeremonies(Search filter)
{
return filter.Ceremonies();
}
Here is my controller:
public class PhoneFeaturesController : ApiController
{
public List<PhoneFeature> GetbyPhoneId(int id)
{
var repository = new PhoneFeatureRepository();
return repository.GetFeaturesByPhoneId(id);
}
public PhoneFeature GetByFeatureId(int id)
{
var repository = new PhoneFeatureRepository();
return repository.GetFeaturesById(id);
}
}
Here is my api routing:
config.Routes.MapHttpRoute(
name: "ApiWithId",
routeTemplate: "Api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { id = #"^[0-9]+$" });
config.Routes.MapHttpRoute(
name: "ApiWithAction",
routeTemplate: "api/{controller}/{action}/{name}",
defaults: null,
constraints: new { name = #"^[a-z]+$" });
config.Routes.MapHttpRoute(
name: "ApiByAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = "Get" },
constraints: new { id = #"^[0-9]+$" });
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I tested it like this:
/api/PhoneFeatures//api/PhoneFeatures/GetFeatureById/101
/api/PhoneFeatures/GetByFeatureId/12
It works smooth in every condition :)
I found another fix that doesn't require moving methods out of the controller or changing the route mapping config. Just add the [NonAction] attribute to the method you want to exclude:
[NonAction]
public IEnumerable<Ceremony> GetFilteredCeremonies(Search filter)
Please check you have two methods which has the different name and same parameters.
If so please delete any of the method and try.
This error was raised because there are two methods which are looking for same parameters. try to delete any one of them and try...
I hope you are doing HttpGet while you invoke GetFilteredCeremonies(Search filter)
In that case, you cannot pass complex object in GET request like Search that you are passing.
If for some reason, you definitely want to get complex types in your get request, there are some work around. You may need to write a custom model binder and then set the attribute. please refer this article.
Edit Your WebApiConfig.cs in App_Start folder on the root of project and add {action} to routeTemplate parameter in MapHttpRoute Method like below :
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);

Categories