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.
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
Consider an Api controller like this:
public class MyApiController
{
[HttpGet]
public IEnumerable<object> GetItems(int from, int count)
{
...
}
[HttpGet]
public IEnumerable<object> GetActiveItems(int from, int count)
{
...
}
}
If I call /MyApi/GetActiveItems/?from=0&count=20 then it's possible to route the action GetItems instead of GetActiveItems because of parameters similarity.
If I change the parameters name, for example (int fromActive, int countActive) it works properly.
Why is that so? Why doesn't it use the action name to match with the method name?
Should I do something in the routing?
It seems the problem was about a bad routing set somewhere other than its usual:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
and after then there was the correct routing:
routes.MapHttpRoute(
name: "DefaultProvider",
routeTemplate: "api/{controller}/{action}"
);
In this case, as I haven't used {action} in the first routing, the action name goes to the {id} and the routing tries to resolve action by its parameters.
I have configured my WebApiConfig like this:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
I have one method which accepts one parameter. The accessing URI is http://localhost:8598/api/WebApi/GetLocationCategory/87.
This gives me an error: No HTTP resource was found that matches the request URI 'http://localhost:8598/api/WebApi/GetLocationCategory/87'
Controller:
public IEnumerable<LocationCategory_CLS> GetLocationCategory(int CatID)
{
var LocCats = (from lct in entities.tdp_LocationCategories join lc in entities.tdp_LocationMaster on lct.FK_LocationID equals lc.LocationID where lct.IsApproved == 0 && lct.FK_CategoryID == CatID select new { lc.LocationID, lc.LocationName }).ToList();
List<LocationCategory_CLS> loc = new List<LocationCategory_CLS>();
foreach (var element in LocCats)
{
loc.Add(new LocationCategory_CLS
{
LocationID = element.LocationID,
LocationName = element.LocationName
});
}
return loc;
}
Try changing your Controller method as
public IEnumerable<LocationCategory_CLS> GetLocationCategory(int id) <-- Change
{
var LocCats = (from lct in entities.tdp_LocationCategories join lc in entities.tdp_LocationMaster on lct.FK_LocationID equals lc.LocationID where lct.IsApproved == 0 && lct.FK_CategoryID == id select new { lc.LocationID, lc.LocationName }).ToList();
List<LocationCategory_CLS> loc = new List<LocationCategory_CLS>();
foreach (var element in LocCats)
{
loc.Add(new LocationCategory_CLS
{
LocationID = element.LocationID,
LocationName = element.LocationName
});
}
return loc;
}
The change is only, changing input parameter from CatId to id.... It works for me many times..
Edit :
Its a long time when I look back I think I know the reason now. Words Like Jared is correct, it's all to do with Routing which we specify. If I have a route(default) as :
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
And my URL is /MyController/GetLocationCategory/123, it will be equivalent to /MyController/GetLocationCategory?id=123.
Similarly, if I want to change my parameter name for Id to say CatId, then I need to change the query string parameter(the way I am calling my Controller Action would change). Which would now be :
/MyController/GetLocationCategory?CatId=123
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Maps to:
http://localhost:8598/api/Controller/Action/id
The WebApi portion of the url is redundant with api. Then modify the method parameter name to match the route:
public IEnumerable<LocationCategory_CLS> GetLocationCategory(int id)
This is a good default as it matches the convention.
Alternatively, you can modify the route to use this unconventional parameter name instead:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{CatID}",
defaults: new { CatID = RouteParameter.Optional }
);
Finally, in either case make sure the controller name ends in Controller.
Good names: CustomController, CustomApiController
Bad names: Custom, CustomApi
what is the name of your controller and action ?
The URL should be
http://localhost:8598/api/Controller/Action
It does not map to the Route Configuration you have specified, hence the pipeline is unable to locate the correct Controller. The /id should not be in the path it must be in the body or the query parameters ( I got stumped in haste !!)
Example : -
public class FooController : ApiController
{
public int GetIndex(int id)
{
return id;
}
}
localhost:58432/api/foo/GetIndex?Id=1
O/P :- 1
Note:- If your action name is GetIndex then the URL must be GetIndex not just Index. Optionally you can specify the [HttpGet] attribute for the action.
If you have the following as well in your WebApi.config
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then http://localhost:58432/api/foo?Id=1 would also be a legitimate route. Not sure if you would want that.
Also, With WebAPI as far as possible stick to non action based routing , MVC is meant for that. It is not the recommended way.
Your request URL is http://localhost:8598/api/WebApi/GetLocationCategory/87
Your route is configured to accept: 'api/{controller}/{action}/{id}'
So you need to ensure that the name of your controller is 'WebApiController'.
Also, as stated alredy by #SDG you need to make sure that the name of the parameter to your action method matches what you have in your Route template i.e change 'CatID' to 'id'
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.
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.