How to correctly canonicalize a URL in an ASP.NET MVC application?

后端 未结 3 865
借酒劲吻你
借酒劲吻你 2021-02-03 11:45

I\'m trying to find a good general purpose way to canonicalize urls in an ASP.NET MVC 2 application. Here\'s what I\'ve come up with so far:

// Using an authoriz         


        
3条回答
  •  面向向阳花
    2021-02-03 12:28

    MVC 5 and 6 has the option of generating lower case URL's for your routes. My route config is shown below:

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

    With this code, you should no longer need the canonicalize the URL's as this is done for you. One problem that can occur if you are using HTTP and HTTPS URL's and want a canonical URL for this. In this case, it's pretty easy to use the above approaches and replace HTTP with HTTPS or vice versa.

    Another problem is external websites that link to your site may omit the trailing slash or add upper-case characters and for this you should perform a 301 permanent redirect to the correct URL with the trailing slash. For full usage and source code, refer to my blog post and the RedirectToCanonicalUrlAttribute filter:

    /// 
    /// 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).
    /// 
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
    public class RedirectToCanonicalUrlAttribute : FilterAttribute, IAuthorizationFilter
    {
        private readonly bool appendTrailingSlash;
        private readonly bool lowercaseUrls;
    
        #region Constructors
    
        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// If set to true append trailing slashes, otherwise strip trailing 
        /// slashes.
        /// If set to true lower-case all URL's.
        public RedirectToCanonicalUrlAttribute(
            bool appendTrailingSlash, 
            bool lowercaseUrls)
        {
            this.appendTrailingSlash = appendTrailingSlash;
            this.lowercaseUrls = lowercaseUrls;
        } 
    
        #endregion
    
        #region Public Methods
    
        /// 
        /// Determines whether the HTTP request contains a non-canonical URL using , 
        /// if it doesn't calls the  method.
        /// 
        /// An object that encapsulates information that is required in order to use the 
        ///  attribute.
        /// The  parameter is null.
        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
    
        /// 
        /// Determines whether the specified URl is canonical and if it is not, outputs the canonical URL.
        /// 
        /// An object that encapsulates information that is required in order to use the 
        ///  attribute.
        /// The canonical URL.
        /// true if the URL is canonical, otherwise false.
        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;
        }
    
        /// 
        /// Handles HTTP requests for URL's that are not canonical. Performs a 301 Permanent Redirect to the canonical URL.
        /// 
        /// An object that encapsulates information that is required in order to use the 
        ///  attribute.
        /// The canonical URL.
        protected virtual void HandleNonCanonicalRequest(AuthorizationContext filterContext, string canonicalUrl)
        {
            filterContext.Result = new RedirectResult(canonicalUrl, true);
        }
    
        #endregion
    }
    

    Usage example to ensure all requests are 301 redirected to the correct canonical URL:

    filters.Add(new RedirectToCanonicalUrlAttribute(
        RouteTable.Routes.AppendTrailingSlash, 
        RouteTable.Routes.LowercaseUrls));
    

提交回复
热议问题