(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

"Классические" friendly url в ASP.NET MVC

Источник: habrahabr

Речь пойдет о том, как сделать ссылки типа www.site.com/helloworld в ASP.NET MVC-приложении. Слышу возмущенные возгласы "зачем это надо?" и "чем тебя не устраивает нормальный человеческий роутинг?" и у меня по этому поводу заготовлен ответ.

 Дело в том, что наш проект переезжает на MVC с Web Forms, и контекстная реклама уже давно оплачена. Routing - прекрасная вещь, однако, учитывая, что сайт состоит не только из приземляющих страниц, мы не можем так по-хитрому его настроить, чтобы продолжали работать и обычные MVC-ссылки /Controller/Action, и наши невероятно дружественные URL'ы. Причина проста: дефолтный роутинг ("{controller}/{action}/{id}", new { controller = "Default", action = "Index", id = UrlParameter.Optional }) предполагает значения по умолчанию, поэтому при обращении к www.site.com/helloworld нас попытаются перекинуть на контроллер helloworld в экшн Index.
 Печаль.
 Придется что-то делать. Причем вариант с созданием контроллера для каждой ссылки нас явно не устроит - их тьма, да и в целом это порнография, правда?

 Мы пойдем другим путём.


Концепция

 Мы повесим хендлер на роутинг, который будет проверять ссылку на френдливость - для этого воспользуемся регулярными выражениями.
 Если ссылка MVC-шная - просто продолжим, не совершая лишних телодвижений.
 Если ссылка не похожа на типичную MVC-ссылку, мы попробуем поискать в базе соответствие - какой контроллер и экшн использовать.
 Если ничего не найдем, попробуем воспользоваться стандартным роутингом и значениями по умолчанию. Тут уж 404, так 404.

Модель

 

Вот так просто и незатейливо. ContentID здесь внешний ключ, однако при желании можно использовать VARCHAR(MAX) и писать контент сразу здесь. У нас такая реализация обусловлена тем, что контент поделен на куски (основной контент, title, мета-тег description...) - SEO, сами понимаете.


Реализация

 Основа реализации заключается вот в чем:


routes.MapRoute(
    "Default",
    "{controller}/{action}/{id}",
    new { controller = "Default", action = "Index", id = UrlParameter.Optional }
).RouteHandler = new FriendlyUrlRouteHandler();

 К нашему роутеру мы приделываем хендлер (наследник MvcRouteHandler), который обрабатывает уже готовые значения. То есть сначала механизм роутинга разберется со ссылкой, заполнит значения по умолчанию, потом мы вступим в дело и при необходимости заменим значения на наши.

    public class FriendlyUrlRouteHandler : MvcRouteHandler
    {
        private static readonly Regex TypicalLink = new Regex("^.+/.+(/.*)?");

        protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            // Path для www.site.com/helloworld?id=1 будет равняться /helloworld
            // поэтому мы убираем начальный слэш
            var url = requestContext.HttpContext.Request.Path.TrimStart('/');

            if (!string.IsNullOrEmpty(url) && !TypicalLink.IsMatch(url))
            {
                PageItem page = RedirectManager.GetPageByFriendlyUrl(url);
                if (page != null)
                {
                    FillRequest(page.ControllerName,
                        page.ActionName ?? "GetStatic",
                        page.ID.ToString(),
                        requestContext);
                }
            }

            return base.GetHttpHandler(requestContext);
        }

        /// <summary> Заполнение request-контекста данными о контроллере, экшне и параметрах </summary>
        private static void FillRequest(string controller, string action, string id, RequestContext requestContext)
        {
            if (requestContext == null)
            {
                throw new ArgumentNullException("requestContext");
            }

            requestContext.RouteData.Values["controller"] = controller;
            requestContext.RouteData.Values["action"] = action;
            requestContext.RouteData.Values["id"] = id;
        }
    }


 Я использую в качестве параметра ID записи, и при загрузке страницы использую вьюху, которая состоит из join'а нескольких таблиц. Если не нужны подобные заморочки, можно просто передавать в качестве параметра ContentID, а не id всей записи.

 По умолчанию используется экшн GetStatic, который определен как виртуальный в нашем базовом классе для контроллеров. В конкретном контроллере он берет текст страницы из базы, и генерирует страничку с учетом особенностей и layout'ов раздела.


    public abstract class BaseController : Controller
    {
        /// <summary> Получение страницы из базы </summary>
        public virtual ActionResult GetStatic(int id)
        {
            return HttpNotFound();
        }
    }


 И наконец сам механизм редиректа. Признаться, уже не помню, почему я вынес его в отдельный класс. С другой стороны, почему бы и нет.
    public static class RedirectManager
    {
        public static PageItem GetPageByFriendlyUrl(string friendlyUrl)
        {
            PageItem page = null;

            using (var cmd = new SqlCommand())
            {
                cmd.Connection = new SqlConnection(/*YourConnectionString*/);
                cmd.CommandText = "select * from FriendlyUrl where FriendlyUrl = @FriendlyUrl";
                cmd.Parameters.Add("@FriendlyUrl", SqlDbType.NVarChar).Value = friendlyUrl.TrimEnd('/');

                cmd.Connection.Open();
                using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection))
                {
                    if (reader.Read())
                    {
                        page = new PageItem
                                   {
                                       ID = (int) reader["Id"],
                                       ControllerName = (string) reader["ControllerName"],
                                       ActionName = (string) reader["ActionName"],
                                       FriendlyUrl = (string) reader["FriendlyUrl"],
                                   };
                    }
                }

                return page;
            }
        }
    }

 

Результат

 В конечном итоге мы получили именно то, к чему стремились. После заполнения базы у нас заработал редирект на правильные страницы, причем в адресной строке ничего не меняется, и мы получаем 200 OK без дополнительных плясок.

 Конечно, ситуация в достаточной степени нетипичная - в большинстве случаев стандартного роутинга более чем достаточно: можно сделать контроллер, например, Static, который также по URL будет забирать данные из базы. Но если контекстная реклама уже оплачена, или просто есть дикое желание сделать ссылки, в которых ни одного лишнего слэша, описанный вариант, на мой взгляд, весьма пригодный.

 В конце концов, мы поставили задачу, и благополучно с ней справились.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 18.11.2011 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Microsoft Office 365 для Дома 32-bit/x64. 5 ПК/Mac + 5 Планшетов + 5 Телефонов. Подписка на 1 год.
Microsoft Office 365 Персональный 32-bit/x64. 1 ПК/MAC + 1 Планшет + 1 Телефон. Все языки. Подписка на 1 год.
Microsoft 365 Apps for business (corporate)
Microsoft Windows Professional 10, Электронный ключ
Microsoft 365 Business Standard (corporate)
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
3D и виртуальная реальность. Все о Macromedia Flash MX.
Компьютерная библиотека: книги, статьи, полезные ссылки
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100