为什么这两个操作被认为是模糊的,尽管它们的参数不同?

10

当前对控制器类型“UsersController”的“Index”操作的请求在以下动作方法之间存在歧义: 类型Controllers.UsersController上的Models.SimpleUser的System.Web.Mvc.ActionResult PostUser(Models.SimpleUser) 类型Controllers.UsersController上的Int32、Models.SimpleUser的System.Web.Mvc.ActionResult PostUser(Int32, Models.SimpleUser)

当我尝试使用表单值提交website.com/users/时会出现此情况。

当没有ID(website.com/users/)时,我希望它创建一个新用户,当有一个ID(例如/users/51)时,我希望它更新该用户,那么如何区分这两个动作?

以下是这两个动作:

    [HttpPost]
    [ActionName("Index")]
    public ActionResult PostUser(SimpleUser u)
    {

        return Content("User to be created...");
    }

    [HttpPost]
    [ActionName("Index")]
    public ActionResult PostUser(int id, SimpleUser u)
    {

        return Content("User to be updated...");
    }

这是MapRoute:

    routes.MapRoute(
        "Users",
        "users/{id}",
        new { controller = "Users", action = "Index" }
    );

我在https://dev59.com/MGw15IYBdhLWcg3wpNYB上问了同样的问题:当两个操作具有不同的参数时,为什么ASP.NET MVC无法区分它们? - KallDrexx
3个回答

8

主要问题在于你的两个操作方法之间存在一些歧义,因为模型绑定无法帮助区分路由配置。你当前的路由仅指向索引方法。

你仍然可以使用相同的URL,但最好将动作命名不同,然后应用一些新的路由。

[HttpPost]
public ActionResult Create(SimpleUser u)
{
    return Content("User to be created...");
}

[HttpPost]
public ActionResult Edit(int id, SimpleUser u)
{
    return Content("User to be updated...");
}

然后在你的路由中尝试:

routes.MapRoute(
    "UserEdit",
    "users/{id}",
    new { controller = "Users", action = "Edit", 
        httpMethod = new HttpMethodConstraint("POST") });

routes.MapRoute(
    "UserCreate",
    "users",
    new { controller = "Users", action = "Create", 
        httpMethod = new HttpMethodConstraint("POST") });

路由将仅受限于POST事件,这意味着您仍然可以在同一路线上为GET方法添加一些路由。例如,用于列表或其他内容。

我很好奇为什么它没有检测到 /{id},因此选择需要 id 的操作。我猜我感到困惑是因为在我使用的大多数语言中,参数列表与名称一样重要,都是方法签名的一部分。 - MetaGuru
2
好的,这是Out of Box ASP.NET MVC如何处理每个请求查找操作的方式。最终,它归结为查找具有与您调用的操作匹配的别名的方法,并且与该方法关联的所有属性都返回true。它实际上不会查看每个方法的参数作为选择要使用哪个操作的手段。因此,当多个方法具有相同的别名并且所有属性在所有方法上都返回true时,框架不知道要调用哪个方法。您可以通过查看MVC源中的ControllerActionInvoker.cs类来深入挖掘。 - Daniel Young

0

你必须通过使用两个不同的动作名称来消除方法的歧义。

为了拥有RESTful API,你可以创建自己的RouteHandler类,该类将根据{id}值是否存在来更改路由。RouteHandler类很简单:

using System.Web.Mvc;
using System.Web.Routing;

  public class MyRouteHandler : IRouteHandler
  {
    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
      var routeData = requestContext.RouteData;
      if (routeData.Values("id").ToString() == "0" /* our default invalid value */ )
      {
        var action = routeData.Values("action");
        routeData.Values("action") = action + "Add";
      }
      var handler = new MvcHandler(requestContext);
      return handler;
    }
  }

接下来,修改Global.asax.cs中的RegisterRoutes()如下所示:
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        AreaRegistration.RegisterAllAreas();

        routes.Add("Default", new Route("{controller}/{action}/{id}",
            new RouteValueDictionary(
                new { controller = "Home", 
                      action = "Index", 
                      id = "0" /* our default invalid value */ }),
            new MyRouteHandler()));
    }

最后,将Add方法的属性更改为“IndexAdd”。当请求没有id时,将提供默认的无效值,这将提示您的RouteHandler将方法更改为“Add”方法。
counsellorben

-1

如果您考虑使用创建和编辑操作来实现您想要的功能,那将会更好。通常情况下,索引可以用于列出所有用户(在您的情况下)。 关于您面临的问题,这是因为没有路由不带任何id参数而导致的。


我不能这样做,我正在创建一个具有必要的幂等URL的RESTful API,GET/PUT/DELETE都将使用相同的/users/[id] URL,而POST将使用/users/(没有ID),实际上现在我也正在使用POST进行创建,但是你现在明白了。此外,这可能比答案更好地作为评论,因为它并没有回答问题。 - MetaGuru

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