代码之家  ›  专栏  ›  技术社区  ›  Jason Young

ASP.NET MVC路由上的尾随斜杠

  •  15
  • Jason Young  · 技术社区  · 16 年前

    routes.MapRoute(
    "Legacy-Firefox", // Route name
    "Firefox-Extension/", // URL with parameters
    new { controller = "Home", action = "Firefox", id = "" } // Parameter defaults
    );
    

    问题是这两个URL的工作: http://example.com/Firefox-Extension http://example.com/Firefox-Extension/

    我只想第二个工作(搜索引擎优化)。另外,当我创建指向该页的链接时,路由引擎会返回一个没有斜杠的URL。

    这是我用来生成链接的代码:

    <%= Html.ActionLink("Firefox Extension", "Firefox", "Home")%>
    

    有人知道如何强制路由使用尾随斜杠吗?

    7 回复  |  直到 8 年前
        1
  •  3
  •   Murad X    15 年前

    如果你在RouteLink上有一个包装器,那么这个问题就很容易解决了。 例如,我有一个包装方法RouteLinkEx:

    public static string RouteLinkEx(this HtmlHelper helper,string text,string routeName,RouteValueDictionary rvd,object htmlAttributes)
          {
    
          UrlHelper uh = new UrlHelper(helper.ViewContext.RequestContext,helper.RouteCollection);
          // Add trailing slash to the url of the link
          string url = uh.RouteUrl(routeName,rvd) + "/";
          TagBuilder builder = new TagBuilder("a")
          {
            InnerHtml = !string.IsNullOrEmpty(text) ? HttpUtility.HtmlEncode(text) : string.Empty
          };
          builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
          builder.MergeAttribute("href",url);
          return builder.ToString(TagRenderMode.Normal);
          //---  
          }
    

    如您所见,我首先使用参数生成URL。然后我在URL的末尾加了“/”。然后我用这些URL生成了完整的链接。

        2
  •  3
  •   Michael Maddox    14 年前

    我偶然看到一篇博文:

    http://www.ytechie.com/2008/10/aspnet-mvc-what-about-seo.html

    http://www.hanselman.com/blog/ASPNETMVCAndTheNewIIS7RewriteModule.aspx

    我很惊讶地发现从这里到那里还没有链接,所以我添加了它。:)

    斯科特的回答建议使用URL重写。

        3
  •  2
  •   Armstrongest    16 年前

    斜杠立即将url标识为指向目录。不需要分析文件。

    支票 HERE for an article about the trailing slash

    编辑:

    我知道两者之间的区别很微妙,但对于路由url,最好坚持使用非斜杠,而不是与框架抗争。

        4
  •  1
  •   Sky    15 年前

    这里是RouteLinkEx的重载(HtmlHelper,string,string,object)

            public static string RouteLinkEx(this HtmlHelper helper, string text, string routeName, object routeValues)
        {
    
            UrlHelper uh = new UrlHelper(helper.ViewContext.RequestContext);
    
            // Add trailing slash to the url of the link 
            string url = uh.RouteUrl(routeName, routeValues) + "/";
            TagBuilder builder = new TagBuilder("a")
            {
                InnerHtml = !string.IsNullOrEmpty(text) ? HttpUtility.HtmlEncode(text) : string.Empty
            };
            //builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
            builder.MergeAttribute("href", url);
            return builder.ToString(TagRenderMode.Normal);
            //---   
        }
    
        5
  •  1
  •   Sergey    14 年前

    这是我的ASP.NET MVC 2版本

        public static MvcHtmlString RouteLinkEx(this HtmlHelper helper, string text, RouteValueDictionary routeValues)
        {
            return RouteLinkEx(helper, text, null, routeValues, null);
        }
    
        public static MvcHtmlString RouteLinkEx(this HtmlHelper htmlHelper, string text, string routeName, RouteValueDictionary routeValues, object htmlAttributes)
        {
            string url = UrlHelper.GenerateUrl(routeName, null, null, null, null, null, routeValues, htmlHelper.RouteCollection, htmlHelper.ViewContext.RequestContext, false);
    
            var builder = new TagBuilder("a")
            {
                InnerHtml = !string.IsNullOrEmpty(text) ? HttpUtility.HtmlEncode(text) : string.Empty
            };
            builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
            // Add trailing slash to the url of the link
            builder.MergeAttribute("href", url + "/");
            return MvcHtmlString.Create(builder.ToString(TagRenderMode.Normal));
        }
    
        6
  •  1
  •   DavidJBerman    10 年前

    这个问题的另一个解决方案是在页面中添加一个规范的标签,告诉搜索引擎哪个是页面的“官方”url。一旦你这样做,你不再需要强迫网址和搜索引擎将不会惩罚你,并将路由搜索结果到你的官方网址。

    https://support.google.com/webmasters/answer/139066?hl=en

        7
  •  0
  •   Muhammad Rehan Saeed    9 年前

    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和HTTP s URL,并且希望使用规范的URL,则可能会出现一个问题。在这种情况下,使用上述方法并将HTTP替换为HTTP s非常容易,反之亦然。

    另一个问题是链接到您网站的外部网站可能会省略尾随斜杠或添加大写字符,为此,您应该使用尾随斜杠执行301永久重定向到正确的URL。有关完整用法和源代码,请参阅 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
    }
    

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