为什么在asp.net mvc中的常用路由之前首先映射特殊路由?

Eli*_*eth 5 asp.net-mvc url-routing asp.net-mvc-routing asp.net-mvc-5 asp.net-mvc-5.2

来自www:

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

我为什么要先绘制专业路线?有人可以举个例子,我可以看到"地图常见路线优先"的失败吗?

Nig*_*888 18

路由引擎将采用与提供的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;
}
Run Code Online (Sandbox Code Playgroud)

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

Route类遵循类似的模式(在GetRouteDataGetVirtualPath方法中).他们可以返回2个州:

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

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

在第二种情况下,MVC将检查下RouteRouteTable,看它是否与请求匹配(内置的行为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 }
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

在这个例子中:

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

因此,如果应用程序传递了URL \Home\About,CustomRoute则匹配,并将以下内容提供RouteValues给MVC:

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

这将使MVC查找在命名About的控制器上命名的操作MyControllerController,如果它不存在则将失败.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 }
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

选项2:添加一个或多个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 }
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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 }
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

选项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 }
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,CustomRoute只会匹配一个包含4个段的URL(注意这些可以是任何值).Default之前的路由仅匹配具有0,1,2或3个段的URL.因此,没有无法到达的执行路径.

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

任何路由不支持开箱即用(例如在特定域或子域上匹配)都可以通过实现自己的RouteBase子类或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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这个类来自:是否可以基于子域创建ASP.NET MVC路由?

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 }
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:这里真正的问题是,大多数人都认为他们的路线应该都像Default路线一样.复制,粘贴,完成,对吧?错误.

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

  1. 几乎所有其他路径都应该至少有一个文字段(如果你是那种东西,那么就是一个约束).
  2. 最符合逻辑的行为通常是使其余路由具有所需的段.

另一个常见的误解是,可选细分意味着您可以忽略任何细分,但实际上您只能忽略最右侧的细分.

Microsoft成功地使路由基于约定,可扩展和强大.他们没有让它直观易懂.实际上每个人都在第一次尝试时失败了(我知道我做过了!).幸运的是,一旦你了解它是如何工作的,这并不是很困难.