AmbiguousActionException:多个动作匹配。以下操作匹配路由数据并满足所有约束

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

我正在使用 ASP.NET 核心 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 的回复,但是我想知道为什么我不能为不同的控制器设置相同的操作路径。我觉得如果我有许多控制器,每个控制器都创建实体,那么您提出的解决方案将无法很好地扩展。

尝试:

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

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

在每个控制器上方添加 [Route("api/[controller]")] 属性,使动作路由在不同的路径下,然后您可以在每个控制器中使用相同的 [HttpGet("{id}")]。这应该可以很好地扩展。请参阅 Microsoft 文档中的 this 示例。

如果您不使用 [Route] 注释为每个控制器指定路由,您的 ASP.NET Core MVC 无法开箱即用地知道选择哪个操作来处​​理请求。

虽然投票最多的答案确实解决了问题,但如@B12Toaster 所述,它会违反 REST 规则。通过我的回答,我将尝试解决问题,同时保持 RESTful.


TLDR:将名称 属性 添加到您的 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)

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 我个人会推荐。


长答案:解释如何在 Web API

中成为 RESTful

首先:让我们关注控制器名称。控制器名称应该是复数且只能是名词。这将导致这两个控制器:

  • 事件:而不是 ChangeEvents。更改可以在 PUT 中发生,而不是作为控制器名称。
  • 产品

Explanation on RESTful naming standards


其次:控制器内的端点应根据 RESTful 标准命名为 CRUD 操作。

  • POST
  • 得到
  • 放置
  • 删除
  • 补丁:可选

这不是 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 中 Roy Fieldings 约束的一部分。如果您想进一步了解,请查看here and here.

您可以通过在每个参数之前添加 [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)

希望这对以后的读者有所帮助。

如果要使用默认路由,请按照 blew 工具:

  1. 从 'ChangeEvents' 控制器(如果存在)顶部删除 [Route("[controller]")]
  2. HttpGet
  3. 中删除路由模式

夏天,试试这个:

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