ASP.NET Core RC2 ~ 構成情報(config)の読み込み

ASP.NET Coreにおける構成情報(ユーザー定義の構成)の読み込みについて記述したいと思います。

ということで全体のアジェンダは以下の通り。

  • 従来のASP.NETでの構成ファイル
  • ASP.NET Coreでの新しい構成ファイル
  • 複数構成情報ファイルのオーバーライド
  • オプションパターンによるオブジェクト構成とDI

従来のASP.NETでの構成ファイル

アプリケーションでは多くの場合、外部の構成設定を利用します。
DB接続先設定であったり、動作モードであったり、フューチャーフラグのようなものであったり・・・ハードコーディングしたくない諸々の設定情報をビルド対象外の構成ファイルに記述します。

従来のASP.NETでは Web.config という仕組みが用意されていました。 Web.configには「ASP.NET自体の動作の振る舞いに関する設定項目」と「ユーザー定義の構成」の記述を行うことができました。本投稿でフォーカスしているのは後者の「ユーザー定義の構成」になります。

そして、ユーザー定義の構成情報を記述するには2つの方法がありました。

方法① にKey/Valueの組み合わせで構成情報を記述する
以下のような形式でキーに対する値を記述してプログラムで読み込む方式です。

~Web.config~
<add key="YourName" value="Ryuichi" />  

~xx.cs~
System.Configuration.Configurationmanager.Appsettings["YourName"]

方法② カスタム構成セクションを記述・実装する
方法①のappSettingsへの構成情報の追記ではプレインなキーと値の組み合わせでしか表現できなかった構成情報を、よりインテリジェントな階層構造データとして管理することができます。その反面、実装が面倒でした。

ASP.NET Coreでの新しい構成ファイル

ASP.NET Coreでは構成ファイルの記述について大きく変更がなされました。
ユーザー定義構成は「JSONファイル」「XMLファイル」「INIファイル」に記述する仕組みを採用しています。
従来の仕組みに対して、ある意味シンプルに分かりやすく、さらに柔軟な仕様となりました。

※RC1ではWeb.configファイル自体の全面撤廃がアナウンスされましたが、RC2では再度復帰しています。

JSON構成ファイルの読み込み

JSONファイルに構成情報を記述した場合の読み取り方法について説明します。
以下のようなJSONファイル(appSettings.jcon)を構成ファイルとして用意することを想定します。
また、appSettings.jsonファイルはWebアプリケーションフォルダ直下に配置します。

{
  "CompanyName": "Knowlbo Corporation"
}

構成ファイルの読み込みには「ConfigurationBuilderクラス(Microsoft.Extensions.Configuration)※1」を利用します。

var builder = new ConfigurationBuilder()
                 .SetBasePath(env.ContentRootPath)
                 .AddJsonFile("appsettings.json");

IConfigurationRoot _config = builder.Build();

string companyName = _config["CompanyName"];
// ↑"Knowlbo Corporation"が取得できます。

ConfigurationBuilderのインスタンス化

ConfigurationBuilderクラスは構成情報の読み込みを行うためのビルダークラスです。まずはnewでインスタンス化します。

SetBasePath()メソッド

SetBasePath()はメソッド名の通りベースパスを設定します。
この後でWebアプリケーションルートフォルダ直下の appSettings.json ファイルを読み込む想定なので、ベースパス=Webアプリケーションルートフォルダとしています。

AddJsonFile()メソッド

これもメソッド名そのままに、JSON形式のファイルを構成情報として追加。とい意味を持ちます。
ここで注意点。AddJsonFile()メソッドは「Microsoft.Extensions.Configuration.Json」アセンブリに実装されていますので、Project.jsonファイルのdependenciesに以下のような記述の追加が必要です。

{
  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.0.0-rc2-3002702",
      "type": "platform"
    },
    ...省略
    "Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-final",
  },
  ...省略
}

Build()メソッド

SetBasePath() / AddJsonFile()は構成情報の読み込みに関する設定の定義です。これらを実際に適用して構成読み込み用のオブジェクトを得るのがBuild()メソッドです。
戻り値の型はIConfigurationRootとなります。
IConfigurationRootはインデクサで「_config["CompanyName"]」というような形式により、JSON構成情報にアクセスすることが可能です。

※1 Microsoft.Extensions.Configuration.ConfigurationBuilderクラスはMicrosoft.Extensions.Configurationアセンブリにて実装

階層型データの利用

前述のappSettings.jsonファイルは単純なキーと値のプレインな設定でした。
せっかくJSONファイル形式なので階層型のデータを扱いたいところです。従来のASP.NETではカスタム構成セクションを実装するような面倒な話に発展するところですが、ASP.NET Coreの構成ファイルの仕組みではシンプルにアクセス可能です。
まず、appSettings.jsonファイルを以下のように定義します。

{
  "Company": {
    "Name": "Knowlbo Corporation",
    "EstablishmentDate": "1986/3/30"
  }
}

構成ファイルの読み込みロジックは以下の通りです。
IConfigurationRootの取得は以前のサンプルと同様。読み取る対象の構成キー名称の階層部分を「:」(コロン区切り)で記述するだけです。

var builder = new ConfigurationBuilder()
                 .SetBasePath(env.ContentRootPath)
                 .AddJsonFile("appsettings.json");

IConfigurationRoot _config = builder.Build();

// ↓「:」(コロン)区切りで階層をたどれる
string companyName = _config["Company:Name"]; 
string establishmentDate = _config["Company:EstablishmentDate"];

INI構成ファイルの読み込み

Windows3.1を思い出されるようなINIファイル。ASP.NET CoreではINIファイルも標準サポートしています。
では、以下のようなappSettings.iniファイルを作成します。セクションを切った3つの構成情報を定義しています。

[Profile]
HandleName=ryuichi111std
FavoriteSports=MotorSports

[ApplicationSetting]
EnableMultiThread=False

次に、project.jsonに「Microsoft.Extensions.Configuration.Ini」へのdependenciesを追加します。

{
  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.0.0-rc2-3002702",
      "type": "platform"
    },
    ...省略
    "Microsoft.Extensions.Configuration.Ini": "1.0.0-rc2-final",
  },
  ...省略
}

そしてConfigurationBuilder構成を行ってアクセスします。セクション→項目は「:」(コロン)区切りで階層的にアクセス可能です。

var builder = new ConfigurationBuilder()
                 .SetBasePath(env.ContentRootPath)
                 .AddIniFile("appsettings.ini");

IConfigurationRoot _config = builder.Build();

string handleName = 
    _config["Profile:HandleName"];
string favoriteSports = 
    _config["Profile:FavoriteSports"];
string enableMultiThresd =
    _config["ApplicationSetting:EnableMultiThread"];

XML構成ファイルの読み込み

XML形式の構成情報も前述2つと基本は変わりません。
では、用意するXML構成ファイル(appSettings.xml)は以下とします。

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
  <Profile>
    <Name>Ryuichi</Name>
    <URL>https://www.facebook.com/ryuichi.daigo</URL>
  </Profile>
</Configuration>

次に、project.jsonに「Microsoft.Extensions.Configuration.Xml」へのdependenciesを追加します。

{
  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.0.0-rc2-3002702",
      "type": "platform"
    },
    ...省略
    "Microsoft.Extensions.Configuration.Xml": "1.0.0-rc2-final",
  },
  ...省略
}

そしてConfigurationBuilder構成を行ってアクセスします。XML階層は「:」(コロン)区切りで階層的にアクセス可能です。

var builder = new ConfigurationBuilder()
                 .SetBasePath(env.ContentRootPath)
                 .AddXmlFile("appSettings.xml");

IConfigurationRoot _config = builder.Build();

string name = _config["Profile:Name"];
string url = _config["Profile:URL"];

複数の構成情報ファイルのオーバーライド

上記の各ファイル形式の構成情報の読み込みでは、単一の構成ファイルを読み込み対象としていました。
「AddJsonFile() / AddIniFile() / AddXmlFile()」メソッドは複数重ねて呼び出すことができます。

var builder = new ConfigurationBuilder()
                 .SetBasePath(env.ContentRootPath)
                 .AddJsonFile("appSettings.json").
                 .AddIniFile("appSettings.ini").
                 .AddXmlFile("appSettings.xml");

IConfigurationRoot _config = builder.Build();

こうした場合、appSettings.json → appSettings.ini → appSettings.xmlの順番に構成情報が読み込まれ、重複する項目は上書きされます。

オプションパターンによるオブジェクト構成とDI

最後にもう一つ。非常にインテリジェントな構成情報の読み込み方法を紹介します。
構成ファイルに記述した階層構造データをPOCOオブジェクトに直接読み込みます。また読み込んだPOCOオブジェクトはDIによりコントローラのコンストラクタで受け取ることができる構成をとることができます。
まず構成ファイル(appSettings.json)は、以下の通り。

{
  "Company": {
    "Name": "Knowlbo Corporation",
    "EstablishmentDate":  "1986/3/26"
  }
}

JSON構成上のCompanyデータを読み込むPOCOクラスは以下の定義とします。

using System;

namespace SampleApp
{
  public class Company
  {
    public string Name { get; set; }

    public DateTime EstablishmentDate { get; set; }
  }
}

project.jsonに「Microsoft.Extensions.Options.ConfigurationExtensions」へのdependenciesを追加します。

{
  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.0.0-rc2-3002702",
      "type": "platform"
    },
    ...省略
    "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0-rc2-final"
  },
  ...省略
}

Startupクラスの「ConfigureServices()」メソッドにオプションパターンによる構成オブジェクトの読み込み構成を行います。

namespace SampleApp
{
  public class Startup
  {
    ...省略

    public IConfigurationRoot Configuration { get; }
    
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json");
        this.Configuration = builder.Build();
    }

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

    ...省略
  }
}

Startup()コンストラクタ

appSettings.json構成ファイルを読み込んで、構成オブジェクトを「Configuration」フィールドに保持しています。

AddOptions()メソッド呼び出し

オプションパターンを有効にする為に「AddOptions()」拡張メソッドを呼び出します。
これは、Microsoft.Extensions.OptionsアセンブリのMicrosoft.Extensions.DependencyInjection名前空間に実装されたIServiceCollectionに対する拡張メソッドです。

Configure()メソッド呼び出し

構成情報から読み込む「POCOクラスの型」を登録します。
実は、「Configure(IConfiguration config)」のオーバーロードメソッド(拡張メソッド)を呼び出すために「Microsoft.Extensions.Options.ConfigurationExtensions」への参照を追加していました。

以上でCompanyクラスをJSON構成ファイルから読み込みDIにより自動注入する構成が整いました。
ではCompanyオブジェクトをMVCのコントローラで受け取りましょう。
HomeController.csの実装を以下に示します。

namespace SampleApp
{
  public class HomeController : Controller
  {
    // Companyオブジェクト保持用のフィールド
    private Company _company = null;

    public HomeController(IOptions<Company> companyAccessor)
    {
      this._company = companyAccessor.Value;
    }

    public IActionResult Index()
    {
      return Content(
          this._company.Name + " " + 
          this._company.EstablishmentDate.ToString("yyyy/MM/dd"));
    }
  }
}

コンストラクタインジェクション

コンストラクタの引数として「IOptions」型の引数を定義すると、ASP.NET CoreのDI機能によりオブジェクトが注入されます。
ここではCompanyオブジェクトを指定しています。ここで受け取れるオブジェクトは、Companyオブジェクト自体ではなく、アクセサオブジェクトであるため、「companyAccessir.Value」プロパティを参照しています。ひとまず、コントローラ内の別のメソッドでCompanyオブジェクトを使用したい為、自クラス内の_companyフィールドにオブジェクトを保持しました。

Index()メソッド

Companyオブジェクトを参照しています。appSettings.jsonに設定した値が読み込まれていることが確認できるはずです。また、EstablishmentDateプロパティはDateTime型ですが、JSON上の文字列からの型変換ももちろん適切に行われています。

まとめ

やはり、ASP.NET Coreは新規に設計されたフレームワークだけあり、今風な設計思想を取り入れて「シンプル」で「柔軟」な機能実装がなされている印象を受けました。
構成情報をPOCOに直接読み込み、DIで注入可能な仕組み等も非常に便利に利用できると思います。