Javascript to perform processing after downloading a file

Page update date :
Page creation date :

Operation verification environment

Visual Studio
  • Visual Studio 2022
ASP.NET Core
  • 8.0 (Razpr Pages, MVC)
Web Browsers
  • Edge 119

Operating environment

Visual Studio (if you also include server programs)
  • Visual Studio 2022
ASP.NET Core (if it also includes server programs)
  • 8.0 (Razpr Pages, MVC)
Web Browsers
  • Edge
  • Google Chrome
  • Other browsers (not all checked)

(* The main body of this tip is the client-side process, so the server side can be anything)

At first

The main focus of these tips is the client program (Javascript), and the server program is only there to download files. Therefore, it does not matter what the server program is, as long as it can make you download the file.

Have users download files when they are accessed on the server

Mr./Ms. allows you to download files when you receive a request. At that time, we deliberately wait for a certain amount of time on the server so that it is easy to understand the start and end of the download.

Here's what happens on the server side of Razor Pages and MVC: You can implement it in other projects, such as Web API.

Razor Pages : Pages/Index.cshtml.cs

// 省略

namespace DownloadCompleteNotifyRazorPages.Pages
{
  public class IndexModel : PageModel
  {
    // 省略

    /// <summary>時間をかけてファイルをダウンロードします。</summary>
    public async Task<IActionResult> OnGetDownload()
    {
      // ダウンロード開始と完了を明確にする目的で待機を入れる
      await Task.Delay(5000);

      // 適当にファイルを作って返す
      var fileSize = 10_000_000;
      var sb = new System.Text.StringBuilder(fileSize);
      for (int i = 0; i < fileSize; i++) sb.Append('a');
      using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(sb.ToString()));
      return File(stream.ToArray(), "text/plain", $"ダウンロード.txt");
    }
  }
}

MVC : Coletollers/HomeController.cs

// 省略

namespace DownloadCompleteNotifyMvc.Controllers
{
  public class HomeController : Controller
  {
    // 省略

    /// <summary>時間をかけてファイルをダウンロードします。</summary>
    public async Task<IActionResult> Download()
    {
      // ダウンロード開始と完了を明確にする目的で待機を入れる
      await Task.Delay(5000);

      // 適当にファイルを作って返す
      var fileSize = 10_000_000;
      var sb = new System.Text.StringBuilder(fileSize);
      for (int i = 0; i < fileSize; i++) sb.Append('a');
      using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(sb.ToString()));
      return File(stream.ToArray(), "text/plain", $"ダウンロード.txt");
    }
  }
}

Client processing

Normally, you download a file by accessing a specified URL in a web browser. If you want to do something after the download is complete, you can run the download with Javascript.

There are many ways to do this, but in this case, we will use , which is also XMLHttpRequest the origin of asynchronous processing. You can also use jQuery ajax functions or the Fetch API.

ahref The download URL is written in the tag and id the process is executed when the one set to download is clicked.

$('#download').click(function (e) {
  console.log('ダウンロードを開始します。');
  e.preventDefault();  // href による画面遷移を抑止
  let url = $(e.target).attr('href');  // href からダウンロード URL 取得

  const xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);   // ダウンロード先 URL 設定
  xhr.responseType = 'blob';   // バイナリデータ取得であることを指示

  // ダウンロード完了後の処理を設定。この時点ではデータは取得してクライアントにありますが保存はしていません。
  xhr.onload = function (oEvent) {
    if (xhr.status !== 200) {
      console.log(`データの取得に失敗しました。(status=${xhr.status})`);
    } else {
      // 取得したデータ
      let blob = xhr.response;

      // レスポンスヘッダーからサーバーから送られてきたファイル名を取得する
      let fileName = '';
      let disposition = xhr.getResponseHeader('Content-Disposition');
      if (disposition && disposition.indexOf('attachment') !== -1) {
        let filenameRegex = /filename[^;=\n]=((['"]).*?\2|[^;\n]*)/;
        let matches = filenameRegex.exec(disposition);
        if (matches != null && matches[1]) {
          // 「filename*=UTF-8''%E3%83%87%E3%83%BC%E3%82%BF.txt;」からファイル名を取得
          fileName = decodeURI(matches[1].replace(/['"]/g, '').replace('utf-8', '').replace('UTF-8', ''));
        }
      }
      if (fileName === '') {
        // ファイル名を取得できなかったら念のため URL をファイル名とする
        let fileName = url.match('.+/(.+?)([\?#;].*)?$')[1];
      }

      // Blob オブジェクトを指す URL オブジェクトを作る
      let objectURL = window.URL.createObjectURL(blob);
      // リンク(<a>要素)を生成し、JavaScript からクリックする
      let link = document.createElement('a');
      document.body.appendChild(link);
      link.href = objectURL;
      link.download = fileName;   // download を指定するとブラウザで開くことなくダウンロードになる
      link.click();
      document.body.removeChild(link);

      console.log('ダウンロードを完了しました。');
    }
  };

  // リクエスト開始
  xhr.send();
});

a Normally, if you leave the element clicked, the default download process will move, so e.preventDefault(); we call to disable the default process.

href Get the download URL from and XMLHttpRequest GET set it to get it from .

XMLHttpRequest.responseTypeblob to instruct the binary data to be downloaded.

XMLHttpRequest.onload Since the process after the data download is completed can be described in the event, the process after the download is completed is written here. Since the data has only been downloaded to the client, the file is not actually stored locally. To save a file locally, you can add a tag that a contains pseudo-data and make it behave as if you had clicked on a link and saved the file. The data at that time can window.URL.createObjectURL be converted to a URL by the function, so href you can set it to and make it work.

The file name to be saved can be retrieved from the response header if it is included in the response from the Content-Disposition server. Since it is a little complicated to acquire, I think that the code should be used as it is. If the file name is not specified on the server, it cannot be helped, so I think that it will be set to the default file name or the file name at the end of the URL path.

Since this time I only display it in the console after saving the file, I think that the desired operation can be executed by rewriting this part with arbitrary code.

Once the data download process is described, the last thing we want to do is XMLHttpRequest.send call a function to actually start the request.

Download run link

Mr./Ms. Code provides links to the pattern of downloading the file directly and the pattern of downloading it with Javascript. a You can write the URL directly in the href tag, but if you write the following in ASP.NET Core, the URL will automatically href expand to .

Razor Pages : Pages/Index.cshtml

<ul>
  <li><a asp-page-handler="Download">ダウンロード (Javascript なし)</a></li>
  <li><a asp-page-handler="Download" id="download">ダウンロード (Javascript あり)</a></li>
</ul>

MVC : Views/Home/Index.cshtml

<ul>
  <li><a asp-controller="Home" asp-action="Download">ダウンロード (Javascript なし)</a></li>
  <li><a asp-controller="Home" asp-action="Download" id="download">ダウンロード (Javascript あり)</a></li>
</ul>