I have a web service written in the ASP.NET MVC 4 framework. Its basic CRUD operations map well to REST verbs, but I have one action that I need to add that does not.
What is the correct way to specify the ability to sometimes pass an ID at the end of the URL that you know will be an Integer type and sometimes have it be an "action" a String type followed by a slash and an int ID? Need help with correct Router map.
Thoughts? Note: I'm more concerned with WebAPI then being 100% REST.
example URLs
// GET list of widgets
http://somedomain.com/api/widget
// GET specific widget
http://somedomain.com/api/widget/1
// POST - take special action on a specific widget - promote
http://somedomain.com/api/widget/promote/1
Would this be the correct way to handle it?
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "DefaultApiExtended",
routeTemplate: "api/{controller}/{action}/{id}"
);
I'd try putting the second pattern (an "action" a String type followed by a slash and an int ID) in at a higher priority than (pass an ID at the end of the URL that you know will be an Integer type).
routes.MapHttpRoute(
name: "ActionWithId",
routeTemplate: "api/widget/{action}/{id}"
);
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Your action and your ID are not optional as you're always expecting them. If there is no match, the DefaultApi rule should take over processing.
EDIT:
Just thought, if you do want it to be "RESTy" then you could always switch the ID, so for this widget, with this id, perform this action.
routes.MapHttpRoute(
name: "ActionWithId",
routeTemplate: "api/widget/{id}/{action}"
);
Take a look at http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api - specifically section "Routing by Action Name"
Slight change recomended from
http://somedomain.com/api/widget/promote/1
To
http://somedomain.com/api/widgets/1/promote
(Use the plural if possible widget*s*)
Your Controller will need to look like this:
public class WidgetsController : ApiController
{
[HttpPost]
/// POST api/widgets/1/promote
public void Promote(int id)
{
}
// GET api/widgets/1
public string Get(int id)
{
return "value";
}
}
And the routes:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "DefaultPromoteActionApi",
routeTemplate: "api/{controller}/{id}/{action}",
defaults: new { action = "Promote" }
);
}
Take a look at AttributeRouting; it should be able to accomplish this with ease.
Related
I have a web API controller:
public List<mytable> Get(string filter1, string filter2){
...
}.
I need to create another method named
List<drpSubject> GetSubject(){
...
}.
My WebApiConfig.cs like this
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
// routeTemplate: "api/{controller}/{id}",
routeTemplate: "{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute("DefaultApiWithAction", "{controller}/{action}");
}
But now I get error message "Multiple actions were found that match the request..." . It only works when there is one Get(). How to create web api that can take multiple actions? Thanks.
There are multiple ways to configure routing.
You could change
"{controller}/{id}",
to
"{controller}/{action}/{id}",
More at Routing by Action Name.
I am new to WEB API and trying to set up routing for multiple GET actions.
Controller Code
// Get api/values
public IEnumerable<tblUser> Get()
{
//whatever
}
// Get api/values/action
[ActionName("GetByQue")]
public IEnumerable<tblQue> GetQue()
{
//whatever
}
// Get api/values/action
[ActionName("GetUserScore")]
public IEnumerable<tblScore> GetScore(string user)
{
//whatever
}
Config
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional}
);
config.Routes.MapHttpRoute(
name: "DefaultActionApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "GetByQue" }
);
config.Routes.MapHttpRoute(
name: "DefaultStringApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "GetUserScore" }
);
When I try with http://localhost:54118/api/remote/GetByQue URL getting this error
{
"Message": "The request is invalid.",
"MessageDetail": "The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.String Get(Int32)' in 'HydTechiesApi.Controllers.HydTechiesApiController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter."
}
is my routing wrong? Any help would be valuable as I am not able to find a solution.
You should add {action} to routeTemplate instead of {id} in the second configuration
config.Routes.MapHttpRoute(
name: "DefaultActionApi",
routeTemplate: "api/{controller}/{action}",
defaults: new { action = "GetByQue" }
);
also you can try to use route attribure on action :
[ActionName("GetByQue")]
[Route("api/remote/GetByQue")]
public IEnumerable<tblQue> GetQue()
{
//whatever
}
or change order(the second configuration and first configuration) of configuration in WebApiConfig.cs
You made a couple of mistakes in your route. As your example code below:
config.Routes.MapHttpRoute(
name: "DefaultActionApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "GetByQue" });
//url: http://localhost:54118/api/remote/GetByQue
(1). routeTemplate: "api/{controller}/{id}". you specify your route has an id and it is not Optional. So your URL must have id. That's what your error showed.you can handle with the issue like below:
defaults: new { id = RouteParameter.Optional }
(2). defaults: new { action = "GetByQue" }); you did not say any thing about action in your routeTemplate. your defaults about action, which does not have any meaning.
(3). From your route, your URL should look like http://localhost:54118/api/remote/5 , you should not get mutiple get method by your route.
There are some solutions, which you may use:
(1). change route like below:
config.Routes.MapHttpRoute(
name: "DefaultActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
please add [HttpGet] in each method just in cause.
[HttpGet]
public IEnumerable<tblUser> Get()
{......}
[HttpGet]
[ActionName("GetByQue")]
public IEnumerable<tblQue> GetQue()
{......}
[HttpGet]
[ActionName("GetUserScore")]
public IEnumerable<tblScore> GetScore(string user)
{......}
Now you can use URL like http://localhost:54118/api/remote/GetByQue
Very Useful Tips: Using [Route("")] tag to specify parameters
**You also have to change Route like above (1)
in Controller, please specify request method to make sure the get method
[HttpGet]
[ActionName("GetUserScore")]
[Route("api/remote/GetScore/{user}")]
public IEnumerable<tblScore> GetScore(string user)
{.....}
//URL: http://localhost:54118/api/remote/GetScore/user
This is driving me crazy. This is my controller action:
[HttpGet]
public List<Product> RetrieveByIds(int[] ids)
{
Product p = new Product();
//return p.RetrieveByIds(ids);
return new List<Product>();
}
This is my routing file:
public static void Register(HttpConfiguration config)
{
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 }
);
config.Routes.MapHttpRoute(
name: "ActionApiMultipleIds",
routeTemplate: "api/{controller}/{action}/{ids}",
defaults: new { ids = RouteParameter.Optional }
);
}
And I keep getting this error when I try to test it using the RestConsole:
{
"Message": "The request is invalid.",
"MessageDetail": "The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Collections.Generic.List`1[GrownHome.Web.Models.Product] RetrieveAllByUserId(Int32)' in 'GrownHome.Web.Controllers.Api.ProductController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter."
}
I keep starring at my screen fiddling around with the code and I am getting nowhere. I don't have to explain to you guys how frustrating this is :)))
I run into these kind of routing issues every now and again and I can't seem to get my head around these kind of issues. Some insight would be appriciated very much!
Thanks!
The framework tries to match a URL to a route based on the order the routes are listed in your Register method, so your third route will actually never be used because the first two routes are the default "catch-all" routes. This is why you are receiving the error saying that parameter id is null.
The general rule of thumb is to place more specific routes before more general routes to ensure the routing framework matches a URL to a route correctly. So in your case your custom route needs to be moved ahead of the two default routes:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "ActionApiMultipleIds",
routeTemplate: "api/{controller}/{action}/{ids}",
defaults: new { ids = RouteParameter.Optional }
);
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 }
);
}
Your third route will never be reaced because the one before is catching all the request that have the same formatting. You probably need to add some constraints.
I'm building a web api that has multiple get/post calls that have the same signatures. Now I know that in the case of multiple identical calls, you generally have 2 options: separate into different controllers, or use {action} in your routes. I have gone the {action} method as it fits best I believe in most of my controllers. However, in one of my controllers I would prefer not to use the action method.
I have a call like so:
[HttpGet]
public Program Program(string venue, string eventId)
//api/{controller}/{venue}/{eventId}
Now I need a new call
[HttpGet]
public Program ProgramStartTime(string venue, string eventId)
//api/{controller}/{venue}/{eventId}
I know I can add an action name to this and call i.e
api/{controller}/{action}/{venue}/{eventId}
But I feel like it breaks the expected. Is there a way that I could some something like
api/Content/LAA/1/PST
api/Content/LAA/1?PST
Also if I have to go the action route, I currently already have a route I use for other controllers, but it simply uses {id} as its only parameter. Will a new route conflict with this one? Is there a better way to setup my routes?
config.Routes.MapHttpRoute(
name: "...",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new {id = RouteParameter.Optional}
);
config.Routes.MapHttpRoute(
name: "...",
routeTemplate: "api/{controller}/{action}/{venue}/{eventId}/{...}/{***}/{###}",
defaults: new {### = RouteParameter.Optional}
);
config.Routes.MapHttpRoute(
name: "...",
routeTemplate: "api/{controller}/{action}/{venue}/{eventId}/{...}",
defaults: new {... = RouteParameter.Optional}
);
config.Routes.MapHttpRoute(
name: "...",
routeTemplate: "api/{controller}/{action}/{venue}",
defaults: new {venue = RouteParameter.Optional}
);
I expect at least one method that would have up to 5 parameters
Here's the answer I found and it does pretty much exactly what I wanted:
config.Routes.MapHttpRoute(
name: "VenuesAllOrStream",
routeTemplate: "api/Racing/{action}",
defaults: new { controller = "Racing", action = "Venues" },
constraints: new { action = "Venues|All|Streaming" }
);
config.Routes.MapHttpRoute(
name: "VenueOrVideo",
routeTemplate: "api/Racing/{venue}/{action}",
defaults: new { controller = "Racing", action = "RaceNumbers" },
constraints: new { action = "RaceNumbers|Video" }
);
config.Routes.MapHttpRoute(
name: "ProgramOrMtp",
routeTemplate: "api/Racing/{venue}/{race}/{action}",
defaults: new { controller = "Racing", action = "Program" },
constraints: new { action = "Program|Mtp", race = #"\d+" }
);
It is important that the VenuesAllOrStream is first as otherwise the VenueOrVideo picks up the route. I most likely will extract out the action constraints into enums later.
Brief note : Setting the action default allows for the route to basically make it an optional parameter. So each route works without the {action} actually being set.
This is my first rodeo with MVC Web API and I'm having some issues understanding the routing aspects. I would like to have a uri template similar to thise:
http://google.com/api/AzureQueue - GET for all items in the queue
http://google.com/api/AzureQueue/DeviceChart/ - GET returns devices and processing time for agent
http://google.com/api/{controller}/{id} <-- default
http://google.com/api/{controller}/{chartType}/{id} where ID is optional
where I'm struggling is:
1. what the french toash do I put in the WebApiConfig.cs file
2. do I need to do anthing special in my controller eg. specifiy NonActions & Actions, Action Names, etc
Any help is appreciated
You are almost there. The default route (in WebApiConfig.cs looks like this:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
There's one very important caveat: the routes are examined in the order that they are declared with the first matching one being used, so the default route needs to go last.
With that out of the way, you need to make a decision, do you want the calls for various chart types to go to one action, or many?
For one action:
WebApiConfig.cs
config.Routes.MapHttpRoute(
name: "AzureQueue",
routeTemplate: "api/AzureQueue/{chartType}/{id}",
defaults: new { controller = "AzureQueue", id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
AzureQueueController.cs
public class AzureQueueController : ApiController
{
public string Get(string chartType)
{
return "chart = " + chartType;
}
public string Get(string chartType, int id)
{
return "chart = " + chartType + ",id = " + id.ToString();
}
}
There are two things to notice here. In the anonymous class assigned to defaults, the value for controller decides which controller to route the request to. This can either be in the route template, or simply defined in the class. Also, a request of type Get is automatically sent to an action that starts with Get and has the arguments in the Url that match the template (there are two different cases since id is optional).
This would be my preferred way to go unless the business logic for various charts is different.
On the other hand you could specify this:
WebApiConfig.cs
config.Routes.MapHttpRoute(
name: "AzureQueue",
routeTemplate: "api/AzureQueue/{action}/{id}",
defaults: new { controller = "AzureQueue", id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Since I'm using the word action is the template, this will get interpreted as an action name.
AzureQueueController.cs
[HttpGet]
public string DeviceChart()
{
return "chart = DeviceChart" ;
}
[HttpGet]
public string DeviceChart(int id)
{
return "chart = DeviceChart" + ",id = " + id.ToString();
}
Here there is no string argument, that part of the url is being used to decide which action (public method) to use. Also, since the action names don't start with Get, I need to add an attribute [HttpGet] for each method to mark them as being able to receive GET requests.
Good luck with your project.