C# ASP.NET MVC 模棱两可的动作方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1045316/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 06:53:51  来源:igfitidea点击:

ASP.NET MVC ambiguous action methods

c#asp.net-mvcasp.net-mvc-routing

提问by Jonathan Freeland

I have two action methods that are conflicting. Basically, I want to be able to get to the same view using two different routes, either by an item's ID or by the item's name and its parent's (items can have the same name across different parents). A search term can be used to filter the list.

我有两种相互冲突的操作方法。基本上,我希望能够使用两个不同的路由到达同一个视图,要么通过项目的 ID 要么通过项目的名称及其父项(项目可以在不同的父项中具有相同的名称)。搜索词可用于过滤列表。

For example...

例如...

Items/{action}/ParentName/ItemName
Items/{action}/1234-4321-1234-4321

Here are my action methods (there are also Removeaction methods)...

这是我的动作方法(也有Remove动作方法)...

// Method #1
public ActionResult Assign(string parentName, string itemName) { 
    // Logic to retrieve item's ID here...
    string itemId = ...;
    return RedirectToAction("Assign", "Items", new { itemId });
}

// Method #2
public ActionResult Assign(string itemId, string searchTerm, int? page) { ... }

And here are the routes...

这里是路线...

routes.MapRoute("AssignRemove",
                "Items/{action}/{itemId}",
                new { controller = "Items" }
                );

routes.MapRoute("AssignRemovePretty",
                "Items/{action}/{parentName}/{itemName}",
                new { controller = "Items" }
                );

I understand why the error is occurring, since the pageparameter can be null, but I can't figure out the best way to resolve it. Is my design poor to begin with? I've thought about extending Method #1's signature to include the search parameters and moving the logic in Method #2out to a private method they would both call, but I don't believe that will actually resolve the ambiguity.

我明白为什么会发生错误,因为page参数可以为空,但我无法找出解决它的最佳方法。我的设计一开始就很差吗?我考虑过扩展Method #1的签名以包含搜索参数并将逻辑移入Method #2他们都会调用的私有方法,但我认为这不会真正解决歧义。

Any help would be greatly appreciated.

任何帮助将不胜感激。



Actual Solution(based on Levi's answer)

实际解决方案(基于李维斯的回答)

I added the following class...

我添加了以下课程...

public class RequireRouteValuesAttribute : ActionMethodSelectorAttribute {
    public RequireRouteValuesAttribute(string[] valueNames) {
        ValueNames = valueNames;
    }

    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) {
        bool contains = false;
        foreach (var value in ValueNames) {
            contains = controllerContext.RequestContext.RouteData.Values.ContainsKey(value);
            if (!contains) break;
        }
        return contains;
    }

    public string[] ValueNames { get; private set; }
}

And then decorated the action methods...

然后修饰动作方法...

[RequireRouteValues(new[] { "parentName", "itemName" })]
public ActionResult Assign(string parentName, string itemName) { ... }

[RequireRouteValues(new[] { "itemId" })]
public ActionResult Assign(string itemId) { ... }

采纳答案by Levi

MVC doesn't support method overloading based solely on signature, so this will fail:

MVC 不支持仅基于签名的方法重载,因此这将失败:

public ActionResult MyMethod(int someInt) { /* ... */ }
public ActionResult MyMethod(string someString) { /* ... */ }

However, it doessupport method overloading based on attribute:

但是,它确实支持基于属性的方法重载:

[RequireRequestValue("someInt")]
public ActionResult MyMethod(int someInt) { /* ... */ }

[RequireRequestValue("someString")]
public ActionResult MyMethod(string someString) { /* ... */ }

public class RequireRequestValueAttribute : ActionMethodSelectorAttribute {
    public RequireRequestValueAttribute(string valueName) {
        ValueName = valueName;
    }
    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) {
        return (controllerContext.HttpContext.Request[ValueName] != null);
    }
    public string ValueName { get; private set; }
}

In the above example, the attribute simply says "this method matches if the key xxxwas present in the request." You can also filter by information contained within the route (controllerContext.RequestContext) if that better suits your purposes.

在上面的例子中,该属性简单地表示“如果请求中存在密钥xxx,则此方法匹配”。如果更适合您的目的,您还可以通过包含在路由 (controllerContext.RequestContext) 中的信息进行过滤。

回答by CoderDennis

The parameters in your routes {roleId}, {applicationName}and {roleName}don't match the parameter names in your action methods. I don't know if that matters, but it makes it tougher to figure out what your intention is.

在你的路由参数{roleId}{applicationName}并且{roleName}不匹配的动作方法的参数名称。我不知道这是否重要,但这会让弄清楚你的意图是什么变得更加困难。

Do your itemId's conform to a pattern that could be matched via regex? If so, then you can add a restraint to your route so that only url's that match the pattern are identified as containing an itemId.

您的 itemId 是否符合可以通过正则表达式匹配的模式?如果是这样,那么您可以向您的路线添加限制,以便仅将与模式匹配的网址标识为包含 itemId。

If your itemId only contained digits, then this would work:

如果您的 itemId 仅包含数字,那么这将起作用:

routes.MapRoute("AssignRemove",
                "Items/{action}/{itemId}",
                new { controller = "Items" },
                new { itemId = "\d+" }
                );

Edit: You could also add a constraint to the AssignRemovePrettyroute so that both {parentName}and {itemName}are required.

编辑:您还可以向AssignRemovePretty路由添加一个约束,以便{parentName}{itemName}都是必需的。

Edit 2: Also, since your first action is just redirecting to your 2nd action, you could remove some ambiguity by renaming the first one.

编辑 2:此外,由于您的第一个操作只是重定向到您的第二个操作,因此您可以通过重命名第一个操作来消除一些歧义。

// Method #1
public ActionResult AssignRemovePretty(string parentName, string itemName) { 
    // Logic to retrieve item's ID here...
    string itemId = ...;
    return RedirectToAction("Assign", itemId);
}

// Method #2
public ActionResult Assign(string itemId, string searchTerm, int? page) { ... }

Then specify the Action names in your routes to force the proper method to be called:

然后在路由中指定 Action 名称以强制调用正确的方法:

routes.MapRoute("AssignRemove",
                "Items/Assign/{itemId}",
                new { controller = "Items", action = "Assign" },
                new { itemId = "\d+" }
                );

routes.MapRoute("AssignRemovePretty",
                "Items/Assign/{parentName}/{itemName}",
                new { controller = "Items", action = "AssignRemovePretty" },
                new { parentName = "\w+", itemName = "\w+" }
                );

回答by Rony

routes.MapRoute("AssignRemove",
                "Items/{parentName}/{itemName}",
                new { controller = "Items", action = "Assign" }
                );

consider using MVC Contribs test routes library to test your routes

考虑使用 MVC Contribs 测试路由库来测试你的路由

"Items/parentName/itemName".Route().ShouldMapTo<Items>(x => x.Assign("parentName", itemName));

回答by RickAndMSFT

Another approach is to rename one of the methods so there is no conflict. For example

另一种方法是重命名其中一个方法,以免发生冲突。例如

// GET: /Movies/Delete/5
public ActionResult Delete(int id = 0)

// POST: /Movies/Delete/5
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id = 0)

See http://www.asp.net/mvc/tutorials/getting-started-with-mvc3-part9-cs

http://www.asp.net/mvc/tutorials/getting-started-with-mvc3-part9-cs

回答by Darkseal

Recently I took the chance to improve @Levi's answer to support a wider range of scenarios I had to deal with, such as: multiple parameter support, match any of them (instead of them all) and even match none of them.

最近我借此机会改进了@Levi 的答案,以支持我必须处理的更广泛的场景,例如:多参数支持、匹配其中任何一个(而不是全部)甚至不匹配任何一个。

Here's the attribute I'm using now:

这是我现在使用的属性:

/// <summary>
/// Flags an Action Method valid for any incoming request only if all, any or none of the given HTTP parameter(s) are set,
/// enabling the use of multiple Action Methods with the same name (and different signatures) within the same MVC Controller.
/// </summary>
public class RequireParameterAttribute : ActionMethodSelectorAttribute
{
    public RequireParameterAttribute(string parameterName) : this(new[] { parameterName })
    {
    }

    public RequireParameterAttribute(params string[] parameterNames)
    {
        IncludeGET = true;
        IncludePOST = true;
        IncludeCookies = false;
        Mode = MatchMode.All;
    }

    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
    {
        switch (Mode)
        {
            case MatchMode.All:
            default:
                return (
                    (IncludeGET && ParameterNames.All(p => controllerContext.HttpContext.Request.QueryString.AllKeys.Contains(p)))
                    || (IncludePOST && ParameterNames.All(p => controllerContext.HttpContext.Request.Form.AllKeys.Contains(p)))
                    || (IncludeCookies && ParameterNames.All(p => controllerContext.HttpContext.Request.Cookies.AllKeys.Contains(p)))
                    );
            case MatchMode.Any:
                return (
                    (IncludeGET && ParameterNames.Any(p => controllerContext.HttpContext.Request.QueryString.AllKeys.Contains(p)))
                    || (IncludePOST && ParameterNames.Any(p => controllerContext.HttpContext.Request.Form.AllKeys.Contains(p)))
                    || (IncludeCookies && ParameterNames.Any(p => controllerContext.HttpContext.Request.Cookies.AllKeys.Contains(p)))
                    );
            case MatchMode.None:
                return (
                    (!IncludeGET || !ParameterNames.Any(p => controllerContext.HttpContext.Request.QueryString.AllKeys.Contains(p)))
                    && (!IncludePOST || !ParameterNames.Any(p => controllerContext.HttpContext.Request.Form.AllKeys.Contains(p)))
                    && (!IncludeCookies || !ParameterNames.Any(p => controllerContext.HttpContext.Request.Cookies.AllKeys.Contains(p)))
                    );
        }
    }

    public string[] ParameterNames { get; private set; }

    /// <summary>
    /// Set it to TRUE to include GET (QueryStirng) parameters, FALSE to exclude them:
    /// default is TRUE.
    /// </summary>
    public bool IncludeGET { get; set; }

    /// <summary>
    /// Set it to TRUE to include POST (Form) parameters, FALSE to exclude them:
    /// default is TRUE.
    /// </summary>
    public bool IncludePOST { get; set; }

    /// <summary>
    /// Set it to TRUE to include parameters from Cookies, FALSE to exclude them:
    /// default is FALSE.
    /// </summary>
    public bool IncludeCookies { get; set; }

    /// <summary>
    /// Use MatchMode.All to invalidate the method unless all the given parameters are set (default).
    /// Use MatchMode.Any to invalidate the method unless any of the given parameters is set.
    /// Use MatchMode.None to invalidate the method unless none of the given parameters is set.
    /// </summary>
    public MatchMode Mode { get; set; }

    public enum MatchMode : int
    {
        All,
        Any,
        None
    }
}

For further info and how-to implementation samples check out this blog postthat I wrote on this topic.

有关更多信息和操作方法示例,请查看我在此主题上写的这篇博文