LoginSignup
2
3

More than 3 years have passed since last update.

jQueryのajax()メソッドを使って、Excelファイルをダウンロードする方法。

Last updated at Posted at 2019-05-01

はじめに

サーバーで生成したExcelファイルをajaxによりダウンロードできるか調べたことをまとめたメモ。
jQueryではなく素のJavaScriptの「XMLHttpRequest()」を使用した例や、jQueryのBinaryTransportを利用して
バイナリ型のデータを取得する方法もググればすぐに出てくるのでこれらを利用してもよかったのだが、
jQueryのajaxで出来ないことに違和感を感じたのでどうせ仕事するんだから調べてみようということで調べてみた。

動作環境

  • サーバー側
    • ASP.NET(VB) MVC
    • OpenXmlを利用してMemoryStreamによりExcelファイルのバイナリデータを生成する
  • クライアント側
    • jQuery3.3.1
    • IE11

Excelファイルとして開けない例と開ける例

ググって出てきた情報から考えるとajaxのパラメータ「responseType」に「blob」が設定できれば
サーバーで生成したバイナリデータの配列を受け取れるはず・・・
しかし下記「Excelファイルとして開けない例」のようにajaxのパラメータに「responseType : "blob"」と書いてもバイナリ型のデータとして受け取ることができない。text型に変換されてしまう。

Excelファイルとして開けない例

  • サーバー側のソース
controller.vb
Imports DocumentFormat.OpenXml
Imports DocumentFormat.OpenXml.Packaging
Imports DocumentFormat.OpenXml.Spreadsheet

Function OutputSpreadsheet(model As FormModel) As ActionResult
    Dim fileContents As Byte()
    Using stream As New MemoryStream()

    ~~~略~~~

        workbookpart.Workbook.Save()
        spreadsheetDocument.Close()
        fileContents = stream.ToArray()
    End Using
    Return File(fileContents, _
                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", _
                "sample.xlsx")
End Function
  • クライアント側のソース
downloadSpreadsheet.js
    $.ajax({
        type         : "post",
        url          : "./OutputSpreadsheet", 
        data         : FormModel,
        responseType : "blob",
     .done(function( data, textStatus, jqXHR ) {
         var downloadData = data;
         window.navigator.msSaveBlob(downloadData, "sample.xlsx");
     })
     .fail(function( jqXHR, textStatus, errorThrown ) {
         ~~~~~~
     })
     .always(function( data, textStatus, errorThrown ) {
         ~~~~~~
     });
         ~~~~~~

Excelファイルとして開ける例

下記クライアント側のソースのようにajaxのパラメータにxhrFieldsを連想配列で指定し、要素に{responseType : 'blob'}と書いてあげればバイナリ型のデータが取得でき「window.navigator.msSaveBlob」にてExcelファイルのダウンロードができる。
「ajaxSetup」や「ajaxPrefilter」の利用および「ajaxTransport」のオーバーライドはしなくてもよい。

  • サーバー側のソース

「Excelファイルとして開けない例」の「controller.vb」と同様

  • クライアント側のソース
downloadSpreadsheet.js
    $.ajax({
        type         : "post",
        url          : "./OutputSpreadsheet", 
        data         : FormModel,
        xhrFields    : {responseType : 'blob'},
     .done(function( data, textStatus, jqXHR ) {
         var downloadData = data;
         window.navigator.msSaveBlob(downloadData, "sample.xlsx");
     })
     .fail(function( jqXHR, textStatus, errorThrown ) {
         ~~~~~~
     })
     .always(function( data, textStatus, errorThrown ) {
         ~~~~~~
     });
         ~~~~~~

調査内容

なぜExcelファイルが開けない?

jQuery-3.3.1.jsのソースを見ながらデバッグすると、9486行目付近の[xhr = options.xhr();]で
Excelファイルとして開けない例で示した方法で指定した「responseType」パラメータの値が空に設定される。

jQuery-3.3.1.js
jQuery.ajaxTransport( function( options ) {
    var callback, errorCallback;

    // Cross domain only allowed if supported through XMLHttpRequest
    if ( support.cors || xhrSupported && !options.crossDomain ) {
        return {
            send: function( headers, complete ) {
                var i,
                    xhr = options.xhr();         <-ここでxhrのresponseTypeの値が""となる

そうすると後続の9556行目付近でbinary型またはtext型の判定でtext型で返すようになる。
そのため、「window.navigator.msSaveBlob」にtext型の値を渡すことになるので破損したExcelファイルが生成される。

jQuery-3.3.1.js
                                    ( xhr.responseType || "text" ) !== "text"  ||    <-ここがfalseになるのでtext型が返る
                                    typeof xhr.responseText !== "string" ?
                                        { binary: xhr.response } :
                                        { text: xhr.responseText },
                                    xhr.getAllResponseHeaders()

Excelファイルが開けるようにするには?

サーバーから返ってくる値の型が「binary」になるようにすればよいので上記9556行目付近の判定で「xhr.responseType」に何か値が入っているようにすればよい。
xhr.responseTypeの値が設定できる箇所が9500行目付近で、ajaxメソッドにてxhrFieldsパラメータを使えばxhrのプロパティ「responseType」に値を設定できる。

jQuery-3.3.1.js
                if ( options.xhrFields ) {
                    for ( i in options.xhrFields ) {
                        xhr[ i ] = options.xhrFields[ i ];    <-ここでresponseTypeに値を設定することができる
                    }
                }

参考サイト

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3