Отправляет email-рассылки с помощью сервиса Sendsay

.NET: Записки программиста

  Все выпуски  

URL rewriting в ASP.NET для поисковой оптимизации (SEO) (окончание)



URL rewriting в ASP.NET для поисковой оптимизации (SEO) (окончание)


И снова я рад вас видеть!
Перед тем, как я продолжу эту статью, я хотел бы напомнить, что эта рассылка является всего лишь частичным отражением сайта .NET: Записки программиста. Частичным - потому что рассылка не может заменить собой сайт, на котором найдется место и разделу - для статей, и блогу - для коротких заметок, и галерее - для полезных утилит или смешных историй.

Так что, если вам интересно, заходите на сайт .NET: Записки программиста, подписывайтесь на блог - и вы будете узнавать о любых изменениях сайта, как только они будут происходить.


А теперь, вернемся к нашей истории ...

Если вы еще помните :), мы собирались разобраться, как реализовать url rewriting своими руками. Все сводится к использованию метода HttpContext.RewritePath. При помощи параметра string filePath можно указать, какая страница нашего веб-приложения будет выполнятся на самом деле. RewritePath содержит несколько переопределенных вариантов, которые позволяют изменить такие параметры запроса как query string или path info.

Прим. Для ASP.NET, Path info - это та часть url, которая может находиться после расширения страницы до query string. Например в Url:
http://www.sport.news.ru/tennis/news.aspx/topnews?type=12&page=2
path info - "/topnews". С точки зрения поисковиков path info - это просто часть Url, так что для них Urls:
  • http://www.sport.news.ru/tennis/news.aspx/topnews
  • http://www.sport.news.ru/tennis/news.aspx/raiting
  • http://www.sport.news.ru/tennis/news.aspx/comments
являются разными страницами. Для ASP.NET это будет одна и та же страница с разными значениями свойства PathInfo в объекте HttpRequest.


Итак, что для того чтобы перенаправить один url на другой, мы должны:
  1. Создать и зарегистрировать в нашем приложении HttpModule.

  2. Добавить в этом модуле свой обработчик к событию приложения, в котором мы будем выполнять перенаправление (если вы используете Forms Authentication, лучше выбрать AuthorizeRequest, если Windows Authentication - подойдет и BeginRequest).

  3. В обработчике выполнить перенаправление Url при помощи вызова RewritePath.

  4. Разобраться с проблемой, связанная с аттрибутом "action" тега <form>. В нем сохраняется url текущей страницы, именно его вызовет браузер во время post-back. Уже догадались, что произойдет? Допустим, мы вызвали страницу http://news.ru/tennis/raiting/ и перенаправили его на raiting.aspx?section=12. Во время отработки страницы, в аттрибут "action" будет занесено именно "raiting.aspx". После того как страница отдастся браузеру и пользователь нажмет какую-нибудь кнопку, post-back пойдет именно на "raiting.aspx" (и этот url высветится в строке запроса браузера), а вовсе не http://news.ru/tennis/raiting/, как мы хотели.

    Так что нам прийдется подменять значение этого аттрибута.
Вот как это может выглядеть:

// UrlRewriterModule.cs file
public class UrlRewriterModule : IHttpModule
{
  public void Init(HttpApplication context)
  {
    context.AuthorizeRequest += new EventHandler(OnAuthorizeRequest);
  }
  public void Dispose()
  {
  }
  void OnAuthorizeRequest(object sender, EventArgs e)
  {
    HttpApplication app = (HttpApplication)sender;
    String oldUrl = app.Request.RawUrl;
    String newUrl = ...;
    HttpContext.Current.RewritePath(newUrl);
  }
}

// web.config file
<configuration>
  <system.web>
    <httpModules>
        <add name="UrlRewriter" type="MyAssembly.UrlRewriterModule"/>
        . . .

Осталось корректно обработать аттрибут "action". Раньше, в ASP.NET 1.1, разработчикам приходилось переопределять элемент управления Form, а это значит - менять код всех страниц, заменяя на них стандартный <asp:form>. Кроме того, иногда это приводило к проблемам в Visual Studio WYSIWYG designer.

PostBack Rewriting В ASP.NET 2.0 все можно сделать проще, благодаря использованию ASP.NET 2.0 CSS Control Adapters. Эта технология позволяет связывать ваши классы с какими-то типами элементов управления. Связанные классы могут изменять html, генерируемый элементами управления в процессе рендеринга. Более подробно я описывал эту технологию в предыдущей статье "Как совместить заказной дизайн и ASP.NET web-сайты".

То есть, мы создаем control adapter для класса HtmlForm, переопределяем в нем метод Render и выполняем rendering используя собственный класс, унаследованный от HtmlTextWriter и отличающися от него только алгоритмом добавления аттрибута "action".

Чтобы упростить себе жизнь, воспользуемся одним малоизвестным трюком - если аттрибут "action" вообще не указан - браузер будет использовать для post-back url, указанный в строке запроса, что нас вполне устраивает. Итак, пишем код для form control adapter:

// HtmlFormControlAdapter.cs file
public class HtmlFormControlAdapter : ControlAdapter
{
  protected override void Render(HtmlTextWriter writer)
  {
    base.Render(new RewriteFormHtmlTextWriter(writer));
  }
}

// RewriteFormHtmlTextWriter.cs file
public class RewriteFormHtmlTextWriter : HtmlTextWriter
{
  public RewriteFormHtmlTextWriter(HtmlTextWriter writer) : base(writer)
  {
    this.InnerWriter = writer.InnerWriter;
  }
  public RewriteFormHtmlTextWriter(System.IO.TextWriter writer) : base(writer)
  {
    this.InnerWriter = writer;
  }
  public override void WriteAttribute(string name, string value, bool fEncode)
  {
    if (name == "action")
    {
      value = String.Empty;
    }

    base.WriteAttribute(name, value, fEncode);
  }
}

Теперь осталось связать наш control adapter с элементом HtmlForm. Создаем класс Form.browser с таким вот содержимым:

<browsers>
  <browser refID="Default">
    <controlAdapters>
      <adapter adapterType="MyAssembly.HtmlFormControlAdapter, MyAssembly" controlType="System.Web.UI.HtmlControls.HtmlForm" />
    </controlAdapters>
  </browser>
</browsers>

и помещаем его в служебную папку App_Browsers нашего приложения.

Вуаля! Теперь значение аттрибута "action" всегда пустое и post-back страниц при любой реализации rewriting должен работать нормально.
Search Engines А теперь пришло время понять, зачем мы разбирали нелегкий жизненный путь запросов :) Вспомните, кроме всего прочего, мы хотели перенаправлять urls типа http://news.ru/fooball/. Все ли мы сделали, чтобы работать с такими запросами? Уже догадались, в чем дело? Правильно! Все, о чем мы только что говорили, реализовано в нашем веб-приложении на ASP.NET. Но для того, чтобы ему выполнить rewriting, он должен как минимум получить управление. По умолчанию IIS настроен так, чтобы передавать управление ASP.NET при запросах страниц с расширением .aspx (и еще некоторых расширений). Если же он получит запрос без расширения, то будет думать, что это каталог и попробует определить для этого пути документ по умолчанию - то есть сделает вовсе не то, что нам нужно.

Поэтому, чтобы мы могли обрабатывать такие запросы, нам прийдется немного повозится с настройками. Для IIS 6.0 мы воспользуемся "wildcard application mappings". Если в диалоге "Application Configuration" (см. рис. 3) мы добавим aspnet_isapi.dll в список приложений, мы будем получать управление при каждом запросе к IIS, вне зависимости от расширения файла.

Для IIS 7.0 все еще проще. Вам не понадобиться менять его настройки, для этого достаточно изменить конфигурационный файл приложения. IIS 7.0 позволяет выполнять модули ASP.NET как будто бы это его собственные модули. Измените web.config следующим образом:

<configuration>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <add name="UrlRewriter" type="MyAssembly.UrlRewriterModule" />
      <validation validateIntegratedModeConfiguration="false" />
    </modules>
  </system.webServer>
</configuration>

Аттрибут runAllManagedModulesForAllRequests позволяет выполнять наш ASP.NET HttpModule в качестве модуля IIS 7.0.

Вот теперь окончательно все, теперь мы знаем достаточно, чтобы реализовать SEO friendly карту сайта для news.ru.

А вот и время десерта! Понятно, что должны существовать библиотеки, реализующие все то, что мы так долго обсуждали. Причем существуют как полностью бесплатные решения, так и бесплатные Lite версии решений, которые в полной версии уже стоят денег. Но и Lite версии полностью функциональны и вполне покроют наши нужды.

Все библиотеки можно разделить на два класса:
  • реализованные как ISAPI Filters на unmanaged С++
  • реализованные как ASP.NET HttpModules (на любом .NET совместимом языке)
Их достоинства и недостатки:
  • ISAPI Filters выполняются под IIS и не требуют передачи управления ASP.NET (т.е. мы экономим на случаях, когда обращения идут не к нашим urls, а, например, к скриптам или изображениям).

  • Для инсталляции ISAPI Filters необходимы права на изменения конфигурации IIS. Правда, если мы хотим обрабатывать urls без расширений, нам понадобится менять настройки IIS даже в случае ASP.NET решения.

  • Конфигурационный файл для ISAPI Filters стараются делать совместимым по формату с mod_Rewrite файлом сервера Apache, т.е. он представляет собой текстовый файл со строками-инструкциями примерно такого вида:

    [ISAPI_Rewrite]

    # Single domain name is news.ru
    RewriteCond Host: www.news.ru
    RewriteRule (.*) http://news.ru$1 [RP,I]

    Конфигурационные файлы ASP.NET библиотек обычно представляют собой xml файлы примерно такого вида:

    <urlrewriter
      rewriteOnlyVirtualUrls = "true"
      contextItemsPrefix = "QueryString"
      defaultPage = "default.aspx"
      defaultProvider = "RegEx"
      xmlns = "http://www.urlrewriting.net/schemas/config/2006/07">
      <rewrites>
        <add name = "RewriteOnDomain" virtualUrl="^http\://(.*)/SampleWeb/domain/(.*)/Detail(.*).aspx"
          rewriteUrlParameter = "ExcludeFromClientQueryString"
          destinationUrl = "~/Default.aspx?language=$3&id=$4;&domain=$1"
          rewrite = "Domain"
          ignoreCase = "true" />
        ...

    С одной стороны у них больше возможностей, с другой, особенно когда правил очень много, понимать и править файлы для IIS намного проще - они не так перегружены дополнительными тегами и свойствами.

  • ASP.NET модули могут реализовывать какие-нибудь дополнительные возможности и вобще легко взаимодействуют с вашим приложением, так как собственно и являются его частью. Например, вы можете реализовать свою логику rewriting, основанную не только на правилах с регулярными выражениями или разработать более функциональную систему логирования.

В своей работе я использовал ISAPI filter "ISAPI_Rewrite" компании Helicon, поэтому решение нашей задачи я покажу именно на его примере. Но сейчас, мы дойдем до конца (точнее, до начала) нашего списка ссылок, чтобы посмореть, что мы вообще можем использовать. Итак, четвертая снизу ссылка

UrlRewritingNet.UrlRewrite


UrlRewritingNet.UrlRewrite - это бесплатная библиотека, написанная под ASP.NET 2.0. Она позволяет задавать правила, основанные на регулярных выражениях и обеспечивает корректную работу страниц при post-back. Судя по описанию, эта библиотека позволяет нормально использовать другие ASP.NET технологии, с которыми могут возникнуть проблемы (а-ля post-back) при rewriting: темы, master pages, cookieless sessions.

Полезный момент - в этом решении для разбора и выполнения правил используется модель, основанная на провайдерах. В библиотеке реализован провайдер правил на базе регулярных выражений. Однако, если вам будет мало этой функциональности, вы сможете добавить своего провайдера, в котором и реализуете всю необходимую вам логику.

Следующая ссылка в нашем списке

UrlRewriter.NET


Библиотека, очень похожая на предыдущую. Отличается намного более обширным набором правил, команд и операторов, которые вы можете использовать при определении ваших правил. Так же бесплатная.

Дальше идут ISAPI Filters

Ionics Isapi Rewrite Filter


Бесплатный ISAPI фильтр, поддерживающий mod_Rewrite-подобный синтаксис конфигурационного файла. По возможностям очень напоминает следующее и последнее в нашем обзоре решение (ISAPI rewrite). Возможно, эти различия можно бы было найти, серьезно используя оба решения в работе, но я в свое время выбрал "ISAPI rewrite". Он до сих пор меня вполне устраивает, так что у меня просто нет необходимости тратить время на более серьезный анализ.

И наконец последняя (то есть первая) ссылка нашей выборки

ISAPI Rewrite


Это - ISAPI Filter фирмы Helicon, разрабатывающей для веб-сайтов и другие продукты, основанные на rewriting (например "Hotlink blocker", позволяющий защитить ваш контент (изображения, файлы) от прямых ссылок или "Helicon Jet", экономящий трафик и увеличивающий производительность ASP приложений).

В отличие от всех предыдущих вариантов, ISAPI Rewrite платный ($99). Но можно воспользоваться бесплатной Lite версией. Ее возможностей вполне достаточно для отдельного веб-сайта. По большому счету, от full версии она отличается всего двумя вещами:
  • Full версия поддерживает раздельные настройки для разных сайтов, в Lite версии возможен только один общий конфигурационный файл.
  • Full версия может работать как прокси. Т.е. при обработке запроса она может обратиться к какому-то другому ресурсу, получить от него ответ и вернуть его в качестве ответа на исходный запрос.
Недавно вышла 3-я версия ISAPI Rewrite.
SEO Давайте теперь посмотрим, как можно реализовать наши требования при помощи ISAPI Rewrite.

  1. Работа с поддоменами
Во-первых, нужно связать наше веб-приложение с нужными доменами под IIS. Делается это в оснастке IIS, при помощи диалога "Advanced Web Site Identification", показанного на рис. 1. В нем мы дожны перечислить все варианты интересующих нас поддоменов, а именно:
  • news.ru
  • www.news.ru
  • sport.news.ru
  • www.sport.news.ru
  • и т.д.
Кроме этого, ISAPI Rewrite позволяет сужать область действия правил, на основании разнообразных условий. В том числе, можно накладывать условия и на домены. Так, например, выглядит правило, перенаправляющее все запросы из домена www.sport.new.ru в sport.news.ru:

RewriteCond %{HTTP:Host} ^www.sport.news.ru$
RewriteRule (.*) http://sport.news.ru$1 [NC,R=301]

Директива NC позволяет не обращать внимание на регистр, а R=301 указывает, что нужно вернуть браузеру код возврата 301 (permanent redirect).
  1. Формирование SEO и User friendly urls
Легко достигается за счет тех же правил. Вот, например, правило, поддерживающее преобразование urls вида
http://sport.news.ru/ukraine/dinamo-kiev/2007
во внутренние urls вида
http://sport.news.ru/list.aspx?country=ukraine&club=dinamo-kiev&year=2007:

RewriteCond %{HTTP:Host} ^sport.news.ru$
RewriteRule /([-\w]+)/([-\w]+)/(\d+) /list.aspx?country=$1&club=$2&year=$3 [NC,L]

Регулярное выражение [-\w]+ обозначает один и больше алфавитно-цифровых символов или дефисов (дефисы тоже используются в названиях), выражение \d+ обозначает одну и больше цифру, директива "L" (Last rule) указывает, что не нужно продолжать применять оставшиеся правила, а сразу выполнять rewriting. Конечно, можно было бы задать более жесткие ограничения (например ограничить год четырмя цифрами), но для того, чтобы понять общую идею, достаточно и такого простого варианта.
  1. Использование статических urls, включающие ключевые слова вместо динамических страниц с параметрами
Это почти то же, что и предыдущий пункт, но я хотел обратить внимание на один ньюанс. Для стран или названий клубов из предыдущего примера вполне допустимо использовать уникальные литеральные имена. Однако, для названий статей этого уже не достаточно - нереально генерировать уникальные да еще и осмысленные (т.е. содержащие ключевые слова) названия для тысяч статей. Поэтому здесь удобнее совмещать оба типа названий, формируя неуникальное литеральное имя (которое во время rewriting будет просто игнорироваться) и уникальный id, добавляемый к окончанию имени через дефис. Вот пример правила, преобразовывающего urls вида
http://sex.news.ru/topmodels/dima-bilan-with-famous-top-model-2369
во внутренние urls вида
http://sex.news.ru/list.aspx?rubric=topmodels&articleId=2369:

RewriteCond %{HTTP:Host} ^sex.news.ru$
RewriteRule /([-\w]+)/([-\w]*)-(\d+) /list.aspx?rubric=$1&articleId=$3 [NC,L]

  1. И, наконец-то, использование только одного url из нескольких, ведущих на одну и ту же страницу (чтобы избежать распыления рейтинга, так как поисковики могут воспринимать эти urls как разные страницы)
Для примера, если у нас есть страница http://sport.news.ru/toplist/, то на нее можно сослаться как минимум еще двумя способами:
  • http://www.sport.news.ru/toplist/ - нельзя забывать о пользователях, которые могут набирать url как с www так и без
  • http://sport.news.ru/TopList/ - поисковики, по крайней мере Google, чуствительны к регистру той части url, которая идет после доменного имени
Для того, чтобы решить эту проблему, нужно перенаправлять обращения ко всем urls на один url, устанавливая код возврата 301 (permanent redirect). Сделать это можно например так:

; Перенаправить все запросы к домену с www на домен без www
RewriteCond %{HTTP:Host} ^www.sport.news.ru$
RewriteRule (.*) http://sport.news.ru$1 [NC,R=301]

; Приводить все запросы к низкому регистру
RewriteRule ^(.*\u.*)$ $1 [R=301,CL]

Директива CL позволяет привести запрос к низкому регистру.

Rewriting Ну вот, пожалуй и все. Обычно, в конце статьи, нужно написать какую-то завершающую фразу, которая создаст иллюзию завершенности, подведет итоги и заставит почуствовать прочитанный текст чем то достаточным и законченным :) Но что писать - я честно говоря не знаю. Обзор есть, решения описаны, ссылки даны, примеры приведены - а дальше идет уже ваша работа: изучение документации, выяснение подробностей, анализ, выбор решения и многое многое другое.

Так что я просто пожелаю вам удачи и желания сделать что-то лучше и красивее!


В избранное