11
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

C#Advent Calendar 2021

Day 16

ASP.NET Web API2 Owin ・ ASP.NET Core でミドルウェアを作成する

Last updated at Posted at 2021-12-16

概要

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ミドルウェア に関する記事が多く存在しています。
本記事では、ドキュメントを見ながら、実際にコードを書きつつ、ミドルウェアの挙動を見ていきたいと思います!

入門的な内容となっており、既に業務で バリバリ とミドルウェアを利用している方には、
やや物足りない可能性がありますが、ご容赦ください:pray:

お品書き

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 にアップしています。
ご興味のある方は、是非手元で動かしながら見ていただけると理解が深まると思います:smiley:
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

image.png

アプリケーションに到達した HTTPリクエスト に対して、各ミドルウェアが順次、処理を行う形になります。

ドキュメントの図の場合、Middleware1 > Middleware2 > Middleware3 の順序でリクエスト情報が到達します。
そして、Middleware3 > Middleware2 > Middleware1 の順番で各ミドルウェアの処理が実施、解決されます。
(※) 実際は、Middleware3 の後続で、MVCコントローラのような終端処理が存在しています。

ミドルウェアの処理ロジック (ドキュメントでは デリゲート と記載されています) は、
HttpContext を共有する形で、各処理を実施していきます。
ベルトコンベアの流れ作業 のようなイメージを持っていただくと、解りやすいかと思います。

同様にMS公式ドキュメントからの引用になりますが、
ASP.NET Core MVC のミドルウェア パイプライン に関するイメージ図があります。

image.png

例外処理を行うミドルウェア 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年 の記事です!! 歴史を感じます:flushed:

image.png
円形の部分がアプリケーションを表現しており、
外側のミドルウェアの層 (複数) と 内側のコア機能で構成されているイメージです。

リクエストがアプリケーションに到達すると、外側のミドルウェアからリクエストが伝達されていきます。
その後、内側のコア機能から順次処理が解決されていき、最終的にレスポンスが返却される という感じです。

ASP.NET Web API2 Owin のミドルウェア

まずは、ASP.NET Web API 2 Owin の ミドルウェア サンプルコードを見ていきます。

StopwatchMiddleware.cs
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 を導入していますので、デバッグ実行で確認出来ます。
image.png

StopwatchMiddleware クラス は OwinMiddleware クラス を継承します。

Owin の場合、IOwinContext に HTTP処理 に関する要素が集約されており、
リクエスト や レスポンス に関する情報は、IOwinContext を参照し、そこから取得・更新を行うことが出来ます。

await Next.Invoke(context); の部分は、後続のミドルウェアを呼び出している部分になります。

StopwatchMiddlewareExtensions は、StopwatchMiddleware をパイプラインに登録するためのヘルパークラスになります。
ミドルウェアは、このようなヘルパークラスを作成し、UseStopwatch という形でパイプラインに登録するのが、通例になります。

ミドルウェアの登録は、Startup クラスで実施します。
以下、コードサンプルを引用します。

Startup.cs
[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 で作成してみました:laughing:

StopwatchMiddleware.cs
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); の部分は、後続のミドルウェアを呼び出している部分になります。

続いて、ミドルウェアの登録部分を見ていきましょう。
以下、コードサンプルを引用します。

Program.cs
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

実行した結果は、以下の通りになります。
image.png
無事、出力が確認出来ました!

まとめ

ASP.NET Core の ミドルウェアは、最初は取っつきにくい部分もあるのですが、
パイプラインのフローをイメージしつつ、少しずつ慣れていくことで、理解を深めることが出来るかなと思います。
アプリケーションの 共通処理 や 基底処理 を実装している方にとっては、強い味方になります。

本記事により、少しでも皆様の理解の助けになれば幸いです。

11
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?