在 C# 控制台应用程序中正确使用 Autofac

Correct use of Autofac in C# console application

我是 Autofac 的新手,所以对于菜鸟问题​​我深表歉意。 我阅读了 Internet 上的每本手册,解释了使用 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" 方法有点复杂,需要使用另一个 dependencies/classes 来完成他的任务。我们仍在使用 Autofac,所以我想我们需要像上面的示例那样做一些事情来创建依赖项。那是对的吗?在我的 "print" 方法中,当我需要使用另一个 class 时,我必须创建另一个容器,填充它,将它与 Resolve() 一起使用等等?有更简单的方法吗?可以在所有解决方案中使用具有所有所需依赖项的静态 class 吗?如何? 我希望清楚。也许我也不能表达我的需要。 :( 对不起我糟糕的英语。学Autofac的时候还在学

想法是您在启动时注册所有依赖项,然后您可以稍后解决它们。你看起来差不多了,只是一些变化:

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

这是对 official getting started guide

中代码的轻微改编(以适合您的代码)

您可以通过构造函数使用注入依赖项(Autofac 还支持 属性 和方法注入)。

通常当依赖注册完成后,你不应该在 classes 中使用容器,因为它使你的 class 耦合到容器,可能有一些情况你想使用子容器(内部范围),您可以在其中定义一个特定的 class 来执行此操作并使您的代码独立于容器。

在您的示例中,您只需要解析 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();
        }
    }
}

假设您有 EmployeeService class,它需要其他 class 才能打印:

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

您不需要更多容器。您所要做的就是将每种类型注册到一个容器中,就像您所做的一样:

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>() 时,它会发现它需要一个 IEmployeeRepository 和一个 IEmployeePrinter。所以在幕后它会调用 Resolve<IEmployeeRepository>()Resolve<IEmployeePrinter>()。然后它看到 EmployeePrinter 需要一个 IEmployeePrintFormatter,所以它也解决了这个问题。

只要您注册了所有需要解决的问题,它就会起作用。这很棒,因为它允许您不断地将开发分解为更小的 class 易于测试的部分。这将导致一堆嵌套的 classes,如果您必须像这样创建它们,将很难使用它们:

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

但容器使您不必担心创建所有这些 classes,即使它们嵌套了很多层。

静态是问题所在

控制台程序的主要问题是主要 Program class 大部分是静态的。这不利于单元测试,也不利于 IoC;例如,一个 static class 永远不会被构造,所以没有构造函数注入的机会。结果,您最终在主代码库中使用 new,或者从 IoC 容器中提取实例,这违反了模式(此时更像是 service locator pattern)。我们可以通过回到将代码放在实例方法中的实践来摆脱这种混乱,这意味着我们需要某个对象的实例。但是什么东西?

两个class模式

我在编写控制台应用程序时遵循一种特定的轻量级模式。欢迎您遵循这种对我来说非常有效的模式。

该模式涉及两个 classes:

  1. 原来的Programclass,是静态的,非常简短,排除在代码覆盖范围之外。此 class 充当 "pass through" 从 O/S 调用到应用程序本身的调用。
  2. 一个实例化的 Application class,它是完全注入和单元测试的。这是您的真实代码应该存在的地方。

计划Class

O/S 需要一个 Main 入口点,并且它必须是静态的。 Programclass就是为了满足这个要求而存在的

保持你的静态程序非常干净;它应该包含 (1) 组合根和 (2) 一个简单的 "pass-through" 调用实际应用程序的入口点(如我们将看到的那样,它是实例化的)。

None Program 中的代码值得进行单元测试,因为它所做的只是组成对象图(无论如何在测试时都会有所不同)并调用主入口点为申请。通过隔离不可单元测试的代码,您现在可以从代码覆盖范围中排除整个 class(使用 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();
    }
}

如你所见,非常简单。

应用程序class

现在实施您的 Application class,就像它是唯一的计划一样。只是现在,因为它是实例化的,所以您可以按照通常的模式注入依赖项。

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

这种方法保持了关注点的分离,避免了过多的静态 "stuff," 并让您遵循 IoC 模式而无需太多麻烦。您会注意到——除了实例化 ContainerBuilder 之外,我的代码示例不包含 new 关键字的单个实例。

如果依赖项有自己的依赖项怎么办?

因为我们遵循这种模式,如果 PrintServiceEmployeeService 有它们自己的依赖项,容器现在会处理这一切。您不必实例化或编写任何代码来注入这些服务,只要您在组合根中的适当接口下注册它们即可。

class EmployeeService : IEmployeeService
{
    protected readonly IPrintService _printService;

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

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

这样容器会处理所有事情,您无需编写任何代码,只需注册您的类型和接口。