Three routes for one verb in Web API - c#

I am trying to get the following three routes to work:
GET /api/category - list all categories
GET /api/category/1 - get details about category 1
GET /api/category/1/questions - get questions from category 1
I am having trouble getting this routing to work.
My CategoryController has the following three method definitions:
public IEnumerable<Category> Get() {}
public Category Get(int id) {}
public IEnumerable<QuestionSummary> GetQuestions(int id) {}
How can I get these to match and not conflict with eachother? We have used AttributeRouting in other parts of the project.

The following should work:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "SubResource",
routeTemplate: "api/{controller}/{categoryId}/{action}"
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
and your controller:
public class CategoryController : ApiController
{
public IEnumerable<Category> Get()
{
...
}
public Category Get(int id)
{
...
}
[HttpGet]
public IEnumerable<QuestionSummary> Questions(int categoryId)
{
...
}
}
ASP.NET Web API 2 makes this scenario much easier as it allows for attribute based routing.
I am sure Microsoft will eventually get it right (the way ServiceStack does it). Hopefully in ASP.NET Web API 3 they will introduce message based routing and they will have done a REST framework the right way - it will be message based and routing will be done on those messages.

I don't think you can have multiple methods for the same verb on the same controller unless you use "action".
I guess you can handle your 3 routes using 3 different controller as long as you register those in the proper order.
config.Routes.MapHttpRoute(
name: "Route1",
routeTemplate: "/api/category/{id}/questions",
controllerType: typeof(YourController1)
);
config.Routes.MapHttpRoute(
name: "Route2",
routeTemplate: "/api/category/{id}",
controllerType: typeof(YourController2)
);
config.Routes.MapHttpRoute(
name: "Route3",
routeTemplate: "/api/category",
controllerType: typeof(YourController3)
);

Related

How to structure Web API code such that the link will support api/values/{id}/name

I am new to learning Web API code in C# and was wondering how to create the following link that gets the name for a specific value ID.
The provided code below does not work as I want it to with the "/name" behind it.
// GET api/values/{id}/name
public string Get(int id)
{
return getNameValue(id);
}
You can use custom route like:
[Route("{id:int}/name")]
public string Get(int id)
{
return getNameValue(id);
}
Update:
In WebApiConfig, modify register method to have custom routes
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id:int}/name",
defaults: new { id = RouteParameter.Optional }
);
You can directly use attribute routing without defining the route in route config.
Add the below code which enable attribute routing(This feature is only applicable to Web API 2).
Attribute routing Web API 2
public static void Register(HttpConfiguration config)
{
// Attribute routing.
config.MapHttpAttributeRoutes(); //**enabling attribute routing here.**
// Convention-based routing.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
then you can make use of:
[Route("{id:int}/name")]
public string Get(int id)
{
return getNameValue(id);
}

Routes and the dreaded 405

I've got a simple web API that registers on one route. At the moment I've got two because only one of them does what I need.
My application only has one controller and one Post method in that Controller. I've registered a single Route which always returns a 405 (method not allowed)
The two routes are configured in the RouteConfig.cs:
routes.MapHttpRoute(
name: "app-events",
routeTemplate: "events",
defaults: new { controller = "Events" },
handler: new GZipToJsonHandler(GlobalConfiguration.Configuration),
constraints: null
);
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
The Controller method is essentially this...
public class EventsController : ApiController
{
public EventsController()
{
_sender = new EventHubSender();
}
public async Task<HttpResponseMessage> Post(HttpRequestMessage requestMessage)
{
// doing fun stuff here…
return new HttpResponseMessage(HttpStatusCode.OK);
}
}
If I only configure the first route and post a request to http://devbox/events I will get a 405. However, if I add the second, default, route, and post to http://devbox/api/events I get back my expected 201.
With both routes configured at the same time, the same pattern, the route explicitly bound to the Controller receives a post request and fails with a 405, but the other URL will work.
I've spent a long time looking around before conceding to ask the question. Most things I read have a lot to do with Webdav and I think I've followed every one of them to fix the issue. I am not very experienced with this stack, so nothing is very obvious to me.
You mentioned RouteConfig File. This is used for configuring the MVC routes not Web API routes.
So it would appear you are configuring the wrong file which would explain why the api/... path works as it is probably mapping to the default configuration in WebApiConfig.Register, which would look like
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
// Convention-based routing.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
You would need to update that file with the other desired route
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
// Convention-based routing.
config.Routes.MapHttpRoute(
name: "app-events",
routeTemplate: "events",
defaults: new { controller = "Events" },
handler: new GZipToJsonHandler(GlobalConfiguration.Configuration),
constraints: null
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Web API routes are usually registered before MVC routes which explains why it was not working with your original configuration.
You should also adorn the action with the respective Http{Verb} attribute.
In this case HttpPost so that the route table knows how to handle POST requests that match the route template.
[HttpPost]
public async Task<IHttpActionResult> Post() {
var requestMessage = this.Request;
// async doing fun stuff here….
return OK();
}

Web Api Routes picking generic over specific

Hi Guys i am new with web api routes and i have this issue where my call will pick up the more generic one over the specific one.
The ajax call i have is
$.getJSON("/api/solutions/GetSolutionByCategory/" + categoryId,
function (data) {//..some other functions}
Within the solutions controller there are 2 methods
[HttpGet]
public IHttpActionResult GetSolutionByCategory(int cateogryId)
{
List<Solution> solutions = _context.Solutions.Where(s => s.CategoryId == cateogryId).ToList();
return Ok(solutions.Select(Mapper.Map<Solution, SolutionDto>));
}
[HttpGet]
public IHttpActionResult GetSolutions()
{
return Ok(_context.Solutions.ToList().Select(Mapper.Map<Solution, SolutionDto>));
}
And then i have the following 3 routes
config.Routes.MapHttpRoute(
name: "WithAction",
routeTemplate: "api/{controller}/GetIssuesByFlag/{flag}",
defaults: new {flag = 3}
);
config.Routes.MapHttpRoute(
name: "SolutionByCategory",
routeTemplate: "api/{controller}/GetSolutionByCategory/{categoryId}",
defaults: new {categoryId = -1}
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new {id = RouteParameter.Optional}
);
What happens is that my ajax call will ignore the 2nd one that is the one i want it to hit and goes to the 3rd one there for instead of calling the GetSolutionsByCategory it hits the generic GetSolutions
What am i doing wrong here?
There is a typo in your action parameter name, its int cateogryId instead of int categoryId - public IHttpActionResult GetSolutionByCategory(int categoryId).
However, I would suggest you to go for attribute routing instead of adding lots of route configurations. Enable attribute routing in your web api config class - config.MapHttpAttributeRoutes(); and in your controller:
[RoutePrefix("api")]
public class SolutionsController:ApiController
{
[HttpGet]
[Route("GetSolutionByCategory/{categoryId})"]
public IHttpActionResult GetSolutionByCategory(int categoryId)
{
....
}
[HttpGet]
[Route("GetSolutions")]
public IHttpActionResult GetSolutions()
{
...
}
}
Using Attribute routing we can have same controller with multiple get and post methods. We need to add the routing on the action methods.
We can provide the constraints as well with attribute routing.

Webapi custom route action

Situation :
I've created controller class that extends ApiController and includes following methods :
// GET api/Posts/5
[ResponseType(typeof(Post))]
public IHttpActionResult GetPost(int id)
{
...
}
// GET api/Posts/ByBoardID/2
[HttpGet]
[ActionName("ByBoardID")]
public IQueryable<Post> GetByBoardID(int boardID)
{
...
}
The idea is to match those method to a given routes (i.e 'api/Posts/ByBoardID/2' to a GetByBoardID(int boardID) method and 'api/Posts/2' to a GetPosts(int id) method).
Here's route config :
config.Routes.MapHttpRoute(
name: "ByParamApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional });
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
Problem :
Second route ('api/Posts/ByBoardID/2') cannot be matched - No HTTP resource was found that matches the request URI.
Question :
Whats the best practice to create such 'nested' routes inside controller? I will use many controllers with the same pattern (/{controller}/{id} and /{controller}/bySpecialParam/{id}) so I don't want to 'hardcode' such route that won't be reusable.
Only way I ever got working such combination is by changing this
[ActionName("ByBoardID")]
to
[Route("api/Posts/ByBoardID/{boardID}")]
Never able to figure out how the ActionName attribute works so always preferred to go with Route attribute
This worked:
1) Provide actionName for both the methods in the controller.
[ActionName("DefaultAction")]
[ActionName("ByBoardID")]
2) In the webapiconfig class, add the following routes
config.Routes.MapHttpRoute(
"defaultActionRoute",
"{controller}/{action}/{id}",
null,
new
{
action = "ByBoardId"
});
config.Routes.MapHttpRoute(
"defaultRoute",
"{controller}/{id}",
new
{
action = "DefaultAction"
});
Make sure you have the GlobalConfiguration.Configuration.EnsureInitialized(); at the end of Application_Start() method.

Web api action selection based on different routes

I have one api Controller with two different routes:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "downloadApi",
routeTemplate: "api/{controller}/{download}/{id}"
);
I want to have these actions with these routes:
1 - For downloading a file: public GetFile()
GET /api/v1/documents/download/id
2 - For getting file info: public GetFileInfo()
GET /api/v1/documents/id
So in my controller I want to have these actions:
public class DocumentsController : apiController
{
// triggers with GET /api/v1/documents/download/id
public HttpResponseMessage GetFile(){}
// triggers with GET /api/v1/documents/id
public HttpResponseMessage GetFileInfo(){}
}
How can I do this?
Thanks
Here's an example to explain my comment. Rather than defining a route in your WebApiConfig.cs that applies to some routes, but not others (which I don't think you can do, but I've never tried so maybe you can), use the Route method attribute to define the route used at the method level.
public class DocumentsController : apiController
{
// triggers with GET /api/v1/documents/download/id
[HttpGet]
[Route("api/v1/documents/download/{id}")]
public HttpResponseMessage GetFile(int id){}
// triggers with GET /api/v1/documents/id
[HttpGet]
[Route("api/v1/documents/{id}")]
public HttpResponseMessage GetFileInfo(int id){}
}
Untested, but it should work

Categories

Resources