Сейчас при разработке широко используется шаблон MVC, подразумевающий разделение модели данных, представления и логики работы. Довольно известная реализация этого шаблона в web-разработке — ASP.NET MVC Framework. Не так давно вышла третья версия этого фреймворка, в которой есть два варианта реализации представлений: ASPX и Razor. Оба движка имеют достаточно широкий набор возможностей, но в этой публикации я попробую описать использование другого средства для реализации представлений — XSLT-шаблоны.
XSLT-шаблон — это XML-документ, описывающий преобразование одного или нескольких входных XML-документов в выходной, который обычно также является XML или HTML-документом. В нашем случае входными данными для шаблона будет XML-представление данных, полученных из соответствующих моделей. Переданные в шаблон данные используются для формирования контента страницы.
Основной проекта нам послужит шаблон ASP.NET MVC 3 Application. Выбираемый View Engine здесь значения не имеет.
Для всех моделей, передаваемых в представления, опишем следующий базовый класс:
Первый атрибут нужен, поскольку объекты этого класса будут сериализованы в XML. О причинах использования второго атрибута будет сказано ниже.
UserModel — пример дочернего класса, который мы будем передавать в представление:
Теперь опишем основной класс, который будет сериализовываться и передаваться в шаблон:
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.