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.
Related
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
I have two routes, but of them are not working on the same time. The one which is on the top working fine but the bottom one not works
config.Routes.MapHttpRoute(
name: "GetKeyWordSearch",
routeTemplate: "api/{controller}/{action}/{keyword}/{Selection}"
//defaults: new { selection = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "GetChapter",
routeTemplate: "api/{controller}/{action}/{bookName}/{chapterNum}/"
//defaults: new {}
);
Any suggestion?
Those 2 routes have the same pattern. There's absolutely no difference between them and the routing engine has no way of disambiguating them. And since routes are evaluated in the order in which they are defined, the first one is always picked.
In order to make this work you need to constrain the parameters using regular expressions or custom constraints. For example:
config.Routes.MapHttpRoute(
name: "GetKeyWordSearch",
routeTemplate: "api/{controller}/{action}/{keyword}/{Selection}",
constraints: new { keyword = #"[0-9]" },
defaults: new { selection = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "GetChapter",
routeTemplate: "api/{controller}/{action}/{bookName}/{chapterNum}"
constraints: new { bookName = #"[a-z]" },
defaults: new { chapterNum = RouteParameter.Optional }
);
In the example I provided, the keyword can only contain numbers whereas the bookName only letters. This way the routing engine will be able to disambiguate the between the following urls and dispatch them to the proper controller action:
api/somecontroller/someaction/123
api/somecontroller/someaction/foo
Obviously you will have to adapt the regular expressions to match your specific needs but remember that there must be some pattern that will make the difference.
I have an ApiController with multiple GET Actions. The problem is that I wan't to name my actions without "Get" in the start of their names.
For instance, I can have an Action named "GetImage" and it will work just fine.
If I will name it "UpdateImage" it wont call the Action, because it probably want an explicit "Get" in the start of the action name.
I can solve it by defining different route for each action I want to use, but I am sure there must be an easier way achieving it.
I also tried [HttpGet] attribute and unfortunately it didn't do the trick.
My Route Config:
routes.MapHttpRoute(
name: "ImagesApi",
routeTemplate: "api/images/{action}/{id}",
defaults: new { controller = "ImageStorageManager",id = RouteParameter.Optional }
);
and I am accessing it by api/images/GetImage or api/images/UpdateImage
The way I've been creating api controller that aren't just for a single object might help you. I got the approach from John Papa's SPA talk on PluralSight (I highly recommend that for learning single page applications). He also walks through this in one of the modules.
It has 2 parts.
Part 1, setting up the routes to do the 2 normal scenarios and then added a 3rd for what i want:
// ex: api/persons
routes.MapHttpRoute(
name: ControllerOnly,
routeTemplate: "api/{controller}"
);// ex: api/sessionbriefs
// ex: api/persons/1
routes.MapHttpRoute(
name: ControllerAndId,
routeTemplate: "api/{controller}/{id}",
defaults: null, //defaults: new { id = RouteParameter.Optional } //,
constraints: new { id = #"^\d+$" } // id must be all digits
);
// ex: api/lookups/all
// ex: api/lookups/rooms
routes.MapHttpRoute(
name: ControllerAction,
routeTemplate: "api/{controller}/{action}"
);
Part 2, in the lookups controler (in John Papa's case), add an ActionName attribute to the methods:
// GET: api/lookups/rooms
[ActionName("rooms")]
public IEnumerable<Room> GetRooms()
{
return Uow.Rooms.GetAll().OrderBy(r => r.Name);
}
// GET: api/lookups/timeslots
[ActionName("timeslots")]
public IEnumerable<TimeSlot> GetTimeSlots()
{
return Uow.TimeSlots.GetAll().OrderBy(ts => ts.Start);
}
Decorate your action with [HttpGet]. See http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api for details on why, and how ApiController routing works.
In addition to all the defined api routes, I want to handle any other url in a LegacyUriRedirect action.
I have tried to leave the routeTemplate empty, but it catches only the root url and ignores any urls with segments.
How do I change the below code so that it catches all urls with any segments?
config.Routes.MapHttpRoute(
name: "LegacyUriRedirect",
routeTemplate: "",
defaults: new { controller = "URI", action = "LegacyUriRedirect" }
);
config.Routes.MapHttpRoute(
name: "LegacyUriRedirect",
routeTemplate: "{*catchall}",
defaults: new { controller = "URI", action = "LegacyUriRedirect" }
);
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.