使用RedirectToRouteResult重定向时路由表无匹配路由问题排查
排查RedirectToRouteResult路由匹配失败问题
让我来帮你定位这个路由错误的根源,看完你的代码后,我发现几个核心问题导致了"No Route in route table matches the supplied values"错误:
问题根源
- 路由约束不匹配:你的
CultureConstraint要求culture参数必须是两位小写字母(正则[a-z]{2}),但你在代码里传递的culture值是"GB"(大写地区代码),这直接违反了约束规则,路由系统自然找不到匹配项。 - 路由参数概念混淆:你把地区代码(比如
GB)赋值给了路由的culture参数,但从路由定义来看,这个参数应该是语言代码(比如en),而非地区代码——这不仅不符合约束,也混淆了语言和地区的概念。 - 路由注册顺序不合理:更具体的路由(比如
TourWithCulture)被放在了通用路由(DefaultWithCulture)之后,路由匹配是从上到下执行的,通用路由会先拦截请求,导致特定路由永远不会被匹配到。 - RouteValueDictionary处理不严谨:你直接复用了当前请求的
RouteData.Values,但没有确保参数格式完全符合路由约束,容易出现格式错误。
修复步骤
1. 修正Culture参数格式,匹配路由约束
首先,把传递给路由的culture值统一为小写的语言代码,确保符合[a-z]{2}的约束:
public override void OnActionExecuting(ActionExecutingContext filterContext) { bool redirectToLanguagedVersion = filterContext.RouteData.Values["culture"].ToString() == "zz"; string defaultLanguage = "en"; string culture = defaultLanguage; // 这里直接用语言代码作为路由的culture参数 if (redirectToLanguagedVersion) { if (filterContext.HttpContext.User.Identity.IsAuthenticated) { var user = IdentityManager.GetUser(filterContext.HttpContext.User.Identity.GetUserId()); // 优先用用户设置的语言代码,转为小写确保符合约束 culture = user.CultureInfoLanguage?.ToLowerInvariant() ?? defaultLanguage; } else { culture = filterContext.RequestContext.HttpContext.Cache["UserCultureInfoLanguage"]?.ToString().ToLowerInvariant() ?? defaultLanguage; } // 构建正确的路由参数,显式指定必要项 var routeValues = new RouteValueDictionary { { "culture", culture }, { "controller", filterContext.RouteData.Values["controller"] ?? "Home" }, { "action", filterContext.RouteData.Values["action"] ?? "Index" }, { "id", filterContext.RouteData.Values["id"] } }; filterContext.Result = new RedirectToRouteResult("DefaultWithCulture", routeValues); } else { // 这里再根据语言和地区设置线程文化,比如如果用户有地区信息,可以组合成"en-GB" string userRegion = filterContext.RouteData.Values["culture"].ToString() == "en" ? "GB" : "US"; // 示例逻辑,可替换为用户实际地区 string fullCulture = $"{culture}-{userRegion}"; Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(fullCulture); Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(fullCulture); } }
2. 调整路由注册顺序
把更具体的路由放在通用路由前面,确保特定请求能匹配到正确的路由:
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); // 先注册属性路由 routes.MapLocalizedMvcAttributeRoutes( urlPrefix: "{culture}/", constraints: new { culture = new CultureConstraint(defaultCulture: "zz", pattern: "[a-z]{2}") } ); // 先注册特定路由(带title参数的Tour路由) routes.MapRoute( name: "TourWithCulture", url: "{culture}/{controller}/{action}/{id}/{title}", defaults: new { controller = "Tours", action = "Explore", id = UrlParameter.Optional, title = UrlParameter.Optional }, constraints: new { culture = new CultureConstraint(defaultCulture: "zz", pattern: "[a-z]{2}") } ); routes.MapRoute( name: "Tour", url: "{controller}/{action}/{id}/{title}", defaults: new { culture = "zz", controller = "Tours", action = "Explore", id = UrlParameter.Optional, title = UrlParameter.Optional }, constraints: new { culture = new CultureConstraint(defaultCulture: "zz", pattern: "[a-z]{2}") } ); // 再注册通用带culture的路由 routes.MapRoute( name: "DefaultWithCulture", url: "{culture}/{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }, constraints: new { culture = new CultureConstraint(defaultCulture: "zz", pattern: "[a-z]{2}") } ); // 最后注册默认不带culture的路由 routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { culture = "zz", controller = "Home", action = "Index", id = UrlParameter.Optional } ); }
3. 优化CultureConstraint的匹配逻辑
如果希望约束可以兼容大小写(或者严格匹配),可以调整CultureConstraint的Match方法:
public class CultureConstraint : IRouteConstraint { private readonly string _defaultCulture; private readonly string _pattern; public CultureConstraint(string defaultCulture, string pattern) { _defaultCulture = defaultCulture; _pattern = pattern; } public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { if (!values.TryGetValue(parameterName, out var value)) return false; string culture = value.ToString(); // 允许默认值"zz"用于触发重定向 if (culture.Equals(_defaultCulture, StringComparison.OrdinalIgnoreCase)) return true; // 用忽略大小写的方式匹配正则,或者去掉IgnoreCase严格匹配小写 return Regex.IsMatch(culture, _pattern, RegexOptions.IgnoreCase); } }
4. OutputCache的跨用户语言隔离优化
为了避免新用户看到其他用户的缓存页面,你可以在OutputCacheAttribute中添加VaryByCustom="culture",然后在Global.asax中实现自定义缓存区分逻辑:
public override string GetVaryByCustomString(HttpContext context, string custom) { if (custom.Equals("culture", StringComparison.OrdinalIgnoreCase)) { return Thread.CurrentThread.CurrentCulture.Name; } return base.GetVaryByCustomString(context, custom); }
内容的提问来源于stack exchange,提问作者DavidShukvani
相关产品推荐
相关产品推荐

