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

时间:2016-11-24 23:16:56

标签: c# asp.net-core asp.net-core-mvc asp.net-core-routing

我正在使用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的回复,但是我想知道为什么我不能为不同的控制器提供相同的操作路径。如果我有许多控制器,每个创建实体,我觉得你提出的解决方案不会很好地扩展。

5 个答案:

答案 0 :(得分:28)

尝试:

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

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

答案 1 :(得分:4)

尽管投票最多的答案确实可以解决问题,如@ 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)

This answer解释了为什么在Web API中的两个不同的控制器上不能有两个具有相同名称的路径。您可以实施答案中讨论的解决方案来避免此问题,也可以使用我个人推荐的ServiceStack


长答案:介绍如何在Web API中实现RESTful

首先:让我们集中讨论控制器名称。控制器名称应为复数形式且只能为名词。这将导致这两个控制器:

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

Explanation on RESTful naming standards


第二:就RESTful标准而言,控制器内的端点应命名为CRUD操作。

  • 开机自检
  • 获取
  • 放置
  • 删除
  • PATCH:可选

这代替Create和CreateChangeEvent。这可以帮助您找到要调用的动词。不需要为操作自定义命名,因为在每个控制器中首先应该没有太多。


第三条:您的路由应 not 都具有自定义名称。同样,坚持我们的方法名称,它们应该仅是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约束的一部分。如果您想进一步了解,请查看herehere

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

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

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

如果要使用默认路由,请按照以下方法操作:

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

夏天,试试这个:

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

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

答案 4 :(得分:0)

使用路由来避免ASP.NET中的模棱两可的方法。将您的代码更改为此

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

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