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

為{幸葍}努か 提交于 2019-12-02 00:09:26

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


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.

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