1
Jun
7

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);
        }
    }
}
Enjoyed reading this post?
Subscribe to the RSS feed and have all new posts delivered straight to you.
7 Comments:
  1. Concerned Citizen 3 Jun, 2009

    I don’t think that would work.

  2. Chris Hampson 3 Jun, 2009

    It does, try it out.

  3. Daniel Cole 4 Jun, 2009

    Interesting idea. this could be useful.

  4. Odhran McConnell 12 Jun, 2009

    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.

  5. Odhran McConnell 12 Jun, 2009

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

    Cheers!

  6. Matt Ross 13 Aug, 2009

    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.

  7. Matt Ross 13 Aug, 2009

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

Post your comment



Celadon theme by the Themes Boutique