Pull to refresh

Использование XSLT-шаблонизатора в ASP.NET MVC

ASP *
Sandbox
Сейчас при разработке широко используется шаблон MVC, подразумевающий разделение модели данных, представления и логики работы. Довольно известная реализация этого шаблона в web-разработке — ASP.NET MVC Framework. Не так давно вышла третья версия этого фреймворка, в которой есть два варианта реализации представлений: ASPX и Razor. Оба движка имеют достаточно широкий набор возможностей, но в этой публикации я попробую описать использование другого средства для реализации представлений — XSLT-шаблоны.

XSLT-шаблон — это XML-документ, описывающий преобразование одного или нескольких входных XML-документов в выходной, который обычно также является XML или HTML-документом. В нашем случае входными данными для шаблона будет XML-представление данных, полученных из соответствующих моделей. Переданные в шаблон данные используются для формирования контента страницы.
Основной проекта нам послужит шаблон ASP.NET MVC 3 Application. Выбираемый View Engine здесь значения не имеет.

Модели данных


Для всех моделей, передаваемых в представления, опишем следующий базовый класс:

[Serializable]
[XmlInclude(typeof(UserModel))]
public abstract class XmlModel { }


Первый атрибут нужен, поскольку объекты этого класса будут сериализованы в XML. О причинах использования второго атрибута будет сказано ниже.
UserModel — пример дочернего класса, который мы будем передавать в представление:

[Serializable]
public class UserModel : XmlModel
{
  [XmlAttribute]
  public int UserId { get; set; }

  public string Login { get; set; }
  public string Email { get; set; }
}


Теперь опишем основной класс, который будет сериализовываться и передаваться в шаблон:

[Serializable]
public class Result
{
  [XmlAttribute]
  public string Controller { get; set; }
  [XmlAttribute]
  public string Action { get; set; }

  public List Data { get; set; }
}


Свойства класса содержат информацию о текущем контроллере, действии, а также список переданных контроллером моделей данных.
Поскольку в список Data будут добавляться не объекты XmlModel, а объекты дочерних классов, то для успешной сериализации этого поля и нужен был атрибут XmlInclude при описании класса XmlModel.
Структура XML-документа после сериализации будет следующей:

<Result Controller="SomeController" Action="SomeAction">
 <Data>
  <!-- переданные в шаблон данные -->
 </Data>
</Result>


Контроллеры


Обычно действия контроллера возвращают результат типа ActionResult. Подходящей реализации этого класса найти не получилось, поэтому я описал свою. Конструктор нашего класса будет принимать строку c XML-данными, передаваемыми в шаблон:

private string _data;
public XmlResult(string data)
{
  _data = data;
}


Основной метод, который нужно реализовать - ExecuteResult. Он формирует ответ клиенту. В нём мы и будем обрабатывать шаблон представления:

public override void ExecuteResult(ControllerContext controllerContext)
{
  var xslTransform = new XslCompiledTransform();

  //загружаем исходный шаблон
  var controller = controllerContext.RouteData.Values["controller"] as string;
  var action = controllerContext.RouteData.Values["action"] as string;
  var templatePath = string.Format("~/Views/{0}/{1}.xsl", controller, action);
  var virtualFile = HostingEnvironment.VirtualPathProvider.GetFile(templatePath);
  //также возможно использование единого главного шаблона:
  //var virtualFile = HostingEnvironment.VirtualPathProvider.GetFile("~/Views/default.xsl");
  var xmlReader = XmlReader.Create(virtualFile.Open());
  xslTransform.Load(xmlReader);
  xmlReader.Close();

  var stringReader = new StringReader(_data);
  xmlReader = XmlReader.Create(stringReader);

  //обработка XML-данных в шаблоне и передача их в http-ответ клиенту
  var xmlWriter = XmlWriter.Create(controllerContext.HttpContext.Response.OutputStream);
  xslTransform.Transform(xmlReader, xmlWriter);

  stringReader.Close();
  xmlReader.Close();
  xmlWriter.Close();
}


В итоге получается следующий класс:

public class XmlResult : ActionResult
{
  private string _data;

  public XmlResult(string data)
  {
    _data = data;
  }

  public override void ExecuteResult(ControllerContext controllerContext)
  {
    var xslTransform = new XslCompiledTransform();

    //загружаем исходный шаблон
    var controller = controllerContext.RouteData.Values["controller"] as string;
    var action = controllerContext.RouteData.Values["action"] as string;
    var templatePath = string.Format("~/Views/{0}/{1}.xsl", controller, action);
    var virtualFile = HostingEnvironment.VirtualPathProvider.GetFile(templatePath);
    //также возможно использование единого главного шаблона:
    //var virtualFile = HostingEnvironment.VirtualPathProvider.GetFile("~/Views/default.xsl");
    var xmlReader = XmlReader.Create(virtualFile.Open());
    xslTransform.Load(xmlReader);
    xmlReader.Close();

    var stringReader = new StringReader(_data);
    xmlReader = XmlReader.Create(stringReader);

    //обработка XML-данных в шаблоне и передача их в http-ответ клиенту
    var xmlWriter = XmlWriter.Create(controllerContext.HttpContext.Response.OutputStream);
    xslTransform.Transform(xmlReader, xmlWriter);

    stringReader.Close();
    xmlReader.Close();
    xmlWriter.Close();
  }
}


Теперь займёмся написанием базового класса контроллера.
Для начала переопределим поле ViewData, в которое будем добавлять данные:

public new List ViewData = new List();

Теперь опишем новый метод View, который будет возвращать объект ActionResult из контроллера. Он выполняет сериализацию наших данных и передаёт их в конструктор описанного выше класса:

public new ActionResult View()
{
  //сериализаем исходные данные
  var result = new Result
           {
             Controller = ControllerContext.RouteData.Values["controller"] as string,
             Action = ControllerContext.RouteData.Values["action"] as string,
             Data = ViewData
           };
  var xmlSerializer = new XmlSerializer(typeof(Result));
  var stringWriter = new StringWriter();
  xmlSerializer.Serialize(stringWriter, result);
  stringWriter.Close();
  //передаём их в новый ActionResult
  return new XmlResult(stringWriter.ToString());
}


В конечном итоге получим такой класс:

public abstract class XsltController : Controller
{
  public new List ViewData = new List();

  public new ActionResult View()
  {
    //сериализаем исходные данные
    var result = new Result
             {
               Controller = ControllerContext.RouteData.Values["controller"] as string,
               Action = ControllerContext.RouteData.Values["action"] as string,
               Data = ViewData
             };
    var xmlSerializer = new XmlSerializer(typeof(Result));
    var stringWriter = new StringWriter();
    xmlSerializer.Serialize(stringWriter, result);
    stringWriter.Close();
    //передаём их в новый ActionResult
    return new XmlResult(stringWriter.ToString());
  }
}

Теперь сделаем простейший контроллер на основе этого класса:

public class HomeController : XsltController
{
  public ActionResult Index()
  {
    var user = new UserModel { UserId = 1, Login = "root", Email = "noreply@localhost" };
    ViewData.Add(user);
    return View();
  }
}


Он передаёт в представление информацию о пользователе. В итоге в шаблон поступает следующий XML:

<?xml version="1.0" encoding="utf-16"?>
<Result xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Controller="Home" Action="Index">
 <Data>
  <XmlModel xsi:type="UserModel" UserId="1">
   <Login>root</Login>
   <Email>noreply@localhost</Email>
  </XmlModel>
 </Data>
</Result>


Представление


Теперь опишем простой XSLT-шаблон, в котором показан пример использования переданных данных:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <xsl:output encoding="utf-8"
       method="xml"
       media-type="application/xhtml+xml"
       doctype-public="-//W3C//DTD XHTML 1.1//EN"
       doctype-system="http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"/>
 <xsl:template match="/">
  <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
    <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8"/>
    <title>XSLT + MVC</title>
   </head>
   <body>
    <p>Controller: <xsl:value-of select="/Result/@Controller"/></p>
    <p>Action: <xsl:value-of select="/Result/@Action"/></p>
    <p>User: <xsl:value-of select="//XmlModel[@xsi:type = 'UserModel']/Login"/></p>
   </body>
  </html>
 </xsl:template>
</xsl:stylesheet>


В результате получаем в браузере нужный нам HTML-код:

<?xml version="1.0" encoding="utf-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <head>
  <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />
  <title>XSLT + MVC</title>
 </head>
 <body>
  <p>Controller: Home</p>
  <p>Action: Index</p>
  <p>User: root</p>
 </body>
</html>


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

* This source code was highlighted with Source Code Highlighter.
Tags:
Hubs:
Total votes 41: ↑27 and ↓14 +13
Views 4.2K
Comments Comments 19