DEV Community

Joni 【ジョニー】
Joni 【ジョニー】

Posted on • Originally published at Medium on

ASP.NET Core 依存性の注入 (DI):同一インタフェース複数実装サービスの解決方法

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

一つのインタフェースで複数実装するサービス例

これまでASP.NET MVC/Web APIプロジェクトをゼロから作る案件があった場合、必ず最初に入れるのはIoCコンテナでしたが、ASP.NET CoreはデフォルトでIoCコンテナをサポートされていますので、IoCコンテナを入れる作業が任意になりました。もちろん用途によりAutofacSimple 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>();
// ...
}
view raw PoopServices.cs hosted with ❤ by GitHub

サービスの解決方法を見ていきましょう。

■パターン①

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> を使用します。

public class PremiumPoopController : Controller
{
private readonly IPoopService _poopService;
public PremiumPoopController(Func<string, IPoopService> poopServiceFactory)
=> _poopService = poopServiceFactory(nameof(PremiumPoopService));
public string Index() => _poopService.Poop();
}

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();
}

このようにいくつかパターンがありますので、必要な時に是非思い出して活用してみてください。

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay