问题
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