代码之家  ›  专栏  ›  技术社区  ›  Jon

ASP.NET MVC不明确的操作方法

  •  129
  • Jon  · 技术社区  · 17 年前

    我有两种相互冲突的行动方法。基本上,我希望能够使用两个不同的路由访问同一个视图,要么通过项目的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) { ... }
    
    5 回复  |  直到 7 年前
        1
  •  176
  •   Levi    17 年前

    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)中包含的信息更适合您的目的,您还可以按该信息进行筛选。

        2
  •  7
  •   CoderDennis    17 年前

    路线中的参数 {roleId} , {applicationName} 和 {roleName} 不匹配操作方法中的参数名称。我不知道这是否重要,但这使得弄清楚你的意图变得更加困难。

    您的itemID是否符合可以通过regex匹配的模式?如果是这样,那么您可以在路由中添加一个约束,这样只有与模式匹配的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+" }
                    );
    
        3
  •  6
  •   Pure.Krome    15 年前

    另一种方法是重命名其中一个方法,这样就不会发生冲突。例如

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

        4
  •  3
  •   Darkseal    7 年前

    最近我利用这个机会改进了@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
        }
    }
    

    有关进一步的信息和如何实现示例,请查看 this blog post 我就这个话题写的。

        5
  •  0
  •   Rony    17 年前
    routes.MapRoute("AssignRemove",
                    "Items/{parentName}/{itemName}",
                    new { controller = "Items", action = "Assign" }
                    );
    

    考虑使用MVC控件测试路由库来测试路由

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