在C#控制台应用程序

时间:2018-01-24 00:30:14

标签: c# dependency-injection inversion-of-control 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时仍然在学习它。

4 个答案:

答案 0 :(得分:16)

静态是问题

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

两级模式

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

该模式涉及两个类:

  1. 原始Program类,它是静态的,非常简短,并且从代码覆盖范围中排除。这个班作为一个&#34;传递&#34;从O / S调用到适当的应用程序调用。
  2. 实例Application类,完全注入并可单元测试。这是您的真实代码应该存在的地方。
  3. 计划类

    O / S需要一个Main入口点,它必须是静态的。 Program类仅存在以满足此要求。

    保持静态程序非常干净;它应该包含(1)组成根和(2)简单的,&#34;传递&#34;调用真实应用程序的入口点(正如我们将看到的那样是实例化的)。

    Program中的所有代码都不值得进行单元测试,因为它所做的只是组成对象图(无论如何在测试时会有所不同)并调用应用程序的主入口点。通过隔离非单元可测试代码,您现在可以从代码覆盖中排除整个类(使用ExcludeFromCodeCoverageAttribute)。

    以下是一个例子:

    [ExcludeFromCodeCoverage]
    static class Program
    {
        static private IContainer CompositionRoot()
        {
            var builder = new ContainerBuilder();
            builder.RegisterType<Application>();
            builder.RegisterType<EmployeeService>().As<IEmployeeService>();
            builder.RegisterType<PrintService>().As<IPrintService>();
            return builder.Build();
        }
    
        static public void Main()  //Main entry point
        {
            CompositionRoot().Resolve<Application>().Run();
        }
    }
    

    如你所见,非常简单。

    应用程序类

    现在实施您的Application课程,就好像它是One and Only计划一样。只有现在,因为它是实例化的,你可以按照通常的模式注入依赖项。

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

    这种方法可以保持关注点的分离,避免太多静态的东西,&#34;并且让您无需太多麻烦即可遵循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());
        }
    }
    

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

答案 1 :(得分:0)

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

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

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

答案 2 :(得分:0)

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

假设您拥有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<EmployeeService>()时,会看到它需要IEmployeeRepositoryIEmployeePrinter。因此,在幕后,它会调用Resolve<IEmployeeRepository>()Resolve<IEmployeePrinter>()。然后,它会发现EmployeePrinter需要IEmployeePrintFormatter,因此它也会解析它。

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

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

但容器使得你不必担心创建所有这些类,即使它们嵌套了很多级别。