Главная страница   /   18.1. Создание пользовательского движка представления (ASP.NET MVC 4

ASP.NET MVC 4

ASP.NET MVC 4

Адам Фриман

18.1. Создание пользовательского движка представления

Эту главу мы начнем с создания пользовательского движка представлений, хотя в реальных проектах он вам понадобится очень редко. MVC Framework включает в себя два встроенных движка представлений, полнофункциональных и тщательно протестированных:

  • Движок Razor, который мы уже использовали в этой книге, появился в третьей версии MVC. У него простой и удобный синтаксис, который мы рассмотрели в главе 5.
  • Движок ASPX, также известный как движок представлений Web Forms, использует синтаксис тегов Web Forms <% ...%>. Этот движок используется для поддержки совместимости в старых приложениях MVC.

Весь смысл создания пользовательского движка представлений состоит в том, чтобы с его помощью продемонстрировать работу конвейера обработки запросов и дополнить ваши знания об устройстве MVC Framework. Вы сможете оценить, какой гибкостью обладает процесс преобразования объектов ViewResult в ответ клиенту. Движки представлений реализуют интерфейс IViewEngine, который показан в листинге 18-1.

Листинг 18-1: Интерфейс IViewEngine
namespace System.Web.Mvc
{
	public interface IViewEngine
	{
		ViewEngineResult FindPartialView(ControllerContext controllerContext, 
			string partialViewName, 
			bool useCache);

		ViewEngineResult FindView(ControllerContext controllerContext, 
			string viewName, 
			string masterName, 
			bool useCache);

		void ReleaseView(ControllerContext controllerContext, IView view);
	}
}

Задача движка представлений заключается в преобразовании запросов к представлениям в объекты ViewEngineResult. Первые два метода в интерфейсе, FindView и FindPartialView, принимают параметры, в которых указывается запрос и обработавший его контроллер (объект ControllerContext), имя представления и его макет, а также сообщение о том, может ли движок представлений использовать предыдущий результат из кэша. Эти методы вызываются во время обработки ViewResult. Последний метод, ReleaseView, вызывается, когда представление больше не требуется.

Примечание

Поддержка движков представлений в MVC Framework реализуется классом ControllerActionInvoker, который является встроенной реализацией интерфейса IActionInvoker, описанного в главе 15. Вы не сможете автоматически использовать движки представлений, если вы реализовали пользовательский механизм вызова действий или фабрику контроллеров непосредственно из интерфейсов IActionInvoker или IControllerFactory.

Класс ViewEngineResult позволяет движку представлений отвечать на запрос представления. Он показан в листинге 18-2.

Листинг 18-2: Класс ViewEngineResult
using System.Collections.Generic;

namespace System.Web.Mvc
{
	public class ViewEngineResult
	{
		public ViewEngineResult(IEnumerable<string> searchedLocations)
		{
			if (searchedLocations == null)
			{
				throw new ArgumentNullException("searchedLocations");
			}
			SearchedLocations = searchedLocations;
		}

		public ViewEngineResult(IView view, IViewEngine viewEngine)
		{
			if (view == null)
			{
				throw new ArgumentNullException("view");
			}
			if (viewEngine == null)
			{
				throw new ArgumentNullException("viewEngine");
			}
			View = view;
			ViewEngine = viewEngine;
		}

		public IEnumerable<string> SearchedLocations { get; private set; }
		public IView View { get; private set; }
		public IViewEngine ViewEngine { get; private set; }
	}
}

Для создания результата необходимо выбрать один из двух конструкторов. Если ваш движок возвращает представления в ответ на запросы, то ViewEngineResult создается помощью следующего конструктора:

public ViewEngineResult(IView view, IViewEngine viewEngine) 

Его параметрами являются реализация интерфейса IView и движок представления (так что позже будет вызван метод ReleaseView). Если ваш движок не возвращает представления на запросы, то используйте следующий конструктор:

public ViewEngineResult(IEnumerable<string> searchedLocations)

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

Примечание

Вам не кажется, что класс ViewEngineResult какой-то громоздкий? Выводить результаты с помощью разных версий конструктора класса – это нехарактерный подход для MVC. Мы не знаем, почему разработчики остановились на этом решении, но, к сожалению, вынуждены с ним мириться. Были надежды, что данный класс будет изменен в MVC 4, но этого не произошло.

Последним структурным элементом движка представления является интерфейс IView, который показан в листинге 18-3.

Листинг 18-3: Интерфейс IView
using System.IO;

namespace System.Web.Mvc
{
	public interface IView
	{
		void Render(ViewContext viewContext, TextWriter writer);
	}
}

Мы передаем реализацию IView в конструктор объекта ViewEngineResult, который будет позже возвращен из методов движка представления. MVC Framework вызывает метод Render. Параметр ViewContext предоставляет информацию о запросе клиента и выводе метода действия. Параметр TextWriter записывает ответ клиенту.

Как мы уже говорили, самый простой способ изучить работу всех этих элементов - IViewEngine, IView и ViewEngineResult - это создать пользовательский движок представлений. Мы напишем простой движок, который возвращает только один вид представления. Оно будет визуализировать результат, который содержит информацию о запросе и данные представления, сгенерированные методом действия. Таким образом мы сможем продемонстрировать, как работают движки представлений, не погружаясь в анализ шаблонов представлений.

Создаем пример проекта

В качестве примера для этой главы мы создали проект под названием Views, используя шаблон Empty. В нем был создан контроллер Home, который вы можете увидеть в листинге 18-4.

Листинг 18-4: Контроллер Home в проекте Views
using System;
using System.Web.Mvc;

namespace Views.Controllers
{
	public class HomeController : Controller
	{
		public ActionResult Index()
		{
			ViewData["Message"] = "Hello, World";
			ViewData["Time"] = DateTime.Now.ToShortTimeString();
			return View("DebugData");
		}

		public ActionResult List()
		{
			return View();
		}
	}
}

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

Создаем пользовательский IView

Начнем с создания пользовательской реализации IView. Мы добавили в пример проекта папку Infrastructure и создали в ней класс под названием DebugDataView, который показан в листинге 18-5.

Листинг 18-5: Пользовательская реализация IView
using System.IO;
using System.Web.Mvc;

namespace Views.Infrastructure
{
	public class DebugDataView : IView
	{
		public void Render(ViewContext viewContext, TextWriter writer)
		{
			Write(writer, "---Routing Data---");
			foreach (string key in viewContext.RouteData.Values.Keys)
			{
				Write(writer, "Key: {0}, Value: {1}", key, viewContext.RouteData.Values[key]);
			}
			Write(writer, "---View Data---");
			foreach (string key in viewContext.ViewData.Keys)
			{
				Write(writer, "Key: {0}, Value: {1}", key, viewContext.ViewData[key]);
			}
		}

		private void Write(TextWriter writer, string template, params object[] values)
		{
			writer.Write(string.Format(template, values) + "<p/>");
		}
	}
}

Как видите, в методе Render этого представления используются два параметра: мы берем значения из ViewContext и записываем ответ клиенту с помощью TextWriter. Немного позже вы увидите, как функционирует этот класс.

Создаем реализацию IViewEngine

Следует помнить, что главная задача движка представления - создавать объект ViewEngineResult, который содержит либо IView, либо список мест для поиска подходящего представления. Теперь, когда у нас есть реализация IView, мы можем создать движок представления. В папке Infrastructure мы создали новый класс под названием DebugDataViewEngine.cs, содержание которого приведено в листинге 18-6.

Листинг 18-6: Пользовательская реализация IViewEngine
using System.Web.Mvc;

namespace Views.Infrastructure
{
	public class DebugDataViewEngine : IViewEngine
	{
		public ViewEngineResult FindView(ControllerContext controllerContext, 
			string viewName, 
			string masterName,
			bool useCache)
		{
			if (viewName == "DebugData")
			{
				return new ViewEngineResult(new DebugDataView(), this);
			}
			else
			{
				return new ViewEngineResult(new string[] {"No view (Debug Data View Engine)"});
			}
		}

		public ViewEngineResult FindPartialView(ControllerContext controllerContext, 
			string partialViewName, 
			bool useCache)
		{
			return new ViewEngineResult(new string[] {"No view (Debug Data View Engine)"});
		}

		public void ReleaseView(ControllerContext controllerContext, IView view)
		{
			// do nothing
		}
	}
}

Мы реализуем поддержку только одного представления, которое называется DebugData. При получении запроса к нему мы будем возвращать экземпляр пользовательской реализации IView, как показано далее:

return new ViewEngineResult(new DebugDataView(), this); 

Если бы мы реализовывали более серьезный движок, то использовали бы возможность поиска шаблонов, учитывали макет и предоставляли параметры кэширования. Но в нашем простом примере требуется только создавать новый экземпляр класса DebugDataView. Если мы получим запрос к другому представлению (не DebugData), то вернем ViewEngineResult, как показано далее:

return new ViewEngineResult(new string[] { "No view (Debug Data View Engine)" }); 

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

Наш пользовательский движок не поддерживает частичные представления, поэтому метод FindPartialView тоже будет возвращать результат, указывающий, что мы не можем найти нужное представление. Далее в этой главе мы вернемся к частичным представлениям и рассмотрим, как они обрабатываются в движке Razor. Метод ReleaseView пока еще ничего не делает, потому что в нашей реализации IView еще нет ресурсов, с которыми он мог бы работать.

Регистрируем пользовательский движок представлений

Движок представлений регистрируется в методе Application_Start файла Global.asax, как показано в листинге 18-7.

Листинг 18-7: Регистрируем пользовательский движок представлений в Global.asax
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
using Views.Infrastructure;

namespace Views
{
	public class MvcApplication : System.Web.HttpApplication
	{
		protected void Application_Start()
		{
			AreaRegistration.RegisterAllAreas();

			ViewEngines.Engines.Add(new DebugDataViewEngine());

			WebApiConfig.Register(GlobalConfiguration.Configuration);
			FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
			RouteConfig.RegisterRoutes(RouteTable.Routes);
		}
	}
}

MVC Framework поддерживает возможность установки нескольких движков в одном приложении – их перечень содержится в статической коллекции ViewEngine.Engines. При обработке ViewResult механизм вызова действий получает набор установленных движков и вызывает их методы FindView по очереди.

Когда механизм вызова действий получает объект ViewEngineResult, содержащий IView, он прекращает вызывать методы FindView. Это значит, что если два и более движка могут обслужить запрос к одному представлению, то решающее значение имеет последовательность, в которой они добавлены в коллекцию ViewEngines.Engines. Если вы хотите назначить вашему движку более высокий приоритет, вставьте его в начале коллекции, как показано здесь:

ViewEngines.Engines.Insert(0, new DebugDataViewEngine());

Тестируем движок представлений

Сейчас мы уже в состоянии протестировать наш движок представлений. Если запустить приложение, браузер автоматически перейдет по корневому URL проекта, который будет соотнесен с действием Index контроллера Home. Используя метод View, метод действия возвращает ViewResult, который содержит представление DebugData. Результат показан на рисунке 18-1.

Рисунок 18-1: Использование пользовательского движка представлений

Это результат вызова метода FindView для представления, которое мы в состоянии обработать. Если мы перейдем по ссылке /Home/List, MVC Framework вызовет метод действия List, который вызовет метод View для запроса своего представления по умолчанию, которое мы не поддерживаем. Результат показан на рисунке 18-2.

Рисунок 18-2: Запрос неподдерживаемого представления

Как видите, в нашем сообщении отображается список адресов для поиска представления. Обратите внимание, что представления Razor и ASPX также появляются в списке, так как эти движки по-прежнему используются. Если мы хотим использовать только наш пользовательский движок, то перед его регистрацией в файле Global.asax нужно вызвать метод Clear, как показано в листинге 18-8.

Листинг 18-8: Удаляем другие движки представлений
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
using Views.Infrastructure;

namespace Views
{
	public class MvcApplication : System.Web.HttpApplication
	{
		protected void Application_Start()
		{
			AreaRegistration.RegisterAllAreas();

			ViewEngines.Engines.Clear();
			ViewEngines.Engines.Add(new DebugDataViewEngine());

			WebApiConfig.Register(GlobalConfiguration.Configuration);
			FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
			RouteConfig.RegisterRoutes(RouteTable.Routes);
		}
	}
}

Если мы перезапустим приложение и перейдем по ссылке /Home/List, будет использоваться только наш пользовательский движок, как показано на рисунке 18-3.

Рисунок 18-3: Использование только пользовательского движка