Here is my first attempt at creating a custom view engine for ASP.NET MVC, I was inspired by http://www.worldofwarcraft.com/ which uses the XSL/XML combination with client side transformations. I would assume this saves them a copious amount of bandwidth. I am surprised this hasn’t been done more often which leads me to believe there must be some flaws with this approach. But nevertheless here is a View Engine that provides the ability to render out XML/XSLT combos as well as do the transforms server side.
Currently I do not have a reliable way of detecting client side XSLT support but this could be done via interrogating the User Agent or even a XSLT test page that sets a cookie. The real win with this approach I can see is the user downloads your visual elements once (the XSL style sheet) and the data can change numerous times. I will post a more detailed tutorial on how to write your own View Engine at a later date.
//Add this to your GlobalASAX protected void Application_Start() { ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new XsltViewEngine("~/Content/Views/")); RegisterRoutes(RouteTable.Routes); } public class XsltViewEngine : VirtualPathProviderViewEngine { public XsltViewEngine(string xsltHome) { ViewLocationFormats = new[] { xsltHome + "{1}/{0}.xslt", xsltHome + "Shared/{0}.xslt" }; PartialViewLocationFormats = ViewLocationFormats; } protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) { return new XmlView(); } protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath) { if (ClientSideXsltSupported()) return new XsltView() { ViewPath = viewPath }; else return new XsltTransformView() { ViewPath = viewPath }; } private bool ClientSideXsltSupported() { //TODO: work out a way to detect. //controllerContext.HttpContext.Request.Cookies["XsltSupported"] return true; } } public class XmlView : IView { public void Render(ViewContext viewContext, System.IO.TextWriter writer) { var serializer = new XmlSerializer(viewContext.ViewData.Model.GetType()); var writerSettings = new XmlWriterSettings { OmitXmlDeclaration = true }; using (var xmlWriter = XmlWriter.Create(writer, writerSettings)) { if (xmlWriter != null) serializer.Serialize(xmlWriter, viewContext.ViewData.Model); } } } public class XsltView : IView { public string ViewPath { get; set; } public void Render(ViewContext viewContext, System.IO.TextWriter writer) { viewContext.HttpContext.Response.ContentType = "text/xml"; writer.WriteLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); writer.WriteLine("<?xml-stylesheet type=\"text/xsl\" href=\"" + VirtualPathUtility.ToAbsolute(ViewPath) + "\"?>"); var serializer = new XmlSerializer(viewContext.ViewData.Model.GetType()); var writerSettings = new XmlWriterSettings { OmitXmlDeclaration = true }; using (var xmlWriter = XmlWriter.Create(writer, writerSettings)) { if (xmlWriter != null) serializer.Serialize(xmlWriter, viewContext.ViewData.Model); } } } public class XsltTransformView : IView { public string ViewPath { get; set; } public void Render(ViewContext viewContext, System.IO.TextWriter writer) { var serializer = new XmlSerializer(viewContext.ViewData.Model.GetType()); using (var ms = new MemoryStream()) { using (var xmlWriter = XmlWriter.Create(ms)) { if (xmlWriter != null) serializer.Serialize(xmlWriter, viewContext.ViewData.Model); } ms.Seek(0, SeekOrigin.Begin); var transformer = new XslCompiledTransform(true); transformer.Load(viewContext.HttpContext.Server.MapPath(ViewPath)); transformer.Transform(XmlReader.Create(ms), null, writer); } } }