Часто возникает необходимость в небольшом сайте, но ASP.NET оказывается слишком тяжеловесен, статичный HTML не устраивает совмещением данных и представления, а PHP или SSI просто брезгуешь.
В случае же использования XSLT кодирование займет ненамного больше времени, чем в случае с HTML. В этом посте я рассмотрю 2 варианта развертывания XSLT-сайта под ASP.NET.
Трансформацию на стороне клиента не рассматриваю, потому как на любителя это. Не в том смысле, что простые пути - не наш выбор, а в том, что выставлять данные напоказ... хмм... нет.
Вариант первый: собственный рендерер страницы
Пример:
/test1/default.aspx:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %> // <root title="Page Title"> <data> <item text="node 1 contents" /> <item text="node 2 contents" /> <item text="node 3 contents" /> </data> </root>
/test1/default.aspx.cs:
public partial class _Default : XslTransformPage { public _Default() : base("test1.xsl") { } }
Как видно, содержимое страницы представляет собой данные в формате XML. И страница эта наследуется от XslTransformPage, реализующего функционал XSL преобразования:
/app_code/xsltransformpage.cs:
using System; using System.IO; using System.Text; using System.Xml; using System.Xml.Xsl; using System.Web; using System.Web.Caching; using System.Web.UI; // public class XslTransformPage : Page { public XslTransformPage() { } // public XslTransformPage(string styleSheetFile) { this.styleSheetFile = styleSheetFile; } // private string styleSheetFile; public string StyleSheetFile { get { return styleSheetFile; } set { styleSheetFile = value; } } // protected override void Render(HtmlTextWriter writer) { if (Cache[Request.RawUrl] != null) { writer.Write(Cache[Request.RawUrl].ToString()); return; } // string xslPath = Server.MapPath("~/App_Code/" + styleSheetFile); if (!File.Exists(xslPath)) throw new HttpException(404, "Cannot find " + Request.RawUrl); // XslCompiledTransform transform = new XslCompiledTransform(); transform.Load(xslPath); // StringBuilder inBuilder = new StringBuilder(); StringBuilder outBuilder = new StringBuilder(); // using (StringWriter sw = new StringWriter(inBuilder)) using (HtmlTextWriter hw = new HtmlTextWriter(sw)) { //Получает содержимое страницы, т.е. данные в xml base.Render(hw); // using (StringReader sr = new StringReader(inBuilder.ToString())) using (XmlReader xr = XmlReader.Create(sr)) // using (XmlWriter xw = XmlWriter.Create(outBuilder)) transform.Transform(xr, xw); } // Cache.Add(Request.RawUrl, outBuilder.ToString(), new CacheDependency(new string[] { Request.PhysicalPath, xslPath }), Cache.NoAbsoluteExpiration, new TimeSpan(24, 0, 0), CacheItemPriority.Normal, null); // writer.Write(outBuilder.ToString()); } }
Кроме преобразования здесь используется также кеширование результата преобразования.
Вариант второй: HttpHandler на .aspx
Как, наверное, понятно, используется HttpHandler, навешиваемый на расширение .aspx, вот таким образом:
app.config:
<httpHandlers> <add verb="*" path="*.aspx" type="XslTransformHandler"/> </httpHandlers>
Теперь сам обработчик:
/app_code/xsltransformhandler.cs:
using System; using System.IO; using System.Text; using System.Web; using System.Web.Caching; using System.Xml; using System.Xml.Xsl; // public class XslTransformHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { string path = context.Request.Path.Substring(context.Request.ApplicationPath.Length); // ProcessRequest(context, path); } // public void ProcessRequest(HttpContext context, string path) { context.Response.ContentType = "text/html"; // if (context.Cache[path] == null) { string xslPath = String.Empty; string xmlPath = String.Empty; // string xslFile = String.Empty; string xmlFile = String.Empty; string xmlDir = String.Empty; // string[] parts = path.Contains("/") ? path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries) : new string[] { path }; // // Замечу, что файл стилей будет один для каждой папки, для корневой: // default.xsl, ... if (parts.Length == 1) { xslFile = "default.xsl"; xmlFile = Path.GetFileNameWithoutExtension(parts[0]) + ".xml"; xmlDir = ""; } // ... для вложенных - вида папка.папка...папка.xsl else { xslFile = String.Join(".", parts, 0, parts.Length - 1) + ".xsl"; xmlFile = Path.GetFileNameWithoutExtension(parts[parts.Length - 1]) + ".xml"; xmlDir = String.Join("/", parts, 0, parts.Length - 1); if (xmlDir.Length > 0) xmlDir += "/"; } // xslPath = context.Server.MapPath("~/App_Code/" + xslFile); xmlPath = context.Server.MapPath("~/App_Data/" + xmlDir + xmlFile); // if (!File.Exists(xmlPath) || !File.Exists(xslPath)) throw new HttpException(404, "Cannot find" + context.Request.RawUrl); // XslCompiledTransform transform = new XslCompiledTransform(); transform.Load(xslPath); // StringBuilder sb = new StringBuilder(); using (XmlReader xr = XmlReader.Create(xmlPath)) using (XmlWriter xw = XmlWriter.Create(sb)) transform.Transform(xr, xw); // context.Cache.Add(path, sb.ToString(), new CacheDependency(new string[] { xmlPath, xslPath }), Cache.NoAbsoluteExpiration, new TimeSpan(12, 0, 0), CacheItemPriority.Normal, null); // context.Response.Write(sb.ToString()); } else context.Response.Write(context.Cache[path].ToString()); } // public bool IsReusable { get { return false; } } }
Пример:
Для запрашиваемого /test2/default.aspx данные будут распологаться в /App_Data/test2/default.xml, а стили в /App_Code/test2.xsl
Для запрашиваемого /test2/subdir/page.aspx: в /App_Data/test2/subdir/page.xml и /App_Code/test2.subdir.xsl соответственно.
А самого файла .aspx может и не существовать, либо в случае со страницами по умолчанию (например, default.aspx) он может присутствовать и быть пустым, чтобы IIS был в курсе о ее существовании.
Также, как и в случае со статичными данными, xml может быть получен и из БД (SELECT FOR XML) и вообще откуда угодно, и оба описанных метода можно приспособить в том числе и для этого.
Да, немногословно как-то получилось :)
Комментариев нет:
Отправить комментарий