依存性の注入 (Dependency Injection) で同一インタフェースで複数実装サービスの解決方法を紹介します。

これまでASP.NET MVC/Web APIプロジェクトをゼロから作る案件があった場合、必ず最初に入れるのはIoCコンテナでしたが、ASP.NET CoreはデフォルトでIoCコンテナをサポートされていますので、IoCコンテナを入れる作業が任意になりました。もちろん用途によりAutofacやSimple Injector等、好みのIoCコンテナに変更することも可能です。
本投稿では一つのインタフェースで複数実装するサービス(クラス)がある場合、どういった解決方法があるのかをいくつか紹介したいと思います。
サンプルとしてインタフェースとその実装するサービスは下記を使用します。
public interface IPoopService | |
{ | |
string Poop(); | |
} | |
public class FancyPoopService : IPoopService | |
{ | |
public string Poop() => nameof(FancyPoopService); | |
} | |
public class CoolPoopService : IPoopService | |
{ | |
public string Poop() => nameof(CoolPoopService); | |
} | |
public class StandardPoopService : IPoopService | |
{ | |
public string Poop() => nameof(StandardPoopService); | |
} | |
public class PremiumPoopService : IPoopService | |
{ | |
public string Poop() => nameof(PremiumPoopService); | |
} | |
// -- Startup -- | |
// This method gets called by the runtime. Use this method to add services to the container. | |
public void ConfigureServices(IServiceCollection services) | |
{ | |
// ... | |
services.AddTransient<IPoopService, FancyPoopService>(); | |
services.AddTransient<IPoopService, CoolPoopService>(); | |
services.AddTransient<IPoopService, StandardPoopService>(); | |
services.AddTransient<IPoopService, PremiumPoopService>(); | |
// ... | |
} |
サービスの解決方法を見ていきましょう。
■パターン①
IServiceProvider を使用します。
public class FancyPoopController : Controller | |
{ | |
private readonly IPoopService _poopService; | |
public FancyPoopController(IServiceProvider serviceProvider) | |
=> _poopService = serviceProvider.GetServices<IPoopService>().OfType<FancyPoopService>().First(); | |
public string Index() => _poopService.Poop(); | |
} |
■パターン②
IEnumerable<T> を使用します。
public class CoolPoopController : Controller | |
{ | |
private readonly IPoopService _poopService; | |
public CoolPoopController(IEnumerable<IPoopService> poopService) | |
=> _poopService = poopService.OfType<CoolPoopService>().First(); | |
public string Index() => _poopService.Poop(); | |
} |
■パターン③
ファクトリーパターンを使用します。
public class StandardPoopController : Controller | |
{ | |
private readonly IPoopService _poopService; | |
public StandardPoopController(IPoopServiceFactory poopServiceFactory) | |
=> _poopService = poopServiceFactory.GetByName(nameof(StandardPoopService)); | |
public string Index() => _poopService.Poop(); | |
} |
public interface IPoopServiceFactory | |
{ | |
IPoopService GetByName(string name); | |
} | |
public class PoopServiceFactory : IPoopServiceFactory | |
{ | |
private readonly IServiceProvider _serviceProvider; | |
public PoopServiceFactory(IServiceProvider serviceProvider) | |
=> _serviceProvider = serviceProvider; | |
public IPoopService GetByName(string name) | |
{ | |
switch (name) | |
{ | |
case nameof(FancyPoopService): | |
return _serviceProvider.GetRequiredService<FancyPoopService>(); | |
case nameof(CoolPoopService): | |
return _serviceProvider.GetRequiredService<CoolPoopService>(); | |
case nameof(StandardPoopService): | |
return _serviceProvider.GetRequiredService<StandardPoopService>(); | |
case nameof(PremiumPoopService): | |
return _serviceProvider.GetRequiredService<PremiumPoopService>(); | |
default: | |
throw new ArgumentException("Oh no!"); | |
} | |
} | |
} |
■パターン④
Func<string, T> を使用します。
Startup クラスの ConfigureServices にはこんな感じで登録します。
// This method gets called by the runtime. Use this method to add services to the container. | |
public void ConfigureServices(IServiceCollection services) | |
{ | |
// .. | |
services.AddTransient<FancyPoopService>(); | |
services.AddTransient<CoolPoopService>(); | |
services.AddTransient<StandardPoopService>(); | |
services.AddTransient<PremiumPoopService>(); | |
services.AddTransient<Func<string, IPoopService>>(serviceProvider => name => | |
{ | |
switch (name) | |
{ | |
case nameof(FancyPoopService): | |
return serviceProvider.GetRequiredService<FancyPoopService>(); | |
case nameof(CoolPoopService): | |
return serviceProvider.GetRequiredService<CoolPoopService>(); | |
case nameof(StandardPoopService): | |
return serviceProvider.GetRequiredService<StandardPoopService>(); | |
case nameof(PremiumPoopService): | |
return serviceProvider.GetRequiredService<PremiumPoopService>(); | |
default: | |
throw new ArgumentException("Oh no!"); | |
} | |
}); | |
// .. | |
} |
■パターン⑤
正式名称わかりませんが、中間インタフェースを使用します。
public interface IFancyPoopService : IPoopService { } | |
public interface ICoolPoopService : IPoopService { } | |
public interface IStandardPoopService : IPoopService { } | |
public interface IPremiumPoopService : IPoopService { } | |
public class AnotherFancyPoopService : IFancyPoopService | |
{ | |
public string Poop() => nameof(AnotherFancyPoopService); | |
} | |
public class AnotherCoolPoopService : ICoolPoopService | |
{ | |
public string Poop() => nameof(AnotherCoolPoopService); | |
} | |
public class AnotherStandardPoopService : IStandardPoopService | |
{ | |
public string Poop() => nameof(AnotherStandardPoopService); | |
} | |
public class AnotherPremiumPoopService : IPremiumPoopService | |
{ | |
public string Poop() => nameof(AnotherPremiumPoopService); | |
} | |
// -- Startup -- | |
services.AddTransient<IFancyPoopService, AnotherFancyPoopService>(); | |
services.AddTransient<ICoolPoopService, AnotherCoolPoopService>(); | |
services.AddTransient<IStandardPoopService, AnotherStandardPoopService>(); | |
services.AddTransient<IPremiumPoopService, AnotherPremiumPoopService>(); | |
// -- Controller -- | |
public class AnotherFancyPoopController : Controller | |
{ | |
private readonly IPoopService _poopService; | |
public AnotherFancyPoopController(IFancyPoopService fancyPoopService) | |
=> _poopService = fancyPoopService; | |
public string Index() => _poopService.Poop(); | |
} |
■パターン⑥
IPoopService<out T> パターンを使用します。
public interface IPoopService | |
{ | |
string Poop(); | |
} | |
public interface IPoopService<out T> : IPoopService { } | |
public class FancyPoopService : IPoopService<FancyPoopService> | |
{ | |
public string Poop() => nameof(FancyPoopService); | |
} | |
public class CoolPoopService : IPoopService<CoolPoopService> | |
{ | |
public string Poop() => nameof(CoolPoopService); | |
} | |
public class StandardPoopService : IPoopService<StandardPoopService> | |
{ | |
public string Poop() => nameof(StandardPoopService); | |
} | |
public class PremiumPoopService : IPoopService<PremiumPoopService> | |
{ | |
public string Poop() => nameof(PremiumPoopService); | |
} | |
// -- Startup -- | |
// This method gets called by the runtime. Use this method to add services to the container. | |
public void ConfigureServices(IServiceCollection services) | |
{ | |
// ... | |
services.AddTransient<IPoopService<FancyPoopService>, FancyPoopService>(); | |
services.AddTransient<IPoopService<CoolPoopService>, CoolPoopService>(); | |
services.AddTransient<IPoopService<StandardPoopService>, StandardPoopService>(); | |
services.AddTransient<IPoopService<PremiumPoopService>, PremiumPoopService>(); | |
// ... | |
} | |
// -- Controller -- | |
public class YetAnotherFancyPoopController : Controller | |
{ | |
private readonly IPoopService _poopService; | |
public YetAnotherFancyPoopController(IPoopService<FancyPoopService> fancyPoopService) | |
=> _poopService = fancyPoopService; | |
public string Index() => _poopService.Poop(); | |
} |
このようにいくつかパターンがありますので、必要な時に是非思い出して活用してみてください。
Top comments (0)