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)
Я надеюсь, что это будет полезно для будущих читателей.
Если вы хотите использовать маршрутизацию по умолчанию, следуйте за инструментом:
- Удалить
[Route("[controller]")]
сверху контроллера 'ChangeEvents' (если существует). - Удалить шаблон маршрутизации из
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 не может сразу узнать, какое действие выбрать для обработки запроса.