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):

Older Posts