ASP.NET MVC 不明确的操作方法
-
20-08-2019 - |
题
我有两种相互冲突的行动方法。基本上,我希望能够使用两条不同的路线到达相同的视图,要么通过项目的 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
参数可以为空,但我无法找出解决它的最佳方法。我的设计一开始就很糟糕吗?我考虑过延长 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) { ... }
解决方案
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)内的信息进行过滤。
其他提示
在你的路由{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) { ... }
然后指定你的路由操作名称,迫使正确的方法被调用:
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+" }
);
另一种方法是重命名的方法之一,所以没有冲突。例如
// GET: /Movies/Delete/5
public ActionResult Delete(int id = 0)
// POST: /Movies/Delete/5
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id = 0)
最近,我借此机会改进了@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
}
}
有关更多信息和如何实施示例,请查看 这篇博文 我就这个话题写的。
routes.MapRoute("AssignRemove",
"Items/{parentName}/{itemName}",
new { controller = "Items", action = "Assign" }
);
考虑使用MVC贡献测试路线库,以测试你的路由
"Items/parentName/itemName".Route().ShouldMapTo<Items>(x => x.Assign("parentName", itemName));