しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET Core でオプションを柔軟に扱えるようになった話

ASP.NET というか .NET Framework は System.Configuration を使って、アプリケーションの設定読み込みを実装するか、完全に自前で XML などを読み込む処理を作っていたかと思いますが、ASP.NET Core ではフレームワークとして用意されました。

Configuration in ASP.NET Core | Microsoft Learn

ASP.NET Core の各ミドルウェアはこのオプション機能を使って、設定周りが実装されています。

このオプション機能を提供しているのは Microsoft.Extensions.Options 名前空間なので、ASP.NET Core と呼ぶのが適切なのかわかってないですが、GitHub では aspnet 内にいるので ASP.NET Core として扱います。

機能としてはシンプルですが、拡張メソッドと DI を上手く使って実装されています。オプションを使う際の基本的な流れとしては以下のようになります。

  1. Configure<T> を使ってオプションを初期化
  2. IOptions<T> を経由して DI から値を受け取る

Startup クラス内で初期化して、その値を DI から取得して挙動を変える。というシンプルなものです。

オプションを初期化する

実際にコードを書く前に、まずはオプションを保持するためのクラスを用意しておきます。

public class MyOption
{
    public int Value1 { get; set; }
    public string Value2 { get; set; }
}

サンプルなので複雑な構造にはしていません。int と string をプロパティを持つだけのクラスです。

では実際に初期化を行っておきますが、2 種類の方法があるので両方とも紹介します。

Configure メソッドで初期化する

オプションは IServiceCollection に用意された Configure<T> 拡張メソッドを使って、DI への登録と値を初期化するための処理を定義します。

引数のラムダ式で値を設定していくだけなので、実行時に変更しない項目の場合に便利な書き方です。

public void ConfigureServices(IServiceCollection services)
{
    services.AddOptions();

    services.Configure<MyOption>(options =>
    {
        options.Value1 = 10;
        options.Value2 = "test";
    });
}

ASP.NET Core は引数にラムダ式を渡して書くことが多い気がします。Factory Method パターンですかね。

IConfiguration を使って初期化する

実行時に JSON や環境変数の値を使って設定したい項目の場合は、上の書き方だとめんどくさいので、ちゃんと IConfiguration の値から初期化する機能が用意されています。

NuGet から Microsoft.Extensions.Options.ConfigurationExtensions を追加する必要がありますが、これで IConfiguration を使ったオプションの初期化が行えるようになります。

{
  "MyOption": {
    "Value1": 10,
    "Value2": "test"  
  } 
}

appsettings.json に上のような項目を追加してきます。MyOption というキーは何でもよいのですが、その中に含まれている値のキーは、クラスに定義したプロパティと同じ名前にしておきます。

NuGet で ConfigurationExtensions パッケージをインストールしてあれば、Configure<T> に IConfiguration を受け取るオーバーロードが追加されるので、それを呼び出します。

public void ConfigureServices(IServiceCollection services)
{
    services.AddOptions();
 
    services.Configure<MyOption>(Configuration.GetSection("MyOption"));
}

Configuration.GetSection には JSON に追加したキーを指定します。これで中の値だけが取れるので、適切な形でバインディングされるようになっています。

オプションを受け取る

Configure<T> で初期化したオプションは DI に登録されていますが、そのままの型では受け取ることが出来ないので、使いたいクラスのコンストラクタでは IOptions<T> 型に包んで受けるようにします。

IOptions<T> は Value プロパティだけを持つ、単純なインターフェースです。

public class HomeController : Controller
{
    public HomeController(IOptions<MyOption> optionsAccessor)
    {
        _options = optionsAccessor.Value;
    }

    private readonly MyOption _options;
}

使うときにはコンストラクタで、MyOption のインスタンスをフィールドに保存しておけば良さそうです。

実際にデバッガーで値を確認してみると、ちゃんと設定された値が取れていることが確認できます。

DI を使っているので、オプションに関係する処理が全て Startup に集約されて、分かりやすくなったと思います。これまで Web.config の appSettings を使っていた部分を綺麗に置き換え出来そうです。

裏技的な感じはしますが IConfiguration を DI に追加すれば、簡単に設定を全て読み取れるようになりますが、基本的には型付けされたオプションを使うべきだと思います。

フレームワークのオプションを変更する

ここまでは自前オプションの話でしたが、ASP.NET Core は全体的にこのフレームワークが使われているので、Configure<T> メソッドを使えばいろいろ変更が出来るようになっています。

分かりやすいものとして、ルーティングで生成される URL を小文字にする設定を有効にしてみたいと思います。MVC 5 と同様に Core MVC 1.0 でも LowercaseUrls プロパティが用意されています。

public void ConfigureServices(IServiceCollection services)
{
    services.AddRouting(options =>
    {
        options.LowercaseUrls = true;
    });

    // Add framework services.
    services.AddMvc();
}

一番分かりやすいのが IServiceCollection に追加するタイミングで設定を行う方法です。AddRouting メソッドには引数に Actoon<RouteOptions> を渡せるオーバーロードがあるので、それを利用します。

もう一つが Configure<T> メソッドを使う方法です。ルーティングのオプションは RouteOptions というクラスなので、型パラメータにそのクラスを渡せば、同じように設定を行えます。

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();

    services.Configure<RouteOptions>(options =>
    {
        options.LowercaseUrls = true;
    });
}

両方とも、実行してみると生成される URL が小文字になるので、設定が効いていることが確認できます。

今のところは CoreFX に入れるほどではないけど、ASP.NET Core のためにモダンな機能を提供するライブラリが Microsoft.Extensions のようです。

名前の付け方としては ASP.NET Core 専用ではないと思うので、他でも便利に使えそうではあります。