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

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

我正在使用ASP.NET Core MVC创建一个网站。 当我点击某个动作时,我收到此错误:

1
2
3
4
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)

这就是我在我的ProductsController的index.cshtmlm中定义我的动作的方法:

1
Create Change Event

这是我的路线:

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

以下是我定义操作的方式:

1
2
3
4
5
6
7
// ChangeEventsController
[HttpGet("{id}")]
public IActionResult Create(Guid id)

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

我做错了什么?

更新

感谢@MegaTron的回复,但是我想知道为什么我不能为不同的控制器提供相同的操作路径。 如果我有许多控制器,每个创建实体,我觉得你提出的解决方案不会很好地扩展。


尝试:

1
2
3
4
5
6
7
// 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,请执行以下操作:

1
2
3
4
5
6
7
8
9
// 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。

答案很长:解释如何在Web API中实现RESTful

首先:让我们关注控制器名称。控制器名称应为复数和名词。这将导致这两个控制器:

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

RESTful命名标准的说明

第二:控制器中的端点应该被命名为与RESTful标准相关的CRUD操作。

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

这不是Create和CreateChangeEvent。这有助于您找到要调用的动词。不需要为操作定制命名,因为在每个控制器中首先应该没有太多的开始。

第三:您的路线不应该为每个路线都有自定义名称。再次,坚持我们的方法名称,它们应该只是CRUD操作。

在这种情况下:

1
2
3
4
5
6
7
8
9
// 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应该通过正文发送一个表示。这是Roy Fieldings在REST中的限制的一部分。如果你想进一步了解,请看这里和这里。

您可以通过在每个参数之前添加[FromQuery]属性来完成此操作。

1
2
3
4
5
6
7
8
9
// 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)

我希望这对未来的读者有所帮助。


使用路由来避免ASP.NET中含糊不清的方法。将您的代码更改为此

1
2
3
4
5
6
7
8
// ChangeEventsController
[HttpGet("{id}")]
public IActionResult Create(Guid id)

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

如果要使用默认路由,请按照吹动仪器进行操作:

  • 从"ChangeEvents"控制器的顶部删除[Route("[controller]")](如果存在)。
  • HttpGet中删除路由模式
  • 夏天,试试这个:

    1
    2
    3
    4
    5
    6
    7
    // ChangeEventsController
    [HttpGet]
    public IActionResult Create(Guid id)

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

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

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