Several actions found Web Api [duplicate] - c#

This question already has answers here:
Routing with multiple Get methods in ASP.NET Web API
(10 answers)
Closed 4 years ago.
I have web api 2.0 project
When i try to implement several method with same parameter, i have this error : Several actions found
namespace WebApi.Controllers
{
public class EventController : ApiController
{
[HttpGet]
public HttpResponseMessage GetTags(string token, int messageId)
{
return ApiCall<List<EntityTag>>.CallApi(token, ServicesMessage.GetTags(messageId));
}
[HttpGet]
public HttpResponseMessage Get(string token, int eventId)
{
return ApiCall<EntityEvent>.CallApi(token, ServicesEvent.Get(eventId));
}
}
}
Any idea?
Thanks

The WebAPI cannot distinquish between these two methods because they have the same parameter types and same HttpVerb, and use implicit routing (no route attribute on them).
I'm a big fan of explicit routing for controllers and methods, instead of depending on naming conventions - so I'd try adding a route attribute to the methods:
public class EventController : ApiController
{
[HttpGet]
[Route("gettags")]
public HttpResponseMessage GetTags(string token, int messageId)
{
return ApiCall<List<EntityTag>>.CallApi(token, ServicesMessage.GetTags(messageId));
}
[HttpGet]
[Route("get")]
public HttpResponseMessage Get(string token, int eventId)
{
return ApiCall<EntityEvent>.CallApi(token, ServicesEvent.Get(eventId));
}
}
... and add a RoutePrefix attribute on the controller itself, like so:
[RoutePrefix("/api/Event")]
public class EventController : ApiController
{
}
Then you should be able to call the methods with a GET request to these URLs:
/api/event/get?token=xxxxx&eventId=xxxx
and
/api/event/gettags?token=xxxxx&messageId=xxxx

Write this line in your webapiconfig.cs
config.Routes.MapHttpRoute("DefaultApiPost", "Api/{controller}", new { action = "Get" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
your Controller:
namespace WebApi.Controllers
{
public class EventController : ApiController
{
[HttpGet]
public HttpResponseMessage Tags(string token, int messageId)
{
return ApiCall<List<EntityTag>>.CallApi(token, ServicesMessage.GetTags(messageId));
}
[HttpGet]
public HttpResponseMessage Events(string token, int eventId)
{
return ApiCall<EntityEvent>.CallApi(token, ServicesEvent.Get(eventId));
}
}
}
Using this you can give the action names for every get request.
POST
when you want same things with post method just write below line in weapiconfig.cs
config.Routes.MapHttpRoute("DefaultApiPost", "Api/{controller}", new { action = "Post" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Post) });
And after that you can give a action name. no need to routing after using this.
When you want to call api than write follows:
api/Event/Tags //parameter as per your requirement
api/Event/Events //parameter as per your requirement

Related

How to call a GET method which accepts same parameter type in web API

I have a Model called Issues which have Id and severity attributes which are integers and an Issue Controller which should handle GET requests and return All Issues and by Severity. Since Dotnet API doesn't support the same parameter type such as int, how do I do this?
I tried using different method names such as Get() and GetBySeverity(int severity) but ambiguous exception arose.
this is my controller
[Route("api/[controller]")]
[ApiController]
public class IssuesController : ControllerBase
{
[HttpGet(Name="issues")]
public IEnumerable<Issue> GetAllIssues() {
IssueService service = new IssueService();
return service.GetIssues();
}
[HttpGet]
public IEnumerable<Issue> GetBySeverity([FromQuery(Name="severity")]int severity)
{
IssueService service = new IssueService();
return service.GetIssuesBySeverity(severity);
}
}
these are the apis which i want to develop
/api/issues/ and
/api/issues/?severity=1
like this
//GET api/issues/5
[HttpGet("{id}")]
public Issue GetByRoute([FromRoute(Name="id")] int id)
{
IssueService service = new IssueService();
return service.GetIssueById(id);
}
[HttpGet()]
public Issue GetByQuery([FromQuery(Name="severity")] int severityId)
{
IssueService service = new IssueService();
return service.GetIssueById(severityId);
}
There is an ambiguous reference because both actions are HttpGet and the routing engine cannot decide which route to take or which route you meant.
Web Api controllers route using HttpVerbs rather than the action/method name (like MVC).
I would use Attribute routing with Http[Verb] attributes. This uses Route("myroute") to define the route.
// GET api/issues/severity/5
[Route("severity/{severity}")]
[HttpGet()]
public IEnumerable<Issue> GetBySeverity(int severity)
{
IssueService service = new IssueService();
return service.GetIssuesBySeverity(severity);
}
// GET api/issues/5
[HttpGet("{id}")]
public Issue Get(int id)
{
IssueService service = new IssueService();
return service.GetIssueById(id);
}
The above methods would route to api/issues/1 and api/issues/severity/1. If you want to map using query param rather than url param then the below should work:
// GET api/issues/severity?severity=5
[HttpGet("severity")]
public IEnumerable<Issue> GetBySeverity([FromQuery] int severity)
{
IssueService service = new IssueService();
return service.GetIssuesBySeverity(severity);
}
// GET api/issues?id=5
[HttpGet()]
public Issue Get([FromQuery] int id)
{
IssueService service = new IssueService();
return service.GetIssueById(id);
}
The above actions will route to api/issues?id=1 and api/issues/severity?severity=1

Getting 404 when calling a post method on Web API

I have an API controller which have standard GET,POST and Delete actions.
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
//Get
[HttpPost]
public async Task Post([FromBody] TestUser testUser, string tempPassword, role = "Guest")
{
}
}
Now I am adding a new action using:
[HttpPost]
[Route("api/[controller]/UpdateRole")]
public async Task Post(string email, List<string> roles)
{
}
When I am trying to call the API using postman ,
Type : POST
Endpoint : http://localhost/api/users/UpdateRole
Request body:
{
"email":"something#mail.com",
"roles":["S1","s3"]
}
But I am getting a 404 as response back. On server I can see ,
the application completed without reading the entire request body.
It seems that your overall route is /api/Users/api/Users/UpdateRoute because of how RouteAttribute works.
[Route("a")]
public class MyController
{
[Route("a/b")]
public IActionResult MyAction()
{
}
}
The above will have a route of /a/a/b because the action route is appended to the controller route in this case.
Your options are:
Change the controller route to [Route("[controller]/[action]")] and remove the action route, in which case the example above would become /MyController/MyAction
Change the action route to simply [Route("b")], in which case the full route would be a/b
Use an absolute path for the action route [Route("/a/b")], in which case the controller route would be ignored and the full route will simply be /a/b.
See here for more information about routing.
As for your issue with null values, ASP.NET Core is currently expecting email and roles as querystring parameters. Instead, you should create a model for your request body:
public class MyModel
{
public string Email { get; set; }
public List<string> Roles { get; set; }
}
And then change your action to accept it:
[HttpPost]
[Route("api/[controller]/UpdateRole")]
public async Task Post([FromBody]MyModel model)
{
}

ASP.Net WebApi change this default request method mapping with action name

I am Creating a new Web API Controller named Customer.
and this Controller has one Action named "Create"
I couldn't make this Action able to be requested via "GET" HTTP Request
in this form
http://ip:port/api/Customer/Create?userId=x&password=y
except in this method :
public class CustomerController : ApiController
{
[System.Web.Mvc.AcceptVerbs("GET", "POST")]
[System.Web.Mvc.HttpGet]
[ActionName("Create")]
public MISApiResult<List<Branch>> GetCreate(string userID, string password)
{
return new MISApiResult<List<Branch>>() { Result = new List<Branch>() { new Branch() { ID = Guid.NewGuid(), Name = "Branch1" } }, ResultCode = 1, ResultMessage = "Sucess" };
}
}
Is there any other solution to preserve the action name as "Create" as next.
public class CustomerController : ApiController
{
[System.Web.Mvc.AcceptVerbs("GET", "POST")]
[System.Web.Mvc.HttpGet]
public MISApiResult<List<Branch>> Create(string userID, string password)
{
return new MISApiResult<List<Branch>>() { Result = new List<Branch>() { new Branch() { ID = Guid.NewGuid(), Name = "Branch1" } }, ResultCode = 1, ResultMessage = "Sucess" };
}
}
Thanks.
Edit:
Sorry for not being clear at the first time.
According to this answer:
How does a method in MVC WebApi map to an http verb?
There is a default http method according to the action names, if it starts with Get it'll bemapped to GET HTTP Method be default otherwise it will be mapped to POST.
Is there a way to change this default mapping with a custom one so I could map an action named "Create" with "GET" Http Method for testing purpose since this way is faster for development
I tried to put HttpGet Attribute and AcceptVerbs("GET") and it still map the action with POST Http method.
I found a way like I said and it's to change the action method name into GetCreate and then put ActionName attribute with "Create" value.
but is there a way to change the default mapping?
Thanks again.
You can use custom route fore this action:
[HttpGet]
[Route("customer/create")]
public MISApiResult<List<Branch>> Create(string userID, string password)
Don't forget to enable attribute routing during application configuration (this should be added before default route definition):
config.MapHttpAttributeRoutes();
Though I would recommend to follow the conventions and use appropriate HTTP verbs - if you are creating a customer, then by convention you should use POST request to endpoint api/customers. Otherwise your API can be confusing for other people.
Also I would recommend to use IHttpActionResult as the return type of your method:
public IHttpActionResult Post(string userID, string password)
{
if (_userRepository.Exists(userID))
return BadRequest($"User with ID {userID} already exists");
_userRepository.Create(userID, password);
return StatusCode(HttpStatusCode.Created) // or CreatedAtRoute
}
Further reading: Attribute Routing in ASP.NET Web API 2
why dont you specify route. You actual issue is using System.Web.Mvc
use System.Web.Http instead
using System.Web.Http;
[RoutePrefix("api/Customer")]
public class CustomerController : ApiController
{
[HttpGet]
[Route("Create")]
public MISApiResult<List<Branch>> Create(string userID, string password)
{
return new MISApiResult<List<Branch>>() { Result = new List<Branch>() { new Branch() { ID = Guid.NewGuid(), Name = "Branch1" } }, ResultCode = 1, ResultMessage = "Sucess" };
}
}

Multiple controllers with same URL routes but different HTTP methods

I've got a following two controllers:
[RoutePrefix("/some-resources")
class CreationController : ApiController
{
[HttpPost, Route]
public ... CreateResource(CreateData input)
{
// ...
}
}
[RoutePrefix("/some-resources")
class DisplayController : ApiController
{
[HttpGet, Route]
public ... ListAllResources()
{
// ...
}
[HttpGet, Route("{publicKey:guid}"]
public ... ShowSingleResource(Guid publicKey)
{
// ...
}
}
All three actions got in fact three different routes:
GET /some-resources
POST /some-resources
GET /some-resources/aaaaa-bbb-ccc-dddd
If I put them into single controller everything works just fine, however if I separate them (as shown above) WebApi throws following exception:
Multiple controller types were found that match the URL. This can
happen if attribute routes on multiple controllers match the requested
URL.
This message is quite obvious. It seems WebApi does not take HTTP method into account when looking for a right candidate for controller/action.
How could I achieve the expected behavior?
UPDATE: I've digged a little into Web API internals and I understand that's the way it works by default. My goal is to separate the code and logic - in real world case those controllers have different dependencies and are a bit more complex. For the sake of maintenance, testability, project organization etc. they should be different objects (SOLID and stuff).
I thought I could override some WebAPI services (IControllerSelector etc) however this seems to be a little bit risky and non-standard approach for this simple and - as I assumed - common case.
UPDATE
Based on your comments, updated question and the answer provided here
Multiple Controller Types with same Route prefix ASP.NET Web Api
Desired result can be achieved via custom route constraints for the HTTP method applied to controller actions.
On inspection of the default Http{Verb} attributes ie [HttpGet], [HttpPost] and the RouteAttribute, which by the way are sealed, I realized that their functionality can be combine into one class similar to how they are implemented in Asp.Net-Core.
The following is for GET and POST, but it shouldn't be difficult to create constraints for the other HTTP methods PUT, DELETE...etc to be applied to the controllers.
class HttpGetAttribute : MethodConstraintedRouteAttribute {
public HttpGetAttribute(string template) : base(template, HttpMethod.Get) { }
}
class HttpPostAttribute : MethodConstraintedRouteAttribute {
public HttpPostAttribute(string template) : base(template, HttpMethod.Post) { }
}
The important class is the route factory and the constraint itself. The framework already has base classes that take care of most of the route factory work and also a HttpMethodConstraint so it is just a matter of applying the desired routing functionality.
class MethodConstraintedRouteAttribute
: RouteFactoryAttribute, IActionHttpMethodProvider, IHttpRouteInfoProvider {
public MethodConstraintedRouteAttribute(string template, HttpMethod method)
: base(template) {
HttpMethods = new Collection<HttpMethod>(){
method
};
}
public Collection<HttpMethod> HttpMethods { get; private set; }
public override IDictionary<string, object> Constraints {
get {
var constraints = new HttpRouteValueDictionary();
constraints.Add("method", new HttpMethodConstraint(HttpMethods.ToArray()));
return constraints;
}
}
}
So given the following controller with the custom route constraints applied...
[RoutePrefix("api/some-resources")]
public class CreationController : ApiController {
[HttpPost("")]
public IHttpActionResult CreateResource(CreateData input) {
return Ok();
}
}
[RoutePrefix("api/some-resources")]
public class DisplayController : ApiController {
[HttpGet("")]
public IHttpActionResult ListAllResources() {
return Ok();
}
[HttpGet("{publicKey:guid}")]
public IHttpActionResult ShowSingleResource(Guid publicKey) {
return Ok();
}
}
Did an in-memory unit test to confirm functionality and it worked.
[TestClass]
public class WebApiRouteTests {
[TestMethod]
public async Task Multiple_controllers_with_same_URL_routes_but_different_HTTP_methods() {
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
var errorHandler = config.Services.GetExceptionHandler();
var handlerMock = new Mock<IExceptionHandler>();
handlerMock
.Setup(m => m.HandleAsync(It.IsAny<ExceptionHandlerContext>(), It.IsAny<System.Threading.CancellationToken>()))
.Callback<ExceptionHandlerContext, CancellationToken>((context, token) => {
var innerException = context.ExceptionContext.Exception;
Assert.Fail(innerException.Message);
});
config.Services.Replace(typeof(IExceptionHandler), handlerMock.Object);
using (var server = new HttpTestServer(config)) {
string url = "http://localhost/api/some-resources/";
var client = server.CreateClient();
client.BaseAddress = new Uri(url);
using (var response = await client.GetAsync("")) {
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
using (var response = await client.GetAsync("3D6BDC0A-B539-4EBF-83AD-2FF5E958AFC3")) {
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
using (var response = await client.PostAsJsonAsync("", new CreateData())) {
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
}
}
public class CreateData { }
}
ORIGINAL ANSWER
Referencing : Routing and Action Selection in ASP.NET Web API
That's because it uses the routes in the route table to find the controller first and then checks for Http{Verb} to select an action. which is why it works when they are all in the same controller. if it finds the same route to two different controllers it doesn't know when one to select, hence the error.
If the goal is simple code organization then take advantage of partial classes
ResourcesController.cs
[RoutePrefix("/some-resources")]
partial class ResourcesController : ApiController { }
ResourcesController_Creation.cs
partial class ResourcesController {
[HttpPost, Route]
public ... CreateResource(CreateData input) {
// ...
}
}
ResourcesController_Display.cs
partial class ResourcesController {
[HttpGet, Route]
public ... ListAllResources() {
// ...
}
[HttpGet, Route("{publicKey:guid}"]
public ... ShowSingleResource(Guid publicKey) {
// ...
}
}

How can I specify a specific URL in an API controller?

Right now, my API controller has 2 methods: one to get ALL events, and one to get ONE event.
namespace HobbsEventsMobile.Controllers
{
public class EventController : ApiController
{
// GET api/event
[HttpGet]
public List<HobbsEventsMobile.Models.Event> Get()
{
return HobbsEventsMobile.Models.Event.GetEventSummary();
}
// GET api/event/5
[HttpGet]
public HobbsEventsMobile.Models.Event Get(int id)
{
return HobbsEventsMobile.Models.Event.GetEventDetails(id);
}
}
}
Newly requested functionality requires me to add a way to call events for the current week. I have a stored proc and a method to call this, but I am not sure how to specify the URL. I would like to add this:
[HttpGet]
public List<HobbsEventsMobile.Models.Event> Get()
{
return HobbsEventsMobile.Models.Event.GetThisWeeksEvents();
}
but make it accessible at m.mydomain.com/api/event/thisweek (or something). How do I do that?
You have two different options depending on what version of ASP.NET Web API you're running. If you're on version one you can simply follow the convention based routing and use:
public class EventController : ApiController
{
[HttpGet]
public List<HobbsEventsMobile.Models.Event> ThisWeek()
{
return HobbsEventsMobile.Models.Event.GetThisWeeksEvents();
}
}
You will also need to modify your route definitions to support an action name (by default the framework picks the method based upon the HTTP verb):
config.Routes.MapHttpRoute(
"DefaultApiWithId",
"api/{controller}/{id}",
new { id = RouteParameter.Optional }, new { id = #"\d+" }
);
config.Routes.MapHttpRoute(
"DefaultApiWithAction",
"api/{controller}/{action}"
);
config.Routes.MapHttpRoute(
"DefaultApiGet",
"api/{controller}",
new { action = "Get" },
new { httpMethod = new HttpMethodConstraint("GET") }
);
If you're using version two, you can still use the convention based routing, but you also have the ability to use attribute routing:
public class EventController : ApiController
{
[HttpGet]
[Route("event/thisweek")]
public List<HobbsEventsMobile.Models.Event> ICanNameThisWhateverIWant()
{
return HobbsEventsMobile.Models.Event.GetThisWeeksEvents();
}
}

Categories

Resources