【问题标题】:Correct use of Autofac in C# console application [duplicate]在 C# 控制台应用程序中正确使用 Autofac [重复]
【发布时间】:2018-01-24 00:30:14
【问题描述】:

我是使用 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);

让我们假设“打印”方法有点复杂,需要使用另一个依赖项/类来完成他的任务。我们仍在使用 Autofac,所以我想我们需要像上面的示例一样创建依赖项。那是对的吗?在我的“打印”方法中,当我需要使用另一个类时,我必须创建另一个容器,填充它,将它与 Resolve() 一起使用等等?有更简单的方法吗?可以在所有解决方案中使用具有所有所需依赖项的静态类?如何? 我希望清楚。也许我都无法表达我的需要。 :( 对不起我的英语不好。我在学习 Autofac 的同时还在学习它。

【问题讨论】:

  • 看起来你已经走上正轨了。你的EmployeeService 需要IEmployeeRepository,所以你注册了它。如果EmployeeService 依赖于注入到其构造函数中的其他内容,那么您只需将其注册到同一个容器以及您已经拥有的两个注册。当您解析EmployeeService 时,容器将解析它需要的任何内容,即使这些类具有依赖关系,这些类也是如此,等等,只要这些类型都已注册。这就是它如此棒的原因。
  • @NightOwl888 我的问题比这更进一步。我需要知道当你必须在依赖于 Main() 的子进程中创建依赖项时会发生什么。
  • 你没有。 DI 容器就是这样做的。您只需使用 autofac 注册它们,它就会一次性解析应用程序的整个对象图
  • @SebastianGarcia - 我更新了“重复的”答案,使其更清楚它是如何工作的。在您的情况下,您将在ApplicationLogic 构造函数中接受EmployeeService,并将Employee 的创建和对employeeService.Print(employee) 的调用移至ApplicationLogic.Run() 方法。当然,注入接口而不是具体类型(如下面的答案中所指出的)也是很好的建议。
  • @NightOwl888 谢谢。现在您指出我的解决方案更加清晰,我可以更好地理解它。再次感谢!

标签: c# dependency-injection inversion-of-control autofac


【解决方案1】:

静态是问题

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

二分类模式

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

该模式涉及两个类:

  1. 原始的Program 类,它是静态的,非常简短,并且被排除在代码覆盖范围之外。此类充当从 O/S 调用到应用程序调用的“传递”。
  2. 一个实例化的Application 类,它是完全注入和单元测试的。这是您真正的代码应该存在的地方。

程序类

操作系统需要一个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 类,就好像它是唯一的程序一样。只是现在,因为它是实例化的,所以您可以按照通常的模式注入依赖项。

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 模式而无需太多麻烦。您会注意到——我的代码示例不包含new 关键字的单个实例,除了实例化ContainerBuilder。

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

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

class EmployeeService : IEmployeeService
{
    protected readonly IPrintService _printService;

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

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

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

【讨论】:

  • 谢谢。这告诉我我必须如何去做。再次感谢您的宝贵时间和精彩的解释。
  • @SebastianGarcia,您似乎需要通过单击勾选将答案标记为真。谢谢约翰。
【解决方案2】:

您可以通过构造函数使用注入依赖项(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();
        }
    }
}

【讨论】:

    【解决方案3】:

    这个想法是您在启动时注册所有依赖项,然后您可以稍后解决它们。你看起来就快到了,只是做了一些改变:

    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中的代码(以适合您的代码)的轻微改编

    【讨论】:

      【解决方案4】:

      假设您有 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();
          }
      }
      

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

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

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

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

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

      【讨论】:

        猜你喜欢
        • 2014-02-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-07-11
        • 1970-01-01
        • 1970-01-01
        • 2021-10-03
        相关资源
        最近更新 更多