web-dev-qa-db-ja.com

C#コンソールアプリケーションでのAutofacの正しい使用

私はAutofacを初めて使用するので、noobの質問に対する私の謝罪。 Autofac(または、Structuremap、Unityなどの他のツール)を使用するときの基本を説明するインターネットのすべてのマニュアルを読みました。しかし、私が見つけた例はすべて基本的なものです。コードにAutofacをより深く実装する方法を知る必要があります。この例であるコンソールアプリケーションで知っておくべきことを説明してみましょう。

class Program
{
    static void Main(string[] args)
    {
        var container = BuildContainer();
        var employeeService = container.Resolve<EmployeeService>();
        Employee employee = new Employee
        {
            EmployeeId = 1,
            FirstName = "Peter",
            LastName = "Parker",
            Designation = "Photographer"
        };

        employeeService.Print(employee);
    }

    static IContainer BuildContainer()
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<EmployeeRepository>().As<IEmployeeRepository>();
        builder.RegisterType<EmployeeService>();
        return builder.Build();
    }
}

これはシンプルで簡単です。私が理解しようとしているのは、コードを深く掘り下げたときにこれをどのように実装するかです。この例では、この行を実行すると

employeeService.Print(employee);

「Print」メソッドが少し複雑で、彼のタスクを実行するために別の依存関係/クラスを使用する必要があると仮定しましょう。まだAutofacを使用しているので、その依存関係を作成するには、上記の例のようなことをする必要があると思います。あれは正しいですか? 「print」メソッド内で、別のクラスを使用する必要がある場合は、別のコンテナを作成し、それを埋め、Resolve()などで使用する必要がありますか?それを行う簡単な方法はありますか?必要なすべての依存関係を持つ静的クラスをすべてのソリューションで使用できますか?どうやって?はっきりさせたい。どちらも必要なものを表現できないかもしれません。 :(英語が下手で申し訳ありません。Autofacを習っている間、まだ英語を勉強しています。

12

静電気が問題です

コンソールプログラムの主な問題は、メインのProgramクラスがほとんど静的であることです。これは単体テストには適しておらず、IoCにも適していません。たとえば、静的クラスが構築されることはないため、コンストラクター注入の可能性はありません。結果として、メインコードベースでnewを使用するか、IoCコンテナーからインスタンスをプルします。これは、パターンの違反です(これは サービスロケーターパターン)に似ています その時点で)。コードをインスタンスメソッドに配置する練習に戻ることで、この混乱から抜け出すことができます。つまり、何かのオブジェクトインスタンスが必要になります。しかし、何か。

2つのクラスのパターン

コンソールアプリを作成するときは、特定の軽量パターンに従います。私にとって非常にうまく機能するこのパターンに従うことを歓迎します。

パターンには2つのクラスが含まれます。

  1. 元のProgramクラスは静的で、非常に簡潔で、コードカバレッジから除外されています。このクラスは、O/S呼び出しから適切なアプリケーションの呼び出しへの「パススルー」として機能します。
  2. インスタンス化されたApplicationクラス。完全に注入され、単体テストが可能です。これは、実際のコードが存在する場所です。

プログラムクラス

O/SにはMainエントリポイントが必要であり、静的である必要があります。 Programクラスは、この要件を満たすためにのみ存在します。

静的プログラムを非常にクリーンに保ちます。これには、(1)コンポジションルートと、(2)実際のアプリケーションを呼び出す単純な「パススルー」エントリポイントが含まれている必要があります(これはインスタンス化されます)。

Programのコードはいずれも単体テストに値しません。これは、オブジェクトグラフを構成し(テスト中は異なる場合があります)、アプリケーションのメインエントリポイントを呼び出すためです。単体テスト不可能なコードを隔離することで、クラス全体をコードカバレッジから除外できます( ExcludeFromCodeCoverageAttribute を使用)。

次に例を示します。

[ExcludeFromCodeCoverage]
static class Program
{
    private static IContainer CompositionRoot()
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<Application>();
        builder.RegisterType<EmployeeService>().As<IEmployeeService>();
        builder.RegisterType<PrintService>().As<IPrintService>();
        return builder.Build();
    }

    public static void Main()  //Main entry point
    {
        CompositionRoot().Resolve<Application>().Run();
    }
}

ご覧のとおり、非常に単純です。

Applicationクラス

次に、Applicationクラスを、唯一無二のプログラムであるかのように実装します。現在、インスタンス化されているため、通常のパターンに従って依存関係を挿入できます。

class Application
{
    protected readonly IEmployeeService _employeeService;
    protected readonly IPrintService _printService;

    public Application(IEmployeeService employeeService, IPrintService printService)
    {
        _employeeService = employeeService; //Injected
        _printService = printService; //Injected
    }

    public void Run()
    {
        var employee = _employeeService.GetEmployee();
        _printService.Print(employee);
    }
}

このアプローチは、懸念事項の分離を維持し、あまりにも多くの静的な「もの」を回避し、あまり面倒なくIoCパターンに従うことができます。そして、お気づきでしょう-私のコード例には、ContainerBuilderをインスタンス化する場合を除いて、newキーワードの単一のインスタンスは含まれていません。

依存関係に独自の依存関係がある場合はどうなりますか?

このパターンに従っているため、PrintServiceまたはEmployeeServiceに独自の依存関係がある場合、コンテナーがすべてを処理します。これらのサービスをインジェクションするために、コンポジションルートの適切なインターフェースで登録する限り、コードをインスタンス化したり書き込んだりする必要はありません。

class EmployeeService : IEmployeeService
{
    protected readonly IPrintService _printService;

    public EmployeeService(IPrintService printService)
    {
        _printService = printService; //injected
    }

    public void Print(Employee employee)
    {
        _printService.Print(employee.ToString());
    }
}

このようにして、コンテナーがすべてを処理し、コードを記述する必要はありません。タイプとインターフェースを登録するだけです。

26
John Wu

コンストラクターを介して依存関係を注入することができます(Autofacはプロパティとメソッドの注入もサポートしています)。

通常、依存関係の登録が行われるときは、クラスをコンテナーに結合するため、クラス内でコンテナーを使用しないでください。特定のクラスを定義できる子コンテナー(内部スコープ)を使用したい場合があります。これにより、コードがコンテナから独立します。

あなたの例では、IEmployeeServiceを解決する必要があるだけで、そのすべての依存関係はコンテナによって自動的に解決されます。

これを実現する方法を示す例を次に示します。

using Autofac;
using System;
using System.Collections.Generic;
using System.Linq;

namespace AutofacExample
{
    public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    public interface IEmployeeRepository
    {
        Employee FindById(int id);
    }

    public interface IEmployeeService
    {
        void Print(int employeeId);
    }

    public class EmployeeRepository : IEmployeeRepository
    {
        private readonly List<Employee> _data = new List<Employee>()
        {
            new Employee { Id = 1, Name = "Employee 1"},
            new Employee { Id = 2, Name = "Employee 2"},
        };
        public Employee FindById(int id)
        {
            return _data.SingleOrDefault(e => e.Id == id);
        }
    }

    public class EmployeeService : IEmployeeService
    {
        private readonly IEmployeeRepository _repository;
        public EmployeeService(IEmployeeRepository repository)
        {
            _repository = repository;
        }
        public void Print(int employeeId)
        {
            var employee = _repository.FindById(employeeId);
            if (employee != null)
            {
                Console.WriteLine($"Id:{employee.Id}, Name:{employee.Name}");
            }
            else
            {
                Console.WriteLine($"Employee with Id:{employeeId} not found.");
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var container = BuildContainer();
            var employeeSerive = container.Resolve<IEmployeeService>();
            employeeSerive.Print(1);
            employeeSerive.Print(2);
            employeeSerive.Print(3);
            Console.ReadLine();
        }

        static IContainer BuildContainer()
        {
            var builder = new ContainerBuilder();
            builder.RegisterType<EmployeeRepository>()
                   .As<IEmployeeRepository>()
                   .InstancePerDependency();
            builder.RegisterType<EmployeeService>()
                   .As<IEmployeeService>()
                   .InstancePerDependency();
            return builder.Build();
        }
    }
}
0
HojjatK

EmployeeServiceクラスがあり、印刷するには他のクラスが必要だとします。

_public class EmployeeService 
{
    private readonly IEmployeeRepository _employeeRepository;
    private readonly IEmployeePrinter _printer;

    public EmployeeService(IEmployeeRepository employeeRepository, 
        IEmployeePrinter printer)
    {
        _employeeRepository = employeeRepository;
        _printer = printer;
    }
    public void PrintEmployee(Employee employee)
    {
        _printer.PrintEmployee(employee);
    }
}
_

そして、あなたはIEmployeePrinterの実装を持っていて、さらに多くの依存関係があります:

_public class EmployeePrinter : IEmployeePrinter
{
    private readonly IEmployeePrintFormatter _printFormatter;

    public EmployeePrinter(IEmployeePrintFormatter printFormatter)
    {
        _printFormatter = printFormatter;
    }

    public void PrintEmployee(Employee employee)
    {
        throw new NotImplementedException();
    }
}
_

これ以上のコンテナは必要ありません。あなたがしなければならないのは、あなたがやったのとほとんど同じように、1つのコンテナに各タイプを登録することです:

_static IContainer BuildContainer()
{
    var builder = new ContainerBuilder();
    builder.RegisterType<EmployeeRepository>().As<IEmployeeRepository>();
    builder.RegisterType<EmployeePrinter>().As<IEmployeePrinter>();
    builder.RegisterType<SomeEmployeeFormatter>().As<IEmployeePrintFormatter>();
    builder.RegisterType<EmployeeService>();
    return builder.Build();
}
_

Resolve<EmployeeService>()を呼び出すと、IEmployeeRepositoryIEmployeePrinterが必要であることがわかります。そのため、舞台裏ではResolve<IEmployeeRepository>()およびResolve<IEmployeePrinter>()を呼び出します。次に、EmployeePrinterにはIEmployeePrintFormatterが必要であることを認識しているので、それも解決します。

解決する必要があるすべてを登録している限り、機能します。開発を継続的にテストしやすい小さなクラスに分割できるので、すばらしいことです。これにより、次のように作成しなければならない場合、操作が非常に難しいネストされたクラスの束が作成されます。

_var service = new EmployeeService(
    new EmployeeRespository("connectionString"),
    new EmployeePrinter(new SomeEmployeeformatter()));
_

しかし、コンテナはそれを行うので、たとえそれらが多くのレベルにネストされたとしても、それらのクラスのすべてを作成することを心配する必要はありません。

0
Scott Hannen

起動時にすべての依存関係を登録し、後でそれらを解決できるという考え方です。あなたはほとんどそこにいるように見えますが、いくつかの変更点があります:

_class Program
{
    // Declare your container as a static variable so it can be referenced later
    static IContainer Container { get; set; }

    static void Main(string[] args)
    {
        // Assign the container to the static IContainer
        Container = BuildContainer();
        var employeeService = container.Resolve<EmployeeService>();
        Employee employee = new Employee
        {
            EmployeeId = 1,
            FirstName = "Peter",
            LastName = "Parker",
            Designation = "Photographer"
        };

        employeeService.Print(employee);
    }

    static IContainer BuildContainer()
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<EmployeeRepository>().As<IEmployeeRepository>();
        builder.RegisterType<EmployeeService>();
        return builder.Build();
    }
}
_

その後、後で解決できます。 employeeService.Print()関数内:

_public void Print(Employee employee)
{
        // Create the scope, resolve your EmployeeRepository,
        // use it, then dispose of the scope.
        using (var scope = Container.BeginLifetimeScope())
        {
            var repository = scope.Resolve<IEmployeeRepository>();
            repository.Update(employee);
        }
}
_

これは 公式の入門ガイド からのコードの(コードに合うように)少し変更したものです==

0
Kirlac