22

I am creating a website using ASP.NET Core MVC. When I click on an action I get this error:

AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied:

Web.Controllers.ChangeEventsController.Create (Web)
Web.Controllers.ProductsController.CreateChangeEvent (Web)

This is how I defined my action in the index.cshtmlm for my ProductsController:

<a asp-controller="ChangeEvents" asp-action="Create" asp-route-id="@item.Id">Create Change Event</a>

Here is my routing:

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });

Here is how I defined the actions:

// ChangeEventsController
[HttpGet("{id}")]
public IActionResult Create(Guid id)

// ProductsController
[HttpGet("{id}")]
public IActionResult CreateChangeEvent(Guid id)

What have I done wrong?

Update

Thanks @MegaTron for your response, however I would like to know why I can't have the same action path for different controllers. I feel like the solution you proposed won't scale well if I have many controllers that each create entities.

abatishchev
  • 92,232
  • 78
  • 284
  • 421
Zeus82
  • 4,761
  • 6
  • 42
  • 71

5 Answers5

31

Try:

// ChangeEventsController
[HttpGet("Create/{id}")]
public IActionResult Create(Guid id)

// ProductsController
[HttpGet("CreateChangeEvent/{id}")]
public IActionResult CreateChangeEvent(Guid id)
Roman Marusyk
  • 19,402
  • 24
  • 55
  • 90
  • 7
    Why is it that different controller can't have the same action name? – Zeus82 Nov 29 '16 at 21:35
  • 6
    Don't follow this approach! It works, but it results in bad REST API design and is hard to maintain for lots of controllers and actions. Instead of annotating all your actions with uniquely prefixed routes, annotate your controller class with a unique route to distinguish the actions held by each controller. – B12Toaster Mar 19 '18 at 21:16
  • As @B12Toaster mentioned, this answer doesn't help in explaining why different controllers can't have the same action name and it doesn't follow RESTful design. – AzzamAziz Sep 23 '19 at 16:57
15

While the most up-voted answer does solve the issue, as mentioned by @B12Toaster it would violate the rules of REST. With my answer I will try to solve the problem while remaining RESTful.


TLDR: Add the Name property to your HTTP verb attribute (GET or otherwise)

In order to get both GET to work in both controllers do this:

// ChangeEventsController
[HttpGet(Name = "Get an event")]
[Route("{id}")]
public IActionResult Create(Guid id)

// ProductsController
[HttpGet(Name = "Get a product")]
[Route("{id}")]
public IActionResult CreateChangeEvent(Guid id)

This answer explains why you can't have two paths with the same name on two different controllers in Web API. You can implement the solution discussed in the answer to avoid this problem, or you can use ServiceStack which I personally would recommend.


Long answer: Explaining how to be RESTful within Web API

First: let's focus on the controller names. The controller names should be plural and nouns only. That would result in these two controllers:

  • Events: Instead of ChangeEvents. The change can happen within a PUT, not as controller name.
  • Products

Explanation on RESTful naming standards


Second: The endpoints within a controller should be named as CRUD operations in respect to RESTful standards.

  • POST
  • GET
  • PUT
  • DELETE
  • PATCH: Optional

This is instead of Create and CreateChangeEvent. This helps you locate which verbs you're invoking. There is no need for custom naming for the operations, as there shouldn't be too many in the first place to begin with in each controller.


Third: Your routes should not have custom names for each. Again, sticking to our method names, they should be CRUD operations only.

In this case:

// EventsController
[HttpGet(Name = "Get an event")]
[Route("events/{id}")]
public IActionResult Get(Guid id)

// ProductsController
[HttpGet(Name = "Get a product")]
[Route("products/{id}")]
public IActionResult Get(Guid id)

This would result in:

  • GET for /events/{id}
  • GET for /products/{id}

Last: For GET HTTP calls, you should send your input via query rather than body. Only PUT/POST/PATCH should send a representation via body. This is part of the Roy Fieldings constraints in REST. If you want to know further, look here and here.

You can do this by adding the [FromQuery] attribute before each of the parameters.

// EventsController
[HttpGet(Name = "Get an event")]
[Route("events/{id}")]
public IActionResult Get([FromQuery] Guid id)

// ProductsController
[HttpGet(Name = "Get a product")]
[Route("products/{id}")]
public IActionResult Get([FromQuery] Guid id)

I hope this would be helpful to future readers.

AzzamAziz
  • 1,744
  • 1
  • 21
  • 31
2

If you want to use default routing , follow blew instrument:

  1. Remove [Route("[controller]")] from top of 'ChangeEvents' controller (if exists).
  2. Remove routing pattern from HttpGet

summery, try this :

// ChangeEventsController
[HttpGet]
public IActionResult Create(Guid id)

// ProductsController
[HttpGet]
public IActionResult CreateChangeEvent(Guid id)
D.L.MAN
  • 690
  • 9
  • 14
0

Use route to avoid ambiguous methdos in ASP.NET. Change your code to this

// ChangeEventsController
[HttpGet("{id}")]
public IActionResult Create(Guid id)

// ProductsController
[HttpGet]
[Route("[action]/{id}")]
public IActionResult CreateChangeEvent(Guid id)
Rohan Shenoy
  • 607
  • 6
  • 17
-1

Add the [Route("api/[controller]")] attribute above each of your controllers to have the action routes under different paths, then you can use the same [HttpGet("{id}")] in each controller. This should scale pretty well. See this example in the microsoft docs.

If you don't use the [Route] annotation to specify a route for each controller, your ASP.NET Core MVC cannot know out of the box which action to chose to handle the request.

B12Toaster
  • 8,944
  • 5
  • 48
  • 48