ASP.NET Core - ミドルウェアパイプラインを作ってみる

ミドルウェアとかミドルウェアパイプラインとかリクエストデリゲートなどを理解するために、次のドキュメントを読みながら記事内にあるミドルウェアパイプラインの画像の動きを作ってみました。

ASP.NET Core のミドルウェア | Microsoft Docs

この画像のことです。

https://docs.microsoft.com/ja-jp/aspnet/core/fundamentals/middleware/index/_static/request-delegate-pipeline.png

Use/Run拡張メソッドを使ってパイプラインを作る

Startup.Configureメソッド内でミドルウェアパイプラインを構築します。Use拡張メソッドやRun拡張メソッドを使うと、ミドルウェアをインライン(ラムダ式)で記述できます。Use拡張メソッドは次のミドルウェアコンポーネントとも言ったりしてる)を呼び出しますが、Run拡張メソッドは次のミドルウェアを呼び出さずパイプラインの終端になります。

public class Startup {
    public void ConfigureServices(IServiceCollection services) {
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
        // 最初に呼び出されるミドルウェア
        app.Use(async (context, next) => {
            context.Response.ContentType = "text/plain";

            // 次を呼び出す前の処理
            await context.Response.WriteAsync("Middleware 1 before\n");

            // 次のミドルウェアを呼び出す
            await next.Invoke();

            // 次を呼び出した後の処理
            await context.Response.WriteAsync("Middleware 1 after\n");
        });

        // 2つ目のミドルウェア
        app.Use(async (context, next) => {
            await context.Response.WriteAsync("\tMiddleware 2 before\n");

            await next.Invoke();

            await context.Response.WriteAsync("\tMiddleware 2 after\n");
        });

        // 最後に呼び出されるミドルウェア
        app.Run(async context => {
            await context.Response.WriteAsync("\t\tMiddleware 3\n");
        });
    }
}

実行すると次のレスポンスが返ります。

Middleware 1 before
    Middleware 2 before
        Middleware 3
    Middleware 2 after
Middleware 1 after

ミドルウェアクラスとUseMiddleware拡張メソッドを使ってパイプラインを作る

上記と同じことをクラスを使って再現したいと思います。インラインでの処理をミドルウェアクラスにカプセル化します。

ミドルウェアクラスはHttpContextを引数に持つInvokeAsyncメソッドを持っていればいいみたいです。

// 最初に呼び出されるミドルウェア
public class Middleware1 {
    private readonly RequestDelegate _next;

    public Middleware1(RequestDelegate next) {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context) {
        context.Response.ContentType = "text/plain";

        // 次を呼び出す前の処理
        await context.Response.WriteAsync("Middleware 1 before\n");

        // 次のミドルウェアを呼び出す
        await _next(context);

        // 次を呼び出した後の処理
        await context.Response.WriteAsync("Middleware 1 after\n");
    }
}

// 2つ目のミドルウェア
public class Middleware2 {
    private readonly RequestDelegate _next;

    public Middleware2(RequestDelegate next) {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context) {
        await context.Response.WriteAsync("\tMiddleware 2 before\n");

        await _next(context);

        await context.Response.WriteAsync("\tMiddleware 2 after\n");
    }
}

// 最後に呼び出されるミドルウェア
public class Middleware3 {
    // 使わないけど
    private readonly RequestDelegate _next;

    public Middleware3(RequestDelegate next) {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context) {
        await context.Response.WriteAsync("\t\tMiddleware 3\n");
    }
}

public class Startup {
    public void ConfigureServices(IServiceCollection services) {
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
        // 最初に呼び出されるミドルウェア
        app.UseMiddleware<Middleware1>();

        // 2つ目のミドルウェア
        app.UseMiddleware<Middleware2>();

        // 最後に呼び出されるミドルウェア
        app.UseMiddleware<Middleware3>();
    }
}

実行すると上記と同じレスポンスが返るようになります。

普段UseStaticFilesメソッドやUseMvcメソッドを使っていると思いますが、その中で実際にはミドルウェアはクラスとして用意されていて、UseMiddleware拡張メソッドを使っているようです。

勉強になりました。