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

注意

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

警告

このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、「.NET および .NET Core サポート ポリシー」を参照してください。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

重要

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

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

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

ファイルのダウンロード

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

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

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

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

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

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

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

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

比較的小さいファイル (< 250 MB) のダウンロードに推奨される方法は、JavaScript (JS) 相互運用を使用して、クライアント上の生のバイナリ データ バッファーにファイルの内容をストリーミングすることです。 この方法は、対話型レンダリング モードを採用するが、静的サーバー側レンダリング (静的 SSR) を採用するコンポーネントではないコンポーネントに有効です。

比較的小さいファイル (< 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

<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() => File.OpenRead(@"{PATH}");

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

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

URL からダウンロードする

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

静的にレンダリングされるコンポーネント用に対話的にレンダリングされたコンポーネントまたは任意のサイズのファイルを含む比較的大きなファイル (> = 250 MB) をダウンロードするには、JS を使用して、ファイルの名前と URL を持つアンカー要素をトリガーすることをお勧めします。

比較的大きなファイル (> = 250 MB) をダウンロードするには、JS を使用して、ファイルの名前と URL を持つアンカー要素をトリガーすることをお勧めします。

このセクションの例では、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/p00vd5g2)
  Copyright 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/p00vd5g2)
  Copyright 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/p00vd5g2)
  Copyright 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/p00vd5g2)
  Copyright 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)」セクションを参照してください。

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);
    }
}

対話型コンポーネントでは、前述の例のボタンが DownloadFileFromURL ハンドラーを呼び出して JavaScript (JS) 関数 triggerFileDownload を呼び出します。

コンポーネントが静的サーバー側レンダリング (静的 SSR) を採用している場合は、静的サーバー側レンダリング (静的 SSR) を使用する ASP.NET Core Blazor JavaScript のガイダンスに従って triggerFileDownload を呼び出すボタン (addEventListener (MDN ドキュメント)) のイベント ハンドラーを追加します。

@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);
    }
}

対話型コンポーネントでは、前述の例のボタンが DownloadFileFromURL ハンドラーを呼び出して JavaScript (JS) 関数 triggerFileDownload を呼び出します。

コンポーネントが静的サーバー側レンダリング (静的 SSR) を採用している場合は、静的サーバー側レンダリング (静的 SSR) を使用する ASP.NET Core Blazor JavaScript のガイダンスに従って triggerFileDownload を呼び出すボタン (addEventListener (MDN ドキュメント)) のイベント ハンドラーを追加します。

@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);
    }
}

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

@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);
    }
}

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

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

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

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

その他のリソース