C#での依存関係の挿入は、依存関係を整理するとき、特により複雑な ASP.NET Core アプリケーションの場合に救世主となります。すでにIServiceCollection に精通している場合、またはすでに提供されている DI 製品にできるだけ近づけたいと考えている場合、 C# の Scrutor は優れた機能強化です。
この記事では、C# の依存関係の挿入と Scrutor の概要を説明します。しかし、そこから、Scrutor で使用でき、アプリケーションで非常に役立つ可能性がある 3 つのヒントに進みます。
Scrutor は、依存関係の注入を強化する C# の強力な NuGet パッケージです。依存関係の登録と解決が簡素化され、コードの管理と整理が容易になります。
依存関係の注入は、疎結合を促進し、テスト容易性と保守容易性を向上させる、アプリケーションを構成するための一般的なアプローチです。これには、依存関係をクラス内に直接作成するのではなく、クラスに依存関係を注入することが含まれます。これは、依存関係を作成する責任は、それを必要とするクラスが所有するのではなく、コール スタック上の誰かが所有することを意味します。最終的には、ほぼすべての依存関係の作成がアプリケーションのエントリ ポイントに押し込まれ、扱いにくくなります。ただし、依存関係注入フレームワークは、 このロジックすべてをクリーンアップして整理するのに役立ちます。
Scrutor は、依存関係を登録および解決するためのシンプルかつ直感的な方法を提供することで、この概念をさらに一歩進めています。 Scrutor を使用すると、依存関係を 1 つずつ手動で登録する必要がなくなりました。代わりに、規則と属性を使用してプロセスを自動化できます。
C# で Scrutor を使用して登録と解決を行う方法を説明するために、簡単なシナリオを考えてみましょう。 UserRepository や ProductRepostiory など、さまざまなデータ リポジトリの使用を必要とするアプリケーションがあると想像してください。
まず、Scrutor NuGet パッケージをプロジェクトにインストールする必要があります。パッケージ マネージャー コンソールを開き、次のコマンドを実行します。
Install-Package Scrutor
次に、リポジトリとそれに対応するインターフェイスを定義する必要があります。この例は本質的には空ですが、これらについては後ほど参照します。
public interface IUserRepository { // Interface methods } public class UserRepository : IUserRepository { // Implementation } public interface IProductRepository { // Interface methods } public class ProductRepository : IProductRepository { // Implementation }
次に、Scrutor を使用して依存関係を結び付けてみましょう。スタートアップ コード (ASP.NET Core の ConfigureServices メソッドなど) に、次のコードを追加します。
services.Scan(scan => scan .FromAssemblyOf<Startup>() .AddClasses(classes => classes.AssignableToAny( typeof(IUserRepository), typeof(IProductRepository))) .AsImplementedInterfaces() .WithScopedLifetime());
このコードは、Scrutor の Scan メソッドを使用して、Startup クラスを含むアセンブリをスキャンします。次に、IUserRepository インターフェイスまたは IProductRepository インターフェイスに割り当て可能なクラスをフィルターで除外します。最後に、これらのインターフェイスの実装をマップし、それぞれのインターフェイスに登録します。
これで、これらの依存関係をクラスに注入できるようになりました。たとえば、IUserRepository を必要とする UserService クラスがあるとします。
public class UserService { private readonly IUserRepository _userRepository; public UserService(IUserRepository userRepository) { _userRepository = userRepository; } // Rest of the class implementation }
コンストラクターで IUserRepository を依存関係として宣言すると、Scrutor と IServiceCollection が自動的に解決し、UserRepository インスタンスを挿入します。
サービス インターフェイスIService
と実装MyService
があると想像してください。 MyService
の問題でビジネス ロジックを汚染することなく、MyService の各メソッド呼び出しの開始と終了をログに記録したいと考えています。
まず、 IService
インターフェイスとその実装を定義します。
public interface IService { void DoWork(); } public class MyService : IService { public void DoWork() { // Business logic here } }
次に、 IService
を実装するデコレータ クラスLoggingDecorator
を作成します。このクラスは実際のサービスをラップし、委任されたメソッド呼び出しの周囲にロギングを追加します。
public class LoggingDecorator : IService { private readonly IService _decorated; private readonly ILogger<LoggingDecorator> _logger; public LoggingDecorator(IService decorated, ILogger<LoggingDecorator> logger) { _decorated = decorated; _logger = logger; } public void DoWork() { _logger.LogInformation("Starting work."); _decorated.DoWork(); _logger.LogInformation("Finished work."); } }
ここで、Scrutor を使用して、サービスとそのデコレーターをStartup.cs
またはサービスを構成する場所に登録します。
public void ConfigureServices(IServiceCollection services) { // Register the base service services.AddScoped<IService, MyService>(); // Use Scrutor to apply the decorator services.Decorate<IService, LoggingDecorator>(); // Make sure to register the ILogger or ILogger<T> dependencies if not already done }
このセットアップでは、Scrutor を使用してIService
登録をLoggingDecorator
でラップします。 IService
を解決するとき、DI コンテナはLoggingDecorator
のインスタンスを提供し、それがMyService
のインスタンスをラップします。このアプローチでは、ロギング ロジックをビジネス ロジックの外部に保持することで、懸念事項の分離を実現します。
複雑なシステムを構築していると、機能フラグのようなものを導入したい場合がよくあります。これらにより、構成に基づいて異なるコード パスを選択できるため、アプリケーション全体を再コンパイルしてデプロイする必要がなくなります。状況によっては、単に別のコード パスを選択するだけではなく、何かの実装全体を交換することも必要になります。
一緒に例を試してみましょう! NewFeatureService
(新しい実験的な機能) とStandardFeatureService
(現在の安定した機能) という複数の実装を備えたIFeatureService
インターフェイスがあるとします。構成フラグに基づいて、実行時にこれらの実装を切り替える必要があります。
まず、 IFeatureService
インターフェイスとその実装を定義します。
public interface IFeatureService { void ExecuteFeature(); } public class NewFeatureService : IFeatureService { public void ExecuteFeature() { // New feature logic } } public class StandardFeatureService : IFeatureService { public void ExecuteFeature() { // Standard feature logic } }
次に、機能の切り替え設定に基づいて、どの実装を使用するかを決定する方法が必要です。これは、 appsettings.json
、データベース設定、またはその他の構成ソースの値である可能性があります。
次に、Scrutor を使用して、機能の切り替えに基づいて適切なサービス実装を動的に登録します。
public void ConfigureServices(IServiceCollection services) { // Assume GetFeatureToggleValue() retrieves the current feature toggle setting var useNewFeature = GetFeatureToggleValue("UseNewFeature"); // Dynamically register the appropriate implementation based on the feature toggle if (useNewFeature) { services.AddScoped<IFeatureService, NewFeatureService>(); } else { services.AddScoped<IFeatureService, StandardFeatureService>(); } // Optional: You could combine this with the previous example // to use the decorator pattern too! // services.Decorate<IFeatureService, FeatureServiceProxy>(); } private bool GetFeatureToggleValue(string featureName) { // This method would typically check your // configuration source to determine if the feature is enabled // For simplicity, this is just a placeholder return false; // or true based on actual configuration }
このようなアプローチを採用すると、次のことに役立ちます。
この記事では、C# で Scrutor を使用した依存関係注入に関する 3 つの簡単なヒントを提供しました。依存関係注入とは何か、Scrutor が依存関係注入にどのように適合するのかについて簡単に説明した後、早速例に移ります。アセンブリのスキャン、依存関係を修飾する方法、さらには実装全体に機能フラグを付ける方法を検討する方法についても学びました。
依存関係の注入は、複雑さが増すアプリケーションを大幅に簡素化できるものであり、 Microsoft が提供する組み込みの IServiceCollectionを使い続けたい場合には、Scrutor が非常に役立ちます。これが役立つと思われ、さらに学習の機会を探している場合は、無料の毎週のソフトウェア エンジニアリング ニュースレターの購読を検討し、 YouTube で私の無料ビデオをチェックしてください。私や他のコミュニティメンバーと一緒に Discord に参加してください!