跳到主要内容

Autofac 常用使用说明

Autofac 是一个功能更强大的第三方 IoC 容器,在 ASP.NET Core 自带 DI 的基础上,补上了程序集扫描、Module等更灵活的能力。

本文以 Autofac 9.x 和普通 .NET 应用为例,介绍 Autofac 使用方式。适用于控制台程序、桌面应用、后台任务、Windows Service 以及其他 .NET 应用。

Autofac 也可以在 ASP.NET Core 中使用,参考 ASP.NET Core 集成官方文档

1. Autofac 是什么

Autofac 是一个 .NET IoC(控制反转)容器,主要负责:

  • 创建对象。
  • 自动注入对象依赖。
  • 管理对象生命周期。
  • 自动释放 IDisposableIAsyncDisposable 对象。
  • 在接口存在多个实现时选择合适的实现。
  • 通过 Module、程序集扫描等方式组织服务注册。

Autofac 的基本使用流程是:

创建 ContainerBuilder

注册服务

调用 Build() 创建容器

创建 LifetimeScope

解析应用入口对象

使用结束后释放 Scope

2. 安装 Autofac

在项目目录执行:

dotnet add package Autofac

或者在项目文件中添加:

<ItemGroup>
<PackageReference Include="Autofac" Version="9.*" />
</ItemGroup>

3. 一个完整的入门示例

3.1 定义服务接口

public interface IMessageWriter
{
void Write(string message);
}

3.2 编写实现类

public sealed class ConsoleMessageWriter : IMessageWriter
{
public void Write(string message)
{
Console.WriteLine(message);
}
}

3.3 编写应用入口类

public sealed class Application
{
private readonly IMessageWriter _messageWriter;

public Application(IMessageWriter messageWriter)
{
_messageWriter = messageWriter;
}

public void Run()
{
_messageWriter.Write("Hello Autofac!");
}
}

3.4 创建容器并运行程序

using Autofac;

var builder = new ContainerBuilder();

// 注册接口与实现
builder.RegisterType<ConsoleMessageWriter>()
.As<IMessageWriter>();

// 注册应用入口
builder.RegisterType<Application>();

// 构建容器
using var container = builder.Build();

// 创建一次工作作用域
using var scope = container.BeginLifetimeScope();

// 只在应用入口处主动解析对象
var application = scope.Resolve<Application>();

application.Run();

当 Autofac 创建 Application 时,会发现它需要 IMessageWriter,于是自动创建 ConsoleMessageWriter 并传入构造函数。

4. 常用注册方式

4.1 按接口注册

builder.RegisterType<ProductService>()
.As<IProductService>();

解析:

var service = scope.Resolve<IProductService>();

4.2 按自身类型注册

builder.RegisterType<ProductService>()
.AsSelf();

AsSelf() 可以省略,因为默认就是按自身类型注册:

builder.RegisterType<ProductService>();

解析:

var service = scope.Resolve<ProductService>();

4.3 同时注册接口和自身类型

builder.RegisterType<ProductService>()
.As<IProductService>()
.AsSelf();

以下两种方式都可以解析:

var service1 = scope.Resolve<IProductService>();
var service2 = scope.Resolve<ProductService>();

4.4 注册实例

var configuration = new AppConfiguration
{
ConnectionString = "Data Source=app.db"
};

builder.RegisterInstance(configuration)
.As<AppConfiguration>();

默认情况下,Autofac 会取得该实例的所有权。如果实例实现了 IDisposable,容器释放时也会释放它。

如果实例由外部负责释放:

builder.RegisterInstance(configuration)
.As<AppConfiguration>()
.ExternallyOwned();

4.5 使用工厂方法注册

当对象创建过程比较复杂时,可以使用 Lambda:

builder.Register(context =>
{
var configuration = context.Resolve<AppConfiguration>();

return new ProductRepository(
configuration.ConnectionString);
})
.As<IProductRepository>();

也可以直接使用外部变量:

var connectionString = "Data Source=app.db";

builder.Register(_ =>
new ProductRepository(connectionString))
.As<IProductRepository>();

4.6 注册构造函数参数

builder.RegisterType<ProductRepository>()
.As<IProductRepository>()
.WithParameter(
"connectionString",
"Data Source=app.db");

这种方式依赖参数名称。对于复杂对象,通常更推荐 Lambda 注册或把配置信息封装成专门的配置对象。

5. 生命周期

5.1 InstancePerDependency

每次解析都会创建一个新实例,也是 Autofac 的默认生命周期:

builder.RegisterType<ProductService>()
.As<IProductService>()
.InstancePerDependency();

下面两个对象不是同一个实例:

var service1 = scope.Resolve<IProductService>();
var service2 = scope.Resolve<IProductService>();

适用于:

  • 轻量级服务
  • 无状态对象
  • 每次使用都需要独立实例的对象

5.2 InstancePerLifetimeScope

同一个 LifetimeScope 内共享一个实例:

builder.RegisterType<UnitOfWork>()
.As<IUnitOfWork>()
.InstancePerLifetimeScope();

验证:

using var scope1 = container.BeginLifetimeScope();

var service1 = scope1.Resolve<IUnitOfWork>();
var service2 = scope1.Resolve<IUnitOfWork>();

Console.WriteLine(
ReferenceEquals(service1, service2)); // True

另一个 Scope 会得到不同的实例:

using var scope2 = container.BeginLifetimeScope();

var service3 = scope2.Resolve<IUnitOfWork>();

Console.WriteLine(
ReferenceEquals(service1, service3)); // False

适用于:

  • 一次数据库事务
  • 一次消息处理
  • 一次任务执行
  • 一次导入操作
  • 一个桌面窗口或业务会话

5.3 SingleInstance

整个容器共享一个实例:

builder.RegisterType<MemoryCacheService>()
.As<ICacheService>()
.SingleInstance();

无论在哪个 Scope 中解析,得到的都是同一个实例。

单例服务应该:

  • 保证线程安全。
  • 尽量保持无状态。
  • 不保存某个工作单元的临时数据。
  • 不直接依赖生命周期更短的对象。

6. 正确使用 LifetimeScope

建议把一个 Scope 对应到一个明确的工作单元:

foreach (var message in messages)
{
using var scope = container.BeginLifetimeScope();

var handler = scope.Resolve<MessageHandler>();
await handler.HandleAsync(message);
}

这样,每条消息处理完毕后,相应的数据库连接、事务和其他资源都会被释放。

不要长期从根容器解析临时对象:

// 不推荐
var service = container.Resolve<ProductService>();

如果这个对象或它的依赖实现了 IDisposable,根容器可能会一直持有它,直到整个应用退出。

推荐:

using var scope = container.BeginLifetimeScope();

var service = scope.Resolve<ProductService>();

Autofac 官方也建议从子 Scope 解析工作对象,并及时释放 Scope。参考 生命周期官方文档

7. 自动释放资源

假设数据库会话实现了 IDisposable

public sealed class DatabaseSession : IDisposable
{
public void Dispose()
{
Console.WriteLine("DatabaseSession disposed.");
}
}

注册:

builder.RegisterType<DatabaseSession>()
.InstancePerLifetimeScope();

使用:

using (var scope = container.BeginLifetimeScope())
{
var session = scope.Resolve<DatabaseSession>();
}

// 离开 using 后,DatabaseSession 自动释放

通常不需要手动调用:

session.Dispose();

Autofac 会在拥有该对象的 Scope 被释放时自动处理。

对于 IAsyncDisposable,可以异步释放 Scope:

await using var scope = container.BeginLifetimeScope();

var service = scope.Resolve<AsyncResourceService>();

await service.ExecuteAsync();

详细规则参见 Autofac Disposal 文档

8. 注册多个实现

定义接口:

public interface IMessageHandler
{
Task HandleAsync(string message);
}

多个实现:

public sealed class LoggingHandler : IMessageHandler
{
public Task HandleAsync(string message)
{
Console.WriteLine($"记录日志:{message}");
return Task.CompletedTask;
}
}

public sealed class NotificationHandler : IMessageHandler
{
public Task HandleAsync(string message)
{
Console.WriteLine($"发送通知:{message}");
return Task.CompletedTask;
}
}

注册:

builder.RegisterType<LoggingHandler>()
.As<IMessageHandler>();

builder.RegisterType<NotificationHandler>()
.As<IMessageHandler>();

通过 IEnumerable<T> 注入全部实现:

public sealed class MessageProcessor
{
private readonly IEnumerable<IMessageHandler> _handlers;

public MessageProcessor(
IEnumerable<IMessageHandler> handlers)
{
_handlers = handlers;
}

public async Task ProcessAsync(string message)
{
foreach (var handler in _handlers)
{
await handler.HandleAsync(message);
}
}
}

注册处理器:

builder.RegisterType<MessageProcessor>();

Autofac 会自动构造 IEnumerable<IMessageHandler>

9. Keyed Service

当同一个接口存在多个实现,并且需要根据业务条件选择时,可以使用 Keyed Service。

public enum PaymentChannel
{
Alipay,
WeChatPay
}

注册:

builder.RegisterType<AlipayPaymentService>()
.Keyed<IPaymentService>(PaymentChannel.Alipay);

builder.RegisterType<WeChatPaymentService>()
.Keyed<IPaymentService>(PaymentChannel.WeChatPay);

直接解析:

var paymentService =
scope.ResolveKeyed<IPaymentService>(
PaymentChannel.Alipay);

不过,业务代码不建议直接访问容器。更好的方式是注入 IIndex<TKey,TValue>

using Autofac.Features.Indexed;

public sealed class PaymentManager
{
private readonly
IIndex<PaymentChannel, IPaymentService> _services;

public PaymentManager(
IIndex<PaymentChannel, IPaymentService> services)
{
_services = services;
}

public Task PayAsync(
PaymentChannel channel,
decimal amount)
{
return _services[channel].PayAsync(amount);
}
}

IIndex<TKey,TValue> 不需要注册,Autofac 会自动提供。参考 Keyed Service 官方文档

10. 程序集扫描

当项目中有大量服务时,可以按照约定批量注册。

10.1 按名称扫描

using System.Reflection;

var assembly = Assembly.GetExecutingAssembly();

builder.RegisterAssemblyTypes(assembly)
.Where(type => type.Name.EndsWith("Service"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();

例如:

ProductServiceIProductService
OrderServiceIOrderService
PaymentServiceIPaymentService

10.2 扫描指定程序集

var applicationAssembly =
typeof(ProductService).Assembly;

builder.RegisterAssemblyTypes(applicationAssembly)
.Where(type => type.Name.EndsWith("Handler"))
.AsImplementedInterfaces();

10.3 排除抽象类型

RegisterAssemblyTypes 默认处理具体类型,但还可以加入业务过滤条件:

builder.RegisterAssemblyTypes(applicationAssembly)
.Where(type =>
type.IsPublic &&
type.Name.EndsWith("Repository"))
.AsImplementedInterfaces();

程序集扫描使用反射,不适合直接用于严格 trimming 或 Native AOT 场景。这类项目应优先显式注册类型。参考 程序集扫描文档

11. 使用 Module 组织注册

大型项目建议使用 Module 分类管理服务。

using Autofac;

public sealed class ApplicationModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<ProductService>()
.As<IProductService>()
.InstancePerLifetimeScope();

builder.RegisterType<OrderService>()
.As<IOrderService>()
.InstancePerLifetimeScope();
}
}

基础设施模块:

public sealed class InfrastructureModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<ProductRepository>()
.As<IProductRepository>()
.InstancePerLifetimeScope();

builder.RegisterType<MemoryCacheService>()
.As<ICacheService>()
.SingleInstance();
}
}

加载 Module:

var builder = new ContainerBuilder();

builder.RegisterModule<ApplicationModule>();
builder.RegisterModule<InfrastructureModule>();

using var container = builder.Build();

Module 也可以接收配置参数:

public sealed class DataModule : Module
{
private readonly string _connectionString;

public DataModule(string connectionString)
{
_connectionString = connectionString;
}

protected override void Load(ContainerBuilder builder)
{
builder.Register(_ =>
new ProductRepository(_connectionString))
.As<IProductRepository>()
.InstancePerLifetimeScope();
}
}

注册时手动传入:

builder.RegisterModule(
new DataModule("Data Source=app.db"));

需要注意:Module 是容器配置对象,它本身不会经过依赖注入。参考 Module 官方文档

12. 开放泛型注册

定义通用仓储:

public interface IRepository<TEntity>
{
Task<TEntity?> FindAsync(int id);
}

public class Repository<TEntity> : IRepository<TEntity>
{
public Task<TEntity?> FindAsync(int id)
{
return Task.FromResult<TEntity?>(default);
}
}

注册开放泛型:

builder.RegisterGeneric(typeof(Repository<>))
.As(typeof(IRepository<>))
.InstancePerLifetimeScope();

之后可以直接注入:

public sealed class ProductService
{
private readonly IRepository<Product> _repository;

public ProductService(IRepository<Product> repository)
{
_repository = repository;
}
}

Autofac 会自动构造:

Repository<Product>

13. 属性注入

构造函数注入仍然是推荐方式,但 Autofac 也支持属性注入:

public sealed class ReportService
{
public ILogger? Logger { get; set; }
}

注册:

builder.RegisterType<ReportService>()
.PropertiesAutowired();

Autofac 创建 ReportService 后,会尝试为可写属性注入已注册的服务。

从 Autofac 7 开始,反射创建的组件还支持自动处理 C# required 属性:

public sealed class ReportService
{
public required ILogger Logger { get; init; }
}

属性注入适合:

  • 遗留代码。
  • 可选依赖。
  • 无法修改构造函数的类型。
  • 某些框架创建的对象。

普通业务服务仍应优先使用构造函数注入。参考 属性注入文档

14. 判断服务是否存在

if (scope.TryResolve<IProductService>(out var service))
{
service.Execute();
}

必需服务使用:

var service = scope.Resolve<IProductService>();

服务不存在时,Resolve<T>() 会抛出异常。

可选服务虽然可以使用 TryResolve,但在业务类中更推荐通过明确的抽象或空对象模式表达,避免让业务代码依赖 Autofac 容器。