CMS Umbraco я изучаю несколько месяцев. Так получилось, что основное приложение моих исследовательских усилий направлено на последнюю на сей день 7-ю версию этой системы, причём в контексте её работы на MVC-движке, как альтернативе веб-формам прежних версий.
В какой-то момент, разрабатывая сайт одной компании, мне захотелось вынести репозиторий новостей этой компании в отдельный элемент верхнего уровня в content tree. В примерах и видеоинструкциях, размещённых на сайте Umbraco, предлагается самый простой вариант такой организации – новости хранятся как дочерние элементы какого-нибудь пункта меню (рис.1). В качестве примера в разделе «С какой стороны подойти к Umbraco» такой подход оправдывается, но на живом сайте, где новостей будут десятки и сотни, это выглядит довольно неуклюже. Чтобы работать с новостями, редактору сайта нужно будет опускаться вглубь по дереву контента – Главная-Новости-ОтдельнаяНовость. Да и сама концепция такого подхода меня не очень устраивает — в разделе пунктов многоуровневого меню вдруг появляется список новостей…
Более предпочтительным выглядит другой вариант, как на рис.2 – узел, содержащий новости, является отдельным элементом верхнего уровня дерева контента.
Более того — при организации доступа к новостям по ссылке, захотелось, чтобы они имели красивый канонический вид:
- /News – список всех новостей;
- /News/1234 – вывод отдельной новости;
- /News/Page12 – переход на страницу списка новостей.
Парадигма MVC для решения данной задачи предполагает наличие контроллера и представления (модель в данном случае не используем). Поскольку всё выполняется «под крылом» Umbraco, мы не можем взять и внедрить контроллер новостей, наследуя его от класса Controller – ничего не произойдёт. Документация по Umbraco предлагает наследоваться от специально существующего в инфраструктуре данной CMS класса SurfaceController. Всё бы хорошо, но в таком случае ссылки, по которым вызываются действия контроллера, приобретают вид "/umbraco/surface/_controllername_/_actionname_". Такая структура url выглядит довольно громоздкой, да и с точки зрения поисковой индексации страница с подобным url воспринимается как глубоко упрятанная в структуре сайта, что, вероятно, понижает её поисковую значимость.
Исследование данного вопроса привело к следующему решению – наследоваться нужно не от класса SurfaceController, а от PluginController. В этом случае ссылки генерируются по канонам MVC, так, как и требуется. Однако при этом необходимо учитывать некоторые дополнительные обстоятельства. Ниже предоставлено полное решение данной задачи. Итак:
Роуты
Роуты прописываются не в файле App_Start/RouteConfig.cs, который стандартно вызывается как RouteConfig.RegisterRoutes(RouteTable.Routes) в Global.asax. В Umbraco MVC для классов PluginController роуты прописываются в файле App_Code/Startup.cs. В этом файле объявляем класс, имплементирующий интерфейс IApplicationEventHandler. Выглядит это так:
public class MyStartupHandler : IApplicationEventHandler
{
public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
//Create a custom routes
// News controller
RouteTable.Routes.MapRoute(
"",
"News",
new
{
controller = "News",
action = "Index",
page = 1
});
RouteTable.Routes.MapRoute(
"",
"News/Index",
new
{
controller = "News",
action = "Index",
page = 1
});
RouteTable.Routes.MapRoute(
"",
"News/Page{page}",
new
{
controller = "News",
action = "Index",
page = UrlParameter.Optional
});
RouteTable.Routes.MapRoute(
"",
"News/{id}",
new
{
controller = "News",
action = "News",
id = UrlParameter.Optional
});
}
public void OnApplicationInitialized(
UmbracoApplicationBase umbracoApplication,
ApplicationContext applicationContext)
{
}
public void OnApplicationStarting(
UmbracoApplicationBase umbracoApplication,
ApplicationContext applicationContext)
{
}
}
Контроллер
Контроллер создаётся обычным образом в файле, расположенном в папке Controllers. Как было сказано, класс контроллера наследует класс PluginController. Выглядит это примерно так:
public class NewsController : PluginController
{
public NewsController() : this(UmbracoContext.Current) { }
public NewsController(UmbracoContext umbracoContext) : base(umbracoContext) { }
public ActionResult Index(string id)
{
/*
Здесь находится код, осуществляющий поиск и получение узлов новостей
для той или иной цели - построение постраничного списка,
демонстрация отдельной новости и т.д.
*/
return View("News", CreateRenderModel(renderModel));
}
private RenderModel CreateRenderModel(IPublishedContent content)
{
var model = new RenderModel(content, CultureInfo.CurrentUICulture);
//add an umbraco data token so the umbraco view engine executes
RouteData.DataTokens["umbraco"] = model;
return model;
}
}
Здесь требуется одно уточнение. Код поиска узлов с новостями возвращает объект типа интерфейса IPublishedContent или IEnumerable<IPublishedContent>. Однако Umbraco требует, чтобы представления, вызываемые из PluginController’a, были строго типизированы и принимали модель типа RenderModel. Для этого в контроллере объявляется приватный метод CreateRenderModel, который из IPublishedContent создаёт объект требуемого типа.
Представление
Представление создаётся по стандартной схеме, никаких нюансов в плане решения данной задачи в нём нет.
Сторонний эффект
В использовании данного подхода обнаружился один сторонний эффект, а именно – невозможность использования макросов в контенте на страницах, вызываемых подобным образом. Возникает ошибка с определением “PublishedContentRequest missing”. Как я понял, связана она с тем, что отображаемый через такой контроллер документ не проходит все стадии разбора Umbraco, в процессе которого и создаётся этот самый PublishedContentRequest, от которого отталкивается код генерации макросов. Утешает мысль, что работа с макросами при использовании MVC partial views становится менее востребованной. К тому же сами создатели Umbraco говорят, что код, реализующий вызов макросов из Rich-text-контента, довольно запутан и несёт на себе серьёзный отпечаток тяжёлого доэмвсишного прошлого…
Ссылки
Surface Controllers
Custom MVC routing in Umbraco
Автор: theyur