概要
2021年11月 Visual Studio 2022 のリリースにより、.NET Core は .NET6 (LTS) になりました。
C# の Webアプリケーション開発は、ASP.NET Core で開発するケースが増えてきたと思います。
ASP.NET Core は、ASP.NET MVC / ASP.NET Web API2 と比較して様々な点が改良されているかと思いますが、
本件では、その中でも ミドルウェア を題材に挙げたいと思います。
MS公式ドキュメントには、Owinミドルウェア や ASP.NET Coreミドルウェア に関する記事が多く存在しています。
本記事では、ドキュメントを見ながら、実際にコードを書きつつ、ミドルウェアの挙動を見ていきたいと思います!
入門的な内容となっており、既に業務で バリバリ とミドルウェアを利用している方には、
やや物足りない可能性がありますが、ご容赦ください
お品書き
1. ミドルウェアについておさらい
2. ASP.NET Web API2 Owin のミドルウェア
3. ASP.NET Core のミドルウェア
環境
- Windows10
- Visual Studio 2022
- ASP.NET Core
- ASP.NET Web API 2 Owin
サンプルコード
サンプルコードを Github にアップしています。
ご興味のある方は、是非手元で動かしながら見ていただけると理解が深まると思います
https://github.com/tYoshiyuki/dotnet-middleware-sample
ミドルウェアについておさらい
ミドルウェアに関しては、MS公式ドキュメントの図が参考になるかと思います。
https://docs.microsoft.com/ja-jp/aspnet/core/fundamentals/middleware/?view=aspnetcore-6.0#create-a-middleware-pipeline-with-webapplication
アプリケーションに到達した HTTPリクエスト に対して、各ミドルウェアが順次、処理を行う形になります。
ドキュメントの図の場合、Middleware1
> Middleware2
> Middleware3
の順序でリクエスト情報が到達します。
そして、Middleware3
> Middleware2
> Middleware1
の順番で各ミドルウェアの処理が実施、解決されます。
(※) 実際は、Middleware3
の後続で、MVCコントローラのような終端処理が存在しています。
ミドルウェアの処理ロジック (ドキュメントでは デリゲート
と記載されています) は、
HttpContext
を共有する形で、各処理を実施していきます。
ベルトコンベアの流れ作業 のようなイメージを持っていただくと、解りやすいかと思います。
同様にMS公式ドキュメントからの引用になりますが、
ASP.NET Core MVC のミドルウェア パイプライン に関するイメージ図があります。
例外処理を行うミドルウェア ExceptionHandler
や 静的ファイルをサポートするミドルウェア Static Files
、認証を行うミドルウェア Authentication
等、
Webアプリケーションを利用するにあたり、必要となる基本機能はミドルウェアで実装されていることが判るかと思います。
独自のミドルウェアは、Custom middlewares
で導入し、最終的には Endpoint
が呼び出される・・・という感じです。
普段、何となく利用している ASP.NET Core の機能 と ミドルウェアが、密接に関わっていることが何となくイメージ出来るかなと思います!
尚、ミドルウェアという概念は、ASP.NET MVC (.NET Framework) 時代からも存在しており、
その際は、Owin (Open Web Interface for .NET) と呼ばれる類似の仕組みで、ミドルウェアを利用することが出来ました。
(※) 細かい話になりますが、Owin は、インターフェースの仕様を定義したものであり、Microsoft.Owin (Katana) がその実装になります。
Owinのミドルウェアについては、neueccさん の記事が非常に参考になります。
https://neue.cc/2014/01/06_442.html
なんと 2014年 の記事です!! 歴史を感じます
円形の部分がアプリケーションを表現しており、
外側のミドルウェアの層 (複数) と 内側のコア機能で構成されているイメージです。
リクエストがアプリケーションに到達すると、外側のミドルウェアからリクエストが伝達されていきます。
その後、内側のコア機能から順次処理が解決されていき、最終的にレスポンスが返却される という感じです。
ASP.NET Web API2 Owin のミドルウェア
まずは、ASP.NET Web API 2 Owin の ミドルウェア サンプルコードを見ていきます。
namespace DotNetMiddlewareSample.WebApi.Middleware
{
/// <summary>
/// 実行時間を記録するミドルウェアです。
/// </summary>
public class StopwatchMiddleware : OwinMiddleware
{
public StopwatchMiddleware(OwinMiddleware next) : base(next) { }
public override async Task Invoke(IOwinContext context)
{
var sw = new Stopwatch();
sw.Start();
context.Response.OnSendingHeaders(x =>
{
// リクエスト実行時間をレスポンスヘッダーに記載します。
context.Response.Headers.Add("X-Execution-Time", new[] { sw.ElapsedMilliseconds.ToString() });
}, context);
await Next.Invoke(context);
sw.Stop();
}
}
/// <summary>
/// <see cref="StopwatchMiddleware"/> 用の拡張メソッドです。
/// </summary>
public static class StopwatchMiddlewareExtensions
{
/// <summary>
/// <see cref="StopwatchMiddleware"/> をパイプラインに登録します。
/// </summary>
/// <param name="app"><see cref="IAppBuilder"/>></param>
/// <returns><see cref="IAppBuilder"/></returns>
public static IAppBuilder UseStopwatch(this IAppBuilder app)
{
return app.Use<StopwatchMiddleware>();
}
}
}
非常に単純なサンプルになりますが、リクエスト処理時間を 独自のレスポンスヘッダ に出力するというものです。
以下、Swagger UI で実行した結果になります。x-execution-time
というレスポンスヘッダに処理時間 (ミリ秒) が設定されていることが判るかと思います。
(※) サンプルコードには、NSwag
を導入していますので、デバッグ実行で確認出来ます。
StopwatchMiddleware
クラス は OwinMiddleware
クラス を継承します。
Owin の場合、IOwinContext
に HTTP処理 に関する要素が集約されており、
リクエスト や レスポンス に関する情報は、IOwinContext
を参照し、そこから取得・更新を行うことが出来ます。
await Next.Invoke(context);
の部分は、後続のミドルウェアを呼び出している部分になります。
StopwatchMiddlewareExtensions
は、StopwatchMiddleware
をパイプラインに登録するためのヘルパークラスになります。
ミドルウェアは、このようなヘルパークラスを作成し、UseStopwatch
という形でパイプラインに登録するのが、通例になります。
ミドルウェアの登録は、Startup
クラスで実施します。
以下、コードサンプルを引用します。
[assembly: OwinStartup(typeof(Startup))]
namespace DotNetMiddlewareSample.WebApi
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var configuration = new HttpConfiguration();
configuration.MapHttpAttributeRoutes();
app.UseSwaggerUi3(typeof(Startup).Assembly, settings => { });
app.UseStopwatch();
app.UseWebApi(configuration);
}
}
}
[assembly: OwinStartup(typeof(Startup))]
のアノテーションにより、
当該クラスが Owin のスタートアップクラス として認識されています。
app.UseStopwatch();
の部分が、ミドルウェアを登録している部分になります。
ASP.NET Core のミドルウェア
いよいよ真打ち、ASP.NET Core のミドルウェアを見ていきましょう。
折角なので、.NET6・Visual Studio 2022 で作成してみました
namespace DotNetMiddlewareSample.NetCoreApi.Middleware
{
/// <summary>
/// 実行時間を記録するミドルウェアです。
/// </summary>
public class StopwatchMiddleware
{
private readonly RequestDelegate next;
public StopwatchMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
var sw = new Stopwatch();
sw.Start();
// リクエスト実行時間をレスポンスヘッダーに記載します。
context.Response.OnStarting(_ =>
{
context.Response.Headers.Add("X-Execution-Time", new[] { sw.ElapsedMilliseconds.ToString() });
return Task.CompletedTask;
}, context);
await next(context);
sw.Stop();
}
}
/// <summary>
/// <see cref="StopwatchMiddleware"/> 用の拡張メソッドです。
/// </summary>
public static class StopwatchMiddlewareExtensions
{
/// <summary>
/// <see cref="StopwatchMiddleware"/> をパイプラインに登録します。
/// </summary>
/// <param name="builder"><see cref="IApplicationBuilder"/></param>
/// <returns><see cref="IApplicationBuilder"/></returns>
public static IApplicationBuilder UseLogging(this IApplicationBuilder builder)
{
return builder.UseMiddleware<StopwatchMiddleware>();
}
}
}
大まかな構造は、ほぼ同じような感じです。
ミドルウェアをパイプラインに登録するための拡張メソッド StopwatchMiddlewareExtensions
を準備している点も同様ですね。
ASP.NET Web API 2 Owin と比較して、StopwatchMiddleware
クラス は 何も継承していないピュアなC#のクラス となっています。
Owin で利用していた IOwinContext
は無くなっており、
代わりに HttpContext
を用いて、パイプラインの処理を行っていきます。
await next(context);
の部分は、後続のミドルウェアを呼び出している部分になります。
続いて、ミドルウェアの登録部分を見ていきましょう。
以下、コードサンプルを引用します。
using DotNetMiddlewareSample.NetCoreApi.Middleware;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Host.ConfigureServices(services =>
{
services.AddSwaggerDocument();
services.AddRouting(options => options.LowercaseUrls = true);
services.AddControllers();
});
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseAuthorization();
app.MapControllers();
app.UseOpenApi();
app.UseSwaggerUi3();
app.UseStopwatch();
app.Run();
ASP.NET Core のアプリケーション起点処理 (ホスティングモデル) は、.NET5 から .NET6 でかなり簡素化されています!
https://docs.microsoft.com/ja-jp/aspnet/core/migration/50-to-60?view=aspnetcore-6.0&tabs=visual-studio#new-hosting-model
Startup.cs
は不要となり、Program.cs
でアプリケーションの起動処理が完結するようになっています。
app.UseStopwatch();
の部分が、ミドルウェアを登録している部分になります。
トップレベルステートメント (C# 9.0の機能) により、非常にスッキリとしたコードになっていますね!
https://docs.microsoft.com/ja-jp/dotnet/csharp/fundamentals/program-structure/top-level-statements
実行した結果は、以下の通りになります。
無事、出力が確認出来ました!
まとめ
ASP.NET Core の ミドルウェアは、最初は取っつきにくい部分もあるのですが、
パイプラインのフローをイメージしつつ、少しずつ慣れていくことで、理解を深めることが出来るかなと思います。
アプリケーションの 共通処理 や 基底処理 を実装している方にとっては、強い味方になります。
本記事により、少しでも皆様の理解の助けになれば幸いです。