Как маршрутизировать древовидные URL-адреса с помощью маршрутизации ASP.NET?

Я хотел бы добиться чего-то очень похожего на этот вопрос, с некоторыми улучшениями.

Есть веб-приложение ASP.NET MVC.

У меня есть дерево сущностей.
Например, класс Page, у которого есть свойство Children, которое имеет тип IList<Page>. (Экземпляр класса Page соответствует строке в базе данных.)

Обратите внимание, что владельцы сайта могут добавить новую страницу в любое время или удалить существующие, и URL-адреса также должны отражать эти изменения.

Я хотел бы назначить уникальный URL-адрес каждому Page в базе данных.
Я обрабатываю объекты Page с помощью контроллера с именем PageController.

Примеры URL:

http://mysite.com/Page1/
http://mysite.com/Page1/SubPage/
http://mysite.com/Page/ChildPage/GrandChildPage/

Вы видите картинку.
Итак, я бы хотел, чтобы каждый объект Page имел свой собственный URL-адрес, равный URL-адресу его родительского элемента, плюс собственное имя.
В дополнение к этому, я также хотел бы иметь возможность сопоставить одиночный Page с / (корневым) URL.

Я хочу применить эти правила:

  1. Если URL-адрес может быть обработан с помощью любого другого маршрута или файл существует в файловой системе по указанному URL-адресу, разрешить сопоставление URL-адресов по умолчанию.
  2. Если URL-адрес может быть обработан поставщиком виртуального пути, позвольте ему обработать его.
  3. Если другого нет, сопоставьте остальные URL-адреса с классом PageController.

Я также нашел этот вопрос и также этот и this one, но они не сильно помогли, поскольку не дают объяснения по поводу моих первых двух пунктов.

Я вижу следующие возможные варианты:

  • Сопоставьте маршрут для каждой страницы по отдельности.
    Это требует, чтобы я прошел по всему дереву при запуске приложения и добавил маршрут с точным соответствием в конец таблицы маршрутов.
  • Я мог бы добавить маршрут с помощью {*path} и написать собственный IRouteHandler, который его обрабатывает, но я не понимаю, как я могу справиться с первыми двумя правилами тогда, поскольку этот обработчик должен обрабатывать все.

Пока что первое решение кажется правильным, потому что оно также является самым простым. Но все же даже в этом случае я не уверен, как я могу заставить PageController обрабатывать запросы.

Я был бы очень признателен за ваши мысли по этому поводу.

Заранее спасибо!

РЕДАКТИРОВАТЬ: Теперь у меня было время изучить все аспекты каждого полученного ответа. Я принял ответ Нила, так как он лучше всех объясняет, как все работает. Я также поддержал все остальные ответы, поскольку они дают хорошие идеи.


person Venemo    schedule 22.05.2010    source источник


Ответы (4)


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

По умолчанию маршрутизация MVC направляется к существующим файлам перед применением любых маршрутов, хранящихся в коллекции маршрутов; см. http://msdn.microsoft.com/en-us/library/system.web.routing.routecollection.routeexistingfiles.aspx. (шапка Павлу - см. комментарии).

Чтобы направить запросы к контроллеру страницы, просто создайте настраиваемый маршрут, который проверяет виртуальный путь, и, если он соответствует шаблону для страницы в базе данных, возвращает RouteData. Настройте свой RouteData с соответствующими значениями, извлеченными из виртуального пути (например, установите для ключа Path значение / Parent / Child / Grandchild), установите ключ контроллера на имя контроллера вашей страницы (например, Page) и действие на имя действия которые вы хотите выполнить (например, Показать). RouteData должен быть создан с MvcRouteHandler (не уверен, что это правильное имя класса).

Чтобы убедиться, что URL-адреса страниц, управляемых вашей базой данных, возвращаются правильно, переопределите метод GetVirtualPath( RequestContext, RouteValueDictionary ) для RouteBase и используйте переданные значения маршрута, чтобы определить, является ли это страница, управляемая базой данных, и создает ли она требуемые данные виртуального пути (или возвращает null, в противном случае ).

Чтобы получить помощь по переопределению GetRouteData и GetVirtualPath, посмотрите отраженный исходный код System.Web.Routing.RouteBase и System.Web.Routing.Route; после этого Google - ваш друг.

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

person Neal    schedule 31.05.2010
comment
Зачем и как мне добавлять маршруты для существующих файлов? Кроме того, как мне построить маршрут для дерева и направить его к соответствующему контроллеру? - person Venemo; 31.05.2010
comment
Вам не нужно; по умолчанию система маршрутизации будет проверять наличие существующего файла и отдавать предпочтение его обслуживанию, а не попыткам найти соответствующий контроллер и действие. - person Paul; 05.06.2010
comment
@ Пол - Спасибо. Относится ли это также к файлам, предоставленным поставщиком виртуального пути? - person Venemo; 08.06.2010
comment
@Venemo ›Должно быть и то, и другое; RouteCollection имеет поле с именем _vpp, которое является VirtualPathProvider, которое он вводит через свой конструктор (или берет статический экземпляр из HostingEnvironment). Затем он вызывает this._vpp.FileExists и возвращает null, если файл существует. Возвращение null сообщает системе, что подсистема маршрутизации не обрабатывает этот запрос. Поэтому, если он не получает копию VirtualPathProvider, который вы используете по какой-либо причине, он должен игнорировать эти файлы. - person Paul; 08.06.2010
comment
@Paul - Большое спасибо за это! - person Venemo; 08.06.2010
comment
@Neal - Спустя столько времени у меня наконец-то появилось время подумать над этим и реализовать вашу идею. Работает нормально, спасибо вам большое за это! - person Venemo; 14.09.2010
comment
@Venemo - Приятно слышать. Рад, что смог помочь =) - person Neal; 15.09.2010
comment
@Neal - одна небольшая проблема: я должен поместить свой маршрут перед маршрутом MVC по умолчанию, потому что в противном случае он не сработает. - person Venemo; 15.09.2010

Еще одна идея - использовать T4 (набор инструментов преобразования текстовых шаблонов) для однократного чтения ваших дочерних элементов и генерации содержимого вашего файла Global.asax.

РЕДАКТИРОВАТЬ: в основном с T4 вы можете автоматизировать создание текстовых файлов. Например, вместо того, чтобы вручную копировать элементы какой-то огромной коллекции и вставлять их с определенным контекстом в текстовый файл (например, INSERT INTO [MyTable] (Text) VALUES (@ItemText)), вы могли бы заставить движок T4 читать коллекцию и генерировать эти операторы вставки для вас. Он статичен и не предназначен для выполнения.

Я считаю, что очень хорошее введение можно найти в книге Pro Entity Framework 4.0.

Но если вы говорите, что вам нужно делать это динамически, возможно, этот инструмент не для вас.

person Community    schedule 23.05.2010
comment
Я был бы рад, если бы вы могли предоставить более подробную информацию, я никогда не слышал о T4. Кстати, страницы могут меняться во время выполнения. Админы сайта могут добавлять / удалять / изменять их сколько угодно. - person Venemo; 23.05.2010
comment
Спасибо, что указали, что это может быть не для меня. Действительно, коллекция меняется во время выполнения. В любом случае, спасибо, что нашли время ответить мне. :) - person Venemo; 24.05.2010

Вы знаете структуру своих страниц, когда сохраняете страницу. Итак, вы можете сгенерировать URL-адрес для каждой страницы и сохранить его в записи базы данных. Затем вы можете использовать правило {*path} и найти точное совпадение в базе данных. Это правило должно быть последним в вашем определении правил, чтобы вы могли сопоставить другие маршруты.

Например, у вашего Page1 нет родительской страницы, это URL Page1. Ваш SubPage знает, что это родитель, поэтому он может обрабатывать URL Page1/SubPage и т. Д.

person Community    schedule 05.06.2010
comment
rarouš - Спасибо за ответ! Это хорошая идея, но {*path} не улавливает ли она и реальные (и виртуальные) имена файлов? - person Venemo; 07.06.2010

Вы можете использовать шаблон "Page/{*path}". Затем вы можете либо разложить путь, разделив строку на «/» и пройти по нему, либо вы можете использовать предложение Раруша о сохранении [сгенерированного] пути в базе данных и выполнить прямой поиск.

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

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

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

Ваш путь будет выглядеть так:

http://mysite.com/Page/Page1/ 
http://mysite.com/Page/Page1/SubPage/ 
http://mysite.com/Page/Page/ChildPage/GrandChildPage/ 

Конечно, вы можете использовать префикс, отличный от «Страница».

person Andre Artus    schedule 07.06.2010
comment
@Andre - Спасибо за ответ! На данный момент это лучшая идея. :) - person Venemo; 07.06.2010
comment
Вы можете упорядочить записи маршрута от наиболее конкретных до наименее конкретных и отказаться от префикса, используя RouteTester (haacked.com/archive/2008/03/13/url-routing-debugger.aspx), чтобы проверить свои маршруты. - person Andre Artus; 07.06.2010
comment
stephenwalther.com/blog/archive/2008/08/03/ - person Andre Artus; 07.06.2010
comment
@Venemo - Удовольствие, ты уже нашел ответ? - person Andre Artus; 09.06.2010
comment
@Andre - Все ответы очень хороши, но у меня еще нет времени на реализацию решения. (Этим проектом я занимаюсь в свободное время, которого у меня не было много в последние недели.) - person Venemo; 09.06.2010