How do I generically implement URL-rewriting in a MapRoute method?

一世执手 提交于 2019-12-31 03:22:06

问题



I am attempting to rewrite URL's from C#'s Pascal-case to SEO-friendly format.
For example, I want something like /User/Home/MyJumbledPageName to look like this:

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


Here is my method for converting each "token" in the 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();
}


...and in my RouteConfig.cs file in the root, I have mapped these routes:

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 }
    );
}


With this code, a URL such as /AboutTheAuthor is not converted to what I want, which would be /about-the-author.

It seems that my method call is ignored, what's happening here? And what's the conventional way to implement this?


回答1:


You have to define your own RouteBase class or subclass 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); 
    }
}

Usage

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 }))
);



回答2:



For reference, I have found how to make @LostInComputer 's code work for areas as well. The class used for an area must implement IRouteWithArea for context.Routes.Add to work in an area's RegisterArea method.

Here's a generic class that can be used for areas (it extends the above SEOFriendlyRoute class):

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;
        }
    }
}


...and its' usage:

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 }))
    );
}


Note that I am passing an extra argument when calling context.Routes.Add, which is defined as areaName. The formal parameter of the same name in the constructor is used to return this value from the Area method, in which is implemented from IRouteWithArea.


So a link such as:
@Html.ActionLink("my link text", "MyJumbledPageName", "MyController",
new { area = "MyArea" }, null)


...would result in the url my-area/my-jumbled-page-name. Also note that the preceding "my-area" in the url is obtained by hard-coding this in the route's url argument.



来源:https://stackoverflow.com/questions/20651661/how-do-i-generically-implement-url-rewriting-in-a-maproute-method

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!