今回はミドルウェアASP.NET Coreのミドルウェアの概念について学んでいきます。
ミドルウェア
ASP.NET Coreでは、Webサーバーがクライアントからリクエストは、ミドルウェアというものを通して処理されます。
ミドルウェアは複数定義され、それらがパイプラインとなって順に実行されていきます。
↓のリンク先の図のようなイメージ。
https://docs.asp.net/en/latest/fundamentals/middleware.html#creating-a-middleware-pipeline-with-iapplicationbuilder
たぶんこの辺の概念は、Node.jsでExpressとかを使ってた人は直感的に理解できるのでは、、と思います。
これらのミドルウェアは、StartupクラスのConfigureメソッドの引数として渡される、IApplicationBuilder型の引数を通して設定をすることができます。
このIApplicationBuilderの引数に対して、Run/Use/Mapなどのメソッドを呼び出して設定を行います。
Runメソッド
今までのサンプルでも何度か使用してきましたが、このメソッドの引数に書いたデリゲートでリクエストに対するレスポンスを定義できます。
以下の例では、レスポンスに「Hello, World!」という文字列を書いて応答します。
publicvoid Configure(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Hello, World!"); }); }
dotnet run
で実行し、ブラウザからlocalhostにアクセスすると、Runメソッドで定義したレスポンスが返ってきていることが確認できます。
ミドルウェアの終端
Runメソッドは、ミドルウェアのチェーンを終端させます。
そのため、以下のように複数Runメソッドを書いても、最初のRunメソッドで書いた部分しか実行されません。
publicvoid Configure(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Hello, World!"); }); app.Run(async context => { // 以下の部分は実行されない。 await context.Response.WriteAsync("Hello, Again"); }); }
Useメソッド
Useメソッドは、ミドルウェアのチェーンに処理を追加します。
Runメソッドとは異なり、処理を終端させないので、Useメソッド呼出し後に別のミドルウェアを繋げることができます。
以下のように、Use/Runメソッドを順に呼び出しておくと、メソッドの呼び出し順に実行されていることが確認できます。
app.Use(async (context, next) => { await context.Response.WriteAsync("Hello, World!"); // 後続のミドルウェアを呼び出す await next.Invoke(); }); app.Run(async context => { await context.Response.WriteAsync("Hello, Again"); });
next.Invokeメソッド
この関数を呼び出すと、後続のミドルウェアが実行されます。
先ほどの例では、UseでWriteAsyncした後、Runで定義した内容が続けて実行されます。
後続のミドルウェアの処理が終わると、awaitの後の部分が実行されます。 以下のサンプルのように、コンソール出力をしてみると、どのような順で実行されているか確認できます。
publicvoid Configure(IApplicationBuilder app) { app.Use(async (context, next) => { Console.WriteLine("--[1]--"); await context.Response.WriteAsync("Hello, World!"); // 後続のミドルウェアを呼び出す await next.Invoke(); Console.WriteLine("--[3]--"); }); app.Run(async context => { Console.WriteLine("--[2]--"); await context.Response.WriteAsync("Hello, Again"); }); }
ちなみに、このnext.Invoke()というのを呼び出さないと、後続のミドルウェア実行されず、Runメソッドでミドルウェアのチェーンを終端させたような動作になるので注意が必要です。
Mapメソッド
Mapメソッドを使うと、リクエストで受けたURLのパスに応じて、ミドルウェアのチェーンを分岐できます。
以下の例では、/hello
と/world
でそれぞれ別の処理を実行します。
publicvoid Configure(IApplicationBuilder app) { app.Map("/hello", hello => { hello.Run(async context => { await context.Response.WriteAsync("This is hello page."); }); }); app.Map("/world", world => { world.Run(async context => { await context.Response.WriteAsync("This is world page."); }); }); }
それぞれのURLにアクセスすると、以下のように異なったレスポンスが返ります。
標準で用意されているミドルウェア
ASP.NET Coreでは以下のようなミドルウェアが標準で用意されています。
- Authentication
- CORS
- Routing
- Session
- Static Files
https://docs.asp.net/en/latest/fundamentals/middleware.html#built-in-middleware
ここでは、簡単に使える例として、StaticFilesミドルウェアを使い、指定フォルダ内の内容を静的に配信するサーバーを作ってみます。
Static Filesミドルウェアの使用
まず、このミドルウェアを使用するには、project.jsonのdependenciesの項目に"Microsoft.AspNetCore.StaticFiles": "1.0.0"
というのを追記し、dotnet restore
コマンドを実行します。
{"version": "1.0.0-*", "buildOptions": {"debugType": "portable", "emitEntryPoint": true}, "dependencies": {"Microsoft.Extensions.Logging": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.AspNetCore.StaticFiles": "1.0.0"},
Startup.cs
publicclass Startup { publicvoid Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseStaticFiles(); } }
Program.cs
Main関数では、UseContentRoot()
という関数を呼び出しておきます。
このメソッドで、Webアプリのルートとなるディレクトリを設定します。
publicstaticvoid Main(string[] args) { var host = new WebHostBuilder().UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseStartup<Startup>() .UseEnvironment("Production") .Build(); host.Run(); }
また、wwwroot
フォルダを作り、その中にWebサーバーで配信したいファイル/フォルダなどを配置します。
ここでは以下のような二つのファイルを作成しました。
index.html
<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><title>Document</title></head><body><h1>This is index.html page</h1></body></html>
hello/world.html
<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><title>Document</title></head><body><h1>This is hello/world.html page</h1></body></html>
dotnet run
コマンドで実行すると、それぞれwwwrootに配置した通りのファイルが静的に配信できていることが確認できます。
このような静的なファイルサーバーの実装方法については、以下のドキュメントに詳細情報がまとまっています。
https://docs.asp.net/en/latest/fundamentals/static-files.html
ミドルウェアの作成
ミドルウェアは、独立したクラスとして作成することもできます。
独立したクラスとして作成しておくと、複雑な処理にも対応しやすく、再利用しやすいコードになります。
公式ドキュメントのサンプルと同じように、ここでは受け付けたリクエストのログ出力を行うミドルウェアを作ってみます。
ミドルウェアの定義
まずは、ミドルウェア定義用のクラスを作ります。
publicclass RequestLoggerMiddleware { privatereadonly RequestDelegate _next; privatereadonly ILogger _logger; public RequestLoggerMiddleware(RequestDelegate next, ILoggerFactory loggerFactory) { this._next = next; this._logger = loggerFactory.CreateLogger<RequestLoggerMiddleware>(); } public async Task Invoke(HttpContext context) { this._logger.LogInformation($"Handling request: {context.Request.Path}"); await this._next.Invoke(context); this._logger.LogInformation("Finished handling request."); } }
使い方
このミドルウェアを使うには、型引数として先ほど作ったクラスを指定してUseMiddlewareメソッドを呼び出します。
また、ログ出力を伴うミドルウェアなので、Configureメソッドの最初でログ出力を行うためloggerfactory.AddConsole();
を実行しています。
publicvoid Configure(IApplicationBuilder app, ILoggerFactory loggerfactory) { loggerfactory.AddConsole(); app.UseMiddleware<RequestLoggerMiddleware>();
動作確認
次のサンプルのように、Mapメソッドでのミドルウェア定義と合わせて書いておくと、それぞれのURLにアクセスした際に、コンソールにリクエストのパス情報が出力されるようになります。
publicvoid Configure(IApplicationBuilder app, ILoggerFactory loggerfactory) { loggerfactory.AddConsole(); app.UseMiddleware<RequestLoggerMiddleware>(); app.Map("/hello", hello => { hello.Run(async context => { await context.Response.WriteAsync("This is hello page."); }); }); app.Map("/world", world => { world.Run(async context => { await context.Response.WriteAsync("This is world page."); }); }); }
拡張メソッドを定義してから使う
以下のような拡張メソッドを定義しておくと、
publicstaticclass RequestLoggerExtensions { publicstatic IApplicationBuilder UseRequestLogger(this IApplicationBuilder builder) { return builder.UseMiddleware<RequestLoggerMiddleware>(); } }
このように、拡張メソッドの呼び出し一発で使えるようになります。
また、app.
と打った時点でインテリセンスの候補にも出るの便利です。
publicvoid Configure(IApplicationBuilder app, ILoggerFactory loggerfactory) { loggerfactory.AddConsole(); app.UseRequestLogger();
ミドルウェア作成のまとめ
ミドルウェアを作る場合は、以下のようなステップで作成します。
- ミドルウェア定義用のクラスを作成
- 上記クラスで、コンストラクタやInvokeメソッドなどを実装し、ミドルウェアの処理を実装
- IApplicationBuilderに対する拡張メソッドを作って、利用しやすくする
自分でミドルウェアを作る機会がどの程度あるかは、まだよくわかりません。
ですが、ミドルウェアがどのように作られているかを知っておくと、フレームワーク標準のミドルウェアを使うときなどに、内部でどのような処理が行われているか、具体的なイメージを持って実装ができるのでは、と思います。
今回はここまで。