Multiple HttpPost method in Web API controller - c#

I am starting to use MVC4 Web API project, I have controller with multiple HttpPost methods. The Controller looks like the following:
Controller
public class VTRoutingController : ApiController
{
[HttpPost]
public MyResult Route(MyRequestTemplate routingRequestTemplate)
{
return null;
}
[HttpPost]
public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
{
return null;
}
}
Here MyRequestTemplate represents the template class responsible for handling the Json coming through the request.
Error:
When I make a request using Fiddler for http://localhost:52370/api/VTRouting/TSPRoute or http://localhost:52370/api/VTRouting/Route I get an error:
Multiple actions were found that match the request
If I remove one of the above method it works fine.
Global.asax
I have tried modifying the default routing table in global.asax, but I am still getting the error, I think I have problem in defining routes in global.asax. Here is what I am doing in global.asax.
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapHttpRoute(
name: "MyTSPRoute",
routeTemplate: "api/VTRouting/TSPRoute",
defaults: new { }
);
routes.MapHttpRoute(
name: "MyRoute",
routeTemplate: "api/VTRouting/Route",
defaults: new { action="Route" }
);
}
I am making the request in Fiddler using POST, passing json in RequestBody for MyRequestTemplate.

You can have multiple actions in a single controller.
For that you have to do the following two things.
First decorate actions with ActionName attribute like
[ActionName("route")]
public class VTRoutingController : ApiController
{
[ActionName("route")]
public MyResult PostRoute(MyRequestTemplate routingRequestTemplate)
{
return null;
}
[ActionName("tspRoute")]
public MyResult PostTSPRoute(MyRequestTemplate routingRequestTemplate)
{
return null;
}
}
Second define the following routes in WebApiConfig file.
// Controller Only
// To handle routes like `/api/VTRouting`
config.Routes.MapHttpRoute(
name: "ControllerOnly",
routeTemplate: "api/{controller}"
);
// Controller with ID
// To handle routes like `/api/VTRouting/1`
config.Routes.MapHttpRoute(
name: "ControllerAndId",
routeTemplate: "api/{controller}/{id}",
defaults: null,
constraints: new { id = #"^\d+$" } // Only integers
);
// Controllers with Actions
// To handle routes like `/api/VTRouting/route`
config.Routes.MapHttpRoute(
name: "ControllerAndAction",
routeTemplate: "api/{controller}/{action}"
);

Another solution to your problem would be to use Route which lets you specify the route on the method by annotation:
[RoutePrefix("api/VTRouting")]
public class VTRoutingController : ApiController
{
[HttpPost]
[Route("Route")]
public MyResult Route(MyRequestTemplate routingRequestTemplate)
{
return null;
}
[HttpPost]
[Route("TSPRoute")]
public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
{
return null;
}
}

use:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
it's not a RESTful approach anymore, but you can now call your actions by name (rather than let the Web API automatically determine one for you based on the verb) like this:
[POST] /api/VTRouting/TSPRoute
[POST] /api/VTRouting/Route
Contrary to popular belief, there is nothing wrong with this approach, and it's not abusing Web API. You can still leverage on all the awesome features of Web API (delegating handlers, content negotiation, mediatypeformatters and so on) - you just ditch the RESTful approach.

A web api endpoint (controller) is a single resource that accepts get/post/put/delete verbs. It is not a normal MVC controller.
Necessarily, at /api/VTRouting there can only be one HttpPost method that accepts the parameters you are sending. The function name does not matter, as long as you are decorating with the [http] stuff. I've never tried, though.
Edit: This does not work. In resolving, it seems to go by the number of parameters, not trying to model-bind to the type.
You can overload the functions to accept different parameters. I am pretty sure you would be OK if you declared it the way you do, but used different (incompatible) parameters to the methods. If the params are the same, you are out of luck as model binding won't know which one you meant.
[HttpPost]
public MyResult Route(MyRequestTemplate routingRequestTemplate) {...}
[HttpPost]
public MyResult TSPRoute(MyOtherTemplate routingRequestTemplate) {...}
This part works
The default template they give when you create a new one makes this pretty explicit, and I would say you should stick with this convention:
public class ValuesController : ApiController
{
// GET is overloaded here. one method takes a param, the other not.
// GET api/values
public IEnumerable<string> Get() { .. return new string[] ... }
// GET api/values/5
public string Get(int id) { return "hi there"; }
// POST api/values (OVERLOADED)
public void Post(string value) { ... }
public void Post(string value, string anotherValue) { ... }
// PUT api/values/5
public void Put(int id, string value) {}
// DELETE api/values/5
public void Delete(int id) {}
}
If you want to make one class that does many things, for ajax use, there is no big reason to not use a standard controller/action pattern. The only real difference is your method signatures aren't as pretty, and you have to wrap things in Json( returnValue) before you return them.
Edit:
Overloading works just fine when using the standard template (edited to include) when using simple types. I've gone and tested the other way too, with 2 custom objects with different signatures. Never could get it to work.
Binding with complex objects doesn't look "deep", so thats a no-go
You could get around this by passing an extra param, on the query string
A better writeup than I can give on available options
This worked for me in this case, see where it gets you. Exception for testing only.
public class NerdyController : ApiController
{
public void Post(string type, Obj o) {
throw new Exception("Type=" + type + ", o.Name=" + o.Name );
}
}
public class Obj {
public string Name { get; set; }
public string Age { get; set; }
}
And called like this form the console:
$.post("/api/Nerdy?type=white", { 'Name':'Slim', 'Age':'21' } )

It is Possible to add Multiple Get and Post methods in the same Web API Controller. Here default Route is Causing the Issue. Web API checks for Matching Route from Top to Bottom and Hence Your Default Route Matches for all Requests. As per default route only one Get and Post Method is possible in one controller. Either place the following code on top or Comment Out/Delete Default Route
config.Routes.MapHttpRoute("API Default",
"api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });

When creating another Http Method add [HttpPost("Description")]
[HttpPost("Method1")]
public DataType Method1(MyRequestTemplate routingRequestTemplate)
{
return null;
}
[HttpPost("Method2")]
public DataType Method2(MyRequestTemplate routingRequestTemplate){}

Put Route Prefix [RoutePrefix("api/Profiles")] at the controller level and put a route at action method [Route("LikeProfile")]. Don't need to change anything in global.asax file
namespace KhandalVipra.Controllers
{
[RoutePrefix("api/Profiles")]
public class ProfilesController : ApiController
{
// POST: api/Profiles/LikeProfile
[Authorize]
[HttpPost]
[Route("LikeProfile")]
[ResponseType(typeof(List<Like>))]
public async Task<IHttpActionResult> LikeProfile()
{
}
}
}

You can use this approach :
public class VTRoutingController : ApiController
{
[HttpPost("Route")]
public MyResult Route(MyRequestTemplate routingRequestTemplate)
{
return null;
}
[HttpPost("TSPRoute")]
public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
{
return null;
}
}

I think the question has already been answered. I was also looking for something a webApi controller that has same signatured mehtods but different names. I was trying to implement the Calculator as WebApi. Calculator has 4 methods with the same signature but different names.
public class CalculatorController : ApiController
{
[HttpGet]
[ActionName("Add")]
public string Add(int num1 = 1, int num2 = 1, int timeDelay = 1)
{
Thread.Sleep(1000 * timeDelay);
return string.Format("Add = {0}", num1 + num2);
}
[HttpGet]
[ActionName("Sub")]
public string Sub(int num1 = 1, int num2 = 1, int timeDelay = 1)
{
Thread.Sleep(1000 * timeDelay);
return string.Format("Subtract result = {0}", num1 - num2);
}
[HttpGet]
[ActionName("Mul")]
public string Mul(int num1 = 1, int num2 = 1, int timeDelay = 1)
{
Thread.Sleep(1000 * timeDelay);
return string.Format("Multiplication result = {0}", num1 * num2);
}
[HttpGet]
[ActionName("Div")]
public string Div(int num1 = 1, int num2 = 1, int timeDelay = 1)
{
Thread.Sleep(1000 * timeDelay);
return string.Format("Division result = {0}", num1 / num2);
}
}
and in the WebApiConfig file you already have
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional });
Just set the authentication / authorisation on IIS and you are done!
Hope this helps!

Best and simplest explanation I have seen on this topic -
http://www.binaryintellect.net/articles/9db02aa1-c193-421e-94d0-926e440ed297.aspx
Edited -
I got it working with only Route, and did not need RoutePrefix.
For example, in the controller
[HttpPost]
[Route("[action]")]
public IActionResult PostCustomer
([FromBody]CustomerOrder obj)
{
}
and
[HttpPost]
[Route("[action]")]
public IActionResult PostCustomerAndOrder
([FromBody]CustomerOrder obj)
{
}
Then, the function name goes in jquery as either -
options.url = "/api/customer/PostCustomer";
or
options.url = "/api/customer/PostCustomerAndOrder";

I am using .Net6. please find the following code. I have achieve like the following.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace ProjectName.Controllers
{
[Route("[controller]")]
[ApiController]
public class WizardAPIController : ControllerBase
{
[HttpGet("Methord1")]
public async Task<IActionResult> Methord1()
{
return Ok("all good");
}
[HttpGet("Methord2")]
public async Task<IActionResult> Methord2()
{
return Ok("all good");
}
}
}

public class Journal : ApiController
{
public MyResult Get(journal id)
{
return null;
}
}
public class Journal : ApiController
{
public MyResult Get(journal id, publication id)
{
return null;
}
}
I am not sure whether overloading get/post method violates the concept of restfull api,but it workds. If anyone could've enlighten on this matter. What if I have a uri as
uri:/api/journal/journalid
uri:/api/journal/journalid/publicationid
so as you might seen my journal sort of aggregateroot, though i can define another controller for publication solely and pass id number of publication in my url however this gives much more sense. since my publication would not exist without journal itself.

Related

Can Derivatives of a Generic Controller define routes

I have defined a generic controller and have a couple derived classes that are working.
This is fabulous.
I now run into the case where I want to add a route to one of the concrete derivations.
It does not seem to want to pick up the new routes. Error messages tell me that it is looking at the right class.
I have defined the generic and a derived class here.
webapi controller calling wrong method
I am about to throw in the towel on this exercise.
Is is possible for the derived classes to add routes? Does anyone know of examples (i know, i cant ask for examples...)
Thanks
paratial listing of the generic
GetItemById() works if I uncomment it
[RoutePrefix("api/{contoller}")]
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class baseController<T, T_Q, M> : ApiController
where T : pgBaseClass, new()
where T_Q : sbQuery<T>, new()
where M : new()
{
//[System.Web.Http.HttpGet]
//[Route("{id}")]
//public CustomJsonStringResult GetItemById([FromUri] string id)
//{
// T_Q q = new T_Q();
// T p = q.GetById(Convert.ToInt32(id));
// if (p == null)
// {
// return JSONStringResultExtension.JSONString(this, "Item not Found", HttpStatusCode.NotFound);
// }
// else
// {
// return JSONStringResultExtension.JSONString(this, p.JSON, HttpStatusCode.OK);
// }
//}
[System.Web.Http.HttpGet]
[Route("")]
public CustomJsonStringResult GetAllItems()
{
T_Q q = new T_Q();
List<T> l = q.Items();
string json = q.ListToJSON(l);
return JSONStringResultExtension.JSONString(this, json, HttpStatusCode.OK);
}
}
partial listing of one concrete class
public class ProjectsController : baseController<pgProject,pgProjectQuery, Models.mpgProject>
{
[System.Web.Http.HttpGet]
[Route("{id}/cfs")]
public CustomJsonStringResult GetCashFlows([FromUri] string id)
{
pgCashFlowQuery q = new pgCashFlowQuery();
pgCashFlow p = q.GetById( Convert.ToInt32(id ));
if (p == null)
{
return JSONStringResultExtension.JSONString(this, "Item not Found", HttpStatusCode.NotFound);
}
else
{
List<pgCashFlowString> l = p.CashFlowStrings;
pgCashFlowStringQuery q2 = new pgCashFlowStringQuery();
string json = q2.ListToJSON(l);
return JSONStringResultExtension.JSONString(this, p.JSON, HttpStatusCode.OK);
}
}
}
full listing of webApiCOnfig.cs w/ 1 route hard coded
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.EnableCors();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "Collection",
routeTemplate: "api/{controller}/{id}/cfs",
defaults: new { id = RouteParameter.Optional }
);
}
}
When I call the api w/
http://localhost:4200/api/projects/3
I will get the json i expect (except i have it commented out here..)
same for http://localhost:4200/api/projects
I get the list.
now when I call http://localhost:4200/api/projects/3/cfs
the GetAllItems() method is triggered.
This is exactly the example posted by MS at
https://learn.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/create-a-rest-api-with-attribute-routing
where they have
[Route("{id:int}/details")]
[ResponseType(typeof(BookDetailDto))]
public async Task<IHttpActionResult> GetBookDetail(int id)
describing the method that will return details for a book.
Things I have tried
Changing {id:int} to {id} and changing the method parameter to string. This seems to be more of a complicating factor than i want it to be. I am likely conflating this with something else I have yet to understand.
commenting out the hardcoded route. WHen I do this , and during other experiments I get "ANCM In-Process Handler Load Failure". The peculiar thing here is that all articles i read about this talk about .NetCore. This projechappens to be .Net Framework 4.7.2..
The WEB tab on PROPERTIES shows I am launching IIS Express using default bitness.
I really do not see anything that would bring .NetCore into this equation

webapi controller calling wrong method

I have implemented a generic controller using c# and webApi 2.0
http://localhost:4200/api/projects
will correctly call GetAllItems() and return the expected results
http://localhost:4200/api/projects/1
does not call GetItemById( )
Instead, it calls GetAllItems( )
prior to building the generic, i built a concrete controller for projects.
Its a cut/paste and it calls the correct methods.
My thinking is that my route is wrong on the generic, or should be different because it is a generic, but I can not seem to come up w/ the correct syntax.
Why is the generic not calling the correct method when the url includes a trailing integer?
Things i tried w.o success
Reordering the methods
Enhancing the verb to be System.Web.Http.HttpGet
Combining GET and ROUTE into 1 tag, comma separated
Specifying [FromUri] on the itemId parameter in the function signature
Commenting out the GetAllItems() --> The requested resource does not support http method 'GET' (This has to be the big clue, but for the life of me...)
Here is an abbreviated listing of the generic template
[RoutePrefix("api/{contoller}")]
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class baseController<T, T_Q > : ApiController
where T:pgBaseClass, new()
where T_Q : sbQuery<T> , new()
{
[HttpGet]
[Route("")]
public CustomJsonStringResult GetAllItems()
{
T_Q q = new T_Q();
List<T> l = q.Items();
string json = q.ListToJSON(l);
return JSONStringResultExtension.JSONString(this, json, HttpStatusCode.OK);
}
[HttpGet]
[Route("{itemId:int}")]
public IHttpActionResult GetItemById(int itemId)
{
T_Q q = new T_Q();
T p = q.GetById(itemId);
if (p == null)
{
return JSONStringResultExtension.JSONString(this, "Item not Found", HttpStatusCode.NotFound);
}
else
{
return JSONStringResultExtension.JSONString(this, p.JSON, HttpStatusCode.OK);
}
}
}
Here is the definition for the projects controller using the generic
public class ProjectsController : baseController<pgProject,pgProjectQuery>
{
}
Here is an abbreviated listing of the non generic controller that works as expected.
(I am excluding one or the other to get the project to compile and run...)
[RoutePrefix("api/projects")]
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class ProjectController : ApiController
{
//[HttpGet]
[Route("")]
public CustomJsonStringResult GetAllItems()
{
pgProjectQuery ag = new pgProjectQuery();
ag.SortExpression = " [Name] asc ";
List<pgProject> l = ag.Items();
string json = ag.ListToJSON(l);
return JSONStringResultExtension.JSONString(this, json, HttpStatusCode.OK);
}
[HttpGet]
[Route("{itemId:int}")]
public IHttpActionResult GetItemById(int itemId)
{
pgProjectQuery q = new pgProjectQuery();
pgProject p = q.GetById(itemId);
if (p == null)
{
return JSONStringResultExtension.JSONString(this, "Item not Found", HttpStatusCode.NotFound);
}
else
{
return JSONStringResultExtension.JSONString(this, p.JSON, HttpStatusCode.OK);
}
}
}
I had this configured in webApiConfig.js
here i have the sole route defined w/ an optional parameter called id.
I changed the parameter name in my GetItemById() method and this works.
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.EnableCors();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
[System.Web.Http.HttpGet]
[Route("{id:int}")]
public CustomJsonStringResult GetItemById([FromUri] int id){...}
What I can not explain is why the non generic controller worked as expected but the generic-based controller does not.
I do believe things are probably 'more correct' now, just wish I had a factual answer.
I also ran into the same problem with my patch method. Renamed the parameter to id and it worked.
Hope this helps someone down the line.

Azure API App multiple actions with same verb and different routes gives swagger error

I have a ApiController with those GETs:
public class UsersController : ApiController
{
public IHttpActionResult GetUsers()
{
[...]
}
public IHttpActionResult GetUsers(guid ID)
{
[...]
}
[Route("api/Users/{CodeA}/{CodeB}")]
public IHttpActionResult GetUsers(string CodeA, string CodeB)
{
[...]
}
}
The routing in webapiconfig.cs is the standard one:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/",
defaults: new { id = RouteParameter.Optional}
);
Trying to use swaggerUI I get a 500 error, and by fiddler I get:
Not supported by Swagger 2.0: Multiple operations with path 'api/utenti' and method 'GET'. See the config setting - \"ResolveConflictingActions\" for a potential workaround"
If I remove the last GET method swagger parses the api correctly. I've read from many sources that the problem is solved by specifying a different route, and I've tried to achieve this by adding the Route attribute to the last action.
Can someone please point me out in the right direction?
Thank you in advance.
The following code worked for me:
[Route("users")]
public class UsersController : ApiController
{
[Route("")]
public IHttpActionResult GetUsers()
{
string test = "";
return Ok(test);
}
[Route("{id}")]
public IHttpActionResult GetUsers(Guid id)
{
string test = "";
return Ok(test);
}
[Route("{CodeA}/{CodeB}")]
public IHttpActionResult GetUsers(string CodeA, string CodeB)
{
string test = "";
return Ok(test);
}
}

How can i have multiple GET methods in single Controller

namespace EmployeeApi.Controllers
{
public class EmployeeDetailsController : ApiController
{
// GET api/employeedetails
public IEnumerable<Employee> Get()
{
}
public IEnumerable<Details> Get(int id)
{
}
public IEnumerable<Team> GetTeamMember()
{
}
public IEnumerable<Details> GetTid(int id)
{
}
}
I would like to have my webApi something like this:
1) IEnumerable<Employee> Get() -> api/employeedetails
2) IEnumerable<Details> Get(int id) -> api/employeedetails/id
3) IEnumerable<Team> GetTeamMember() -> api/employeedetails/id/teammember
4) IEnumerable<Details> GetTid(int id) -> api/employeedetails/id/teammember/tid
I tried making changes to routing, but as I am new to it, could'nt understand much.So, please can some one help me understand and guide me on how this should be done.
Thanks in advance..:)
You could do this with Attribute Routing.
I prefere to use them as they give an easy overview on how the routing is configured when reading the controllers method.
namespace EmployeeApi.Controllers
{
public class EmployeeDetailsController : ApiController
{
// GET api/employeedetails
[Route("api/employeedetails")]
[HttpGet]
public IEnumerable<Employee> Get()
{
}
// GET api/employeedetails/1
[Route("api/employeedetails/{id}")]
[HttpGet]
public IEnumerable<Details> Get(int id)
{
}
// GET api/employeedetails/id/teammember
[Route("api/employeedetails/id/teammember")]
[HttpGet]
public IEnumerable<Team> GetTeamMember()
{
}
// GET api/employeedetails/id/teammember/1
[Route("api/employeedetails/id/teammember/{tid}")]
[HttpGet]
public IEnumerable<Details> GetTid(int tid)
{
}
}
You can also use RoutePrefix on top of the controller that specifies the prefix for the controller route, in your case the "api/employeedetails". You can find more details in the "Route Prefixes" section in the link
After the list of relevant comments has grown, I'll restructure my original answer now.
If you're not able to use attribute routing as suggested in Marcus' answer (see my update statement at the bottom), you need to configure your routes (probably in the App_Start/RouteConfig.cs file). You can try the following code there:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
name: "GetEmployeeDetails",
url: "api/employeedetails",
defaults: new { controller = "EmployeeDetails", action = "GetEmployees" }
);
routes.MapRoute(
name: "GetEmployeeDetailsById",
url: "api/employeedetails/{employeeId}",
defaults: new { controller = "EmployeeDetails", action = "GetDetails", employeeId = UrlParameter.Optional }
);
routes.MapRoute(
name: "GetTeamMember",
url: "api/employeedetails/{employeeId}/teammember",
defaults: new { controller = "EmployeeDetails", action = "GetTeams", employeeId = UrlParameter.Optional }
);
routes.MapRoute(
name: "GetTeamMemberById",
url: "api/employeedetails/{employeeId}/teammember/{teamId}",
defaults: new { controller = "EmployeeDetails", action = "GetDetailsForTeam", employeeId = UrlParameter.Optional, teamId = UrlParameter.Optional }
);
}
}
There'll probably be more routes (for example a generic default route) and also routes to be ignored, but this is out if scope for this question.
These routes correspond with the following action methods within your controller class:
public class EmployeeDetailsController : Controller
{
public IEnumerable<Employee> GetEmployees()
{
// Get your list of employees here
return ...;
}
public IEnumerable<Detail> GetDetails(int employeeId = 0)
{
// Get your list of details here
return ...;
}
public IEnumerable<Team> GetTeams(int employeeId = 0)
{
// Get your list of teams here
return ...;
}
public IEnumerable<Detail> GetDetailsForTeam(int employeeId = 0, int teamId = 0)
{
// Get your list of details here
return ...;
}
}
There is a chance that you do not need the employeeId parameter for the GetDetailsForTeam() method, since maybe the teamId is sufficient to get the desired information. If that is the case you can remove the parameter from the action method and the corresponding route.
These route configurations are pretty straightforward. Each route needs a unique name, otherwise you'll end up with a runtime error. The url - well - contains the url that route is supposed to handle. And after that you can specify the controller name, the action method to be called (these are your Get methods) and their respective parameters.
A word or two regarding naming conventions: In a controller named EmployeeDetailsController I would expect every "generically named" action method to return one or many EmployeeDetails objects (or their respective ActionResults). Therefore, a simple Get() method should return one or many EmployeeDetails objects.
In case you want to return objects of different types I would choose specific names (as suggested in my code above). In your case that would be a GetEmployees() method, a GetDetails(int employeeId = 0) method, a GetTeams(int employeeId = 0) method and a GetDetailsForTeam(int employeeId = 0, int teamId = 0) method. Note the optional parameters here.
If you have these methods in place I'd start with the routing. You need to make sure that each route can be connected to exactly one action method; that's why I asked for the complete URL in one of the comments. If you keep getting the "multiple actions were found" error, you're route URLs are not configured in such a way.
Also please note that route order does matter, though in your example I don't see any conflicting routes.
UPDATE: As an alternative you could use attribute routing, where you put the desired route directly into an attribute of your action method inside the controller. But for this to work with ASP.NET MVC 4 you'd need to install the AttributeRouting NuGet package.

Configuring Routes asp.net webapi

I am building an angularJS application with a asp.net webapi backend. In my routeconfig file, i have this
routes.MapRoute(
name: "default",
url: "{*url}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
This works fine. Any Url that is called is returned the Home/Index view (the only view i have) to the application, and angularJS works out if there is a querystring and works out which state to show.
I have the basic Get, Put, Post and Delete methods in my WebApi, and i can call them fine. Examples are
public class CompanyController : ApiController
{
private CompanyService _service;
public CompanyController(CompanyService service)
{
_service = service;
}
public async Task<IHttpActionResult> Get()
{
...
return Ok(model);
}
public async Task<IHttpActionResult> Get(int id)
{
...
return Ok(model);
}
public async Task<IHttpActionResult> Post(CompanyModel model)
{
...
return Ok();
}
public async Task<IHttpActionResult> Put(Company model)
{
...
return Ok();
}
public async Task<IHttpActionResult> Delete(CompanyModel model)
{
...
return Ok();
}
}
Now i would like to add another method to my api, where the user can load companies, but also pass in a term to search for (a string), a pageSize (int) and a page number (int). Something like this
public async Task<IHttpActionResult> Get(string term, int page, int pageSize) {
...
return Ok(results);
}
Now i understand that i need to add another route, to make sure this method can be called. Fine, so i add this to my RouteConfig.
// search
routes.MapRoute(
name: "search",
url: "api/{controller}/{page}/{pageSize}/{term}",
defaults: new { page = #"\d+", pageSize = #"\d+", term = UrlParameter.Optional }
);
Why doesnt this work?? I got a resource cannot be found error, when trying to call it via postman using the url localhost/api/company/1/10/a, where 1 = page, 10 = pageSize and a = term
Its probably a simple answer, but new to MVC so still learning.
1- You are using Get method, which means you can pass your search option via Url, so you can create a search option object like :
public class SearchOptions
{
public string Term{get; set;}
public int Page {get; set;}
public int PageSize {get; set;}
}
then you can change your method to be like this
[HttpGet]
[Route("api/blabla/SearchSomething")]
public async Task<IHttpActionResult> Get([FromUri]SearchOptions searchOptions) {
...
return Ok(results);
}
Notice the Route attribute that I've decorated the method by, you can use different constraints for the method parameters, have a look at this.
Finally you can call the method from the client like this
api/blabla/SearchSomething?term=somevalue&page=1&pageSize=10
Hope that helps.

Categories