【问题标题】:Why map special routes first before common routes in asp.net mvc?为什么在 asp.net mvc 中的公共路由之前先映射特殊路由?
【发布时间】:2016-06-10 05:17:04
【问题描述】:

来自www:

...路由引擎将采用与提供的 URL 匹配的第一个路由,并尝试使用该路由中的路由值。因此,应先将不太常见或更专业的路线添加到表格中,而稍后应添加更通用的路线...

为什么要先绘制专门的路线?有人可以给我一个例子,我可以看到“首先映射公共路线”的失败吗?

【问题讨论】:

  • 我不明白你的问题是什么。 “路由引擎将采用与提供的 URL 匹配的第一条路由......”是不言自明的。
  • 如果第二条路由与提供的 URL 匹配怎么办。那么顺序就不重要了!?
  • 但如果它也匹配第一个,那么将使用第一个。
  • 所以映射路由的顺序只有在匹配多个路由时才重要,对吗?
  • @Elisabeth,假设您想要一条路线/Cars/BMW 来显示汽车的详细信息,因此路线可能是url: Cars/{model} 并重定向到Details(string model)CarsController 方法。如果你没有把它放在默认值之前,那么它也会匹配默认值,你会被重定向到CarsController的(不存在的)BMW()方法

标签: asp.net-mvc asp.net-mvc-5 url-routing asp.net-mvc-routing asp.net-mvc-5.2


【解决方案1】:

路由引擎将采用与提供的 URL 匹配的第一个路由,并尝试使用该路由中的路由值。

发生这种情况的原因是RouteTable 被用作 switch-case 语句。如下图:

int caseSwitch = 1;
switch (caseSwitch)
{
    case 1:
        Console.WriteLine("Case 1");
        break;
    case 1:
        Console.WriteLine("Second Case 1");
        break;
    default:
        Console.WriteLine("Default case");
        break;
}

如果caseSwitch1,则永远不会到达第二个块,因为第一个块会捕获它。

Route 类遵循类似的模式(在GetRouteDataGetVirtualPath 方法中)。它们可以返回 2 种状态:

  1. 一组路由值(或VirtualPath 对象GetVirtualPath)。这表明路由与请求匹配。
  2. null。这表明路由与请求不匹配。

在第一种情况下,MVC 使用路由生成的路由值来查找Action 方法。在这种情况下,RouteTable 不会被进一步分析。

在第二种情况下,MVC 将检查RouteTable 中的下一个Route 以查看它是否与请求匹配(内置行为匹配 URL 和约束,但从技术上讲,您可以匹配 HTTP 请求中的任何内容)。再一次,该路由可以根据结果返回一组RouteValuesnull

如果您尝试使用上述 switch-case 语句,程序将无法编译。但是,如果您配置的路由从不返回null或返回RouteValues对象的次数多于应有的情况,则程序将编译,但会出现异常。

错误配置示例

这是我经常在 StackOverflow(或它的一些变体)上看到的经典示例:

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            name: "CustomRoute",
            url: "{segment1}/{action}/{id}",
            defaults: new { controller = "MyController", action = "Index", id = UrlParameter.Optional }
        );

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

在这个例子中:

  1. CustomRoute 将匹配任何长度为 1、2 或 3 段的 URL(请注意,segment1 是必需的,因为它没有默认值)。
  2. Default 将匹配任何长度为 0、1、2 或 3 段的 URL。

因此,如果应用程序传递 URL \Home\AboutCustomRoute 将匹配,并将以下 RouteValues 提供给 MVC:

  1. segment1 = "Home"
  2. controller = "MyController"
  3. action = "About"
  4. id = {}

这将使 MVC 在名为 MyControllerController 的控制器上查找名为 About 的操作,如果它不存在则会失败。在这种情况下,Default 路由是一个无法访问的执行路径,因为即使它会匹配一个 2 段 URL,框架也不会给它机会,因为第一个匹配获胜。

修复配置

关于如何继续修复配置有几个选项。但所有这些都取决于第一场比赛获胜的行为,然后路由不会再看下去了。

选项 1:添加一个或多个文字段

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            name: "CustomRoute",
            url: "Custom/{action}/{id}",

            // Note, leaving `action` and `id` out of the defaults
            // makes them required, so the URL will only match if 3
            // segments are supplied begining with Custom or custom.
            // Example: Custom/Details/343
            defaults: new { controller = "MyController" }
        );

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

选项 2:添加 1 个或多个 RegEx 约束

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            name: "CustomRoute",
            url: "{segment1}/{action}/{id}",
            defaults: new { controller = "MyController", action = "Index", id = UrlParameter.Optional },
            constraints: new { segment1 = @"house|car|bus" }
        );

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

选项 3:添加 1 个或多个自定义约束

public class CorrectDateConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        var year = values["year"] as string;
        var month = values["month"] as string;
        var day = values["day"] as string;

        DateTime theDate;
        return DateTime.TryParse(year + "-" + month + "-" + day, System.Globalization.CultureInfo.InvariantCulture, DateTimeStyles.None, out theDate);
    }
}

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            name: "CustomRoute",
            url: "{year}/{month}/{day}/{article}",
            defaults: new { controller = "News", action = "ArticleDetails" },
            constraints: new { year = new CorrectDateConstraint() }
        );

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

选项 4:使 必需 分段 + 使分段数与现有路由不匹配

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            name: "CustomRoute",
            url: "{segment1}/{segment2}/{action}/{id}",
            defaults: new { controller = "MyController" }
        );

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

在上述情况下,CustomRoute 将仅匹配具有 4 个段的 URL(注意这些可以是任何值)。和以前一样,Default 路由只匹配具有 0、1、2 或 3 段的 URL。因此没有不可达的执行路径。

选项 5:为自定义行为实现 RouteBase(或 Route)

路由不支持开箱即用的任何事情(例如在特定域或子域上匹配)都可以通过implementing your own RouteBase subclass 或 Route 子类来完成。这也是了解路由如何/为何以这种方式工作的最佳方式。

public class SubdomainRoute : Route
{
    public SubdomainRoute(string url) : base(url, new MvcRouteHandler()) {}

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        var routeData = base.GetRouteData(httpContext);
        if (routeData == null) return null; // Only look at the subdomain if this route matches in the first place.
        string subdomain = httpContext.Request.Params["subdomain"]; // A subdomain specified as a query parameter takes precedence over the hostname.
        if (subdomain == null) {
            string host = httpContext.Request.Headers["Host"];
            int index = host.IndexOf('.');
            if (index >= 0)
                subdomain = host.Substring(0, index);
        }
        if (subdomain != null)
            routeData.Values["subdomain"] = subdomain;
        return routeData;
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        object subdomainParam = requestContext.HttpContext.Request.Params["subdomain"];
        if (subdomainParam != null)
            values["subdomain"] = subdomainParam;
        return base.GetVirtualPath(requestContext, values);
    }
}

该课程借用自:Is it possible to make an ASP.NET MVC route based on a subdomain?

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.Add(new SubdomainRoute(url: "somewhere/unique"));

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

注意:这里真正的问题是,大多数人认为他们的路线都应该看起来像Default 路线。复制,粘贴,完成,对吗?错了。

这种方法通常会出现两个问题:

  1. 几乎所有其他路线都应该至少有一个文字段(如果您喜欢这种东西,则应该有一个约束)。
  2. 最合乎逻辑的行为通常是使其余路由具有必需段。

另一个常见的误解是,可选段意味着您可以省略任何段,但实际上您只能省略最右边的一个或多个段。

Microsoft 成功地使路由约定基于、可扩展且功能强大。他们未能使其直观地理解。几乎每个人第一次尝试都失败了(我知道我做到了!)。幸运的是,一旦您了解了它的工作原理,这并不难。

【讨论】:

    猜你喜欢
    • 2010-09-05
    • 1970-01-01
    • 2012-09-03
    • 2011-04-10
    • 2010-11-08
    • 2015-12-26
    • 2016-12-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多