ASP.NET MVC XSLT IViewEngine

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);
        }
    }
}

7 Responses to “ASP.NET MVC XSLT IViewEngine”


  • I don’t think that would work.

  • It does, try it out.

  • Interesting idea. this could be useful.

  • I like the idea and want to make use of it, simply because I think Xslt is a much better templating language than the Microsoft MVC views (in their current state).

    Unfortunately, I’m having some issues with implementation. I’m trying to put these classes into a separate DLL. I’ve referenced System.Web.Mvc, System.Xml.Serialization and System.Xml. The project isn’t being built on this line:

    viewContext.HttpContext.Response.ContentType = “text/xml”;

    It’s telling me that Response isn’t a part of HttpContextBase (i.e. ViewContext.HttpContext). The same happens where it tries to get viewContext.HttpContext.Server. What am I missing?

    Thanks.

    Odhran.

  • Ah, ignore my previous post … System.Web.Abstractions was needed too. :)

    Cheers!

  • I would just like to second Odhran’s comment about XSLT being better. I think the current “bee-sting” views are so messy and XSL is much more elegant and is IMO the natural choice for Views in MVC.
    Great to see others exploring this route.

  • … of course, Steven Sanderson describes exactly this in his book :) (page 358)

Leave a Reply