Игорь Чакрыгин Игорь Чакрыгин

У любой задачи существует по крайней мере одно очевидное и невероятно простое для понимания неправильное решение

ASP.NET - Добавление search engine friendly адресов на сайт

Сейчас, наверное, очень сложно себе представить хороший сайт без «красивых» адресов страниц. Они одновременно и радуют глаз, и помогают поисковикам лучше ориентироваться на сайте.

Для asp.net существует несколько библиотек, позволяющих добавить поддержку таких адресов на сайт. Все они имеют свои плюсы и минусы, но я хотел бы поделиться своим методом, который мне кажется наиболее простым, поскольку не требует большого количества кода и позволяет автоматически обрабатывать все aspx-файлы на сайте без необходимости прописывать маршруты для каждого из них.

Суть метода

Суть метода достаточно проста. Если пользователь зашёл на сайт по какому-то адресу, то этот адрес разбивается на две части: обработчик и параметры. Обработчиком считается та часть адреса, которую можно сопоставить с каким-то файлом на сервере, параметрами - всё остальное.

Например, если пользователь зашёл на сайт по адресу ~/forum/asp.net/42/, то в качестве обработчика может быть выбран один из следующих файлов с соответствующими параметрами:

  • ~/forum/asp.net/42/default.aspx (без параметров);
  • ~/forum/asp.net/42.aspx (без параметров);
  • ~/forum/asp.net/default.aspx (с параметром 42);
  • ~/forum/asp.net.aspx (с параметром 42);
  • ~/forum/default.aspx (c параметрами asp.net и 42);
  • ~/forum.aspx (c параметрами asp.net и 42).

Эти виртуальные пути проверяются в указанном порядке, пока не будет найден тот, который соответствует существующему на сервере файлу. Он и считается обработчиком.

Так же по аналогии можно искать файлы других типов, например ashx-файлы.

Реализация

Как я уже отметил в самом начале, реализация не требует большого количества кода и может быть добавлена в проект всего за несколько минут. Все изменения необходимо вносить в файл Global.asax.

Прежде всего подпишемся на событие PostResolveRequestCache, переопределив метод Init.

В обработчике этого события получим текущий виртуальный путь и проверим, соответствует ли он существующему на сервере файлу. Если это не так, то при помощи метода MapSefPathToExistingPath попробуем сопоставить его с другим виртуальным путём, который соответствует существующему файлу.

В случае успеха при помощи метода RewritePath заменим текущий виртуальный путь на тот, который вернёт метод MapSefPathToExistingPath, а параметры сохраним в коллекции Items.

Хотел бы отметить, что коллекция Items имеет тип IDictionary, поэтому хорошей практикой считается в качестве ключей использовать не строки, а экземпляры класса Object. В данном случае я просто упростил код.

Наконец, добавим метод MapSefPathToExistingPath, который будет выполнять сопоставление виртуальных путей. В данном методе будем убирать на конце текущего виртуального пути по одному сегменту (сегменты разделяются символом слэш), пока не получим виртуальный путь, соответствующий существующему файлу.

Всё готово. Теперь для всех запросов к несуществующим файлам будет автоматически определяться подходящий обработчик.

Заключение

Я не буду подробно описывать способ получения параметров в коде. Думаю, очевидно, что на странице их можно получить через свойство Context.Items. Для большей простоты можно даже создать свой класс страницы, в который добавить методы для быстрого получения параметров.

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

Скачать демо (ASP.NET 4.0, Web Site)

6 комментариев

  1. А зачем ещё велосипед?
    Есть же всё это из коробки, называется ASP.NET Routing. Работает думаю надежнее.

    ОтветитьУдалить
  2. Безусловно, в некоторых случаях Routing отлично справляется.
    Я предлагаю альтернативное решение по двум причинам:
    1. Не нужно при добавлении новой страницы прописывать новый маршрут.
    2. При большом количестве страниц при каждом запросе будут проверяться все маршруты на предмет их соответствия запрашиваемому адресу, что может сказаться на производительности. (Не сомневаюсь, что там всё значительно оптимизировано, но всё же)

    ОтветитьУдалить
  3. Ну вообще есть в ASP.NET Routing возможность и таких конструкций
    routes.MapPageRoute("rule1", "{path}/{page}/{*queryvalues}", "~/default.aspx")
    так что для КАЖДОЙ новой страницы делать правило нет смысла

    ОтветитьУдалить
  4. Такая конструкция:
    routes.MapPageRoute("rule1", "{path}/{page}/{*queryvalues}", "~/default.aspx")
    Сделает так, что обработчиком всех запросов будет страница ~/default.aspx. Разве не так?

    ОтветитьУдалить
  5. routes.MapPageRoute("rule1", "{path}/{page}/{*queryvalues}", "~/default.aspx")
    Тут смысл немного другой, главное идея.
    Можно и так
    routes.MapPageRoute("rule1", "forum/{topic}/{*queryvalues}", "~/forum/default.aspx")
    смысл будет в том что подпадает любой адрес типа /forum/*/**
    например /forum/topic2/page2 или /forum/to2pic/reply/post2
    только в последнем случае reply/post2 будет находиться в одной переменной и её нужно будет разбирать.

    ОтветитьУдалить
  6. Это только если форум, а если на сайте ещё есть страницы login.aspx, registration.aspx, catalog.aspx и всякие другие *.aspx?

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

    Кстати, справедливости ради стоит отметить, что похожий метод используется в Asp.net Web Pages. Если установить WebMatrix, то IIS начинает обрабатывать cshtml-файлы как обычные aspx-файлы (только с синтаксисом Razor).
    Т.е если в корень сайта положить файл forum.cshtml, то он будет доступен по адресам ~/forum.cshtml, ~/forum/, ~/forum/bla-bla/, ~/forum/bla-bla/bla-bla/ итд.

    ОтветитьУдалить

© Игорь Чакрыгин. Все права защищены при помощи чёрной магии. Технологии Blogger.