LA.NET [EN]

Oct 17

In the last few days I”ve returned to the ASP.NET AJAX forums. One of the most requested features I”ve seen is generating “excel responses” (do you think i should copyright the term? :) ,,) from data presented on a gridview. There”s already a known strategy for doing this kind of stuff from a traditional ASP.NET page: we start by clearing the previous contents that might have been written into the stream associated with the HttpResponse object, create a new Stream derived object and render the grid passing it the previously created stream. then it”s just a matter of ending the response (oh, and I”ve also assumed that you”ve set the correct type for the response). Here”s an example for the previous description.

Enters MS ASP.NET AJAX and the UpdatePanel. the problem is that you cannot apply the previous strategy since the client platform expects to receive a response on a predefined format and, if you follow the Response.Write strategy presented on the previous example, you”ll probably end up getting a parsing exception on the client side. So, the problem remains: how can you export the contents of a GridView from an ASP.NET AJAX page?

If you”ve looked at the previous example, it”s obvious that the only thing you need is the table”s HTML. After getting it, you only need to set the content type of the response so that it is understood by the browser. If you stop to think for a minute, you”ll see that in the client side you”ve already have the HTML for the table (well, at least that is what I”m assuming from now on). The only thing you need to do is save it to the disk. And this is the problem you have to solve.

The solution I”m presenting for this problem consists in performing the following steps:

  1. create a “hidden” iframe and add it to the current page
  2. add a form and an input field to that iframe
  3. get the html from the grid by using the non-standard innerHTML (which is available in all browsers – at least on the ones I use)
  4. set the input field”s value to the innerHTML obtained from grid and submit the form
  5. add a handler which receives the previous form submission and just returns the value of the input field (while setting the content type of the message)

Do notice that the code I”m presenting here was written in about an hour and isn”t really production code. The main objective is to present the ideas that you might follow if you need to export a GridView to excel while using the traditional Response.Write technique.

Having said that, it”s time to present the code. Since I need to have some data to show in my grid, I”ve started by creating a new cs file with the following contents:

public class Obj
{
    private Int32 _id;
    private String _name;

    public int Id
    {
        get { return _id; }
        set { _id = value; }
    }

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
}

public class ObjDS
{
    public List<Obj> Get()
    {
        List<Obj> objs = new List<Obj>
        {
          new Obj{Id = 1, Name = “Luis”}  ,
          new Obj{Id = 2, Name = “Jose”} 
        }
        ;
        return objs;
    }
}

The ObjDS class” objective is to feed the grid automatically through an ObjectDataSource control. The basic code of the page is the following:

<form id=”form1″ runat=”server”>
  <div>
      <asp:ScriptManager runat=”server” ID=”manager” />
      <asp:UpdatePanel runat=”server” ID=”panel”>
          <contenttemplate>
              <asp:GridView ID=”GridView1″ runat=”server” DataSourceID=”source”>
              </asp:GridView>
              <asp:ObjectDataSource  
                  TypeName=”ObjDs”
                  SelectMethod=”Get”
                  ID=”source” runat=”server”>
              </asp:ObjectDataSource>
          </contenttemplate>
      </asp:UpdatePanel>
  </div>   
  <input type=”button” value=”save” onclick=”save()” />
  </form>

It”s really a simple form with a grid and an HTML button which is going to be used to export the contents of the grid. When the user clicks the button, the save method gets called:

function save(){
    var grid   = $get(“GridView1″);
    var html   = grid.parentNode.innerHTML;
    var frmWnd = getIFrameWnd();
    var txt    = frmWnd.document.getElementById(“body”);
    txt.value  = encodeURI(html);
    var frm    = frmWnd.document.getElementById(“frm”);
    frm.submit();
}

After getting the grid”s HTML content, we need to get a reference to the window associated with the iframe that will let us get the “excel response” from the server. Notice that we need a reference to the window object associated with the iframe (not a reference the iframe objec that is introduced on the page – they are different kind of objects!).

Through the iframe”s window reference, we can get to the textbox maintained by the form that was injected on the iframe and we can set its value. Submiting the form is all that is need to get the traditional dialog that will let you download the excel file with the grid contents.

You can say that the tricky part is the getIFrameWnd:

function getIFrameWnd(){
    var ifr = $get(“xslFrame”);
    if( !ifr ){
      createFrame();
    }          
    var wnd =  window.frames[“xslFrame”];       
    return wnd;
}

 

We start by trying to get a reference to the iframe object (this will return non null if a previous call has been made). When we don”t get a valid reference, we need to create the iframe element and set its content to the form we”ll use to get the response from the server. Lets start with the createFrame method:

function createFrame() {
  var frame = document.createElement( “iframe” );
  frame.name = “xslFrame”;
  frame.id = “xslFrame”;

  document.body.appendChild( frame );
  generateIFrameContent();
  frame.style.width = “0px”;
  frame.style.height = “0px”;
  frame.style.border = “0px”;
}

You might be wondering why I simply didn”t set th
e display to none instead of  setting the width, height and border to 0. Well, this was the first thing that worked with both IE and Firefox (in Firefox I was only able to submit the form if i didn”t set the display of the iframe to none). The generateIFrameContent method is also simple:

 function generateIFrameContent(){
    var frameWnd = window.frames[“xslFrame”];
    var content = “<form id=”frm” method=”post” enctype=”application/data” action=”excelHandler.ashx”><input type=”text”id=”body” name=”body” /></form>”;
    frameWnd.document.open();
    frameWnd.document.write( content );
    frameWnd.document.close();
}

Again, notice how we must get a reference to the window object associated with the iframe. The contents of the iframe are set up by using the document.write method. The excelHandler.ashx looks like this:

<%@ WebHandler Language=”C#” Class=”excelHandler” %>

using System;
using System.Web;

public class excelHandler : IHttpHandler {
    public bool IsReusable
    {
        get { return true; }
    }

    public void ProcessRequest(HttpContext context)
    {
        String info = HttpUtility.UrlDecode(context.Request.Params[“body”]);
        context.Response.Clear();
        context.Response.AddHeader(“content-disposition”, “attachment; filename=file.xls”);
        context.Response.Charset = “”;

        context.Response.ContentType = “application/vnd.xls”;
        System.IO.StringWriter stringWrite = new System.IO.StringWriter();
        context.Response.Write(info);
    }

}

 

As you can see, there really are lots of things which could be improved in the previous code. For instance, it”s easy to change the previous snippets so that it doesn”t hardcode the handler or event the file name of the “excel response” that is generated by the server. You might even configure the client save method so that it receives a reference to another another method that would let you filter the html that is sent back to ther server (something you might do if you have buttons in your grid – ex.: select button).

In fact, you could even get fancy and generate an extender that encapsulates all this code and exposes several properties that give you extra power over the way things work…As I”ve said, my main objective was to present some ideas that might help achieving this kind of things. I”ll leave that encapsulation for anyone that is interested :)

Now, this solution won”t work in all the scenarios. For instance, if you”ve got paging, you might need to get everything when you export the grid to excel. The current  code won”t work because it simply captures the table presented to the user in the page. Another thing that might look strange is that you”re sending the HTML back to the server which is sending it back to you again. This is needed so that you can get the “traditional” dialog that will let you save the file. You might be thinking that this isn”t really great when you have a huge grid (and yes, you”re right). But imagine that you”re using the traditional approach…if you don”t have the viewstate disabled (and yes, there are still many guys out there that haven”t disabled it yet – ok, if you”re one of them, you”re still in time to do just that :) ), you”ll end up sending even more info back to the server (specially if you have more fields on your page).

So, the code I”ve presented here isn”t perfect (far from it) but the ideas might help you in several scenarios. At least, that is what I”m hoping for…

6 comments so far

  1. achintya
    6:19 pm - 10-18-2007

    This still postbacks . Am I doing something wrong ?
    I can still see the progress bar in the IE bottom right corner.

  2. Elena
    5:06 pm - 4-4-2008

    Thanks a lot, I was looking for the Ajax solution for quite a while!

  3. Crave
    8:10 am - 7-11-2008

    Nice workaround man!

    My problem was a bit different though, but this solution helped me resolve it. I was actually creating hyperlinks at runtime which was being done inside a DynamicPopulateExtender. Needed to show the save as dialogue.

    Thanks again! ;o)

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>