NET MVC 模糊动作方法

我有两种互相矛盾的行动方式。基本上,我希望能够使用两种不同的路径来获得相同的视图,通过项目的 ID 或者项目的名称和它的父视图(项目可以在不同的父视图中使用相同的名称)。可以使用搜索词来筛选列表。

比如说..。

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参数可以是 null,但是我不知道解决这个问题的最佳方法。我的设计一开始就很糟糕吗?我已经考虑过扩展 Method #1的签名以包含搜索参数,并将 Method #2中的逻辑移动到一个它们都会调用的私有方法,但我不认为这会实际解决模棱两可的问题。

如果你能帮忙,我将不胜感激。


实际解 (基于 Levi 的答案)

我添加了以下类..。

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) { ... }
62117 次浏览

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

ItemId 是否符合可以通过正则表达式匹配的模式?如果是这样,那么您可以为您的路由添加一个限制,以便只有与模式匹配的 url 被识别为包含 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) { ... }

然后在路由中指定 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+" }
);
routes.MapRoute("AssignRemove",
"Items/{parentName}/{itemName}",
new { controller = "Items", action = "Assign" }
);

考虑使用 MVC 贡献测试路由库来测试您的路由

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

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

在上面的示例中,属性只是说“如果请求中存在键 谢谢,则此方法匹配”您还可以根据路由中包含的信息进行筛选(ControlerContext。RequestContext) ,如果这更适合您的目的的话。

另一种方法是重命名其中一个方法,这样就不会出现冲突

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


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

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

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

下面是我现在使用的属性:

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

关于进一步的信息和如何实现示例,请查看我在这个主题上写的 这篇博文

哈里克里希纳在答案中引用了许多解决方案。

在下面的 NET MVC-如何修复不明确的操作方法错误并实现多个具有相同操作名称的操作方法文章中,提到了使用 overloadalias

使用 System.Web.Mvc.ActionNameAttribute 声明重载别名,如[ ActionName (“ MyOverloadAliasName”)]。

也许能行。