我正在尝试找到一种在ASP.NET MVC 2应用程序中规范化URL的通用方法。以下是迄今为止我想出的方法:
// Using an authorization filter because it is executed earlier than other filters
public class CanonicalizeAttribute : AuthorizeAttribute
{
public bool ForceLowerCase { get;set; }
public CanonicalizeAttribute()
: base()
{
ForceLowerCase = true;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
RouteValueDictionary values = ExtractRouteValues(filterContext);
string canonicalUrl = new UrlHelper(filterContext.RequestContext).RouteUrl(values);
if (ForceLowerCase)
canonicalUrl = canonicalUrl.ToLower();
if (filterContext.HttpContext.Request.Url.PathAndQuery != canonicalUrl)
filterContext.Result = new PermanentRedirectResult(canonicalUrl);
}
private static RouteValueDictionary ExtractRouteValues(AuthorizationContext filterContext)
{
var values = filterContext.RouteData.Values.Union(filterContext.RouteData.DataTokens).ToDictionary(x => x.Key, x => x.Value);
var queryString = filterContext.HttpContext.Request.QueryString;
foreach (string key in queryString.Keys)
{
if (!values.ContainsKey(key))
values.Add(key, queryString[key]);
}
return new RouteValueDictionary(values);
}
}
// Redirect result that uses permanent (301) redirect
public class PermanentRedirectResult : RedirectResult
{
public PermanentRedirectResult(string url) : base(url) { }
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.RedirectPermanent(this.Url);
}
}
现在我可以像这样标记我的控制器:
[Canonicalize]
public class HomeController : Controller { /* ... */ }
这似乎都工作得相当好,但我有以下几个问题:
我仍然需要将
CanonicalizeAttribute
添加到我想要规范化的每个控制器(或操作方法)中,但很难想到不想要此行为的情况。似乎应该有一种方法可以在整个站点范围内获得此行为,而不是逐个控制器。在过滤器中实现“强制小写”规则似乎是错误的。肯定有更好的方法将其合并到路由 URL 逻辑中,但是我想不出如何在我的路由配置中实现。我考虑为控制器和操作参数(以及任何其他字符串路由参数)添加
@"[a-z]*"
约束,但我认为这会导致路由无法匹配。而且,因为小写规则未在路由级别应用,所以可能会在页面中生成带有大写字母的链接,这似乎相当糟糕。
是否有什么明显的问题我忽略了?