代码之家  ›  专栏  ›  技术社区  ›  Bennor McCarthy

如何正确规范化ASP.NETMVC应用程序?

  •  9
  • Bennor McCarthy  · 技术社区  · 14 年前

    我正试图找到一个好的通用方法来规范化一个ASP.NETMVC2应用程序。到目前为止,我想到的是:

    // 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 { /* ... */ }
    

    1. 我还得加上 CanonicalizeAttribute 对于每一个我想要规范化的控制器(或动作方法),当很难想到我不想要这种行为的情况时。似乎应该有一种方法让这种行为在整个网站范围内发生,而不是一次一个控制器。

    2. @"[a-z]*" 对控制器和操作参数(以及任何其他字符串路由参数)的约束,但我认为这将导致路由不匹配。另外,因为小写规则没有在路由级别应用,所以在我的页面中可能会生成包含大写字母的链接,这看起来很糟糕。

    有什么明显的东西我忽略了吗?

    3 回复  |  直到 12 年前
        1
  •  20
  •   Jørn Schou-Rode dscher    13 年前

    对于违约的放松性质,我也有同样的“渴望”ASP.NETMVC路由,忽略字母大小写,尾部斜杠,等等。像你一样,我想要一个通用的解决方案,最好是作为我的应用程序路由逻辑的一部分。

    在网上到处搜索,没有找到有用的图书馆后,我决定自己滚一个。结果是 Canonicalize

    您可以通过NuGet安装库: Install-Package Canonicalize

    在您的路线登记中: routes.Canonicalize().Lowercase();

    除了小写以外,包中还包括其他几种URL规范化策略。力 www 域名前缀开或关,强制一个特定的主机名,一个尾随斜杠,等等。它也很容易添加自定义的URL规范化策略,我非常愿意接受补丁添加更多的策略到“官方” 规范化 分配。

    我希望你或其他任何人会觉得这很有帮助,即使这个问题已经有一年了:)

        2
  •  8
  •   Muhammad Rehan Saeed    9 年前

    MVC5和MVC6可以为您的路由生成小写URL。我的路由配置如下:

    public static class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            // Imprive SEO by stopping duplicate URL's due to case or trailing slashes.
            routes.AppendTrailingSlash = true;
            routes.LowercaseUrls = true;
    
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
        }
    }
    

    有了这段代码,您应该不再需要规范化URL,因为这是为您所做的。如果您使用的是HTTP和HTTPS URL,并且需要一个规范的URL,则可能会出现一个问题。在这种情况下,使用上述方法并用HTTPS替换HTTP非常容易,反之亦然。

    另一个问题是外部网站链接到您的网站可能会省略尾随斜杠或添加大写字符,为此,你应该执行301永久重定向到正确的网址与尾随斜杠。有关完整用法和源代码,请参阅我的 blog post 以及 RedirectToCanonicalUrlAttribute 过滤器:

    /// <summary>
    /// To improve Search Engine Optimization SEO, there should only be a single URL for each resource. Case 
    /// differences and/or URL's with/without trailing slashes are treated as different URL's by search engines. This 
    /// filter redirects all non-canonical URL's based on the settings specified to their canonical equivalent. 
    /// Note: Non-canonical URL's are not generated by this site template, it is usually external sites which are 
    /// linking to your site but have changed the URL case or added/removed trailing slashes.
    /// (See Google's comments at http://googlewebmastercentral.blogspot.co.uk/2010/04/to-slash-or-not-to-slash.html
    /// and Bing's at http://blogs.bing.com/webmaster/2012/01/26/moving-content-think-301-not-relcanonical).
    /// </summary>
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
    public class RedirectToCanonicalUrlAttribute : FilterAttribute, IAuthorizationFilter
    {
        private readonly bool appendTrailingSlash;
        private readonly bool lowercaseUrls;
    
        #region Constructors
    
        /// <summary>
        /// Initializes a new instance of the <see cref="RedirectToCanonicalUrlAttribute" /> class.
        /// </summary>
        /// <param name="appendTrailingSlash">If set to <c>true</c> append trailing slashes, otherwise strip trailing 
        /// slashes.</param>
        /// <param name="lowercaseUrls">If set to <c>true</c> lower-case all URL's.</param>
        public RedirectToCanonicalUrlAttribute(
            bool appendTrailingSlash, 
            bool lowercaseUrls)
        {
            this.appendTrailingSlash = appendTrailingSlash;
            this.lowercaseUrls = lowercaseUrls;
        } 
    
        #endregion
    
        #region Public Methods
    
        /// <summary>
        /// Determines whether the HTTP request contains a non-canonical URL using <see cref="TryGetCanonicalUrl"/>, 
        /// if it doesn't calls the <see cref="HandleNonCanonicalRequest"/> method.
        /// </summary>
        /// <param name="filterContext">An object that encapsulates information that is required in order to use the 
        /// <see cref="RedirectToCanonicalUrlAttribute"/> attribute.</param>
        /// <exception cref="ArgumentNullException">The <paramref name="filterContext"/> parameter is <c>null</c>.</exception>
        public virtual void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException("filterContext");
            }
    
            if (string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.Ordinal))
            {
                string canonicalUrl;
                if (!this.TryGetCanonicalUrl(filterContext, out canonicalUrl))
                {
                    this.HandleNonCanonicalRequest(filterContext, canonicalUrl);
                }
            }
        }
    
        #endregion
    
        #region Protected Methods
    
        /// <summary>
        /// Determines whether the specified URl is canonical and if it is not, outputs the canonical URL.
        /// </summary>
        /// <param name="filterContext">An object that encapsulates information that is required in order to use the 
        /// <see cref="RedirectToCanonicalUrlAttribute" /> attribute.</param>
        /// <param name="canonicalUrl">The canonical URL.</param>
        /// <returns><c>true</c> if the URL is canonical, otherwise <c>false</c>.</returns>
        protected virtual bool TryGetCanonicalUrl(AuthorizationContext filterContext, out string canonicalUrl)
        {
            bool isCanonical = true;
    
            canonicalUrl = filterContext.HttpContext.Request.Url.ToString();
            int queryIndex = canonicalUrl.IndexOf(QueryCharacter);
    
            if (queryIndex == -1)
            {
                bool hasTrailingSlash = canonicalUrl[canonicalUrl.Length - 1] == SlashCharacter;
    
                if (this.appendTrailingSlash)
                {
                    // Append a trailing slash to the end of the URL.
                    if (!hasTrailingSlash)
                    {
                        canonicalUrl += SlashCharacter;
                        isCanonical = false;
                    }
                }
                else
                {
                    // Trim a trailing slash from the end of the URL.
                    if (hasTrailingSlash)
                    {
                        canonicalUrl = canonicalUrl.TrimEnd(SlashCharacter);
                        isCanonical = false;
                    }
                }
            }
            else
            {
                bool hasTrailingSlash = canonicalUrl[queryIndex - 1] == SlashCharacter;
    
                if (this.appendTrailingSlash)
                {
                    // Append a trailing slash to the end of the URL but before the query string.
                    if (!hasTrailingSlash)
                    {
                        canonicalUrl = canonicalUrl.Insert(queryIndex, SlashCharacter.ToString());
                        isCanonical = false;
                    }
                }
                else
                {
                    // Trim a trailing slash to the end of the URL but before the query string.
                    if (hasTrailingSlash)
                    {
                        canonicalUrl = canonicalUrl.Remove(queryIndex - 1, 1);
                        isCanonical = false;
                    }
                }
            }
    
            if (this.lowercaseUrls)
            {
                foreach (char character in canonicalUrl)
                {
                    if (char.IsUpper(character))
                    {
                        canonicalUrl = canonicalUrl.ToLower();
                        isCanonical = false;
                        break;
                    }
                }
            }
    
            return isCanonical;
        }
    
        /// <summary>
        /// Handles HTTP requests for URL's that are not canonical. Performs a 301 Permanent Redirect to the canonical URL.
        /// </summary>
        /// <param name="filterContext">An object that encapsulates information that is required in order to use the 
        /// <see cref="RedirectToCanonicalUrlAttribute" /> attribute.</param>
        /// <param name="canonicalUrl">The canonical URL.</param>
        protected virtual void HandleNonCanonicalRequest(AuthorizationContext filterContext, string canonicalUrl)
        {
            filterContext.Result = new RedirectResult(canonicalUrl, true);
        }
    
        #endregion
    }
    

    确保将所有请求重定向到正确的规范URL的用法示例:

    filters.Add(new RedirectToCanonicalUrlAttribute(
        RouteTable.Routes.AppendTrailingSlash, 
        RouteTable.Routes.LowercaseUrls));
    
        3
  •  6
  •   Andrew    14 年前

    Full blog post )

    将其添加到标题部分的母版页中,如下所示:

    <%=ViewData["CanonicalURL"] %>
    <!--Your other head info here-->
    

    创建过滤器属性(规范URL.cs):

    public class CanonicalURL : ActionFilterAttribute
    {
        public string Url { get; private set; }
    
        public CanonicalURL(string url)
        {
           Url = url;
        }
    
        public override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            string fullyQualifiedUrl = "http://www.example.com" + this.Url;
            filterContext.Controller.ViewData["CanonicalUrl"] = @"<link rel='canonical' href='" + fullyQualifiedUrl + "' />";
            base.OnResultExecuting(filterContext);
        }
    }
    

    从你的行动来看:

    [CanonicalURL("Contact-Us")]
    public ActionResult Index()
     {
          ContactFormViewModel contact = new ContactFormViewModel(); 
          return View(contact);
    }
    

    推荐文章