次の方法で共有


ASP.NET Core でホストされる Blazor WebAssembly アプリの展開レイアウト

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

警告

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

重要

この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft は、ここで提供される情報に関して明示的または黙示的な保証を行いません。

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

この記事では、ダイナミック リンク ライブラリ (DLL) ファイルのダウンロードと実行をブロックする環境でホストされた Blazor WebAssembly 展開を有効にする方法について説明します。

このガイダンスでは、クライアントによる DLL のダウンロードと実行をブロックする環境について説明します。 .NET 8 以降では、Blazor は Webcil ファイル形式を使用してこの問題に対処します。 詳しくは、「ASP.NET Core Blazor WebAssembly のホストと展開」をご覧ください。 この記事で説明する実験用 NuGet パッケージを使用したマルチパート バンドルは、.NET 8 以降の Blazor アプリではサポートされていません。 この記事のガイダンスを使用して、.NET 8 以降用の独自のマルチパート バンドル NuGet パッケージを作成できます。

Blazor WebAssembly アプリが機能するには ダイナミック リンク ライブラリ (DLL) が 必要ですが、一部の環境ではクライアントによる DLL のダウンロードと実行がブロックされます。 セキュリティ製品は、多くの場合、ネットワークを通過するファイルの内容をスキャンし、DLL ファイルをブロックまたは検疫することができます。 この記事では、これらの環境で Blazor WebAssembly アプリを有効にする方法の 1 つについて説明します。この方法では、セキュリティ制限をバイパスして DLL を一緒にダウンロードできるように、アプリの DLL からマルチパート バンドル ファイルを作成します。

Blazor WebAssembly アプリが機能するには ダイナミック リンク ライブラリ (DLL) が 必要ですが、一部の環境ではクライアントによる DLL のダウンロードと実行がブロックされます。 これらの環境のサブセットでは、 DLL ファイルのファイル名拡張子 (.dll) を変更 するだけでセキュリティ制限を回避できますが、セキュリティ製品では、ネットワークを通過するファイルの内容をスキャンして、DLL ファイルをブロックまたは検疫できることがよくあります。 この記事では、これらの環境で Blazor WebAssembly アプリを有効にする方法の 1 つについて説明します。この方法では、セキュリティ制限をバイパスして DLL を一緒にダウンロードできるように、アプリの DLL からマルチパート バンドル ファイルを作成します。

ホスト Blazor WebAssembly アプリでは、次の機能を使用して、発行されたファイルとアプリ DLL のパッケージをカスタマイズできます。

  • ブート プロセスをカスタマイズできる Blazor。
  • 公開されたファイルの一覧を変換し、公開Blazor定義するための MSBuild 拡張機能。 Blazor 発行拡張機能は、発行プロセス中に定義されたファイルであり、発行された Blazor WebAssembly アプリを実行するために必要なファイルのセットに代わる表現を提供します。 この記事では、1 つのファイルにパックされたすべてのアプリの DLL を含むマルチパート バンドルを生成する Blazor 発行拡張機能を作成し、DLL を一緒にダウンロードできるようにします。

この記事で示すアプローチは、開発者が独自の戦略とカスタム読み込みプロセスを考案するための出発点として機能します。

警告

セキュリティ制限を回避するために行われるアプローチは、セキュリティへの影響について慎重に検討する必要があります。 この記事のアプローチを採用する前に、組織のネットワーク セキュリティの専門家にこのテーマをさらに詳しく調査することをお勧めします。 検討すべき代替手段を次に示します。

  • セキュリティ アプライアンスとセキュリティ ソフトウェアを有効にして、ネットワーク クライアントが Blazor WebAssembly アプリに必要な正確なファイルをダウンロードして使用できるようにします。
  • Blazor WebAssembly ホスティング モデルから Blazor Server ホスティング モデルに切り替えます。これにより、サーバー上のすべてのアプリの C# コードが維持され、クライアントに DLL をダウンロードする必要はありません。 Blazor Server また、 Blazor WebAssembly アプリで C# コードのプライバシーのために Web API アプリを使用しなくても、C# コードをプライベートに保つという利点もあります。

試験段階の NuGet パッケージとサンプル アプリ

この記事で説明する方法は、.NET 6 以降を対象とするアプリ用のMicrosoft.AspNetCore.Components.WebAssembly.MultipartBundleパッケージ (NuGet.org) によって使用されます。 このパッケージには、Blazor発行出力をカスタマイズする MSBuild ターゲットと、カスタム ブート リソース ローダーを使用する JavaScript 初期化子が含まれています。各ターゲットについては、この記事の後半で詳しく説明します。

警告

試験的機能とプレビュー機能は、フィードバックの収集を目的として提供されており、運用環境での使用はサポートされていません。

この記事の後半の「 NuGet パッケージを使用した Blazor WebAssembly の読み込みプロセスのカスタマイズ 」セクションとその 3 つのサブセクションでは、 Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle パッケージの構成とコードについて詳しく説明します。 詳細な説明は、 Blazor WebAssembly アプリ用に独自の戦略とカスタム読み込みプロセスを作成する際に理解しておくことが重要です。 カスタマイズなしで公開済みの試験的でサポートされていない NuGet パッケージを ローカル デモとして使用するには、次の手順を実行します。

  1. 既存のホステッド Blazor WebAssemblyソリューションを使用するか、Visual Studio を使用して Blazor WebAssembly プロジェクト テンプレートから新しいソリューションを作成するか、-ho|--hosted コマンドに dotnet newを渡します (dotnet new blazorwasm -ho)。 詳しくは、「ASP.NET Core Blazor 用のツール」をご覧ください。

  2. Client プロジェクトで、試験的なMicrosoft.AspNetCore.Components.WebAssembly.MultipartBundle パッケージを追加します。

    .NET アプリへのパッケージの追加に関するガイダンスについては、「パッケージ利用のワークフロー」 (NuGet ドキュメント) の "パッケージのインストールと管理" に関する記事を参照してください。 NuGet.org で正しいパッケージ バージョンを確認します。

  3. Server プロジェクトで、バンドル ファイル (app.bundle) を提供するためのエンドポイントを追加します。 コード例については、この記事の 「ホスト サーバー アプリからのバンドルの提供 」セクションを参照してください。

  4. リリース構成でアプリを発行します。

NuGet パッケージを使用して Blazor WebAssembly 読み込みプロセスをカスタマイズする

警告

このセクションの 3 つのサブセクションのガイダンスは、独自の戦略とカスタム読み込みプロセスを実装するために NuGet パッケージをゼロから構築することに関連しています。 .NET 6 および 7 用のMicrosoft.AspNetCore.Components.WebAssembly.MultipartBundleパッケージ (NuGet.org) は、このセクションのガイダンスに基づいています。 提供されたパッケージをマルチパート バンドル ダウンロード アプローチの ローカル デモ で使用する場合は、このセクションのガイダンスに従う必要はありません。 提供されたパッケージの使用方法に関するガイダンスについては、「 試験段階の NuGet パッケージとサンプル アプリ 」セクションを参照してください。

Blazor アプリ リソースはマルチパート バンドル ファイルにパックされ、カスタム JavaScript (JS) 初期化子を介してブラウザーによって読み込まれます。 JS初期化子を使用してパッケージを使用するアプリの場合、アプリでは要求されたときにバンドル ファイルが提供される必要があります。 このアプローチの他のすべての側面は透過的に処理されます。

既定の公開 Blazor アプリを読み込む方法には、次の 4 つのカスタマイズが必要です。

  • 発行ファイルを変換する MSBuild タスク。
  • Blazor発行プロセスにフックし、出力を変換し、1 つ以上の Blazor Publish Extension ファイル (この場合は 1 つのバンドル) を定義する MSBuild ターゲットを含む NuGet パッケージ。
  • バンドルを読み込み、個々のファイルをアプリに提供するように、JS リソース ローダーコールバックを更新するBlazor WebAssembly初期化子。
  • ホスト上のヘルパー Server アプリは、要求に応じてバンドルがクライアントに確実に提供されるようにします。

MSBuild タスクを作成して発行済みファイルの一覧をカスタマイズし、新しい拡張機能を定義する

MSBuild コンパイルの一部としてインポートでき、ビルドと対話できるパブリック C# クラスとして MSBuild タスクを作成します。

C# クラスには次のものが必要です。

この記事の例の NuGet パッケージには、Microsoft Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle が提供するパッケージにちなんだ名前が付けられています。 独自の NuGet パッケージの名前付けと生成に関するガイダンスについては、次の NuGet の記事を参照してください。

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks/Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks.csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>8.0</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Build.Framework" Version="{VERSION}" />
    <PackageReference Include="Microsoft.Build.Utilities.Core" Version="{VERSION}" />
  </ItemGroup>

</Project>

NuGet.org で、 {VERSION} プレースホルダーの最新のパッケージ バージョンを決定します。

MSBuild タスクを作成するには、(Microsoft.Build.Utilities.Taskではなく) System.Threading.Tasks.Taskを拡張するパブリック C# クラスを作成し、次の 3 つのプロパティを宣言します。

  • PublishBlazorBootStaticWebAsset: Blazor アプリ用に発行するファイルの一覧。
  • BundlePath: バンドルが書き込まれるパス。
  • Extension: ビルドに含める新しい発行拡張機能。

次の例 BundleBlazorAssets クラスは、さらにカスタマイズするための開始点です。

  • Executeメソッドでは、バンドルは次の 3 種類のファイルから作成されます。
    • JavaScript ファイル (dotnet.js)
    • WASM ファイル (dotnet.wasm)
    • アプリ DLL (.dll)
  • multipart/form-data バンドルが作成されます。 各ファイルは、 Content-Disposition ヘッダーと Content-Type ヘッダー を介してそれぞれの説明と共にバンドルに追加 されます
  • バンドルが作成されると、バンドルはファイルに書き込まれます。
  • ビルドは拡張機能用に構成されます。 次のコードでは、拡張項目を作成し、 Extension プロパティに追加します。 各拡張項目には、次の 3 つのデータが含まれています。
    • 拡張ファイルへのパス。
    • Blazor WebAssembly アプリのルートを基準とした URL パス。
    • 特定の拡張子によって生成されたファイルをグループ化する拡張子の名前。

上記の目標を達成すると、 Blazor 発行出力をカスタマイズするための MSBuild タスクが作成されます。 Blazor では、拡張機能を収集し、拡張機能が発行出力フォルダー内の正しい場所 (たとえば、 bin\Release\net6.0\publish) にコピーされるようにします。 他のファイルに適用されるのと同じ最適化 (圧縮など) が JavaScript、WASM、DLL ファイル Blazor 適用されます。

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks/BundleBlazorAssets.cs:

using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks
{
    public class BundleBlazorAssets : Task
    {
        [Required]
        public ITaskItem[]? PublishBlazorBootStaticWebAsset { get; set; }

        [Required]
        public string? BundlePath { get; set; }

        [Output]
        public ITaskItem[]? Extension { get; set; }

        public override bool Execute()
        {
            var bundle = new MultipartFormDataContent(
                "--0a7e8441d64b4bf89086b85e59523b7d");

            foreach (var asset in PublishBlazorBootStaticWebAsset)
            {
                var name = Path.GetFileName(asset.GetMetadata("RelativePath"));
                var fileContents = File.OpenRead(asset.ItemSpec);
                var content = new StreamContent(fileContents);
                var disposition = new ContentDispositionHeaderValue("form-data");
                disposition.Name = name;
                disposition.FileName = name;
                content.Headers.ContentDisposition = disposition;
                var contentType = Path.GetExtension(name) switch
                {
                    ".js" => "text/javascript",
                    ".wasm" => "application/wasm",
                    _ => "application/octet-stream"
                };
                content.Headers.ContentType = 
                    MediaTypeHeaderValue.Parse(contentType);
                bundle.Add(content);
            }

            using (var output = File.Open(BundlePath, FileMode.OpenOrCreate))
            {
                output.SetLength(0);
                bundle.CopyToAsync(output).ConfigureAwait(false).GetAwaiter()
                    .GetResult();
                output.Flush(true);
            }

            var bundleItem = new TaskItem(BundlePath);
            bundleItem.SetMetadata("RelativePath", "app.bundle");
            bundleItem.SetMetadata("ExtensionName", "multipart");

            Extension = new ITaskItem[] { bundleItem };

            return true;
        }
    }
}

発行出力を自動的に変換する NuGet パッケージを作成する

パッケージが参照されたときに自動的に含まれる MSBuild ターゲットを含む NuGet パッケージを生成します。

  • 新しい Razor クラス ライブラリ (RCL) プロジェクトを作成します
  • NuGet 規則に従ってターゲット ファイルを作成し、使用しているプロジェクトにパッケージを自動的にインポートします。 たとえば、 build\net6.0\{PACKAGE ID}.targetsを作成します。ここで、 {PACKAGE ID} はパッケージのパッケージ識別子です。
  • MSBuild タスクを含むクラス ライブラリから出力を収集し、出力が適切な場所にパックされていることを確認します。
  • Blazor パイプラインにアタッチし、MSBuild タスクを呼び出してバンドルを生成するために必要な MSBuild コードを追加します。

このセクションで説明する方法では、パッケージを使用してターゲットとコンテンツを配信するだけです。これは、パッケージにライブラリ DLL が含まれているほとんどのパッケージとは異なります。

警告

このセクションで説明するサンプル パッケージでは、 Blazor 発行プロセスをカスタマイズする方法を示します。 サンプル NuGet パッケージは、ローカル デモとしてのみ使用します。 運用環境でのこのパッケージの使用はサポートされていません。

この記事の例の NuGet パッケージには、Microsoft Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle が提供するパッケージにちなんだ名前が付けられています。 独自の NuGet パッケージの名前付けと生成に関するガイダンスについては、次の NuGet の記事を参照してください。

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle/Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.csproj:

<Project Sdk="Microsoft.NET.Sdk.Razor">

  <PropertyGroup>
    <NoWarn>NU5100</NoWarn>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <Description>
      Sample demonstration package showing how to customize the Blazor publish 
      process. Using this package in production is not supported!
    </Description>
    <IsPackable>true</IsPackable>
    <IsShipping>true</IsShipping>
    <IncludeBuildOutput>false</IncludeBuildOutput>
  </PropertyGroup>

  <ItemGroup>
    <None Update="build\**" 
          Pack="true" 
          PackagePath="%(Identity)" />
    <Content Include="_._" 
             Pack="true" 
             PackagePath="lib\net6.0\_._" />
  </ItemGroup>

  <Target Name="GetTasksOutputDlls" 
          BeforeTargets="CoreCompile">
    <MSBuild Projects="..\Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks\Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks.csproj" 
             Targets="Publish;PublishItemsOutputGroup" 
             Properties="Configuration=Release">
      <Output TaskParameter="TargetOutputs" 
              ItemName="_TasksProjectOutputs" />
    </MSBuild>
    <ItemGroup>
      <Content Include="@(_TasksProjectOutputs)" 
               Condition="'%(_TasksProjectOutputs.Extension)' == '.dll'" 
               Pack="true" 
               PackagePath="tasks\%(_TasksProjectOutputs.TargetPath)" 
               KeepMetadata="Pack;PackagePath" />
    </ItemGroup>
  </Target>

</Project>

前の例の <NoWarn>NU5100</NoWarn> プロパティは、 tasks フォルダーに配置されたアセンブリに関する警告を抑制します。 詳細については、「 NuGet 警告 NU5100」を参照してください。

MSBuild タスクをビルド パイプラインに接続する .targets ファイルを追加します。 このファイルでは、次の目標が達成されます。

  • タスクをビルド プロセスにインポートします。 DLL へのパスは、パッケージ内のファイルの最終的な場所に対する相対パスであることに注意してください。
  • ComputeBlazorExtensionsDependsOn プロパティは、カスタム ターゲットを Blazor WebAssembly パイプラインにアタッチします。
  • タスク出力の Extension プロパティをキャプチャし、 BlazorPublishExtension に追加して、拡張機能について Blazor に通知します。 ターゲットでタスクを呼び出すと、バンドルが生成されます。 発行されたファイルの一覧は、Blazor WebAssembly項目グループのPublishBlazorBootStaticWebAsset パイプラインによって提供されます。 バンドル パスは、 IntermediateOutputPath (通常は obj フォルダー内) を使用して定義されます。 最終的に、バンドルは発行出力フォルダー内の適切な場所 (たとえば、 bin\Release\net6.0\publish) に自動的にコピーされます。

パッケージが参照されると、発行時に Blazor ファイルのバンドルが生成されます。

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle/build/net6.0/Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.targets:

<Project>
  <UsingTask 
    TaskName="Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks.BundleBlazorAssets" 
    AssemblyFile="$(MSBuildThisProjectFileDirectory)..\..\tasks\Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks.dll" />

  <PropertyGroup>
    <ComputeBlazorExtensionsDependsOn>
      $(ComputeBlazorExtensionsDependsOn);_BundleBlazorDlls
    </ComputeBlazorExtensionsDependsOn>
  </PropertyGroup>

  <Target Name="_BundleBlazorDlls">
    <BundleBlazorAssets
      PublishBlazorBootStaticWebAsset="@(PublishBlazorBootStaticWebAsset)"
      BundlePath="$(IntermediateOutputPath)bundle.multipart">
      <Output TaskParameter="Extension" 
              ItemName="BlazorPublishExtension"/>
    </BundleBlazorAssets>
  </Target>

</Project>

バンドルから Blazor を自動的にブートストラップする

NuGet パッケージでは 、JavaScript (JS) 初期化子 を利用して、個々の DLL ファイルを使用するのではなく、バンドルから Blazor WebAssembly アプリを自動的にブートストラップします。 JS 初期化子は、 Blazorboot リソース ローダー を変更し、バンドルを使用するために使用されます。

JS初期化子を作成するには、JSという名前の{NAME}.lib.module.js ファイルをパッケージ プロジェクトのwwwroot フォルダーに追加します。ここで、{NAME} プレースホルダーはパッケージ識別子です。 たとえば、Microsoft パッケージのファイルの名前は Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js です。 エクスポートされた関数 beforeWebAssemblyStartafterWebAssemblyStarted は、読み込みを処理します。

JS の初期化子:

  • 発行拡張機能が使用可能かどうかを検出するために、extensions.multipartセクションで提供される拡張機能名ExtensionNameに相当するが存在するかどうかを確認します。
  • バンドルをダウンロードし、生成されたオブジェクト URL を使用してコンテンツをリソース マップに解析します。
  • オブジェクト URL を使用してリソースを解決するカスタム関数を使用して、 ブート リソース ローダー (options.loadBootResource) を更新します。
  • アプリが起動したら、オブジェクト URL を取り消して、 afterWebAssemblyStarted 関数のメモリを解放します。

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle/wwwroot/Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js:

const resources = new Map();

export async function beforeWebAssemblyStart(options, extensions) {
  if (!extensions || !extensions.multipart) {
    return;
  }

  try {
    const integrity = extensions.multipart['app.bundle'];
    const bundleResponse = 
      await fetch('app.bundle', { integrity: integrity, cache: 'no-cache' });
    const bundleFromData = await bundleResponse.formData();
    for (let value of bundleFromData.values()) {
      resources.set(value, URL.createObjectURL(value));
    }
    options.loadBootResource = function (type, name, defaultUri, integrity) {
      return resources.get(name) ?? null;
    }
  } catch (error) {
    console.log(error);
  }
}

export async function afterWebAssemblyStarted(blazor) {
  for (const [_, url] of resources) {
    URL.revokeObjectURL(url);
  }
}

JS初期化子を作成するには、JSという名前の{NAME}.lib.module.js ファイルをパッケージ プロジェクトのwwwroot フォルダーに追加します。ここで、{NAME} プレースホルダーはパッケージ識別子です。 たとえば、Microsoft パッケージのファイルの名前は Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js です。 エクスポートされた関数 beforeStartafterStarted は、読み込みを処理します。

JS の初期化子:

  • 発行拡張機能が使用可能かどうかを検出するために、extensions.multipartセクションで提供される拡張機能名ExtensionNameに相当するが存在するかどうかを確認します。
  • バンドルをダウンロードし、生成されたオブジェクト URL を使用してコンテンツをリソース マップに解析します。
  • オブジェクト URL を使用してリソースを解決するカスタム関数を使用して、 ブート リソース ローダー (options.loadBootResource) を更新します。
  • アプリが起動したら、オブジェクト URL を取り消して、 afterStarted 関数のメモリを解放します。

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle/wwwroot/Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js:

const resources = new Map();

export async function beforeStart(options, extensions) {
  if (!extensions || !extensions.multipart) {
    return;
  }

  try {
    const integrity = extensions.multipart['app.bundle'];
    const bundleResponse = 
      await fetch('app.bundle', { integrity: integrity, cache: 'no-cache' });
    const bundleFromData = await bundleResponse.formData();
    for (let value of bundleFromData.values()) {
      resources.set(value, URL.createObjectURL(value));
    }
    options.loadBootResource = function (type, name, defaultUri, integrity) {
      return resources.get(name) ?? null;
    }
  } catch (error) {
    console.log(error);
  }
}

export async function afterStarted(blazor) {
  for (const [_, url] of resources) {
    URL.revokeObjectURL(url);
  }
}

ホスト サーバー アプリからバンドルを提供する

セキュリティの制限により、ASP.NET Core は app.bundle ファイルを処理しません。 クライアントから要求されたときにファイルを処理するには、要求処理ヘルパーが必要です。

アプリのファイルに適用される発行拡張機能にも同じ最適化が透過的に適用されるため、 app.bundle.gz および app.bundle.br 圧縮されたアセット ファイルは発行時に自動的に生成されます。

フォールバック ファイルをProgram.cs (Server) に設定する行の直前に、index.html プロジェクトのapp.MapFallbackToFile("index.html");に C# コードを配置して、バンドル ファイルの要求に応答します (たとえば、app.bundle)。

app.MapGet("app.bundle", (HttpContext context) =>
{
    string? contentEncoding = null;
    var contentType = 
        "multipart/form-data; boundary=\"--0a7e8441d64b4bf89086b85e59523b7d\"";
    var fileName = "app.bundle";

    var acceptEncodings = context.Request.Headers.AcceptEncoding;

    if (Microsoft.Net.Http.Headers.StringWithQualityHeaderValue
        .StringWithQualityHeaderValue
        .TryParseList(acceptEncodings, out var encodings))
    {
        if (encodings.Any(e => e.Value == "br"))
        {
            contentEncoding = "br";
            fileName += ".br";
        }
        else if (encodings.Any(e => e.Value == "gzip"))
        {
            contentEncoding = "gzip";
            fileName += ".gz";
        }
    }

    if (contentEncoding != null)
    {
        context.Response.Headers.ContentEncoding = contentEncoding;
    }

    return Results.File(
        app.Environment.WebRootFileProvider.GetFileInfo(fileName)
            .CreateReadStream(), contentType);
});

コンテンツ タイプは、ビルド タスクで前に定義した型と一致します。 エンドポイントは、ブラウザーで受け入れられるコンテンツ エンコードをチェックし、最適なファイル Brotli (.br) または Gzip (.gz) を提供します。