03 April 2008 AJAX, ASP.NET, Excel, Export, Javascript, Json Robert Muehsig

Wir (d.h. Oliver und ich) haben bei einem Projekt die Anforderung gehabt, in einer sehr AJAX und Javascriptlastigen Anwendung ein Excel Export mit einzubauen.

Durch ein Postback oder durch Aufruf einer Webmethode etc. ist dies sehr einfach zu lösen, allerdings gab es ein Problem:

Die letztendliche Ergebnisliste wird über verschiedene Javascript Methoden befüllt, d.h. das momentan angezeigte (und in einem JSON Objekt befindliche) Ergebnis muss am besten direkt irgendwie übergeben werden.

Da die Lösung etwas "geschickt" ist (der Erfinder ist Oliver) Schritt für Schritt erklärt:

1. Grundsätzlicher Aufbau

image

2. Default.aspx vorbereiten

Die ASPX Seite (abgesehen von dem Javascript - welches gleich näher beschrieben wird) ist simpel:

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
    <script type="text/javascript">
    // wird gleich gezeigt
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <button type="button" onclick="createXlsData()">Create XLS</button>
    </div>
    </form>
</body>
</html>

Zwischenbemerkung: Wie erstellt man eigentlich eine Excel List?

Excel kann auch HTML Tabellen "interpretieren" und entsprechend darstellen.

Das HTML drumherum muss man weg lassen, um eine einfache Tabelle zu erzeugen muss man Excel sowas übergeben:

<table>
  <tr>
     <td>Sample</td>
  </tr>
</table>

3. Im Javascript Tabelle vorbereiten: "createXlsData()"

In diesem Teil können wir nun auf unsere JSON Objekte zugreifen - ohne nochmal einen Webservice etc. zu befragen.

function createXlsData()
    {
    var data = "<table>";
            data += "<tr>";
                data += "<td>Test 1</td>";
                data += "<td>Test 2</td>";
             data += "</tr>"
        
    for(i=0; i < 10; i++)
    {
        var singleLine = "<tr>";
                singleLine += "<td> Test Data1: " + i + "</td>";
                singleLine += "<td> Test Data2: "  + i + "</td>";
        singleLine += "</tr>";
        data += singleLine;   
    }
        data += "</table>";
    sendXlsData(data);
    }

4. Daten an Handler übermitteln und Handler aufrufen

Da der String für einen "GET" Request zu lang ist, muss er per "POST" an den Handler übermittelt werden . natürlich mit AJAX.

Problem: Wenn man dies per AJAX macht, sieht man nie dieses Fenster:

image

Lösung: Der Handler speichert die übergebenen Werte in der Session und das Javascript ruft expliziert nochmal den Handler aus, welcher die Daten aus der Session holt.

Hier der komplette Source-Code der Default.aspx:

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
    <script type="text/javascript">
    var DataExchangeHttpRequest;

    function createXlsData()
    {
    var data = "<table>";
            data += "<tr>";
                data += "<td>Test 1</td>";
                data += "<td>Test 2</td>";
             data += "</tr>"
        
    for(i=0; i < 10; i++)
    {
        var singleLine = "<tr>";
                singleLine += "<td> Test Data1: " + i + "</td>";
                singleLine += "<td> Test Data2: "  + i + "</td>";
        singleLine += "</tr>";
        data += singleLine;   
    }
        data += "</table>";
    sendXlsData(data);
    }

    function sendXlsData(data)
    {
    if (window.XMLHttpRequest) // Mozilla, Safari, Opera, IE7
        {
        DataExchangeHttpRequest = new XMLHttpRequest();
        }
    else if (window.ActiveXObject) // IE6, IE5
        {
        DataExchangeHttpRequest = new ActiveXObject("Microsoft.XMLHTTP");
        }

    DataExchangeHttpRequest.onreadystatechange = openXlsPage;
    DataExchangeHttpRequest.open('POST', 'ExcelHandler.ashx', true);
    DataExchangeHttpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
    DataExchangeHttpRequest.setRequestHeader('Content-Length',data.length);

    DataExchangeHttpRequest.send(data);
    }
    function openXlsPage()
    {
        if (DataExchangeHttpRequest.readyState == 4 && DataExchangeHttpRequest.status == 200)
        {
        window.open("ExcelHandler.ashx?openXls=true","xls");
        }
    }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <button type="button" onclick="createXlsData()">Create XLS</button>
    </div>
    </form>
</body>
</html>

5. Der ASHX Handler

<%@ WebHandler Language="C#" Class="ExcelHandler" %>

using System;
using System.Web;

public class ExcelHandler : IHttpHandler, System.Web.SessionState.IRequiresSessionState
{

    public void ProcessRequest(HttpContext context)
    {

        if (context.Request.Params["openXls"] == "true")
        {
            // display data
            if (context.Session["ExcelData"] != null)
            {
                context.Response.ContentType = "application/vnd.ms-excel";
                context.Response.ContentEncoding = System.Text.Encoding.Default;
                context.Response.Write(context.Session["ExcelData"]);
            }
            else
            {
                context.Response.Write("session empty");
            }
        }
        else
        {
            // save data in session
            context.Response.ContentType = "application/vnd.ms-excel";
            string data = "";
            using (System.IO.StreamReader reader = new System.IO.StreamReader(context.Request.InputStream))
            {
                data = reader.ReadToEnd();
            }

            context.Session.Add("ExcelData", data);
        }
    }
    public bool IsReusable {
        get {
            return true;
        }
    }

}

Durch den Content Type "application/vnd.ms-excel" wird diese Javascript-generierte Tabelle von Excel erkannt.

Schematische Darstellung

image

Als Nutzer sieht das dann so aus

Klicken:

image

Fenster öffnet sich:

image

Im Excel sieht das dann so aus:

image

Bei der momentanen Lösung kommt da allerdings am Anfang noch eine Warnung, dass eine ASHX versucht was an Excel zu schicken - das bekommt man sicherlich auch noch irgendwie raus.

Wo macht das Sinn?

Das ganze macht da Sinn, wo man mit viel Javascript und AJAX sich Daten z.B. in einem JSON Objekt gesammelt hat und diese nun auch an Excel exportieren möchte - was bei uns der Fall war. Wenn man natürlich die Daten aus einer DB holen kann, ist sowas natürlich über ein Postback oder wie auch immer "sauberer".

Das ganze gehört eindeutig in die Kategorie: Freakige Sachen ;)

[ Download Source ]


Written by Robert Muehsig

Software Developer - from Dresden, Germany, now living & working in Switzerland. Microsoft MVP & Web Geek.
Other Projects: KnowYourStack.com | ExpensiveMeeting | EinKofferVollerReisen.de

If you like the content and want to support me you could buy me a beer or a coffee via Litecoin or Bitcoin - thanks for reading!