Der er en masse forskellige viewengines, som man kan vælge at bruge, i stedet for standard webformviewengine’en. I MVCContrib ligger der bl.a. NVelocity, Brail, NHaml, XSLT. Jeg syntes XSLT lød semifornuftigt, så den har jeg kigget lidt på, men har ikke kunne finde noget dokumentation/eksempler. Så hvad gør man, man forsøger at skrive en selv. Det viser sig at være ret nemt, eftersom parseren allerede er skrevet for os.
Dsuden giver det ret god mening i min optik med en XSLTViewengine, eftersom man ofte har brug for at formattere og præsentere data, som modtages i XML.
Lad os gå lige til essensen, controlleren.
public class XsltViewEngine : VirtualPathProviderViewEngine
{
public XsltViewEngine(string xsltHome)
{
//sæt stierne, så controlleren ved hvor viewet kan findes
ViewLocationFormats = new[] {
xsltHome + "{1}/{0}.xslt",
xsltHome + "Shared/{0}.xslt" };
PartialViewLocationFormats = ViewLocationFormats;
}
//den helt overordnede metode til at "rendere" et view
protected override IView CreateView(ControllerContext controllerContext,
string viewPath, string masterPath)
{
return new XsltTransformView() { ViewPath = viewPath };
}
protected override IView CreatePartialView(ControllerContext controllerContext,
string partialPath)
{
throw new NotImplementedException();
}
//lav tranformationen
public class XsltTransformView : IView
{
public string ViewPath { get; set; }
public void Render(ViewContext viewContext, System.IO.TextWriter writer)
{
XsltArgumentList xslArg = new XsltArgumentList();
//tilføj alt ViewData til XsltArgumentList, så de kan bruges fra XSLT.
foreach (var item in viewContext.ViewData)
{
xslArg.AddParam(item.Key, "", item.Value);
}
//lav XSLT transformationen
var doc = XDocument.Parse(viewContext.ViewData.Model.ToString());
var path = viewContext.HttpContext.Server.MapPath(ViewPath);
var xslt = XDocument.Load(path);
var transformer = new XslCompiledTransform(true);
transformer.Load(xslt.CreateReader());
transformer.Transform(doc.CreateReader(), xslArg, writer);
}
}
}
For at kunne bruge min viewengine skal den registreres i Global.asax:
protected void Application_Start()
{
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new WebFormViewEngine());
ViewEngines.Engines.Add(new XsltViewEngine("~/Views/"));
RegisterRoutes(RouteTable.Routes);
}
Det er faktisk det. Så er jeg klar til at bruge min viewengine. Se følgende controller:
public class DemoController : Controller
{
public ActionResult Index()
{
ViewData.Model = XDocument.Load(Server.MapPath("Web.sitemap"));
ViewData["header"]= "XSLT viewengine";
return View();
}
}
Jeg kan nu i min View folder, lave en underfolder der hedder Demo, der matcher min DemoController, og i denne ligger jeg en Index.xslt.
Fordi det view der nu skal renderes, matcher det mønster jeg instantierede i constructoren på min controller ("..{1}/{0}.xslt"), så vil min viewengine blive valgt til renderingen.
Mit view (Index.xslt) ud som følgende:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="html" omit-xml-declaration="yes" indent="yes" encoding="utf-8" />
<xsl:param name="header"></xsl:param>
<xsl:template name="renderSiteMapEntry">
<xsl:variable name="depth" select="count(ancestor::node())-2"></xsl:variable>
<xsl:variable name ="url" select="@url"/>
<tr>
<td style="text-indent: {$depth*10}px">
<a href="{$url}">
<xsl:value-of select="@title"/>
</a>
</td>
</tr>
</xsl:template>
<xsl:template match="/">
<xsl:if test="$header !=''">
<xsl:value-of select="$header"/>
</xsl:if>
<xsl:for-each select="//siteMapNode">
<table>
<xsl:call-template name="renderSiteMapEntry" >
</xsl:call-template>
</table>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Bemærk at jeg bruger xsl:param, til at føre ViewData over med. I dette tilfælde bruger jeg parameteren header.
En anden pointe er, at du selvfølgelig kan bruge denne XSLT viewengine, fra andre views. Se nu f.eks. dette “webform” view
<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
<%= Html.ActionLink("SiteMap", "Index", "Demo") %>
<% Html.RenderAction<DemoController>( a => a.Index() ); %>
</asp:Content>
Med Html.RenderAction kan man kalde actions på andre controllere. Ovenstående resulterer i, at min sitemap bliver indsat inline. Html.RenderAction kommer med ASP.NET MVC Futures.