ASP.NET Core Blazor でのファイルのダウンロード

注意

これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

重要

この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。

現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

この記事では、Blazor アプリでファイルをダウンロードする方法について説明します。

ファイルのダウンロード

ファイルは、アプリ独自の静的アセット、または他の任意の場所からダウンロードできます。

  • ASP.NET Core アプリは、静的ファイル ミドルウェアを使用して、ファイルをサーバー側アプリのクライアントに提供します。
  • この記事のガイダンスは、コンテンツ配信ネットワーク (CDN) など、.NET を使用しない他の種類のファイル サーバーにも適用されます。

この記事では、ファイルをブラウザーで開くのではなく、クライアントにダウンロードして保存する必要がある次のシナリオに対するアプローチについて説明します。

アプリとは別のオリジンからファイルをダウンロードする場合、クロス オリジン リソース共有 (CORS) に関する考慮事項が該当します。 詳細については、「クロス オリジン リソース共有 (CORS)」セクションを参照してください。

セキュリティに関する考慮事項

サーバーからファイルをダウンロードする機能をユーザーに提供する場合は、十分に注意してください。 攻撃者は、サービス拒否 (DOS) 攻撃や API 悪用攻撃を実行したり、他の方法でネットワークやサーバーの侵害を試したりする可能性があります。

攻撃の成功の可能性を少なくするセキュリティ手順は、次のとおりです。

  • サーバー上のファイル ダウンロード専用領域 (好ましいのは、システム ドライブ以外) からファイルをダウンロードします。 専用の場所を使用することで、ダウンロード可能なファイルに対してセキュリティ制限を適用しやすくなります。 ファイルのダウンロード領域のアクセス許可を無効にします。
  • 悪意のあるユーザーは、クライアント側のセキュリティ チェックを簡単に回避できます。 常にサーバーでもクライアント側のセキュリティ チェックを実行してください。
  • ユーザーやその他の信頼されていないソースからファイルを受信し、そのファイルに対してセキュリティ チェックを実行せずにそのファイルをすぐにダウンロードできるようにしないでください。 詳細については、「ASP.NET Core でファイルをアップロードする」をご覧ください。

ストリームからのダウンロード

"このセクションは、通常、サイズが最大 250 MB までのファイルに当てはまります。"

比較的小さいファイル (< 250 MB) のダウンロードに推奨される方法は、JavaScript (JS) 相互運用を使用して、クライアント上の生のバイナリ データ バッファーにファイルの内容をストリーミングすることです。

警告

このセクションの方法では、ファイルの内容を JS ArrayBuffer に読み取ります。 この方法では、ファイル全体がクライアントのメモリに読み込まれるため、パフォーマンスが低下するおそれがあります。 比較的大きなファイル (>= 250 MB) をダウンロードするには、「URL からダウンロードする」セクションのガイダンスに従うことをお勧めします。

次の downloadFileFromStreamJS 関数:

  • 指定されたストリームを ArrayBuffer に読み取ります。
  • Blob を作成して、ArrayBuffer をラップします。
  • ファイルのダウンロード アドレスとして機能するオブジェクト URL を作成します。
  • HTMLAnchorElement (<a> 要素) を作成します。
  • ダウンロード用のファイル名 (fileName) と URL (url) を割り当てます。
  • アンカー要素で click イベントを発生させることでダウンロードをトリガーします。
  • アンカー要素を削除します。
  • URL.revokeObjectURL を呼び出すことでオブジェクト URL (url) を取り消します。 クライアント側でメモリがリークしないようにするため、この手順は非常に重要です。
<script>
  window.downloadFileFromStream = async (fileName, contentStreamReference) => {
    const arrayBuffer = await contentStreamReference.arrayBuffer();
    const blob = new Blob([arrayBuffer]);
    const url = URL.createObjectURL(blob);
    const anchorElement = document.createElement('a');
    anchorElement.href = url;
    anchorElement.download = fileName ?? '';
    anchorElement.click();
    anchorElement.remove();
    URL.revokeObjectURL(url);
  }
</script>

Note

JS の場所と実稼働アプリの推奨事項に関する一般的なガイダンスについては、ASP.NET Core Blazor アプリの JavaScript の位置に関する記事を参照してください。

次の コンポーネントでは、次を実行します。

  • ネイティブ バイト ストリーミング相互運用を使用して、ファイルをクライアントに効率的に転送します。
  • クライアントにダウンロードされるファイルの Stream を取得するための GetFileStream というメソッドがあります。 別の方法として、ストレージからファイルを取得したり、C# コードでファイルを動的に生成したりする方法もあります。 このデモでは、アプリは新しいバイト配列 (new byte[]) から 50 KB のランダム データのファイルを作成します。 このバイト配列は MemoryStream でラップされ、例の動的に生成されたバイナリ ファイルとして使用されます。
  • DownloadFileFromStream メソッド:
    • GetFileStream から Stream を取得します。
    • ユーザーのマシンにファイルが保存されるときのファイル名を指定します。 次の例では、ファイルに quote.txt という名前を付けています。
    • ファイル データをクライアントにストリーミングできるようにするため、StreamDotNetStreamReference でラップします。
    • downloadFileFromStreamJS 関数を呼び出して、クライアント上でデータを受け入れます。

FileDownload1.razor:

@page "/file-download-1"
@using System.IO
@inject IJSRuntime JS

<PageTitle>File Download 1</PageTitle>

<h1>File Download Example 1</h1>

<button @onclick="DownloadFileFromStream">
    Download File From Stream
</button>

@code {
    private Stream GetFileStream()
    {
        var randomBinaryData = new byte[50 * 1024];
        var fileStream = new MemoryStream(randomBinaryData);

        return fileStream;
    }

    private async Task DownloadFileFromStream()
    {
        var fileStream = GetFileStream();
        var fileName = "log.bin";

        using var streamRef = new DotNetStreamReference(stream: fileStream);

        await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
    }
}
@page "/file-download-1"
@using System.IO
@inject IJSRuntime JS

<h1>File Download Example</h1>

<button @onclick="DownloadFileFromStream">
    Download File From Stream
</button>

@code {
    private Stream GetFileStream()
    {
        var randomBinaryData = new byte[50 * 1024];
        var fileStream = new MemoryStream(randomBinaryData);

        return fileStream;
    }

    private async Task DownloadFileFromStream()
    {
        var fileStream = GetFileStream();
        var fileName = "log.bin";

        using var streamRef = new DotNetStreamReference(stream: fileStream);

        await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
    }
}
@page "/file-download-1"
@using System.IO
@inject IJSRuntime JS

<h1>File Download Example</h1>

<button @onclick="DownloadFileFromStream">
    Download File From Stream
</button>

@code {
    private Stream GetFileStream()
    {
        var randomBinaryData = new byte[50 * 1024];
        var fileStream = new MemoryStream(randomBinaryData);

        return fileStream;
    }

    private async Task DownloadFileFromStream()
    {
        var fileStream = GetFileStream();
        var fileName = "log.bin";

        using var streamRef = new DotNetStreamReference(stream: fileStream);

        await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
    }
}

物理ファイルの Stream を返す必要があるサーバー側アプリ内のコンポーネントの場合、コンポーネントは、次の例に示すように、File.OpenRead を呼び出すことができます。

private Stream GetFileStream()
{
    return File.OpenRead(@"{PATH}");
}

前の例では、{PATH} プレースホルダーがファイルへのパスです。 @ プレフィックスは、文字列が "逐語的文字列リテラル" であることを示します。このリテラルでは、Windows OS パスのバックスラッシュ (\) と、パスの単一引用符を表す埋め込みの二重引用符 ("") を使用できます。 または、文字列リテラル (@) を使用せず、次のいずれかの方法を使うこともできます。

  • エスケープしたバックスラッシュ (\\) と引用符 (\") を使います。
  • ASP.NET Core アプリのプラットフォーム全体でサポートされているパスのスラッシュ (/) と、エスケープされた引用符 (\") を使います。

URL からダウンロードする

"このセクションは、比較的大きいファイル (通常は 250 MB 以上) に当てはまります。"

このセクションの例では、quote.txt という名前のダウンロード ファイルを使用します。これは、アプリの Web ルート (wwwroot フォルダー) の files という名前のフォルダーに配置されます。 files フォルダーは、デモンストレーションのためにのみ使います。 お好みの Web ルート (wwwroot フォルダー) 内の任意のフォルダー レイアウトで、ダウンロード可能なファイルを整理できます。wwwroot フォルダーから直接ファイルを提供することもできます。

wwwroot/files/quote.txt:

When victory is ours, we'll wipe every trace of the Thals and their city from the face of this land. We will avenge the deaths of all Kaleds who've fallen in the cause of right and justice and build a peace which will be a monument to their sacrifice. Our battle cry will be "Total extermination of the Thals!"

- General Ravon (Guy Siner, http://guysiner.com/)
  Dr. Who: Genesis of the Daleks (https://www.bbc.co.uk/programmes/p00pq2gc)
  ©1975 BBC (https://www.bbc.co.uk/)
When victory is ours, we'll wipe every trace of the Thals and their city from the face of this land. We will avenge the deaths of all Kaleds who've fallen in the cause of right and justice and build a peace which will be a monument to their sacrifice. Our battle cry will be "Total extermination of the Thals!"

- General Ravon (Guy Siner, http://guysiner.com/)
  Dr. Who: Genesis of the Daleks (https://www.bbc.co.uk/programmes/p00pq2gc)
  ©1975 BBC (https://www.bbc.co.uk/)
When victory is ours, we'll wipe every trace of the Thals and their city from the face of this land. We will avenge the deaths of all Kaleds who've fallen in the cause of right and justice and build a peace which will be a monument to their sacrifice. Our battle cry will be "Total extermination of the Thals!"

- General Ravon (Guy Siner, http://guysiner.com/)
  Dr. Who: Genesis of the Daleks (https://www.bbc.co.uk/programmes/p00pq2gc)
  ©1975 BBC (https://www.bbc.co.uk/)

次の triggerFileDownloadJS 関数:

  • HTMLAnchorElement (<a> 要素) を作成します。
  • ダウンロード用のファイル名 (fileName) と URL (url) を割り当てます。
  • アンカー要素で click イベントを発生させることでダウンロードをトリガーします。
  • アンカー要素を削除します。
<script>
  window.triggerFileDownload = (fileName, url) => {
    const anchorElement = document.createElement('a');
    anchorElement.href = url;
    anchorElement.download = fileName ?? '';
    anchorElement.click();
    anchorElement.remove();
  }
</script>

Note

JS の場所と実稼働アプリの推奨事項に関する一般的なガイダンスについては、ASP.NET Core Blazor アプリの JavaScript の位置に関する記事を参照してください。

次のコンポーネント例では、アプリが使用するのと同じオリジンからファイルをダウンロードします。 別のオリジンからファイルのダウンロードが試行される場合は、クロス オリジン リソース共有 (CORS) を構成します。 詳細については、「クロス オリジン リソース共有 (CORS)」セクションを参照してください。

次の例のポートを、自分の環境の localhost 開発ポートと一致するように変更します。

FileDownload2.razor:

@page "/file-download-2"
@inject IJSRuntime JS

<PageTitle>File Download 2</PageTitle>

<h1>File Download Example 2</h1>

<button @onclick="DownloadFileFromURL">
    Download File From URL
</button>

@code {
    private async Task DownloadFileFromURL()
    {
        var fileName = "quote.txt";
        var fileURL = "/files/quote.txt";
        await JS.InvokeVoidAsync("triggerFileDownload", fileName, fileURL);
    }
}
@page "/file-download-2"
@inject IJSRuntime JS

<h1>File Download Example 2</h1>

<button @onclick="DownloadFileFromURL">
    Download File From URL
</button>

@code {
    private async Task DownloadFileFromURL()
    {
        var fileName = "quote.txt";
        var fileURL = "https://localhost:5001/files/quote.txt";
        await JS.InvokeVoidAsync("triggerFileDownload", fileName, fileURL);
    }
}
@page "/file-download-2"
@inject IJSRuntime JS

<h1>File Download Example 2</h1>

<button @onclick="DownloadFileFromURL">
    Download File From URL
</button>

@code {
    private async Task DownloadFileFromURL()
    {
        var fileName = "quote.txt";
        var fileURL = "https://localhost:5001/files/quote.txt";
        await JS.InvokeVoidAsync("triggerFileDownload", fileName, fileURL);
    }
}

クロスオリジン リソース共有 (CORS)

アプリと同じオリジンを持たないファイルに対してクロス オリジン リソース共有 (CORS) を有効にする追加の手順を実行しないと、ファイルのダウンロードはブラウザーによって行われる CORS チェックに合格しません。

ダウンロード用のファイルをホストする ASP.NET Core アプリと他の Microsoft 製品とサービスを使用した CORS の詳細については、次のリソースを参照してください。

その他の技術情報