【问题标题】:How do I generically implement URL-rewriting in a MapRoute method?我一般如何在 MapRoute 方法中实现 URL 重写?
【发布时间】:2014-01-06 05:57:59
【问题描述】:


我正在尝试将 URL 从 C# 的 Pascal-case 重写为对 SEO 友好的格式。
例如,我希望像 /User/Home/MyJumbledPageName 这样的东西看起来像这样:

/user/home/my-jumbled-page-name // lower-case, and words separated by dashes


这是我在 URL 中转换每个“令牌”的方法:

public static string GetSEOFriendlyToken(string token)
{
    StringBuilder str = new StringBuilder();

    for (int i = 0, len = token.Length; i < len; i++)
    {
        if (i == 0)
        {
            // setting the first capital char to lower-case:
            str.Append(Char.ToLower(token[i]));
        }
        else if (Char.IsUpper(token[i]))
        {
            // setting any other capital char to lower-case, preceded by a dash:
            str.Append("-" + Char.ToLower(token[i]));
        }
        else
        {
            str.Append(token[i]);
        }
    }
    return str.ToString();
}


...在我的 RouteConfig.cs 根文件中,我映射了这些路由:

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

    // without this the first URL is blank:
    routes.MapRoute(
        name: "Default_Home",
        url: "index", // hard-coded?? it works...
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );

    routes.MapRoute(
        name: "Home",
        // the method calls here do not seem to have any effect:
        url: GetSEOFriendlyToken("{action}") + "/" + GetSEOFriendlyToken("{id}"),
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}


使用此代码,/AboutTheAuthor 等 URL 转换为我想要的,即 /about-the-author

似乎我的方法调用被忽略了,这里发生了什么?实现这一点的传统方法是什么?

【问题讨论】:

    标签: c# url-rewriting seo asp.net-mvc-routing


    【解决方案1】:

    你必须定义你自己的RouteBase类或子类Route

    public class SeoFriendlyRoute : Route
    {
        private readonly string[] _valuesToSeo;
    
        public SeoFriendlyRoute(string url, RouteValueDictionary defaults, IEnumerable<string> valuesToSeo, RouteValueDictionary constraints = null, RouteValueDictionary dataTokens = null, IRouteHandler routeHandler = null)
            : base(url, defaults, constraints ?? new RouteValueDictionary(), dataTokens ?? new RouteValueDictionary(), routeHandler ?? new MvcRouteHandler())
        {
            if (valuesToSeo == null) { throw new ArgumentNullException("valuesToSeo"); }
            _valuesToSeo = valuesToSeo.ToArray();
        }
        public override RouteData GetRouteData(HttpContextBase httpContext)
        {
            var routeData = base.GetRouteData(httpContext);
            if (routeData != null)
            {
                foreach (var key in _valuesToSeo)
                {
                    if (routeData.Values.ContainsKey(key))
                    {
                        routeData.Values[key] = GetActualValue((string)routeData.Values[key]);
                    }
                }
            }
            return routeData;
        }
        public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
        {
            var seoFriendyValues = new RouteValueDictionary(values);
            foreach (var key in _valuesToSeo)
            {
                if (seoFriendyValues.ContainsKey(key))
                {
                    seoFriendyValues[key] = GetSeoFriendlyValue((string)seoFriendyValues[key]);
                }
            }
            return base.GetVirtualPath(requestContext, seoFriendyValues);
        }
    
        private string GetSeoFriendlyValue(string actualValue)
        {
            //your method
            StringBuilder str = new StringBuilder();
            for (int i = 0, len = actualValue.Length; i < len; i++)
            {
                if (i == 0)
                {
                    str.Append(Char.ToLower(actualValue[i]));
                }
                else if (Char.IsUpper(actualValue[i]))
                {
                    str.Append("-" + Char.ToLower(actualValue[i]));
                }
                else
                {
                    str.Append(actualValue[i]);
                }
            }
            return str.ToString();
        }
    
        private static string GetActualValue(string seoFriendlyValue)
        {
            //action name is not case sensitive
            //one limitation is the dash can be anywhere but the action will still be resolved
            // /my-jumbled-page-name is same as /myjumbled-pagename
            return seoFriendlyValue.Replace("-", string.Empty); 
        }
    }
    

    用法

    routes.Add("Default", new SeoFriendlyRoute(
        url: "{controller}/{action}/{id}",
        valuesToSeo: new string[] { "action", "controller" },
        defaults: new RouteValueDictionary(new { controller = "Home", action = "Index", id = UrlParameter.Optional }))
    );
    

    【讨论】:

    • 感谢@LostInComputer!这对我来说有点远,所以我一直在研究你的代码。我了解到您正在使用“空合并运算符”??,它是三元运算符的简写。此外,我看到您正在使用在 Route 上调用 base 所需的构造函数参数,在 Visual Studio 中通过右键单击 Route 并选择“Peek Definition”找到。否则,你怎么知道要覆盖GetRouteDataGetVirtualPath?这些方法在框架中的什么位置被调用?
    • 我的其他问题是 GetActualValue 方法是此逻辑的要求,以及将此类放在项目中的好地方在哪里?
    • GetVirtualPath 被 MVC 调用以生成 URL,就像调用 @Html.Action 时一样。 GetRouteData 是相反的,它由 MVC 调用以从 URL 获取控制器操作。我怎么知道的?我有时会浏览开源的 MVC 源代码。
    • 好的,所以我现在看到GetActualValue 方法 要求——重写的GetRouteData 方法需要任何“子目录”标记 破折号以便 MVC 可以在 RouteData 中找到相应的控制器和/或操作。此外,根据代码状态中的 cmets,MVC 匹配此类“子目录”字符串不区分大小写。
    • 是的。我有点被骗了,因为GetActualValue 只是删除了所有破折号,无论位置如何,所以/index-dash/indexd-ash/indexdash 都被路由到相同的操作。
    【解决方案2】:


    作为参考,我发现了如何使 @LostInComputer 的代码也适用于区域。用于区域的类必须实现IRouteWithArea,以便context.Routes.Add 在区域的RegisterArea 方法中工作。

    这是一个可用于区域的通用类(它扩展了上述SEOFriendlyRoute 类):

    public class AreaSEOFriendlyRoute : SEOFriendlyRoute, IRouteWithArea
    {
        private readonly string _areaName;
    
        // constructor:
        public AreaSEOFriendlyRoute(string areaName, string url, RouteValueDictionary defaults, IEnumerable<string> valuesToSeo,
            RouteValueDictionary constraints = null, RouteValueDictionary dataTokens = null, IRouteHandler routeHandler = null)
            : base(url, defaults, valuesToSeo, constraints, dataTokens, routeHandler)
        {
            this._areaName = areaName;
        }
    
        // implemented from IRouteWithArea:
        public string Area
        {
            get
            {
                return this._areaName;
            }
        }
    }
    


    ...及其用法:

    public override void RegisterArea(AreaRegistrationContext context)
    {
        context.Routes.Add("Example", new AreaSEOFriendlyRoute(
            areaName: this.AreaName,
            url: "my-area/{action}/{id}",
            valuesToSeo: new string[] { "action", "controller" },
            defaults: new RouteValueDictionary(new { controller = "MyController", action = "MyDefaultPage", id = UrlParameter.Optional }))
        );
    }
    


    请注意,我在调用context.Routes.Add 时传递了一个额外的参数,该参数定义为areaName。构造函数中的同名形参用于从Area方法返回这个值,该方法是从IRouteWithArea实现的。


    所以一个链接,如:
    @Html.ActionLink("my link text", "MyJumbledPageName", "MyController",
    new { area = "MyArea" }, null)
    


    ...将导致 URL my-area/my-jumbled-page-name。另请注意,url 中前面的“my-area”是通过在路由的url 参数中硬编码获得的。

    【讨论】:

      猜你喜欢
      • 2012-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-30
      • 1970-01-01
      • 2013-03-05
      • 1970-01-01
      • 2010-09-12
      相关资源
      最近更新 更多