Создаем ViewEngine для ASP.NET MVC 3

Давайте разберемся как работают движки представлений в ASP.NET MVC 3. Лучший для этого способ – написать свою реализацию. Причем эта задача не такая сложная, как может показаться на первый взгляд.

Для начала создадим пустое (Empty) ASP.NET MVC 3 веб-приложение. Не будем останавливаться на этом шаге подробно. Стоит только отметить название проекта – CustomMVCViewEngineDemo.

Кроме того, вполне логично разместить классы создаваемого движка представлений в отдельной сборке. Однако, в целях упрощения, ограничимся отдельной папкой CustomViewEngine.

Структура движка представлений

В ASP.NET MVC 3 можно выделить две составляющие движка представлений:

  • IViewEngine – непосредственно движок. В его задачи входит поиск и создание необходимых Представлений. Кроме того, он может обеспечивать, например, их кэширование.
  • IView – Преставления, которые должны обеспечивать генерацию HTML кода страницы.

Реализацией этих двух интерфейсов и займемся.

Реализация движка представлений

1. Представления

Разработку начнем с реализации Представления, т.к. именно оно генерирует HTML код. Для этого в папке CustomViewEngine создадим класс SimpleView. Он реализует единственный метод интерфейса IView:

public void Render(ViewContext viewContext, TextWriter writer);

Задача Render() – записать HTML код страницы в writer, используя данные из контекста viewContext.

Определим язык создаваемых Представлений: для любого слова, заключенного в фигурные скобки, будет осуществляться замена на значение из массива ViewData. Например, {Message} – на содержимое ViewData["Message"]. Остальной код файла разметки будет выводится без изменений.

namespace CustomMVCViewEngineDemo.CustomViewEngine
{
    using System.IO;
    using System.Text.RegularExpressions;
    using System.Web.Mvc;

    internal class SimpleView : IView
    {
        private readonly string _viewPath;

        public SimpleView(string viewPath)
        {
            this._viewPath = viewPath;
        }

        #region IView Members

        public void Render(ViewContext viewContext, TextWriter writer)
        {
            string content = File.ReadAllText(this._viewPath);

            string parsedContents = this.Parse(content, viewContext.ViewData);

            writer.Write(parsedContents);
        }

        #endregion

        private string Parse(string content, ViewDataDictionary viewDataDictionary)
        {
            return Regex.Replace(
                content, 
                "\\{(.+)\\}", 
                m => this.MatchEvaluator(m, viewDataDictionary));
        }

        private string MatchEvaluator(Match match, ViewDataDictionary viewData)
        {
            if (!match.Success) {
                return string.Empty;
            }

            string key = match.Result("$1");
            if (!viewData.ContainsKey(key)) {
                return string.Empty;
            }

            return viewData[key].ToString();
        }
    }
}

2. Движок представлений

Теперь остается только реализовать IViewEngine. Здесь можно сильно упростить задачу. Дело в том, что в ASP.NET MVC 3 существует абстрактный класс VirtualPathProviderViewEngine. Он обеспечивает поиск нужных Представлений и их кэширование. Необходимо только переопределить два метода:

  • CreatePartialView() – обеспечивает создание частичного Представления;
  • CreateView() – создает Представления, в том числе и на основе шаблона разметки (MasterPage).

Кстати, именно VirtualPathProviderViewEngine является базовым классом для реализаций таких движков представлений как WebForms и Razor.

Еще один важный момент. В конструкторе SimpleViewEngine необходимо определить пути, по которым будет осуществляться поиск файлов с разметкой (свойства PartialViewLocationFormats и ViewLocationFormats). В данном примере будем использовать стандартные варианты, определив только свое расширение ".sve".

Создадим в папке CustomViewEngine файл, в котором разместим код класса SimpleViewEngine:

namespace CustomMVCViewEngineDemo.CustomViewEngine
{
    using System.Web.Mvc;

    public class SimpleViewEngine : VirtualPathProviderViewEngine
    {
        public SimpleViewEngine()
        {
            this.ViewLocationFormats = new string[] {
                "~/Views/{1}/{0}.sve", 
                "~/Views/Shared/{0}.sve"
            };

            this.PartialViewLocationFormats = new string[] { 
                "~/Views/{1}/{0}.sve",
                "~/Views/Shared/{0}.sve"
            };
        }

        protected override IView CreatePartialView(
            ControllerContext controllerContext, string partialPath)
        {
            var path = controllerContext.HttpContext.Server.MapPath(partialPath);
            return new SimpleView(path);
        }

        protected override IView CreateView(
            ControllerContext controllerContext, string viewPath, string masterPath)
        {
            var path = controllerContext.HttpContext.Server.MapPath(viewPath);
            return new SimpleView(path);
        }
    }
}

Все готово.

Тестирование

Проверим созданный движок представлений в работе. Для этого необходимо его зарегистрировать в веб-приложении. Сделаем это, добавив строку в метод Application_Start():

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);

    ViewEngines.Engines.Add(new SimpleViewEngine());
}

Теперь создадим Контроллер Home в соответствующей папке. В действии Index определим значение для строки {Message}:

namespace CustomMVCViewEngineDemo.Controllers
{
    using System.Web.Mvc;

    public class HomeController : Controller
    {
        // GET: /Home/
        public ActionResult Index()
        {
            this.ViewData["Message"] = "Custom ASP.NET MVC 3 ViewEngine Demo";

            return this.View();
        }
    }
}

Создадим Представление Index.sve в папке Views/Home:

<!DOCTYPE html>

<html>

<head>
    <title>Index</title>
</head>

<body>
    <div>{Message}</div>
</body>

</html>

Запустим веб-приложение и убедимся в его работоспособности. В браузере будет выведена заданная в Контроллере строка.

Разумеется, созданный движок представлений очень простой. В нем нет, например, поддержки MasterPages и у него очень простой язык. Однако, его разработка позволила лучше понять принципы функционирования ASP.NET MVC 3.


Исходный код проекта (C#, Visual Studio 2010): CustomMVCViewEngineDemo.zip

Комментарии (9) -

Денис 09.12.2011 15:43:15

Привет от земляка) Интересная статья, но еще интереснее в связи с чем тебя эта тема заинтересовала?)

@ Денис: Привет. Большей частью именно как ответ на вопрос "как устроен ViewEngine" (просто сейчас время нашел написать про это). А если это был вопрос не собираюсь ли я создавать свой, то ответ нет, т.к. Razor устраивает. PS: Есть мысли поиграться с простым своим ViewEngine, но когда свободное время будет Smile Но это любопытства ради.

Денис 09.12.2011 16:03:57

Я понимаю, что не любопытства ради. Я так думаю, что в каком-то проекте тебе понадобилось разобраться в этой теме. И вот интересно как раз в связи чем.

@ Денис: Перечитай что я написал, мне кажется ты не понял меня.

Айгиз 09.12.2011 22:47:00

Ну например можно использовать в CMS, допустим указать путь, где нужно искать вьюшки для каждого плагина.

Хорошая, а главное, - полезная статья. На многое проливает свет. Спасибо.

@ Calabonga: Пожалуйста.

Отличная статья! Но у меня вопорос: возможно ли иметь возможность править вьюшку(как в примере) но при этом движек представлений должен работать как обычно. Попробую объяснить. При данном методе, обычные разоровские шаблоны в итоге будут просто текстом, в котором заменятся {бла-бла}. А возможно что-бы движек работал нормально, но при этом перехватив какое-то событие я бы смог подправить текст вьюшки? Заранне спасибо за ответ, очень нужна помощь!

@ Олег: Все ��авно не понял что конкретно требуется.

Добавить комментарий