简体   繁体   English

ASP.NET MVC模糊的动作方法

[英]ASP.NET MVC ambiguous action methods

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). 基本上,我希望能够使用两个不同的路径来获取相同的视图,可以是项目的ID,也可以是项目的名称及其父项(项目可以在不同的父项中具有相同的名称)。 A search term can be used to filter the list. 搜索项可用于过滤列表。

For example... 例如...

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

Here are my action methods (there are also Remove action 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 page parameter can be null, but I can't figure out the best way to resolve it. 我理解为什么错误发生,因为page参数可以为null,但我无法找出解决它的最佳方法。 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 #2 out to a private method they would both call, but I don't believe that will actually resolve the ambiguity. 我已经考虑过扩展Method #1的签名以包含搜索参数并将Method #2的逻辑移到他们都会调用的私有方法中,但我不相信这实际上会解决歧义。

Any help would be greatly appreciated. 任何帮助将不胜感激。


Actual Solution (based on Levi's answer) 实际解决方案 (基于Levi的回答)

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) { ... }

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 does support 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 xxx was present in the request." 在上面的示例中,该属性简单地说“如果请求中存在密钥xxx,则此方法匹配”。 You can also filter by information contained within the route (controllerContext.RequestContext) if that better suits your purposes. 您还可以根据路径(controllerContext.RequestContext)中包含的信息进行过滤,如果更适合您的目的。

The parameters in your routes {roleId} , {applicationName} and {roleName} don't match the parameter names in your action methods. 路径{roleId}{applicationName}{roleName}中的参数与操作方法中的参数名称不匹配。 I don't know if that matters, but it makes it tougher to figure out what your intention is. 我不知道这是否重要,但它更难以弄清楚你的意图是什么。

Do your itemId's conform to a pattern that could be matched via regex? 你的itemId是否符合可以通过正则表达式匹配的模式? 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. 如果是这样,那么您可以为路径添加约束,以便只有与模式匹配的url被标识为包含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 AssignRemovePretty route 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+" }
                );

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

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 post that I wrote on this topic. 有关更多信息和操作方法示例,请查看我在此主题上撰写的博文

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));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM