Umbraco CMS MVC — собственный контроллер и красивые url

в 8:31, , рубрики: .net, cms, mvc, umbraco, метки: , ,

CMS Umbraco я изучаю несколько месяцев. Так получилось, что основное приложение моих исследовательских усилий направлено на последнюю на сей день 7-ю версию этой системы, причём в контексте её работы на MVC-движке, как альтернативе веб-формам прежних версий.

В какой-то момент, разрабатывая сайт одной компании, мне захотелось вынести репозиторий новостей этой компании в отдельный элемент верхнего уровня в content tree. В примерах и видеоинструкциях, размещённых на сайте Umbraco, предлагается самый простой вариант такой организации – новости хранятся как дочерние элементы какого-нибудь пункта меню (рис.1). В качестве примера в разделе «С какой стороны подойти к Umbraco» такой подход оправдывается, но на живом сайте, где новостей будут десятки и сотни, это выглядит довольно неуклюже. Чтобы работать с новостями, редактору сайта нужно будет опускаться вглубь по дереву контента – Главная-Новости-ОтдельнаяНовость. Да и сама концепция такого подхода меня не очень устраивает — в разделе пунктов многоуровневого меню вдруг появляется список новостей…

image

Более предпочтительным выглядит другой вариант, как на рис.2 – узел, содержащий новости, является отдельным элементом верхнего уровня дерева контента.

image

Более того — при организации доступа к новостям по ссылке, захотелось, чтобы они имели красивый канонический вид:

  • /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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js