views:

190

answers:

2

Hey Everyone,

I'm looking at building a templating engine for my website. I'm using ASP.NET MVC and Linq-to-SQL.

So,

What I'm wanting to do is generate an XML document like this:

<?xml version="1.0" encoding="utf-8" ?>
<MainPage>
  <MainPageHtml><![CDATA[<p>lol!</p><br/>wtf? totally!]]></MainPageHtml>
  <Roles>
    <Role RoleId="1">User</Role>
    <Role RoleId="2">Administrator</Role>
  </Roles>
</MainPage>

And transform it with XSLT like this:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
  <xsl:output method="text" indent="no" encoding="iso8859-1" omit-xml-declaration="yes"/><xsl:template match="/">
  <xsl:value-of select="/MainPage/MainPageHtml"/>
  <![CDATA[<select>]]><xsl:for-each select="//Roles/Role">
    <![CDATA[<option value="]]><xsl:value-of select="@RoleId"/><![CDATA[">]]><xsl:value-of select="."/><![CDATA[</option>]]></xsl:for-each>
  <![CDATA[</select>]]></xsl:template>
</xsl:stylesheet>

Using this sort of code:

XmlDocument data = new XmlDocument(); data.Load("Data.xml");

XslCompiledTransform transform = new XslCompiledTransform(); transform.Load("Transform.xslt");

StringWriter sw = new StringWriter();
transform.Transform(data, null, sw);
Console.WriteLine(sw.ToString());

Console.ReadKey(true);

Is there a quick way to turn a Linq based Model like this:

return View("MainPage", new MainPageModel
{
    MainPageHtml = Config.MainPageHtml,
    Roles = Config.GetAllRoles()
});

Into the model above?

+1  A: 

I doubt there's a simple way to do what you're after.

What I do for a similar situation is add a ToXML() method to my data classes (the new lines aren't necessary but help when printing out the XML for diagnostic purposes so i always put them in).

public string ToXML() {
    string xml =
        " <provider>" + m_newLine +
        "   <id>" + m_id + "</id>" + m_newLine +
        "   <providerid>" + m_providerId + "</providerid>" + m_newLine +
        "   <providername>" + ProviderName + "</providername>" + m_newLine +
        " </provider>";
    return xml;
}

That apporach wouldn't do the whole job for you but would get you on the path.

Roles = Config.GetAllRoles(); 
string rolesXML = "<MainPage><MainPageHtml><p>lol!</p><br/>wtf? totally!></MainPageHtml>  <Roles>"
Roles.ForEach(r => rolesXML += String.Format("<Role RoleId='{0}'>{1}</Role>", r.ID, r.Description));
string rolesXML += "</Roles>";

In your XSL you don't need the CDATA sections for new elements. Instead of:

<![CDATA[<option value="]]><xsl:value-of select="@RoleId"/><![CDATA[">]]><xsl:value-of select="."/><![CDATA[</option>]]>

you can use the much clearer:

<option>
    <xsl:attribute name="value"><xsl:value-of select="@RoleId"/></xsl:attribute>
    <xsl:value-of select="."/>
</option>
sipwiz
Code like your first snippet should be used only for diagnostics or prototyping because it will create invalid XML if the input contains characters such as < or >. It's much safer to use the XmlDocument, XmlWriter or XDocument APIs instead, as these will make sure things get escaped correctly for you.
itowlson
sipwiz
I don't need a good feel, since the XML is an intermediary step. I am actually looking for a way to NOT serialize first. I want it to stay as the DOM. (I think that the way I showed may do that.)
John Gietzen
Sure go for whatever suits. I was actually a it sad that ASP.Net MVC didn't have better support from XML inbuilt. I've been using a customised version of Maverick.Net http://mavnet.sourceforge.net/ for 7 years to build MVC web apps with a clean model-view by way of XML-XSL separation.
sipwiz
+1  A: 

Well, I found this method:

        var roles = from r in db.Roles
                    orderby r.Name
                    select new XElement("Role",
                        new XAttribute("RoleId", r.RoleID),
                        r.Name);

        var data = new XElement("MainPage", 
                        new XElement("Roles", roles));

        return View(data);

Which allows pretty easy creation. Is there anything better?

EDIT: I wrote an XSLT View Engine

Here is almost my whole solution:

public class XsltViewEngine : VirtualPathProviderViewEngine
{
    public XsltViewEngine()
    {
        base.ViewLocationFormats = new string[]
        {
            "~/Views/{1}/{0}.xslt",
            "~/Views/Shared/{0}.xslt",
        };

        base.PartialViewLocationFormats = base.ViewLocationFormats;
    }

    protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
    {
        return new XsltViewPage(partialPath);
    }

    protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
    {
        return new XsltViewPage(viewPath);
    }
}

class XsltViewPage : IView
{
    protected string ViewPath { get; private set; }

    public XsltViewPage(string viewPath)
    {
        this.ViewPath = viewPath;
    }

    public void Render(ViewContext viewContext, TextWriter writer)
    {
        XslCompiledTransform transform = new XslCompiledTransform();
        transform.Load(viewContext.HttpContext.Server.MapPath(this.ViewPath));

        object model = viewContext.ViewData.Model;

        if (model == null)
        {
            throw new InvalidOperationException("An attempt was made to render an XSL view with a null model.  This is invalid.  An XSL view's model must, at very least, be an empty XML document.");
        }

        if(model is IXPathNavigable)
        {
            transform.Transform((IXPathNavigable)model, null, writer);
        }
        else if (model is XNode)
        {
            transform.Transform(((XNode)model).CreateReader(), null, writer);
        }
        else
        {
            throw new InvalidOperationException("An attempt was made to render an XSL view with an invalid model.  An XSL view's model must be either an IXPathNavigable or an XNode.");
        }
    }
}

And, in Globals.asaxcs:

    protected void Application_Start()
    {
        ViewEngines.Engines.Add(new XsltViewEngine());

        RegisterRoutes(RouteTable.Routes);
    }
John Gietzen