AmbiguousActionException: выполняется несколько действий. Следующие действия соответствовали данным маршрута и удовлетворяли всем ограничениям

Я создаю веб-сайт с использованием ASP.NET Core MVC. Когда я нажимаю на действие, я получаю эту ошибку:

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)

Вот как я определил свое действие в index.cshtmlm для моего ProductsController:

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

Вот моя маршрутизация:

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

Вот как я определил действия:

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

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

Что я сделал неправильно?

Обновление

Спасибо @MegaTron за ваш ответ, однако я хотел бы знать, почему у меня не может быть такого же пути действий для разных контроллеров. Я чувствую, что решение, которое вы предложили, не будет хорошо масштабироваться, если у меня будет много контроллеров, каждый из которых создаст объекты.

Ответ 1

Try:

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

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

Ответ 2

Хотя ответ, получивший наибольшее количество голосов, действительно решает проблему, как упоминалось @B12Toaster, он нарушает правила REST. Своим ответом я постараюсь решить проблему, оставаясь при этом RESTful.


TLDR: добавьте свойство Name в свой атрибут HTTP-глагола (GET или иным образом)

Чтобы оба GET работали на обоих контроллерах, сделайте следующее:

// 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)

Этот ответ объясняет, почему нельзя использовать два пути с одним и тем же именем на двух разных контроллерах в Web API. Вы можете реализовать решение, описанное в ответе, чтобы избежать этой проблемы, или использовать ServiceStack, который я лично рекомендовал бы.


Длинный ответ: объяснение того, как быть RESTful в веб-API

Во-первых, давайте сосредоточимся на именах контроллеров. Имена контроллеров должны быть только во множественном числе и существительными. Это привело бы к этим двум контроллерам:

  • События: вместо ChangeEvents. Изменение может произойти в PUT, а не в качестве имени контроллера.
  • Продукты

Пояснения по стандартам именования RESTful


Второе: конечные точки в контроллере должны называться операциями CRUD в отношении стандартов RESTful.

  • POST
  • GET
  • PUT
  • УДАЛЕНИЕ
  • Патч: необязательно

Это вместо Create и CreateChangeEvent. Это поможет вам определить, какие глаголы вы вызываете. Для операций не нужно настраивать именование, поскольку в каждом контроллере не должно быть слишком много начальных значений.


Третье: у ваших маршрутов не должно быть пользовательских имен для каждого. Опять же, придерживаясь имен наших методов, они должны быть только операциями CRUD.

В этом случае:

// 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)

Это приведет к:

  • GET for/events/{id}
  • ПОЛУЧИТЬ для /products/{id}

Последнее: для GET HTTP-вызовов вы должны отправлять свои данные с помощью запроса, а не тела. Только PUT/POST/PATCH должен отправлять представление через тело. Это часть ограничений Роя Филдинга в REST. Если вы хотите узнать больше, посмотрите здесь и здесь.

Вы можете сделать это, добавив атрибут [FromQuery] перед каждым из параметров.

// 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)

Я надеюсь, что это будет полезно для будущих читателей.

Ответ 3

Добавьте атрибут [Route("api/[controller]")] над каждым из ваших контроллеров, чтобы иметь маршруты действий под разными путями, тогда вы можете использовать один и тот же [HttpGet("{id}")] в каждом контроллере. Это должно масштабироваться довольно хорошо. См. Этот пример в документах Microsoft.

Если вы не используете аннотацию [Route] для указания маршрута для каждого контроллера, ваш ASP.NET Core MVC не сможет узнать из коробки, какое действие выбрать для обработки запроса.

Ответ 4

Если вы хотите использовать маршрутизацию по умолчанию, следуйте за дульным инструментом:

  1. Удалите [Route("[controller]")] сверху контроллера 'ChangeEvents' (если существует).
  2. Удалить шаблон маршрутизации из HttpGet

summery, попробуйте следующее:

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

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

Ответ 5

Используйте маршрут, чтобы избежать неоднозначных метаданных в ASP.NET. Измените свой код на этот

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

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