【问题标题】:html.ActionLink using wrong routehtml.ActionLink 使用错误的路线
【发布时间】:2017-02-15 16:48:53
【问题描述】:

我已经设置了以下路由,以便我可以使用重复的控制器名称(在不同的命名空间中)。这很好用,但是当我从任何控制器使用 html.actionlink 时,它总是在链接中包含“CRUD”子文件夹。

var route1 = routes.MapRoute(
    "CRUD",
    "CRUD/{controller}/{action}/{id}",
    new { action = "Index", id = UrlParameter.Optional }
);
route1.DataTokens["Namespaces"] = new string[] { "College.Controllers.CRUD" };
route1.DataTokens["UseNamespaceFallback"] = false;

var route2 = routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
    namespaces: new[] { "College.Controllers" }
);

route2.DataTokens["Namespaces"] = new string[] { "College.Controllers" };
route2.DataTokens["UseNamespaceFallback"] = false;

所以http://localhost/students/index 中的 html.actionlink 看起来像这样 http://localhost/CRUD/students/Edit/1

我想要的是这个 http://localhost/students/Edit/1

我知道我可以通过在操作链接中指定路线来解决此问题,但我不想这样做,因为我想在将来重新搭建脚手架,而我的更改将被覆盖。

【问题讨论】:

    标签: asp.net asp.net-mvc routes


    【解决方案1】:

    这里的问题是您的 2 条路线在构建 URL 时不明确。基本上有3种方法可以解决这个问题:

    1. 添加另一个路由值以匹配不属于 URL 的部分。
    2. 使用RouteLink 按名称指定路由(连同其他路由值条件以使其匹配)。
    3. 创建自定义路由以处理将其“约束”到特定命名空间或进行自定义路由约束。

    由于您明确表示第二个选项是不可接受的,因此这里是第一个选项的示例:

    var route1 = routes.MapRoute(
                   "CRUD",
                   "CRUD/{controller}/{action}/{id}",
                   new { crud = "crud", action = "Index", id = UrlParameter.Optional }
    
               );
        route1.DataTokens["Namespaces"] = new string[] { "College.Controllers.CRUD" };
        route1.DataTokens["UseNamespaceFallback"] = false;
    
    var route2 = routes.MapRoute(
                   "Default",
                   "{controller}/{action}/{id}",
                   new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    
               );
        route1.DataTokens["Namespaces"] = new string[] { "College.Controllers" };
        route1.DataTokens["UseNamespaceFallback"] = false;
    

    现在当您调用@Html.ActionLink("Students", "Index", "Students", new { crud = (string)null }, null) 时,它不会匹配CRUD 路由,而是匹配Default 路由。

    要使其与 CRUD 路由匹配,您必须将路由值显式添加到 ActionLink:@Html.ActionLink("Students", "Index", "Students", new { crud = "crud" }, null) 或完全省略:@Html.ActionLink("Students", "Index", "Students")

    限制路线

    这是第 3 个选项的示例。

    不幸的是,我们不能使用常规路由约束,因为 Microsoft 决定不在 IRouteConstraint 接口中提供 RequestContext 对象。这意味着有关请求绑定到哪个控制器的命名空间信息不可用。因此,我们需要降到较低的级别,并创建一个自定义的 RouteBase 类,该类实现了装饰器模式来包装我们现有的 Route 类配置。

    此类只是在生成 URL 之前检查请求中的命名空间是否与特定命名空间匹配。

    public class NamespaceConstrainedRoute : RouteBase
    {
        private readonly string namespaceToMatch;
        private readonly RouteBase innerRoute;
    
        public NamespaceConstrainedRoute(string namespaceToMatch, RouteBase innerRoute)
        {
            if (string.IsNullOrEmpty(namespaceToMatch))
                throw new ArgumentNullException("namespaceToMatch");
            if (innerRoute == null)
                throw new ArgumentNullException("innerRoute");
            this.namespaceToMatch = namespaceToMatch;
            this.innerRoute = innerRoute;
        }
    
        public override RouteData GetRouteData(HttpContextBase httpContext)
        {
            return innerRoute.GetRouteData(httpContext);
        }
    
        public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
        {
            object namespaces;
            if (requestContext.RouteData.DataTokens.TryGetValue("Namespaces", out namespaces)
                && namespaces is IList<string>
                && ((IList<string>)namespaces).Contains(namespaceToMatch))
            {
                return innerRoute.GetVirtualPath(requestContext, values);
            }
    
            // null indicates to try to match the next route in the route table
            return null;
        }
    }
    

    用法

    var route1 = new Route(
            url: "CRUD/{controller}/{action}/{id}",
            defaults: new RouteValueDictionary(new { action = "Index", id = UrlParameter.Optional }),
            routeHandler: new MvcRouteHandler()
        )
    {
        DataTokens = new RouteValueDictionary
        {
            { "Namespaces",  new string[] { "College.Controllers.CRUD" }},
            { "UseNamespaceFallback", false }
        }
    };
    
    var route2 = new Route(
            url: "{controller}/{action}/{id}",
            defaults: new RouteValueDictionary(new { controller = "Home", action = "Index", id = UrlParameter.Optional }),
            routeHandler: new MvcRouteHandler()
        )
    {
        DataTokens = new RouteValueDictionary
        {
            { "Namespaces",  new string[] { "College.Controllers" }},
            { "UseNamespaceFallback", false }
        }
    };
    
    routes.Add(
        name: "CRUD",
        item: new NamespaceConstrainedRoute(
            namespaceToMatch: "College.Controllers.CRUD", 
            innerRoute: route1));
    
    routes.Add(
        name: "Default",
        item: new NamespaceConstrainedRoute(
            namespaceToMatch: "College.Controllers",
            innerRoute: route2));
    

    从这一点开始,如果您选择让上述配置看起来更简洁,您可以构建自己的 MapRoute 扩展方法。

    【讨论】:

    • 我尝试了您为第一个解决方案建议的路线,但操作链接创建的链接中仍然包含“CRUD”子文件夹。这是在视图中显示的操作链接:@Html.ActionLink("Edit", "Edit", new { id=item.StudentId }) 生成以下链接:localhost/CRUD/students/Edit/1
    • 我用一个适用于第一个选项的示例更新了答案(我的错误),但它不再那么优雅了。如果你问我,使用RouteLink 并命名路线是更好的选择。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-02
    • 1970-01-01
    • 1970-01-01
    • 2010-12-01
    • 1970-01-01
    相关资源
    最近更新 更多