やること
AutofacとAutofac.Configurationを使用してASP.NET Coreで設定ファイルを使用したDIの設定を行います。
設定ファイルを使用することでProduction
、Development
といった環境毎に使用するコンポーネントの変更が容易となります。
また、設定ファイルでコンポーネントのコンストラクタ引数やプロパティの値を指定する際に、標準ではサポートされない型についての対応方法も記述します。
対象読者
- ASP.NET Coreで実行環境に応じて使用されるコンポーネントを差し替えたい人
環境
- Visual Strudio 2017
- Autofac 4.6.0
- Autofac.Configuration 4.0.1
- Autofac.Extensions.DependencyInjection 4.1.0
サンプル
このサンプルでは環境変数ASPNETCORE_ENVIRONMENT
に応じてcomponents.json
またはcomponents.Development.json
からコンポーネントの構築を行っています。
Development
環境ではIService
実装としてDevelopmentService
が使用され、それ以外ではProductionService
が使用される設定となっており、どちらの実装が使用されているかはホーム画面で確認できるサンプルとなっています。
設定ファイルによるIServiceProviderの構築
JSONファイルを使ったAutofacの設定
Autofac.Configuration
を使用すると以下のようなコードでJSONファイルを使用したコンテナへのコンポーネント登録が行えます。
using Autofac;
using Autofac.Configuration;
using Microsoft.Extensions.Configuration;
var config = new ConfigurationBuilder()
.AddJsonFile("components.json");
var module = new ConfigurationModule(config.Build());
var builder = new ContainerBuilder();
builder.RegisterModule(module);
var container = builder.Build();
設定ファイルの例は以下の様になります。
{
"defaultAssembly": "AutofacExample",
"components": [
{
"type": "AutofacExample.Services.HogeService",
"services": [ { "type": "AutofacExample.Services.IService" } ]
}
]
}
これはコードで書くと以下の内容と同じです。
builder
.RegisterType<HogeService>()
.As<IService>();
設定ファイルを使用したAutofacの設定方法詳細についてはJSON/XML Configurationを参照してください。
IServiceProviderの置換
ASP.NET CoreでのIServiceProvider
の置換方法については以前の記事で解説しています。
Autofac.Extensions.DependencyInjection
にはIServiceProvider
としてAutofacを使用するための拡張が用意されており、それを使用したコードは以下の様になります。
using Autofac.Extensions.DependencyInjection;
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
// Autofac
var builder = new ContainerBuilder();
builder.Populate(services);
var container = builder.Build();
return new AutofacServiceProvider(container);
}
環境に応じた設定ファイルの使用
ASP.NET Coreアプリケーションのテンプレートは、設定ファイルappsettings.json
を環境に応じて変更する構成になっているかと思います。
これと同様のことを行えばAutofacの設定ファイルについて環境に応じた物が使用できます。
環境に応じた設定ファイルでのAutofacの構築とIServiceProvider
の置換を組み合わせたコードは以下のようになります。
using System;
using Autofac;
using Autofac.Configuration;
using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
public class Startup
{
public IConfigurationRoot Configuration { get; }
private readonly ConfigurationModule module;
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
// 環境に応じてAutofacの設定ファイルを切り替える
var config = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("components.json", optional: true)
.AddJsonFile($"components.{env.EnvironmentName}.json", optional: true);
module = new ConfigurationModule(config.Build());
}
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
// Autofac
var builder = new ContainerBuilder();
builder.Populate(services); // IServiceCollectionからのコンポーネントの構築
builder.RegisterModule(module); // 設定ファイルによるコンポーネントの構築
var container = builder.Build();
return new AutofacServiceProvider(container);
}
...
}
その他
TypeConverterによる値の変換
設定ファイルを使用してコンポーネントの定義ではコンストラクタ引数やプロパティの値を指定することが可能です。
ただし、Autofac.Configuration
が標準ではサポートしていない型の項目への設定は例外となります。
例えば、以下の様にbyte配列をコンストラクタ引数とするクラスの定義を行いたい場合、設定ファイルで「"token": [1, 2, 3]
」の用に記述しても例外となります。
public class ProductionService : IService
{
private readonly byte[] token;
public ProductionService(byte[] token)
{
this.token = token;
}
}
Autofac.Configuration
がサポートしていない型の設定を行いたい場合、TypeConverter
を用意することで型変換を行っての設定が可能となります。
例えばbyte配列を16進文字列で設定するようにしたければ、以下の様なTypeConverter
を実装して適用先で宣言します。
using System;
using System.ComponentModel;
using System.Globalization;
public class HexStringConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var s = value as string;
if (s != null)
{
return Decode(s, culture);
}
return base.ConvertFrom(context, culture, value);
}
private static byte[] Decode(string code, CultureInfo culture)
{
var bytes = new byte[code.Length / 2];
for (var index = 0; index < bytes.Length; index++)
{
bytes[index] = byte.Parse(code.Substring(index * 2, 2), NumberStyles.HexNumber, culture);
}
return bytes;
}
}
using System.ComponentModel;
public class ProductionService : IService
{
private readonly byte[] token;
public ProductionService([TypeConverter(typeof(HexStringConverter))] byte[] token)
{
this.token = token;
}
}
また、この時の設定ファイルの記述は以下のようになります。
{
"defaultAssembly": "AutofacExample",
"components": [
{
"type": "AutofacExample.Services.ProductionService",
"services": [ { "type": "AutofacExample.Services.IService" } ],
"parameters": {
"token": "0123456789ABCDEF"
}
}
]
}
うさコメ
.NETでのDIというとGuice型のもので、コードを使ってコンテナの構築をするケースが多かったりするんじゃないかと思います(・ω・)
ただ、設定ファイルを使って環境によって使用するコンポーネントを切り替えたいみたいなニーズも当然あるわけで、AutofacとAutofac.Configurationを使うと簡単にできるよ、っというのが今回のお話でした。
そもそも、コードベースでのコンポーネントの構築も、規約ベースでのコンポーネントスキャンによる自動登録も、設定ファイルによるコンポーネントの登録も、用途に応じて組み合わせて使い分けるべきもので、どれかがあれば他はいらないとかそういう種のものじゃないと思いますのだ(´・ω・`)