在 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);
}
}
中代码的轻微改编(以适合您的代码)
您可以通过构造函数使用注入依赖项(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:
- 原来的
Program
class,是静态的,非常简短,排除在代码覆盖范围之外。此 class 充当 "pass through" 从 O/S 调用到应用程序本身的调用。
- 一个实例化的
Application
class,它是完全注入和单元测试的。这是您的真实代码应该存在的地方。
计划Class
O/S 需要一个 Main
入口点,并且它必须是静态的。 Program
class就是为了满足这个要求而存在的
保持你的静态程序非常干净;它应该包含 (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
关键字的单个实例。
如果依赖项有自己的依赖项怎么办?
因为我们遵循这种模式,如果 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());
}
}
这样容器会处理所有事情,您无需编写任何代码,只需注册您的类型和接口。
我是 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);
}
}
中代码的轻微改编(以适合您的代码)
您可以通过构造函数使用注入依赖项(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:
- 原来的
Program
class,是静态的,非常简短,排除在代码覆盖范围之外。此 class 充当 "pass through" 从 O/S 调用到应用程序本身的调用。 - 一个实例化的
Application
class,它是完全注入和单元测试的。这是您的真实代码应该存在的地方。
计划Class
O/S 需要一个 Main
入口点,并且它必须是静态的。 Program
class就是为了满足这个要求而存在的
保持你的静态程序非常干净;它应该包含 (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
关键字的单个实例。
如果依赖项有自己的依赖项怎么办?
因为我们遵循这种模式,如果 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());
}
}
这样容器会处理所有事情,您无需编写任何代码,只需注册您的类型和接口。