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 за ваш ответ, однако я хотел бы знать, почему у меня не может быть одного и того же пути действия для разных контроллеров. Я чувствую, что предложенное вами решение не будет хорошо масштабироваться, если у меня будет много контроллеров, каждый из которых создает объекты.

3 ответа

Пытаться:

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

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

Хотя ответ, получивший наибольшее количество голосов, действительно решает проблему, как упоминается @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.

  • СООБЩЕНИЕ
  • ПОЛУЧИТЬ
  • ПОЛОЖИЛ
  • УДАЛЯТЬ
  • Патч: опционально

Это вместо 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)

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

  • ПОЛУЧИТЬ для /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)

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

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

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

лето, попробуйте это:

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

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

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

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

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

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

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

Другие вопросы по тегам