2008-09-30 9 views
15

W najnowszym MVC podglądu używam tej trasy dla starszych URL:doczepiany ukośnik na trasie ASP.NET MVC

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

Problemem jest to, że oba działają te adresu URL: http://example.com/Firefox-Extension http://example.com/Firefox-Extension/

Chcę tylko drugi do pracy (dla SEO). Ponadto, gdy utworzę link do tej strony, mechanizm routingu przekazuje mi URL bez ukośnego slasha.

Jest to kod używam do generowania link:

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

wierzę można rozwiązać pierwszy problem, korzystając z obsługi HTTP zrobić przekierowanie 301 do adresu URL z ukośnikiem. Jednak chcę połączyć się z adresem URL z końcowym ukośnikiem i mam nadzieję, że nie będę musiał mocno kodować wersji za pomocą ukośnika.

Ktoś wie, jak zmusić trasę do użycia ukośnika?

Odpowiedz

2

Podczas pisania linków zawsze należy dołączyć ostateczny ukośnik. Nie wiem, czy to odnosi się do frameworka mvc (lub ogólnie do routingu URL), ale wiem, że w przypadku zasobów statycznych, jeśli nie wstawisz slasha, dodaj nieznaczny narzut, ponieważ żądanie zostanie wykonane dwukrotnie.

Ukośnik natychmiast identyfikuje adres URL jako katalog. Nie trzeba parsować plików.

Ponownie, nie sądzę, aby miało to zastosowanie, gdy korzystasz z routingu adresów URL, ale nie przyjrzałem się temu.

Sprawdź HERE for an article about the trailing slash

edit: Na myślenie o tym ... Myślę, że to prawdopodobnie lepiej zostawić wyłączyć ukośnik, zamiast próbować umieścić go. Podczas korzystania z routingu adresu URL używasz adresu URL do kierowania bezpośrednio do zasobu. Zamiast wskazywać na katalog z index.html lub default.aspx, wskazujesz konkretny plik.

Wiem, że różnica jest subtelna, ale lepiej będzie trzymać się niezatwierdzonych adresów URL, niż walczyć ze strukturą.

Użyj ściśle ukośnego slasha, gdy faktycznie wskazujesz katalog. Sądzę, że za każdym razem możesz dodać kres do końca, jeśli naprawdę ci się nie podoba.

+1

zgadzam, dla nowych obiektów tak bym to zrobił. Problem polega na tym, że mam istniejącą witrynę, którą próbuję przekonwertować. –

+5

Prawdopodobnie nie spędziłbym na nim zbyt wiele czasu. Domyślam się, że w tych dniach Google jest wystarczająco inteligentny, aby nie dać ci trafienia SEO. – Armstrongest

4

Jeśli masz opaskę na RouteLink niż jest to łatwe rozwiązanie problemu. Na przykład, miałem metody wrapper 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); 
     //--- 
     } 

Jak widać użyłem parametry do generowania URL pierwszy. Następnie dodałem "/" na końcu adresu URL. a następnie wygenerowałem pełny link za pomocą tego adresu URL.

1

Tutaj przeciążenie dla 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); 
     //--- 
    } 
3

zdarzyło mi się na ten blogu:

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

rano przed uruchomieniem na to pytanie na StackOverflow . Że blogu (od autora tej kwestii) ma trackback do tego blogu Scott Hanselman z odpowiedzią na to pytanie:

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

Byłem zaskoczony, aby znaleźć żadnego związku z tutaj, aby tam jeszcze, więc właśnie to dodałem. :)

Odpowiedź Scotta sugeruje użycie Przepisywania URL.

+0

+1 dobry wkład – Maslow

1

Oto moja wersja dla 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)); 
    } 
1

Myślę, że rozwiązanie problemu z niewłaściwym kątem. Powodem, dla którego chcemy wymusić pojedynczy adres URL, jest SEO. Uważam, że odnosi się to do otrzymania podwójnej kary za treści, ponieważ wyszukiwarki uwzględniają te dwa adresy URL z taką samą zawartością.

Kolejnym rozwiązaniem tego problemu jest dodanie do strony tagu CANONICAL, który informuje wyszukiwarki, że jest "oficjalnym" adresem URL strony. Gdy to zrobisz, nie będziesz już zmuszał adresów URL, a wyszukiwarki nie będą Cię karać i przekierują wyniki wyszukiwania na Twój oficjalny URL.

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

0

MVC 5 i 6 ma możliwość generowania małe litery URL do swoich tras. Konfigurację trasy pokazano poniżej:

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

Kod ten nie wymaga już kanonalizacji adresów URL, ponieważ jest to zrobione za Ciebie. Jeden problem, który może wystąpić, jeśli używasz adresów URL HTTP i HTTPS i chcesz mieć kanoniczny adres URL. W tym przypadku dość łatwo jest zastosować powyższe metody i zastąpić HTTP HTTPS lub odwrotnie.

Innym problemem jest to, że zewnętrzne strony internetowe, które prowadzą do Twojej witryny, mogą pomijać ukośny ukośnik lub dodawać wielkie litery, a dla tego powinieneś wykonać stałe przekierowanie 301 na prawidłowy URL z końcowym ukośnikiem. Do pełnego wykorzystania i kodu źródłowego, odnoszą się do mojego blog post a RedirectToCanonicalUrlAttribute filtra: przykład

/// <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 
} 

Wykorzystanie aby wszystkie wnioski są 301 przekierowany do właściwego kanonicznym adresem URL:

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