MVC 6中的路由

4

我有一个非常简单的控制器,其中包含2个方法:

public IActionResult Users(long id)
{
    return Json(new { name = "Example User" });
}

public IActionResult Users()
{
    return Json(new { list = new List<User>() });
}

有两个功能,一个是选择所有用户,另一个是返回所有用户。在Web API 2中,我可以使用以下路由,并且一切正常:

config.Routes.MapHttpRoute(
                name: "Users",
                routeTemplate: "v1/Users",
                defaults: new { action = "Users", controller = "Users" },
                constraints: null,
                handler: new TokenValidationHandler() { InnerHandler = new HttpControllerDispatcher(config) }
            );

我在startup.cs中设置了以下路由:

我在 startup.cs 中设置了以下路由:

app.UseMvc(routes =>
            {
                routes.MapRoute(name: "User_Default", template: "v1/{controller=Users}/{action=Users}/{id?}");
            });

然而这个错误信息给出了 AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied

我做错了什么?


我猜你的控制器上有一个名为Route的属性,它在许多模板中都存在。你能提供整个控制器吗? - Matt DeKrey
@MattDeKrey 我不需要,因为我认为如果我像WebAPI 2路由一样设置我的路由,它们就不会被需要。 - Gaz
1个回答

6
在您原始的webapi代码中,您使用了Routes.MapHttpRoute,它添加了特定于webapi的路由。这与MVC路由不同,因为它不会考虑操作中的参数,例如,如果您使用Routes.MapRoute,则在MVC 5中也会遇到相同的问题。
在您的MVC 6代码中也发生了同样的情况,因为您正在使用routes.MapRoute添加标准的MVC路由。在这两种情况下,框架都会找到匹配同一路由且没有其他约束条件的2个控制器操作。它需要一些帮助才能选择其中一个操作。
消除歧义的最简单方法是使用属性路由而不是定义路由,就像this example中的示例一样。
[Route("v1/[controller]")]
public class UsersController : Controller
{
    [HttpGet("{id:int}")]
    public IActionResult Users(long id)
    {
        return Json(new { name = "Example User" });
    }

    public IActionResult Users()
    {
        return Json(new { list = new[] { "a", "b" } });
    }
}

还有其他选项可以让您在MVC 6中更改MVC路由的行为。您可以创建自己的IActionConstraint属性来强制具有或不具有给定参数。这样,其中一个操作需要在路由中具有id参数,而另一个操作则要求不具有id参数(警告,未经测试的代码):

public class UsersController : Controller
{
    [RouteParameterConstraint("id", ShouldAppear=true)]
    public IActionResult Users(long id)
    {
        return Json(new { name = "Example User" });
    }

    [RouteParameterConstraint("id", ShouldNotAppear=true)]
    public IActionResult Users()
    {
        return Json(new { list = new[] { "a", "b" } });
    }
}

public class RouteParameterConstraintAttribute : Attribute, IActionConstraint
{
    private routeParameterName;

    public RouteParameterConstraintAttribute(string routeParameterName)
    {
        this.routerParamterName = routerParameterName;
    }

    public int Order => 0;
    public bool ShouldAppear {get; set;}
    public bool ShouldNotAppear {get; set;}

    public bool Accept(ActionConstraintContext context)
    {
        if(ShouldAppear) return context.RouteContext.RouteData.Values["country"] != null;
        if(ShouldNotAppear) return context.RouteContext.RouteData.Values["country"] == null;

        return true;
    }
}

更好的处理WebAPI 2风格控制器的选项是将约定添加到MVC管道中。这正是Microsoft.AspNet.Mvc.WebApiCompatShim正在做的,以帮助迁移WebAPI 2控制器。您可以在此处看到添加的约定here。请查看this guide以快速概述此软件包。

我已将此标记为正确答案,但我也想知道为什么在startup.cs文件中指定路由不起作用。 - Gaz
@Gazeth,我已经在我的回答中添加了更多细节。 - Daniel J.G.
链接 https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNet.Mvc.WebApiCompatShim/WebApiCompatShimOptionsSetup.cs 和 https://www.asp.net/vnext/overview/aspnet-vnext/create-a-web-api-with-mvc-6 已失效。 - Michael Freidgeim
已更新。同时采用 IActionConstraint 替代 RouteConstraintAttribute。 - Daniel J.G.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接