18

在最新的 MVC 预览版中,我将此路由用于旧版 URL:

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/

我只希望第二个工作(对于 SEO)。此外,当我创建指向该页面的链接时,路由引擎会返回一个不带斜杠的 URL。

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

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

我相信可以通过使用 HTTP 处理程序执行 301 重定向到带有尾部斜杠的 URL 来解决第一个问题。但是,我想链接到带有斜杠的 URL,并且我希望不必用斜杠对版本进行硬编码。

任何人都知道如何强制路线使用斜杠?

4

7 回答 7

3

如果您在 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 生成了完整的链接。

于 2009-06-05T12:38:56.973 回答
3

我偶然发现了这篇博文:

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

今天早上在 StackOverflow 上遇到这个问题之前。那篇博文(来自这个问题的作者)有一个来自 Scott Hanselman 的这篇博文的引用,并回答了这个问题:

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

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

Scott 的回答建议使用 URL 重写。

于 2010-05-06T13:00:20.667 回答
2

编写链接时,应始终包含最后的斜杠。我不知道这是否适用于 mvc 框架(或一般的 URL 路由),但我知道对于静态资源,如果您不添加斜线,则会在请求完成两次时增加一点开销。

斜杠立即将 url 标识为指向目录。无需解析文件。

同样,我不相信这适用于您使用 URL 路由时,但我没有调查过。

在此处查看有关斜杠的文章

编辑:考虑到这一点......我认为最好不要使用斜线,而不是尝试包含它。当您使用 url 路由时,您正在使用 URL 直接路由到资源。与使用 index.html 或 default.aspx 指向目录相反,您指向的是特定文件。

我知道差异是微妙的,但对于路由 URL,最好坚持使用非斜线,而不是与框架抗争。

当您实际指向一个目录时,请严格使用尾部斜杠。我想我想如果你真的不喜欢它,你可以每次都在末尾附加一个斜杠。

于 2008-11-08T05:57:49.690 回答
2

MVC 5 和 6 可以选择为您的路由生成小写 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,则可能会出现一个问题。在这种情况下,很容易使用上述方法并将 HTTP 替换为 HTTPS,反之亦然。

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

确保所有请求都被 301 重定向到正确的规范 URL 的使用示例:

filters.Add(new RedirectToCanonicalUrlAttribute(
    RouteTable.Routes.AppendTrailingSlash, 
    RouteTable.Routes.LowercaseUrls));
于 2015-07-23T08:47:37.253 回答
1

这是我的 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));
    }
于 2010-08-23T05:25:28.173 回答
1

I think you are solving the problem from the wrong angle. The reason given for wanting to force the single url is for SEO. I believe this refers to getting a duplicate content penalty because search engines consider this two URLs with the same content.

Another solution to this problem then is to add a CANONICAL tag to your page which tells the search engines which is the "official" url for the page. Once you do that you no longer need to force the URLs and search engines will not penalize you and will route search results to your official url.

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

于 2015-02-08T15:00:49.623 回答
1

这里是 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);
        //---   
    }
于 2010-02-01T04:13:16.850 回答