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

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

###動作環境
- サーバー側
	- ASP.NET（VB） MVC
	- OpenXmlを利用してMemoryStreamによりExcelファイルのバイナリデータを生成する
- クライアント側
	- jQuery3.3.1
	- IE11

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

###Excelファイルとして開けない例
- サーバー側のソース

```VB: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
```
- クライアント側のソース

```Javascript: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」と同様

- クライアント側のソース

```Javascript: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」パラメータの値が空に設定される。

```Javascript: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ファイルが生成される。

```Javascript: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」に値を設定できる。

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


##参考サイト
- [AJAXでダウンロードしたExcelファイルが破損](https://teratail.com/questions/111582)
- [ajaxでファイルをダウンロードさせようとした](https://qiita.com/kenta8813/items/d0307c258d61ecebb064)
- [AJAXでファイルダウンロード](https://memo.ecp.plus/download_files_by_ajax/)
- [javascriptでバイナリを扱うために](https://qiita.com/yaegaki/items/909587a2dae20467c74a)
- [jQueryのajaxでバイナリのデータを取得する](https://qiita.com/yuma84/items/fefb95c1a10396070cd2)
- [jQuery BinaryTransport](https://github.com/henrya/js-jquery/tree/master/BinaryTransport)
- [READING BINARY DATA USING JQUERY AJAX](http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/)
- [Ajax: Integrated native datatype, for XHR2 rich responseType support #1525](https://github.com/jquery/jquery/pull/1525)