2009-06-25 64 views
117

我有两个冲突的行动方法。基本上,我希望能够使用两种不同的路线获得相同的视图,无论是通过商品的ID还是商品的名称和其父项(商品可以在不同的父母中具有相同的名称)。搜索项可以用来过滤列表。ASP.NET MVC模糊行为方法

例如...

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

这里是我的操作方法(也有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) { ... } 

而且这里的路...

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

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

我明白错误发生的原因,因为page参数可以为空,但我无法弄清楚解决问题的最佳方法。我的设计很糟糕吗?我曾考虑扩展Method #1的签名以包含搜索参数,并将Method #2中的逻辑移出到他们都会调用的私有方法,但我不相信这实际上可以解决模糊问题。

任何帮助将不胜感激。


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

添加以下类...

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

再点缀作用的方法...

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

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

感谢您发布实际的实施。它确实可以帮助有类似问题的人。就像我今天一样。 :-P – 2011-02-12 09:16:23

+4

令人惊叹!次要更改建议:(imo真正有用)1)params string [] valueNames使属性声明更简洁和(首选项)2)用`返回ValueNames.All(v => controllerContext.RequestContext.RouteData)替换IsValidForRequest方法体。 Values.ContainsKey(v));` – 2012-03-01 08:58:06

+0

嗨,乔恩,我想我没有想过,因为在哪里查询参数在RouteData? – fravelgue 2012-03-22 09:45:48

回答

161

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

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

然而,确实基于属性支持方法重载:

[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; } 
} 

在上面的例子中,属性简单地说“这种方法相匹配,如果所述键XXX存在于请求。”如果更适合您的目的,您还可以通过路由中包含的信息(controllerContext.RequestContext)进行过滤。

7

您的路线参数{roleId}{applicationName}{roleName}与您的操作方法中的参数名称不匹配。我不知道这是否重要,但是弄清楚你的意图很难。

您的itemId是否符合可通过正则表达式匹配的模式?如果是这样,那么你可以添加约束到你的路由,以便只有符合模式的网址被识别为包含itemId。

如果您的itemId只包含数字,那么这会工作:

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

编辑:您还可以添加一个约束到AssignRemovePretty路由,这样既{parentName}{itemName}是必需的。编辑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) { ... } 

然后指定你的路由操作名称,迫使正确的方法被调用:

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+" } 
       ); 
0
routes.MapRoute("AssignRemove", 
       "Items/{parentName}/{itemName}", 
       new { controller = "Items", action = "Assign" } 
       ); 

考虑使用MVC贡献测试路线库,以测试你的路由

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

最近我把改善@李维斯的答案,支持更广泛的场景的机会,我不得不处理,如:多参数的支持,与任何人(而不是全部),甚至都不匹配。

这是我现在使用的属性:

/// <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 
    } 
} 

如需进一步信息,以及如何对执行样本,还可以阅读this post