Web API with Multiple Routes Causes Error - asp.net

I have a Web API that I need to configure multiple route (currently using convention-based) but it looks like the first route is grabbing every request.
Here are the routes from WebApiConfig.cs
config.Routes.MapHttpRoute(
name: "MyProtApi",
routeTemplate: "api/{controller}/{action}/{myprotocolsid}",
defaults: new { myprotocolsid = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "AssessmentApi",
routeTemplate: "api/{controller}/{action}/{assessmentid}",
defaults: new { assessmentid = RouteParameter.Optional }
);
Whats working:
Any calls to the "MyProtocols" controller whether there is an
"myprotocolsid" provided or not
/api/myprotocols/getbyid/642ff9cd-fb32-4a79-aaa4-088278796bb0
public MyProtocolsViewModel GetById(Guid myProtocolsId)
Calls to the "Assessment" controller
that don't provide an "assessmentid"
/api/assessment/list
public IEnumerable List()
Whats not working
Calls to the "Assessment" controller that have an "assessmentid" parameter
/api/assessment/getbyid/8ba32ce6-8a97-4211-b649-afcc8e194f21
public AssessmentTreeViewModel GetById(Guid assessmentId)
"No action was found on the controller 'Assessment' that matches the request."

Here is what I've found. I commented out the second route and found that the behavior did not change at all. Obviously, this indicates that the first route is catching every request.
Upon further thought, that makes sense due to the fact that in the URI, the myProtocolsId and assessmentId are indistinguishable from one another so, in retrospect, it makes sense that the first route is handling all of these requests.
My take is that there are 2 alternatives; either using a more generic parameter name ("id") or to force a named parameter that is distinguished by names query string values /api/assessment/getbyid?assessmentid=foo. I have opted for the former and have things working the way I want.

Related

Define Custom Route asp.net mvc5

I want to assign a route in asp.net mvc application.
What i have is a Measurement Controller. I have 3 types of Measurement in the business scenario.
Blouse
Lhenga
Pardi
Due to which i wanted the url to be like Measurement/Create/Lhenga
Just like this, I want to create Measurement/Create/Blouse and Measurement/Create/Pardi routes.
Although I know I will have to write a route in RouteConfig.cs class.
I have written
routes.MapRoute(
"MeasurementRoute",
"{controller}/{action}/{type}/"
);
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Contact",
url: "Contact",
defaults: new {
controller = "Contact", action = "Address"
});
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new {
controller = "Home", action = "Index", id = UrlParameter.Optional
}
);
}
Every MVC application must configure (register) at least one route, which is configured by MVC framework by default
You can also configure a custom route using MapRoute extension method. You need to provide at least two parameters in MapRoute, route name and url pattern. The Defaults parameter is optional.
You can register multiple custom routes with different names. Consider the following example where we register "Contact" route.
As shown in the above code, URL pattern for the Contact route is Contacts/{id}, which specifies that any URL that starts with domainName/Contacts, must be handled by ContactController. Notice that we haven't specified {action} in the URL pattern because we want every URL that starts with Contact should always use Index action of ContactController. We have specified default controller and action to handle any URL request which starts from domainname/Contacts.
MVC framework evaluates each route in sequence. It starts with first configured route and if incoming url doesn't satisfy the URL pattern of the route then it will evaluate second route and so on. In the above example, routing engine will evaluate Contact route first and if incoming url doesn't starts with /Contacts then only it will consider second route which is default route

MVC controller action causing 404 not found error

NOTE: This is not a duplicate of another question as my first page works fine, it is other pages/actions that are not working.
I've ready many posts so far and nothing comes close. This works just fine when run locally on my development box. After I copy to the server, I see the problem. My default route works fine, I get the expected index page.
public ActionResult Index()
{
return View();
}
My RouteConfig contains:
routes.MapRoute(
name: "AnnualFees",
url: "{controller}/{action}/{id}",
defaults: new { controller = "AnnualFees", action = "Index", id = UrlParameter.Optional }
);
Problems arise when I want to reach anything other than Index. For example, this action causes a 404 not found error:
public ActionResult renderForm()
{
return PartialView("_formPanel");
}
Again, works as it should on my local dev box. But on the server I get Requested URL: /AnnualFees/renderForm 404 error The resource cannot be found.
UPDATE
Ok, doing some more research and trial and error, I discovered something and have a bit more to add.
This app is running under a current website in IIS, where I created a Virtual Folder/application under the website root. If I navigate to www.mysite.com/AnnualFees I get the first page of my MVC app as expected. However, the only way I can get to any other action in my AnnualFeesController, I have to double up the controller name like www.mysite.com/AnnualFees/AnnualFees/renderForm which works but is ugly and not quite right. How can I get rid of the redundancy?
So the problem, as I noted in the comment, is that you have a folder, and under it goes the route. If you do not provide any parts of the route, just calling
www.mysite.com/AnnualFees
this uses all defaults as specified in your route config, and you get the default page. However if you type
www.mysite.com/AnnualFees/ActionName
MVC sees that as controller ActionName and no action no id provided. This does not exist in your app, thus the error.
The root problem here is that websites are not supposed to live under folders, they are supposed to live under top domains. That is, it is not expected that site root URL is www.mysite.com/AnnualFees, it is supposed to be just www.mysite.com. But even that would be fine if you did not have your main controller and IIS folder with the same names, producing unwanted duplication.
You can however change you route to make AnnualFees a default controller. Simply remove the controller part like so:
routes.MapRoute(
name: "AnnualFees",
url: "{action}/{id}",
defaults: new { controller = "AnnualFees", action = "Index", id = UrlParameter.Optional }
);
Now you should be able to use
www.mysite.com/AnnualFees/ActionName
Again, note that in the above URL "AnnualFees" is not a controller name, it is in fact no visible to MVC app at all.
There is however a caveat. Imagine you need to add another controller. Now the default and only route would not work with it. The key is to provide a separate route for this controller, with hardcoded first part
routes.MapRoute(
name: "NewControllerRoute",
url: "NewControllerName/{action}/{id}",
defaults: new { controller = "NewController", action = "Index", id = UrlParameter.Optional }
);
Make sure to put this route before the default one, so that all requests to this controller are routed correctly, and all other requests go to "AnnualFees".

Adding attribute route breaks config-based route (.NET MVC5)

So, we're updating a project from web forms to .NET MVC. To support other applications that deep link into our application, I'm trying to add attribute routes to the relevant controller actions that mimic the old web forms paths.
I have an event action on the Home controller. The configuration has a route for this to remove the controller name.
routes.MapRoute(
name: "eventdetails_nohome",
url: "event/{id}/{occurrenceid}",
defaults: new { Controller = "Home", action = "Event", occurrenceid = UrlParameter.Optional },
constraints: new { id = #"\d+", occurrenceid = #"\d+" }
);
That route works just fine for routes like http://myapp/event/123/456, and the default routing like http://myapp/home/event?id=123&occurrenceid=456 also works.
So far so good, but if I add this route attribute to the action:
[Route("~/ViewEvent.aspx")]
public ActionResult Event(int id, int occurrenceid)
Then the only route that works is http://myapp/ViewEvent.aspx?id=91918&occurrenceid=165045. The routes that worked before start returning
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /event/123/456
I've used the routedebugger extension, and I can verify that even with the attribute route, my old route is still the first to work. So why would I be getting "resource cannot be found" errors?
Note: as a workaround, I've found that I can just do a traditional route configuration like
routes.MapRoute(
name: "Legacy event",
url: "ViewEvent.aspx",
defaults: new { Controller = "Home", action = "Event" }
);
I'm still curious why the attribute route would break existing routes, though, as I thought you were supposed to be able to use both at the same time.
Take a look at Attribute Routing in ASP.NET MVC 5
Another article with the same heading
Attribute Routing in ASP.NET MVC 5
Attribute routes overrides the convention based route. If you use more than one URL for action, you can use multiple route attributes on the action...
[Route("event/{id:int}/{occurrenceid:int}")]
[Route("event")]
[Route("~/ViewEvent.aspx")]
public ActionResult Event(int id = 0, int occurrenceid = 0) {
return View();
}
The following URLs all routed to the above action.
http://myapp/event/123/456
http://myapp/home/event?id=123&occurrenceid=456
http://myapp/ViewEvent.aspx?id=91918&occurrenceid=165045

How to do the proper routing in ASP.Net WebAPI

I am developing an application using ASP.Net Web API and to configure application routing, I'm following this method. But in my application, the url i want is the below. "http://localhost/MY/api/user/AllGridData/false/1342006446883/10/1/Id/desc"
I manually typed this url in my browser window and AllGridData method is fired. But when I access it from the application, it tries to access the url like below.
"http://localhost/MY/api/user/AllGridData?_search=false&nd=1342006446883&rows=10&page=1&sidx=Id&sord=desc"
So then the AllGridData method is not fired because of the following exception.
Failed to load resource: the server responded with a status of 400 (Bad Request) "http://localhost/MY/api/user/AllGridData?_search=false&nd=1342006446883&rows=10&page=1&sidx=Id&sord=desc"
My routing for this url in RouterConfig.cs is like this.
routes.MapHttpRoute(
name: "LoadGridApi",
routeTemplate: "api/{controller}/{action}/{_search}/{nd}/{rows}/{page}/{sidx}/{sord}",
defaults: new { _search = UrlParameter.Optional, nd = UrlParameter.Optional, rows = UrlParameter.Optional, page = UrlParameter.Optional, sidx = UrlParameter.Optional, sord = UrlParameter.Optional }
);
Hope you understand the problem. Ultimately what I want is to get the URL like below, so then it AllGridData method is fired.
"http://localhost/MY/api/user/AllGridData/false/1342006446883/10/1/Id/desc"
What am I doing wrong? Is it because of wrong routing code?
My AllGridData method is in a WebAPI controller, not in a MVC controller.
thilok. I just tried something similar, with a simpler routing.
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{nd}",
defaults: new {id = RouteParameter.Optional, nd = RouteParameter.Optional}
);
and used jQuery getJson calls to call the following method.
public IEnumerable<Organisation> Get(int id, int nd)
{
using (_unitOfWork)
{
I found that I was able to make calls using all the following Urls
/api/organisations/2/3, /api/organisations/2?nd = 3 and /api/userorganisations/?id=2&nd=3
This also worked when directly calling them from a browser window. (Chrome).
What I have notice that you have wrong is that you have an extra placeholder for action {action}. This should not be there for WebApi routing. This is for MVC routing only. There is no concept of action for WebApi's, as this is all handled by the HttpVerbs.
Further information can be found here
WebApi routing
You can use actions just fine in WebApi too. You just don't need them if you are trying to build something RESTful.
example:
public HttpResponseMessage OpenBill(long billId)
Gets it route from:
routes.MapHttpRoute(
name: "DefaultApiWithActionAndId",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Also, you do not have to specifiy querystrings with more than one route. This is why /api/organisations/2/3 works and /2?nd=3 works.
IMHO you should try to keep the routes as simple as possible, otherwise you are in for a world of hurt if something stops working and you have to debug your routes. So i'd go with the querystring version.
Why you are getting urls that are a querystring I cannot answer. It depends who is making the url. This is how GETs usually are done.

ASP.NET Web Api: The requested resource does not support http method 'GET'

I've got the following action on an ApiController:
public string Something()
{
return "value";
}
And I've configured my routes as follows:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
In the beta, this worked just fine, but I just updated to the latest Release Candidate and now I'm seeing errors on calls like this:
The requested resource does not support http method 'GET'.
Why doesn't this work anymore?
(I suppose I could get rid of {action} and just make a ton of controllers, but that feels messy.)
If you have not configured any HttpMethod on your action in controller, it is assumed to be only HttpPost in RC. In Beta, it is assumed to support all methods - GET, PUT, POST and Delete. This is a small change from beta to RC. You could easily decore more than one httpmethod on your action with [AcceptVerbs("GET", "POST")].
All above information is correct, I'd also like to point out that the [AcceptVerbs()] annotation exists in both the System.Web.Mvc and System.Web.Http namespaces.
You want to use the System.Web.Http if it's a Web API controller.
Although this isn't an answer to the OP, I had the exact same error from a completely different root cause; so in case this helps anybody else...
The problem for me was an incorrectly named method parameter which caused WebAPI to route the request unexpectedly. I have the following methods in my ProgrammesController:
[HttpGet]
public Programme GetProgrammeById(int id)
{
...
}
[HttpDelete]
public bool DeleteProgramme(int programmeId)
{
...
}
DELETE requests to .../api/programmes/3 were not getting routed to DeleteProgramme as I expected, but to GetProgrammeById, because DeleteProgramme didn't have a parameter name of id. GetProgrammeById was then of course rejecting the DELETE as it is marked as only accepting GETs.
So the fix was simple:
[HttpDelete]
public bool DeleteProgramme(int id)
{
...
}
And all is well. Silly mistake really but hard to debug.
If you are decorating your method with HttpGet, add the following using at the top of the controller:
using System.Web.Http;
If you are using System.Web.Mvc, then this problem can occur.
This is certainly a change from Beta to RC. In the example provided in the question, you now need to decorate your action with [HttpGet] or [AcceptVerbs("GET")].
This causes a problem if you want to mix verb based actions (i.e. "GetSomething", "PostSomething") with non verb based actions. If you try to use the attributes above, it will cause a conflict with any verb based action in your controller. One way to get arount that would be to define separate routes for each verb, and set the default action to the name of the verb. This approach can be used for defining child resources in your API. For example, the following code supports: "/resource/id/children" where id and children are optional.
context.Routes.MapHttpRoute(
name: "Api_Get",
routeTemplate: "{controller}/{id}/{action}",
defaults: new { id = RouteParameter.Optional, action = "Get" },
constraints: new { httpMethod = new HttpMethodConstraint("GET") }
);
context.Routes.MapHttpRoute(
name: "Api_Post",
routeTemplate: "{controller}/{id}/{action}",
defaults: new { id = RouteParameter.Optional, action = "Post" },
constraints: new { httpMethod = new HttpMethodConstraint("POST") }
);
Hopefully future versions of Web API will have better support for this scenario. There is currently an issue logged on the aspnetwebstack codeplex project, http://aspnetwebstack.codeplex.com/workitem/184. If this is something you would like to see, please vote on the issue.
Have the same Setup as OP.
One controller with many actions... less "messy" :-)
In my case i forgot the "[HttpGet]" when adding a new action.
[HttpGet]
public IEnumerable<string> TestApiCall()
{
return new string[] { "aa", "bb" };
}
Same problem as above, but vastly different root. For me, it was that I was hitting an endpoint with an https rewrite rule. Hitting it on http caused the error, worked as expected with https.
Replace the following code in this path
Path :
App_Start => WebApiConfig.cs
Code:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}/{Param}",
defaults: new { id = RouteParameter.Optional,
Param = RouteParameter.Optional }
);
I got this error when running a query without SSL.
Simply changing the URL scheme of my request from HTTP to HTTPS fixed it.
I don't know if this can be related to the OP's post but I was missing the [HttpGet] annotation and that was what was causing the error, as stated by #dinesh_ravva methods are assumed to be HttpPost by default.
My issue was as simple as having a null reference that didn't show up in the returned message, I had to debug my API to see it.

Resources