ASP.NET Core で SPA の HTTP ヘッダーを変更する
ASP.NET Core には Angular や React といった SPA を組み込んだテンプレートがあります。これらのテンプレートを使用した際に、 SPA のファイルを返す時の HTTP ヘッダー付与方法について解説します。
TL;DR
Startup
の Configure
で UseSpaStaticFiles
や UseSpa
の引数にオプションを設定し、 StaticFileOptions#OnPrepareResponse
にてレスポンスを操作する。
前提条件
この記事は以下のバージョンで検証しています。また ASP.NET Core のテンプレートに Angular テンプレートを使用しています。
- Windows 10 x64 (1803)
- Visual Studio 2017 (15.8.9) Community
- .NET Core SDK 2.1.403 (ASP.NET Core 2.1.5)
SPA 使用時の ASP.NET Core のミドルウェアの流れ
Angular や React といった SPA のテンプレートを使用して ASP.NET Core のプロジェクトを生成すると、普通の MVC テンプレートと異なるミドルウェアが追加されています。
(Startup.cs) public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); // ↓↓↓↓↓この辺↓↓↓↓↓ // In production, the Angular files will be served from this directory services.AddSpaStaticFiles(configuration => { configuration.RootPath = "ClientApp/dist"; }); // ↑↑↑↑↑この辺↑↑↑↑↑ } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); } app.UseStaticFiles(); // ↓↓↓↓↓この辺↓↓↓↓↓ app.UseSpaStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller}/{action=Index}/{id?}"); }); app.UseSpa(spa => { // To learn more about options for serving an Angular SPA from ASP.NET Core, // see https://go.microsoft.com/fwlink/?linkid=864501 spa.Options.SourcePath = "ClientApp"; if (env.IsDevelopment()) { spa.UseAngularCliServer(npmScript: "start"); } }); // ↑↑↑↑↑この辺↑↑↑↑↑ }
これは何かというと、 SPA を構成するファイル(HTML 、 JS 、 CSS など)の解決をしたり、開発時に Angular CLI のDevサーバーに対してプロキシしたりするための機能です。
SpaStaticFiles
SpaStaticFiles
はその名の通り、 SPA の出力ディレクトリ上に存在する JS や CSS などのファイルをレスポンスするための機能です。 Startup
で UseSpaStaticFiles
を呼び出すことで追加されます。
ASP.NET Core 2.1 の Angular/React テンプレートはプロジェクト内の ClientApp
ディレクトリに Angular CLI や react-scripts のプロジェクトがそのまま含まれ、パブリッシュ時にこの SPA プロジェクトもビルドして ClientApp
の下にビルド結果を保持します。 ASP.NET Core の StaticFilesMiddleware
はデフォルトで wwwroot 下のファイルをレスポンスするため、 SpaStaticFiles
を使用して wwwroot 下に存在しない SPA のビルド結果をレスポンスします。
Spa
Spa
はその名のまま、 SPA を動作させるための機能です。
開発時は自動的に Angular CLI や React の開発サーバーを起動してリクエストをプロキシし、開発時以外では SPA の出力ディレクトリ上の index.html
をレスポンスします。
SPA 関連のリクエスト処理ミドルウェア
ここまでを踏まえて、 ASP.NET Core で SPA 利用時にどのミドルウェアがレスポンスを返しているかをまとめてみました。
SPA の HTTP ヘッダーを変更する
StaticFiles
ミドルウェアには Startup
で機能設定時に StaticFilesOptions#PrepareResponse
という Action のプロパティを設定しておくことで、静的ファイルレスポンス時の挙動を制御することができます。例えば sample.png
というファイルのレスポンスをブラウザにキャッシュさせないようにするには、 Startup#Configure
で次の様に設定します。
app.UseStaticFiles(new StaticFileOptions { OnPrepareResponse = context => { // 返却するファイルの名前が sample.png だったら if (context.File.Name == "sample.png") { // HTTPヘッダーのCacheControlとExpiresを設定する context.Context.Response.Headers[HeaderNames.CacheControl] = $"{CacheControlHeaderValue.NoCacheString},{CacheControlHeaderValue.NoStoreString},{CacheControlHeaderValue.MustRevalidateString}"; context.Context.Response.Headers[HeaderNames.Expires] = "0"; } } });
SPA のレスポンスの HTTP ヘッダーを変更するにも同様に、レスポンスするミドルウェアの設定を行います。
// SPA静的ファイル app.UseSpaStaticFiles(new StaticFileOptions { OnPrepareResponse = context => { // 返却するファイルの名前が sample.png だったら if (context.File.Name == "sample.png") { // HTTPヘッダーのCacheControlとExpiresを設定する context.Context.Response.Headers[HeaderNames.CacheControl] = $"{CacheControlHeaderValue.NoCacheString},{CacheControlHeaderValue.NoStoreString},{CacheControlHeaderValue.MustRevalidateString}"; context.Context.Response.Headers[HeaderNames.Expires] = "0"; } } }); // SPA app.UseSpa(spa => { // To learn more about options for serving an Angular SPA from ASP.NET Core, // see https://go.microsoft.com/fwlink/?linkid=864501 spa.Options.SourcePath = "ClientApp"; if (env.IsDevelopment()) { spa.UseAngularCliServer(npmScript: "start"); } else { // 開発時以外ではHTTPヘッダーを設定する // 開発時はSPAの開発サーバーにプロキシしているだけなので spa.Options.DefaultPageStaticFileOptions = new StaticFileOptions { OnPrepareResponse = context => { // 返却するファイルの名前が index.html だったら if (context.File.Name == "index.html") { // HTTPヘッダーのCacheControlとExpiresを設定する context.Context.Response.Headers[HeaderNames.CacheControl] = $"{CacheControlHeaderValue.NoCacheString},{CacheControlHeaderValue.NoStoreString},{CacheControlHeaderValue.MustRevalidateString}"; context.Context.Response.Headers[HeaderNames.Expires] = "0"; } } }; } });