2019年11月27日水曜日

ASP.NET CoreでSignalRを使ってファイルダウンロード

環境

  • ASP.NET Core 2.1
  • Microsoft.AspNetCore.SignalR 1.0.4
  • jQuery 3.3.1
  • Visual Studio 2017  15.9.10

やりたいこと

  •  サーバー上でPDFを作成しダウンロードする機能を作ったが処理に時間がかかる。このため、ただローディング画像を出すだけではなく処理状況の進捗を表示し終わったら自動でダウンロードするようにしたい。(※サイズの大きいファイルのダウンロード状況の進捗ではないです。)

とにかくソース

asp.net core controller (C#)
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;

namespace SignalRFileDownload.Hubs
{
 public class DemoHub : Hub
 {
  public Task Download()
  {
   byte[] contents = null;

   /*
    * 処理に時間がかかる処理
    * Clients.Caller.SendAsync("MessageReceive","通知メッセージ");
    */
   var result = new FileContentResult(contents, "application/pdf") { FileDownloadName = "demo.pdf" };
   return Clients.Caller.SendAsync("Complete", result);
  }
 }
}

※上記に加えサーバー側ではStartup.csにMapHub設定追加
JavaScript

//SignalRコネクション作成
const connection = new signalR.HubConnectionBuilder()
 .withUrl("/demohub")
 .configureLogging(signalR.LogLevel.Information)
 .build();
//コネクションスタート
connection.start().catch(e => console.log(e));
//ダウンロード実行
function execute() {
 connection.invoke('Download').catch(e => console.log(e));
}
//メッセージ表示
connection.on('MessageReceive', (message) => {
 console.log(message);
});
//ファイルデータ受信処理(サーバー側処理完了時)
connection.on('Complete', (data) => {
 let filename = decodeURI(data.fileDownloadName);
 var mimeType = data.contentType;
 var downloadData = toBlob(data.fileContents, mimeType);
 let downloadUrl = (window.URL || window.webkitURL).createObjectURL(downloadData);
 let link = document.createElement('a');

 link.href = downloadUrl;
 link.download = filename;
 link.click();
 (window.URL || window.webkitURL).revokeObjectURL(downloadUrl);
});
// Base64とMIMEコンテンツタイプからBlobオブジェクトを作成する。(日本語対応)
function toBlob(base64, mime_ctype) {
 // 日本語の文字化けに対処するためBOMを作成する。
 var bom = new Uint8Array([0xEF, 0xBB, 0xBF]);
 var bin = atob(base64.replace(/^.*,/, ''));
 var buffer = new Uint8Array(bin.length);
 for (var i = 0; i < bin.length; i++) {
  buffer[i] = bin.charCodeAt(i);
 }
 try {
  var blob = new Blob([bom, buffer.buffer], {
   type: mime_ctype,
  });
 } catch (e) {
  return false;
 }
 return blob;
}


ポイント

  • サーバー側ではFileContentのオブジェクトを返す。
  • クライアント側では戻り値にfileDownloadName、contentType、fileContentsの各プロパティから必要なデータが取得できる。(fileContentsの中身はBase64でエンコードされている。)
  • 日本語に対応するためBlobオブジェクトを作成する際にBOMを付与してやる必要がある。
  • Blobオブジェクト作ってリンクタグ作ってソース上でクリックさせる。

雑感

この対応入れる前はajaxでファイルダウンロード処理してローディング画像表示ってやってたんですがそれでも我慢できないくらい時間のかかる処理だったためSignalR対応決意。思ったよりはまってしまったのですがControllerと同じくSignalRのHubでもFileContentを返せばいいだけでは、と気づいてからは早かったです。

参考にさせて頂いたサイト

 https://www.hos.co.jp/blog/20170213/