skip to main |
skip to sidebar
In previous post I have written about HtmlHelper extensions for rendering XML in ASP.NET MVC. There are scenarios for which that approach is a little bit too much. For example, if all what we want to put in response is transformation result, we would have to make additional view to call our extension method. In such case it would be better to have dedicated ActionResult for XML transformation. So let's write one:
public class XmlActionResult: ActionResult
{
...
}
We will start by adding some necessary fields. Most of them will be also properties, but I will skip their code here (at the end you can find link to complete class).
public class XmlActionResult: ActionResult
{
#region Fields
private XmlDocument _xmlDocument;
private XPathNavigator _xpathNavigator;
private string _documentContent;
private string _documentSource;
private XslCompiledTransform _transform;
private static XslCompiledTransform _transparentTransform;
private string _transformSource;
private XsltArgumentList _transformArgumentList;
#endregion
...
}
Let's also add a static contructor with some initialization logic:
public class XmlActionResult: ActionResult
{
...
#region Constructor
static XmlActionResult()
{
XmlTextReader copyTransformReader = new XmlTextReader(new StringReader(
"<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:template match=\"/\">
<xsl:copy-of select=\".\"/>
</xsl:template>
</xsl:stylesheet>"));
_transparentTransform = new XslCompiledTransform();
_transparentTransform.Load(copyTransformReader);
}
#endregion
...
}
Now it's time for heart of our ActionResult class- the ExecuteResult method:
public class XmlActionResult: ActionResult
{
...
#region
public override void ExecuteResult(ControllerContext context)
{
XPathDocument xpathDocument = null;
//Checking if we have been given XmlDocument or XPathNavigator directly,
if ((_xmlDocument == null) && (_xpathNavigator == null))
{
//Checking if we have document content
if (!String.IsNullOrEmpty(_documentContent))
{
StringReader documentReader = new StringReader(_documentContent);
xpathDocument = new XPathDocument(documentReader);
}
//Checking if we have path for document
else if (!String.IsNullOrEmpty(_documentSource) && (_documentSource.Trim().Length != 0))
{
//Checking if path is absolute or relative
if (!Path.IsPathRooted(_documentSource))
//Mapping the relative path
documentSource = context.HttpContext.Server.MapPath(_documentSource);
//Loading XML from file into XPathDocument
using (FileStream documentStream = new FileStream(_documentSource,
FileMode.Open, FileAccess.Read, FileShare.Read))
{
XmlTextReader documentReader = new XmlTextReader(documentStream);
xpathDocument = new XPathDocument(documentReader);
}
}
}
//Checking if we have been given XslCompiledTransform directly,
//or do we have path for transform
if ((_transform == null) &&
(!String.IsNullOrEmpty(_transformSource) && (_transformSource.Trim().Length != 0)))
{
//Checking if path is absolute or relative
if (!Path.IsPathRooted(_transformSource))
//Mapping the relative path
_transformSource = context.HttpContext.Server.MapPath(_transformSource);
//Loading XSLT from file into XslCompiledTransform
using (FileStream transformStream = new FileStream(_transformSource,
FileMode.Open, FileAccess.Read, FileShare.Read))
{
XmlTextReader tranformReader = new XmlTextReader(transformStream);
_transform = new XslCompiledTransform();
_transform.Load(tranformReader);
}
}
//Checking if we have XML in any form
if (((_xmlDocument != null) || (xpathDocument != null)) || (_xpathNavigator != null))
{
context.HttpContext.Response.ContentType = "text/html";
//Checking if we have XSLT
if (_transform == null)
//If not, let's use transparent one
_transform = _transparentTransform;
//Perform transformation based on form in which we have our XML
if (_xmlDocument != null)
_transform.Transform((IXPathNavigable)_xmlDocument, _transformArgumentList,
context.HttpContext.Response.Output);
else if (_xpathNavigator != null)
_transform.Transform(_xpathNavigator, _transformArgumentList,
context.HttpContext.Response.Output);
else
_transform.Transform((IXPathNavigable)xpathDocument, _transformArgumentList,
context.HttpContext.Response.Output);
}
}
#endregion
}
The XmlActionResult class is ready, so we can prepare a controller action which will use it:
public ActionResult XmlResult()
{
XmlDocument cdCatalogDocument = new XmlDocument();
cdCatalogDocument.Load(Server.MapPath("~/App_Data/CDCatalog.xml"));
XmlActionResult result = new XmlActionResult();
result.Document = cdCatalogDocument;
result.TransformSource = Server.MapPath("~/Xslt/CDCatalog.xsl");
return result;
}
This is the result of our hard work:
Complete class can be downloaded here. I hope someone will find it useful.
In ASP.NET we had Xml control. Let's try providing functionality close to it. How about extension class with following methods:
public static class RenderXmlExtensions
{
public static void RenderXml(this HtmlHelper htmlHelper, XmlDocument xmlDocument, XslCompiledTransform transform) { ... }
public static void RenderXml(this HtmlHelper htmlHelper, XmlDocument xmlDocument, XslCompiledTransform transform, XsltArgumentList transformArgumentList) { ... }
public static void RenderXml(this HtmlHelper htmlHelper, XmlDocument xmlDocument, string transformSource) { ... }
public static void RenderXml(this HtmlHelper htmlHelper, XmlDocument xmlDocument, string transformSource, XsltArgumentList transformArgumentList) { ... }
public static void RenderXml(this HtmlHelper htmlHelper, XPathNavigator xpathNavigator, XslCompiledTransform transform) { ... }
public static void RenderXml(this HtmlHelper htmlHelper, XPathNavigator xpathNavigator, XslCompiledTransform transform, XsltArgumentList transformArgumentList) { ... }
public static void RenderXml(this HtmlHelper htmlHelper, XPathNavigator xpathNavigator, string transformSource) { ... }
public static void RenderXml(this HtmlHelper htmlHelper, XPathNavigator xpathNavigator, string transformSource, XsltArgumentList transformArgumentList) { ... }
public static void RenderXml(this HtmlHelper htmlHelper, string documentSource, XslCompiledTransform transform) { ... }
public static void RenderXml(this HtmlHelper htmlHelper, string documentSource, XslCompiledTransform transform, XsltArgumentList transformArgumentList) { ... }
public static void RenderXml(this HtmlHelper htmlHelper, string documentSource, string transformSource) { ... }
public static void RenderXml(this HtmlHelper htmlHelper, string documentSource, string transformSource, XsltArgumentList transformArgumentList) { ... }
}
Ok, that's a lot of methods. But don't worry, all of them will call single internal method, which will perform actual rendering:
private static void RenderXmlInternal(HtmlHelper htmlHelper, XmlDocument xmlDocument, XPathNavigator xpathNavigator, string documentSource, XslCompiledTransform transform, string transformSource, XsltArgumentList transformArgumentList)
{
XPathDocument xpathDocument = null;
//Checking if we have been given XmlDocument or XPathNavigator directly,
//or do we have path for document
if ((xmlDocument == null) && (xpathNavigator == null) &&
(!String.IsNullOrEmpty(documentSource) && (documentSource.Trim().Length != 0)))
{
//Checking if path is absolute or relative
if (!Path.IsPathRooted(documentSource))
//Mapping the relative path
documentSource = htmlHelper.ViewContext.HttpContext.Server.MapPath(documentSource);
//Loading XML from file into XPathDocument
using (FileStream documentStream = new FileStream(documentSource,
FileMode.Open, FileAccess.Read, FileShare.Read))
{
XmlTextReader documentReader = new XmlTextReader(documentStream);
xpathDocument = new XPathDocument(documentReader);
}
}
//Checking if we have been given XslCompiledTransform directly,
//or do we have path for transform
if ((transform == null) &&
(!String.IsNullOrEmpty(transformSource) && (transformSource.Trim().Length != 0)))
{
//Checking if path is absolute or relative
if (!Path.IsPathRooted(transformSource))
//Mapping the relative path
transformSource = htmlHelper.ViewContext.HttpContext.Server.MapPath(transformSource);
//Loading XSLT from file into XslCompiledTransform
using (FileStream transformStream = new FileStream(transformSource,
FileMode.Open, FileAccess.Read, FileShare.Read))
{
XmlTextReader tranformReader = new XmlTextReader(transformStream);
transform = new XslCompiledTransform();
transform.Load(tranformReader);
}
}
//Checking if we have XML in any form
if (((xmlDocument != null) || (xpathDocument != null)) || (xpathNavigator != null))
{
//Checking if we have XSLT
if (transform == null)
//If not, let's use transparent one
transform = _transparentTransform;
//Perform transformation based on form in which we have our XML
if (xmlDocument != null)
transform.Transform((IXPathNavigable)xmlDocument, transformArgumentList,
htmlHelper.ViewContext.HttpContext.Response.Output);
else if (xpathNavigator != null)
transform.Transform(xpathNavigator, transformArgumentList,
htmlHelper.ViewContext.HttpContext.Response.Output);
else
transform.Transform((IXPathNavigable)xpathDocument, transformArgumentList,
htmlHelper.ViewContext.HttpContext.Response.Output);
}
}
Isn't that simple? All we have to do now is adding our namespace in web.config
<configuration>
...
<system.web>
...
<pages>
...
<namespaces>
...
<add namespace="Lib.Web.Mvc.Html"/>
</namespaces>
</pages>
...
</system.web>
...
</configuration>
prepare controller action, which will pass our XML to view
public ActionResult RenderXml()
{
XmlDocument cdCatalogDocument = new XmlDocument();
cdCatalogDocument.Load(Server.MapPath("~/App_Data/CDCatalog.xml"));
return View(cdCatalogDocument);
}
call our extension method in the view
<asp:Content ID="cContent" ContentPlaceHolderID="cphContent" runat="server">
<% Html.RenderXml((System.Xml.XmlDocument)Model, "~/Xslt/CDCatalog.xsl"); %>
</asp:Content>
and we can see the results.
The extension class can be found here (you can also download sample application from here). I will probably write one more post about this subject, with a little bit different approach.
This is probably the last post about jqGrid. TreeGrid is quite cool and very easy to use feature. It supports both the Nested Set model and the Adjacency model. I'm going to show only the Adjacency model, but all the differences lies in data handling.
We will start with jqGrid initlization function:
<script type="text/javascript">
$(document).ready(function() {
$('#jqgTreeGrid').jqGrid({
//enable TreeGrid
treeGrid: true,
//set TreeGrid model
treeGridModel: 'adjacency',
//set expand column
ExpandColumn: 'Name',
//url from wich data should be requested
url: '/Home/FilesAdjacencyTreeGridData/',
//type of data
datatype: 'json',
//url access method type
mtype: 'POST',
//columns names
colNames: ['Path', 'Name', 'CreationTime', 'LastAccessTime', 'LastWriteTime'],
//columns model
colModel: [
{ name: 'Path', index: 'Path', width: 1, hidden: true, key: true },
{ name: 'Name', index: 'Name', align: 'left' },
{ name: 'CreationTime', index: 'CreationTime', align: 'left' },
{ name: 'LastAccessTime', index: 'LastAccessTime', align: 'left' },
{ name: 'LastWriteTime', index: 'LastWriteTime', align: 'left' }
],
//pager for grid
pager: $('#jqgpTreeGrid'),
//grid width
width: 'auto',
//grid height
height: 'auto'
});
});
</script>
As you can see, the grid is configured to perform POST request. If the request is for child node, it will have three additional parameters: nodeid - the id of the currently expanded node, parentid - the parent id of the currently expanded node, n_level - the level value of the currently expanded node. We also need to add corresponding fields to every cell array in rows array. Here is a simple example:
/// <summary>
/// Provides json data for TreeGrid in adjacency mode
/// </summary>
/// <param name="postData">POST parameters collection</param>
/// <returns></returns>
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult FilesAdjacencyTreeGridData(FormCollection postData)
{
DirectoryInfo root = null;
//Checking if we are expanding existing node
if (postData.AllKeys.Contains("nodeid"))
root = new DirectoryInfo(postData["nodeid"]);
else
root = new DirectoryInfo(@"D:\Books\");
//Getting childrens
var children = from child in root.GetFileSystemInfos()
orderby child is DirectoryInfo descending
select child;
//Preparing result
var filesData = new
{
page = 1,
total = 1,
records = children.Count(),
rows = (from child in children
select new
{
//table of cells values
cell = new object[] {
child.FullName,
child.Name,
child.CreationTime.ToString(),
child.LastAccessTime.ToString(),
child.LastWriteTime.ToString(),
//Level - based on '\' count
((from backslash in child.FullName
where backslash == '\\'
select backslash).Count() - 2),
//Parent id
root.FullName == @"D:\Books\" ? null : root.FullName,
//Is not expandable
child is FileInfo,
//Is expanded
false
}
}
).ToArray()
};
//Returning json data
return Json(filesData);
}
Screenshot of our sample at work (you can download source code here):