Alternative Architecture DOJO

オルターブースのクラウドネイティブ特化型ブログです。

ASP.NET Core のカスタムミドルウェアを作って公開するときに気を付けること

こんにちは。MLBお兄さんこと松村です。 MLB も開幕から1ヶ月経過しましたが、既にノーヒットノーランが4度も達成されています!

www.mlb.com


GW に個人的な制作として NuGet パッケージをリリースしました。
www.nuget.org

今回の記事は ASP.NET Core のカスタムミドルウェアを作るとき、また、パッケージとして配布するときに意識することをまとめます。
基本的なことはドキュメントに書いてあるので、ぜひ参考にしましょう。

docs.microsoft.com

ターゲットフレームワークを決める

C# のプロジェクトを作るときに、まず考えるのはどのバージョンに対応するかです。 (Target Framework Monikers / TFM といいます)
.NET Framework 向けなのか、.NET Core 向けなのか、どちらもなのか、というところです。

ライブラリとして利用する場合は基本的に SDK 形式になると思います。プロジェクトファイル (.csproj) でいうと <Project Sdk="Microsoft.NET.Sdk"> で始まる形式です。
そして .NET Standard を使って実装することが多いと思うので、このあたりが参考になるでしょう。

docs.microsoft.com

docs.microsoft.com

また csproj 関連で言えば GeneratePackageOnBuild 属性を設定しておくことで、ビルドのたびにパッケージファイルを生成することができます。

docs.microsoft.com

パイプライン前後のどちらで実行するかを決める

ミドルウェアの特徴としては HTTP リクエストのパイプラインの途中に処理を差し込めることです。(画像引用元
middleware-pipeline

実装する際は、HTTP リクエストのはじめに実行する処理なのか、あとに実行する処理なのかを考えましょう。
どちらになるかによって next と呼ばれる、次のパイプラインを呼び出すデリゲートの実行タイミングが変わります。

逆にいうとパイプラインを途中で止める(ぶった切る)ミドルウェアを作りたいなら next デリゲートを実行しなければ良いです。

public class CustomMiddleware
{
    public CustomMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    
    public async Task Invoke(HttpContext httpContext)
    {
        // HTTP リクエストのはじめに実行するタイミング
        doSomething();
        
        // 次のパイプラインを実行する
        await _next(httpContext);

        // HTTP リクエストのあとに実行するタイミング
        doSomething();
    }
}

Dependency Injection をきちんと使う

ミドルウェアからも DI コンテナーにアクセスすることができます。必要なリソースは DI を通じて取得するようにしましょう。
Invoke メソッドの引数に指定しておくことで、取得することができます。

例えば私が作ったパッケージでは IConfiguration が必要なので、このように書いています。

public async Task Invoke(HttpContext httpContext, IConfiguration configuration)
{
    ...
}

呼出用の拡張メソッドを提供する

ミドルウェアを使うには当然呼び出してあげなければいけません。ASP.NET Core の場合は Startup.cs の Configure メソッドに書きます。
ライブラリとしてミドルウェアを提供する場合、呼出用の拡張メソッドも一緒に実装しましょう。

namespace Microsoft.AspNetCore.Builder
{
    public static class CustomMiddlewareBuilderExtensions
    {
        public static IApplicationBuilder UseCustomMiddleware(this IApplicationBuilder app)
        {
            return app.UseMiddleware<CustomMiddleware>();
        }
    }
}

このように書いておけば、Startup.cs で他のミドルウェアと同じような書き方に合わせることができます。

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        // 呼出用の拡張メソッドを使う場合
        app.UseCustomMiddleware();
        
        // 呼出用の拡張メソッドを使わない場合
        app.UseMiddleware<CustomMiddleware>();
    }
}

また、ミドルウェアに対してオプションを指定したいなら、オプションクラスを用意して、変数として渡すメソッドと、直接書けるメソッドを用意しておくと便利です。

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        // オプション変数を渡す場合
        CustomMiddlwareOptions options = new CustomMiddlwareOptions { Aaa = ..., Bbb = ... };
        app.UseCustomMiddleware(options);
        
        // オプションを直接書く場合
        app.UseCustomMiddleware(options =>
        {
            options.Aaa = ...;
            options.Bbb = ...;
        });
    }
}

テストを書く

当然テストは書きましょう。ミドルウェアは HttpContext を扱うため、若干の難しさがあります。(モックとか)
テストの書き方についてはドキュメントが揃っているので、参考にしましょう。

docs.microsoft.com

DI 経由で使用するオブジェクトについては、 Moq などのモッキングツールを使ってテストを書くと良いでしょう。

README を書く

ミドルウェアに関わらずパッケージを公開する場合、そのパッケージの使い方や注意点が分かるようにしましょう。
そういった情報は README に書いておくと良いです。ちなみに私は Swashbuckle.AspNetCore の REAMDE を参考にしました。

README を書いておくことで nuget.org に公開したときに、パッケージのページに README の内容が表示されるようになるそうです。

devblogs.microsoft.com

CI/CD を準備する

ミドルウェア関係なくなって来ましたが、CI/CD を準備しておくとパッケージの公開が楽になります。
GitHub Actions での方法は試していませんが、 Azure Pipelines なら公開するためのタスクが用意されています。

docs.microsoft.com

nuget.org にパッケージをプッシュする場合は APIキーが必要になります。
APIキーは nuget.org で発行することができ、Azure DevOps の Service Connection に登録することで使用できるようになります。

f:id:tech-tsubaki:20210515231520p:plain

f:id:tech-tsubaki:20210515231601p:plain


このあたりが、カスタムミドルウェアを NuGet パッケージで公開するときに意識したことになります。