I would like to have 2 methods in my controller that have the same route but differ only in the HTTP method. Specifically, if my route looks like
routes.MapRoute(
name: "DataRoute",
url: "Sample/{user}/{id}/",
defaults: new { controller = "Sample", action = "--not sure--", user = "", id = "" }
);
and I have 2 methods in my controller as such:
[HttpGet]
public void ViewData(string user, string id)
[HttpPost]
public void SetData(string user, string id)
The desired behavior is to call ViewData() if I GET Sample/a/b and call SetData() if I POST to Sample/a/b, the same URL.
I know I can just create 2 separate routes, but for design reasons I want to have one route differentiated only by GET and POST. Is there a way to configure either the route or controller to do this without having to create a new route?
With attribute routing you should be able to set the same route with different methods.
[RoutePrefix("Sample")]
public class SampleController : Controller {
//eg GET Sample/a/b
[HttpGet]
[Route("{user}/{id}")]
public void ViewData(string user, string id) { ... }
//eg POST Sample/a/b
[HttpPost]
[Route("{user}/{id}")]
public void SetData(string user, string id) { ... }
}
Don't forget to enable attribute routing before convention-based routes
routes.MapMvcAttributeRoutes();
You should edit the SetData method to take some payload from the body of the POST.
public void SetData(string user, string id, MyCustomObject data) { ... }
I need to define template based routing to controller and then attribute based for actions in ASP.NET Core. Something like:
public class Foo : Controller
{
[HttpGet]
public object Get()
{
return new
{
ID = "A"
};
}
[HttpPost]
public object Create([FromBody]dynamic entity)
{
return new
{
ID = "B"
};
}
}
Route
app.UseMvc(routes =>
{
routes.MapRoute("Settings", "settings/api/foo",
new { controller = "Foo" }
);
});
And I expect this to work:
GET /settings/api/foo
POST /settings/api/foo
Unfortunately it is not a case. It looks like route attributes are ignored. What is the best way to achieve requirement?
The trick here is to route the URL to a specific controller and action. Then use action method overloading with an action method selector to switch between the GET and POST.
Change your route setup code to this:
app.UseMvc(routes =>
{
routes.MapRoute(
"Settings",
"settings/api/foo",
new {
controller = "Foo", // specific controller
action = "DoThing", // AND specific action
}
);
});
And change the controller to have both action methods (or however many you want - one for each HTTP verb) to have the same name, but using different action methods selectors:
public class FooController : Controller
{
[HttpGet] // different action method selector!
public object DoThing() // same name!
{
return new
{
ID = "A"
};
}
[HttpPost] // different action method selector!
public object DoThing([FromBody]dynamic entity) // same name!
{
return new
{
ID = "B"
};
}
}
This way MVC will route all the requests for that URL to an action called DoThing on controller Foo. Once it gets there it sees "oh my, oh my, there are two actions with the same name!" But then it sees the [HttpGet] and [HttpPost] action method selectors, and whichever one of them says it can handle the request will win.
I have a super simple controller with 2 methods:
public IActionResult Users(long id)
{
return Json(new { name = "Example User" });
}
public IActionResult Users()
{
return Json(new { list = new List<User>() });
}
One to select all users and the other to return all users. In web api 2 I could user the following route and everything worked fine:
config.Routes.MapHttpRoute(
name: "Users",
routeTemplate: "v1/Users",
defaults: new { action = "Users", controller = "Users" },
constraints: null,
handler: new TokenValidationHandler() { InnerHandler = new HttpControllerDispatcher(config) }
);
I have the following routes setup in startup.cs:
app.UseMvc(routes =>
{
routes.MapRoute(name: "User_Default", template: "v1/{controller=Users}/{action=Users}/{id?}");
});
However this gives me a AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied
What am I doing wrong?
In your original webapi code, you were using Routes.MapHttpRoute which adds webapi specific routes. This is different from an MVC route which won´t take into account the parameters in the action, for instance you would have the same problem in MVC 5 if you were using Routes.MapRoute.
The same thing is happening in your MVC 6 code, since you are adding a standard MVC route using routes.MapRoute. In both cases the framework is finding 2 controller actions matching the same route with no additional constraints. It needs some help in order to select one of those 2 actions.
The easiest way to disambiguate the api actions would be using attribute routing instead of defining a route, as in this example:
[Route("v1/[controller]")]
public class UsersController : Controller
{
[HttpGet("{id:int}")]
public IActionResult Users(long id)
{
return Json(new { name = "Example User" });
}
public IActionResult Users()
{
return Json(new { list = new[] { "a", "b" } });
}
}
There are other options that would let you change the behaviour of the MVC routing in MVC 6. You could create your own IActionConstraint attribute to enforce having or not a given parameter. That way one of those actions requires an id parameter in the route while the other requires not to have an id parameter (Warning, untested code):
public class UsersController : Controller
{
[RouteParameterConstraint("id", ShouldAppear=true)]
public IActionResult Users(long id)
{
return Json(new { name = "Example User" });
}
[RouteParameterConstraint("id", ShouldNotAppear=true)]
public IActionResult Users()
{
return Json(new { list = new[] { "a", "b" } });
}
}
public class RouteParameterConstraintAttribute : Attribute, IActionConstraint
{
private routeParameterName;
public RouteParameterConstraintAttribute(string routeParameterName)
{
this.routerParamterName = routerParameterName;
}
public int Order => 0;
public bool ShouldAppear {get; set;}
public bool ShouldNotAppear {get; set;}
public bool Accept(ActionConstraintContext context)
{
if(ShouldAppear) return context.RouteContext.RouteData.Values["country"] != null;
if(ShouldNotAppear) return context.RouteContext.RouteData.Values["country"] == null;
return true;
}
}
A better option to deal with webapi 2 style controllers would be adding conventions into the MVC pipeline. This is exactly what the Microsoft.AspNet.Mvc.WebApiCompatShim is doing to help migrating webapi 2 controllers. You can see the conventions added here. Check this guide for a quick overview of this package.
I have a scenario where I have 2 different controllers that each have multiple Get methods. I have the methods decorated with the ActionName attribute, but the Routing isn't working as I think it should.
ContactController
public ContactModel GetContactByID(string id)
{
...
}
[ActionName("username")]
public ContactModel GetContactByUserName(string text)
{
...
}
PaymentController
public PaymentModel Get(Guid id)
{
...
}
[HttpGet, ActionName("sale")]
public PaymentActivityModel Sale(Guid id)
{
...
}
Routes
config.Routes.MapHttpRoute(
"PaymentControllerActionId",
"api/client/{clientId}/{controller}/{action}/{id}",
defaults: null
);
config.Routes.MapHttpRoute(
"ContactControllerActionText",
"api/client/{clientId}/{controller}/{action}/{text}",
defaults: null
);
config.Routes.MapHttpRoute(
"ClientControllerId",
"api/client/{clientId}/{controller}/{id}",
new { id = RouteParameter.Optional }
);
When I navigate to a Payment, it works fine. But when I navigate to a Contact, I receive:
No HTTP resource was found that matches the request URI, .../api/client/.../contact/username/exampleUserName
No action was found on the controller 'Contact' that matches the request.
I was under the impression that the parameter name would be matched up with the action name (text vs. id).
Is the problem that one uses a Guid id and one uses a string id?
This is a client-facing API, and I have a client-friendly ContactID that is a string. Whereas the PaymentID is a Guid.
First I want to point out your routes don't make a lot of sense.
This route says it is for PaymentControllerActionId, however you left the routing open to use any controller instead of specifying it is only for the Payment Controller.
config.Routes.MapHttpRoute(
"PaymentControllerActionId",
"api/client/{clientId}/{controller}/{action}/{id}",
defaults: null
);
To answer your question. You likely need to specify {text} as an optional RouteParameter. Otherwise the framework usually expects that you have defined a custom routing constraint (and you should have a routing constraint on {clientId} IMO).
Also, if one of your actions expects a GUID. Put it as a Guid argument type, and WebAPI will match it correctly. Alternatively, since both functions are expecting either a name or a guid, you could just have only 1 function that takes a string, and in your db do a select statement matching the GUID or the NAME and return the contact. Much less code, easier to read, makes more sense.
If you are using Web API 2, then you could use attribute routing for this purpose.
In the below example, the controllers use a mix of conventional + attribute routing, but you can change this to only go by attribute routing if you need.
Example:
Route Configuration
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
"ClientControllerId",
"api/client/{clientId}/{controller}/{id}",
new { id = RouteParameter.Optional }
);
ContactController
[RoutePrefix("api/client/{clientId}/Contact")]
public class ContactController
{
// this uses conventional route
public ContactModel GetContactByID(string id)
{
...
}
[Route("username/{userName}")]
public ContactModel GetContactByUserName(string userName)
{
...
}
}
PaymentController
[RoutePrefix("api/client/{clientId}/Payment")]
public class PaymentController
{
// this uses conventional route
public PaymentModel Get(Guid id)
{
...
}
[HttpGet, Route("sale/{id}")]
public PaymentActivityModel Sale(Guid id)
{
...
}
}
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.