我是新手使用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时仍然在学习它。
答案 0 :(得分:16)
控制台程序的主要问题是主Program
类主要是静态的。这不适合单元测试,对IoC也不利;例如,永远不会构造静态类,因此没有构造函数注入的机会。因此,您最终在主代码库中使用new
,或者从IoC容器中提取实例,这违反了模式(它更多的是service locator pattern点)。我们可以通过回到将代码放在实例方法中的实践来摆脱这种混乱,这意味着我们需要一个对象实例。但是什么呢?
在编写控制台应用程序时,我遵循特定的轻量级模式。欢迎您使用这种对我来说非常好的模式。
该模式涉及两个类:
Program
类,它是静态的,非常简短,并且从代码覆盖范围中排除。这个班作为一个&#34;传递&#34;从O / S调用到适当的应用程序调用。Application
类,完全注入并可单元测试。这是您的真实代码应该存在的地方。 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。
因为我们遵循这种模式,如果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());
}
}
这样容器可以处理所有事情,您不必编写任何代码,只需注册您的类型和接口。
答案 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);
}
}
的代码(以适合您的代码)的轻微修改
答案 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>()
时,会看到它需要IEmployeeRepository
和IEmployeePrinter
。因此,在幕后,它会调用Resolve<IEmployeeRepository>()
和Resolve<IEmployeePrinter>()
。然后,它会发现EmployeePrinter
需要IEmployeePrintFormatter
,因此它也会解析它。
只要您注册了需要解决的所有内容,它就会起作用。这很棒,因为它允许您不断将开发分解为易于测试的较小类。这将导致一堆嵌套类,如果你不得不像这样创建它们,那将非常难以使用:
var service = new EmployeeService(
new EmployeeRespository("connectionString"),
new EmployeePrinter(new SomeEmployeeformatter()));
但容器使得你不必担心创建所有这些类,即使它们嵌套了很多级别。