ASP.Net WebAPI ActionName Route Not Found - asp.net-web-api

I am trying to create a WebAPI controller with multiple Get commands using the ActionName method. I successfully did this on another project, but have been having problems with this latest project and cannot see to understand why my knockout view model ajax call cannot find the specific URI.
WebApiConfig.cs:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Controller:
// GET api/lot
[ActionName("Default")]
public IEnumerable<DataObject> Get()
{
//...
}
// GET api/lot/Specific/5
[ActionName("Specific")]
public IEnumerable<DataObject> Get(int? data)
{
//...
}
// GET api/lot/5
public string Get(int id)
{
return "value";
}
My default action for GET works perfect but the Specific action continues to have this error when I attempt to call it from the view-model:
"Failed to load resource: the server responded with a status of 404 (Not Found)"
I added [HttpGet] next to the [ActionName("Specific")] and had the following error:
"GET http://localhost:57492/api/lot/Specific/1 404 (Not Found)"
I've tried several different things such as removing the int? data argument, but then when I attempt to build the project it tells me an existing function with same arguments already exists, even with different action names.
Ultimately I would like to have multiple Get(parameter) actions to call for interacting with my view-model.

I was able to solve this by adding the Route property with HttpGet and renaming all of my functions to unique names like GetAll(), GetSpecific(ind id), etc..
// GET api/lot
[HttpGet]
[Route("api/lot/GetAll")]
public IEnumerable<DataObject> GetAll()
{...}
// GET api/lot/GetSpecific/{id}
[HttpGet]
[Route("api/lot/GetSpecific/{id}")]
public IEnumerable<DataObject> GetSpecific(string id)
{

Adding a new route in config.Routes as below. Will this help? I didn't test this.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/specific/{data}",
defaults: new {
action = "Specific",
data = RouteParameter.Optional }
);

Related

Troubleshoot to trigger web api action method

I work on my web api project.
I have two get action methods in controller.
Here the controller:
namespace Playground.Web.Controllers.API
{
[RoutePrefix("api/DamageEvent/{actionType}")]
public class DamageEventController : ApiController
{
#region API methods
[HttpGet]
public async Task<IHttpActionResult> GetDamageEvent(int damageEventId = 0)
{
//some logic
}
[HttpGet]
[Route("{ddd:int}")]
public async Task<IHttpActionResult> GetDamageEvent2(int ddd = 0)
{
//some logic
}
#endregion
}
}
Here WebApiConfig defenition:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Formatters.JsonFormatter.SerializerSettings.DateFormatString = "dd/MM/yyyy";
}
}
Here the example of URL in fiddler compose to trigger web api action:
http://localhost/playground/api/DamageEvent/GetDamageEvent2/?ddd=22
I expect that for the URL above the GetDamageEvent2 web api action will be fired. But instead GetDamageEvent action method is fired.
Why GetDamageEvent2 not fired? Any idea what do I am missing?
==============================Update================================
After I red answer from Nkosi
I made some changes to my code, I added to class WebApiConfig new route:
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
And here the changes in action type:
namespace Playground.Web.Controllers.API
{
[RoutePrefix("api/DamageEvent")]
public class DamageEventController : ApiController
{
#region API methods
[HttpGet]
[Route("GetDamageEvent/{damageEventId}")]
public async Task<IHttpActionResult> GetDamageEvent(int damageEventId = 0)
{
//some logic
}
[HttpGet]
[Route("GetDamageEvent2/{ddd}")]
public async Task<IHttpActionResult> GetDamageEvent2(int ddd = 0)
{
//some logic
}
#endregion
}
}
After I make the changes above the I tryed to fire the both actions and it worked.
But the problem now is when I try to call another actions in another controllers, For example:
http://localhost/playground/api/Contracts/1
I get 404 error.
So I guess the error occures because of the new route template.
So my question how can I fix the error above and to take the new route template into consideration only when the URI try to access to DamageEventController?
You are mixing attribute routing and convention based routing.
Nothing matches your RoutePrefix because there are no actions in the controller that has both a {actionType} and {ddd} templates.
But your stated URL...
api/DamageEvent/GetDamageEvent2/?ddd=22
...matches the DefaultApi convention based route for GetDamageEvent in the route table because it does not have a [RouteAttribute] and it defaults back the convention where...
api/{controller=DamageEvent}/{id=GetDamageEvent2/?ddd=22}
Take a look at Routing in ASP.NET Web API to understand the convention based routing.
and also Attribute Routing in ASP.NET Web API 2
Each entry in the routing table contains a route template. The default
route template for Web API is "api/{controller}/{id}". In this
template, "api" is a literal path segment, and {controller} and {id}
are placeholder variables.
When the Web API framework receives an HTTP request, it tries to match
the URI against one of the route templates in the routing table. If no
route matches, the client receives a 404 error. For example, the
following URIs match the default route:
/api/DamageEvent
/api/DamageEvent/1
/api/DamageEvent/GetDamageEvent2/?ddd=22
Once a matching route is found, Web API selects the controller and the
action:
To find the controller, Web API adds "Controller" to the value of the {controller} variable.
To find the action, Web API looks at the HTTP method, and then looks for an action whose name begins with that HTTP method name. For
example, with a GET request, Web API looks for an action that starts
with "Get...", such as "GetDamageEvent". This
convention applies only to GET, POST, PUT, and DELETE methods. You can
enable other HTTP methods by using attributes on your controller.
We’ll see an example of that later.
Other placeholder variables in the route template, such as {id}, are mapped to action parameters.
To get your stated route to work you need to update your route templates. Either the attribute route or add a new convention route to the route table

Web Api routes with controller and without controller in conjunction

I have two controllers.
one with classic REST functions.
public class ValuesController : ApiController
{
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
public string Get(int id)
{
return "value";
}
}
and one with custom function names with HttpAttributes
public class Values2Controller : ApiController
{
[HttpGet]
public IEnumerable<string> GetAll()
{
return new string[] { "value2_1", "value2_2" };
}
[HttpGet]
public string GetById(int id)
{
return "value " + id;
}
}
and I have my routes configured like this
config.Routes.MapHttpRoute(
name: "DefaultApi2",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
with this setting
http://localhost:xxxxx/api/Values2/1 does not get resolved
and if I put DefaultApi before DefaultApi2 then trying to access
http://localhost:xxxxx/api/Values2/GetAll
I get "The parameters dictionary contains a null entry for parameter 'id' "
The problem is that your route definitions are ambiguous. For example, for this URL:
http://localhost:xxxxx/api/Values2/1
the routing system can consider that the "1" segment is the action in the DefaultApi2 route, and the Id is null becasue you've defined it that way.
So, to break the ambiguity you need to use route constraints. This allows you to add rules to the different segments of your routes, so that they are not mismatched. Remember that the routes are eveluated in the order in which they are register.
So, for example, if you register first DefaultApi and then DefaultApi2, and you add a constraint which requires the Id for the first route to be numeric, both routes will work perfectly.
You could also add constraints on the controller name, or use a different prefix for each kind of controller for example api/ and api2/. The case is that the routes can be distinguished from each other.
Google "Web API route contraints", and you'll get info like this:
Creating a Route Constraint (C#)
The MSDN docs for RouteCollectionExtensions.MapHttpRoute Method (RouteCollection, String, String, Object, Object)are quite useless.

Request does not support http method 'GET' error

I am getting next message on my WebApi controller request:
Message: "The requested resource does not support http method 'GET'."
My action:
[HttpGet]
[System.Web.Http.AcceptVerbs("GET")]
public EntityDTO Get(long entityId)
{
return new Entity();
}
As you see, it already as AcceptVerbs and GET attributes.
My route in WebApiConfig:
config.Routes.MapHttpRoute(
name: "EntityRoute",
routeTemplate: "api/entity/{id}/{controller}",
defaults: new { id = RouteParameter.Optional, controller = "Entity" }
);
This route fires for sure, I know it because my controller's Constructor fires.
Any ideas?
PS. There are lots of similar issues on StackOverflow, but for those issues adding proper attribute usually helped.
The id parameter in your route does not match entityId in your method. They need to match.

aspnet webapi method that takes Project/42/Children - how?

I can't figure out how to write the method signature for a REST(ish) AJAX call in Aspnet WebAPI.
My route is recognised but I get a "No HTTP resource was found that matches the request URI..."
I try to do a REST(ish) call like
http://mysite.com/api/Project/42/Children
and my idea is to have the server return all children of project 42.
My route is:
config.Routes.MapHttpRoute(
name: "DefaultApiWithAction",
routeTemplate: "api/{controller}/{id}/Children",
defaults: new { action="Children"}
);
and my method signature is:
public class ProjectController : ApiController {
[HttpGet]
public IEnumerable<Project> Children(int projectID) {
...
Why isn't my method recognised?
I am also not sure I am doing the right "restish" thing here.
Modify your action parameter name from 'proejctID' to 'id'
public IEnumerable Children(int id)

Web API route mapping to root

I am setting up an ASP.NET MVC 4 Web API to accept requests from a 3rd party server, and I simply can't figure out how to set the route mappings.
Assume the 3rd party server expects to get responses for:
http://[my_server]/authorize?user=[user name]&session=[session token]&item=[item]
http://[my_server]/release?user=[user name]&session=[session token]
Alternatively, the requests can use a dedicated path, i.e.:
http://[my_server]/***api***/authorize?user=[user name]&session=[session token]&item=[item]
http://[my_server]/***api***/release?user=[user name]&session=[session token]
I would like to be able to support both alternatives.
Additional requests, following the more traditional /controller/id form, should be implemented too, but I'd like to focus on the above (I'm not even sure that Web API is the way to go here).
I have written the following controller:
public class MyController : ApiController
{
[HttpGet]
[ActionName("authorize")]
public string Authorize(string user, string session, string item)
{
...
// return "OK" or "DENY";
}
[HttpGet]
[ActionName("release")]
public string Release(string user, string session)
{
...
return "OK";
}
}
and tried everything I could find in SO and elsewhere in WebAppConfig.Register, but I keep getting a 404 error when I try the request in the browser:
http://localhost:22332/api/authorize?user=ury&session=token&item=an_item
My question is, what do I have to do - specifically in WebAppConfig.Register and in the controller - in order to serve the above requests (assuming my test URL is correct...)?
You're getting tripped up by Web API conventions for matching a controller name in a URL to a controller class.
If the name of your controller is "MyController", then the URL to request is:
http://localhost:22332/api/my/authorize?user=ury&session=token&item=an_item
To support a request w/o the "api" bit in the URL, simply add a second route definition in the Register method of WebApiConfig.cs. Since you're also using "actions" in your URL's, you'd need these two routes:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "RootWebApiRoute",
routeTemplate: "{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
If you want to omit the "api" prefix as well as the controller parameter, I think you need a different route definition. Something like:
public static void Register(HttpConfiguration config) {
config.Routes.MapHttpRoute(
name: "ActionOnlyRoute",
routeTemplate: "{action}",
defaults: new { controller = "My" }
);
}
Here, we'll only look for an "action" in the URL and route everything to your "MyController" class.
After a few hours working on this and with a lot of help from the Route Debugger - Thanks Phil Haack! - I've found both the problem and the solution.
The problem: Route mapping matching is ordered, and RouteTable.Routes, from which the app's route mapping is initialized, contains quite a few of them. The request pattern I was looking for also matched some of these mappings ("authorize" was matched as a controller, for example).
The solution: add "my" route mappings before the default mappings.
Yeah, right...
Since most operations are not supported on HttpRouteCollection, the resulting code is a bit ugly, but it works:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
routesCopy = new RouteBase[RouteTable.Routes.Count];
RouteTable.Routes.CopyTo(routesCopy, 0);
config.Routes.Clear();
config.Routes.MapHttpRoute(name: "AuthorizeWebApiRoute", routeTemplate: "authorize", defaults: new { controller = "My", action = "authorize" });
config.Routes.MapHttpRoute(name: "ReleaseWebApiRoute", routeTemplate: "release", defaults: new { controller = "My", action = "release" });
foreach (var route in routesCopy)
{
RouteTable.Routes.Add(route);
}
}
}
As long as "my" route mappings don't match the default mappings (I made them specific enough not to), I'm all good - I think...

Resources