Skip to content

依赖注入集成原理 ​

🔧 深入理解 MediatR 与 DI 容器的集成机制


📖 概述 ​

MediatR 通过 ServiceFactory 委托与各种 DI 容器解耦。本章深入分析其与 Microsoft.Extensions.DependencyInjection 的集成原理,以及如何适配第三方容器。

源码位置: MediatR.Extensions.Microsoft.DependencyInjection


🏗️ AddMediatR 扩展方法内部实现 ​

源码结构 ​

csharp
// MediatR.Extensions.Microsoft.DependencyInjection/ServiceCollectionExtensions.cs

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddMediatR(
        this IServiceCollection services, 
        Action<MediatRServiceConfiguration> configure)
    {
        // 1. 创建配置对象
        var configuration = new MediatRServiceConfiguration();
        configure(configuration);

        // 2. 注册核心服务
        RegisterCoreServices(services, configuration);

        // 3. 扫描并注册 Handlers 和 Behaviors
        RegisterHandlersAndBehaviors(services, configuration);

        return services;
    }

    private static void RegisterCoreServices(
        IServiceCollection services, 
        MediatRServiceConfiguration configuration)
    {
        // 注册 Mediator
        services.TryAdd(new ServiceDescriptor(
            typeof(IMediator), 
            configuration.MediatorImplementationType ?? typeof(Mediator), 
            configuration.Lifetime));

        // 注册 ISender 和 IPublisher(指向同一个 Mediator 实例)
        services.TryAdd(new ServiceDescriptor(
            typeof(ISender), 
            sp => sp.GetRequiredService<IMediator>(), 
            configuration.Lifetime));

        services.TryAdd(new ServiceDescriptor(
            typeof(IPublisher), 
            sp => sp.GetRequiredService<IMediator>(), 
            configuration.Lifetime));

        // 注册 ServiceFactory(关键!)
        services.TryAdd(new ServiceDescriptor(
            typeof(ServiceFactory), 
            sp => sp.GetService)); // ← 这就是 ServiceFactory 的实现
    }

    private static void RegisterHandlersAndBehaviors(
        IServiceCollection services, 
        MediatRServiceConfiguration configuration)
    {
        foreach (var assemblyToScan in configuration.AssembliesToScan)
        {
            // 扫描 IRequestHandler
            RegisterRequestHandlers(services, assemblyToScan, configuration.Lifetime);

            // 扫描 INotificationHandler
            RegisterNotificationHandlers(services, assemblyToScan, configuration.Lifetime);

            // 扫描 IPipelineBehavior
            RegisterPipelineBehaviors(services, assemblyToScan, configuration.Lifetime);
        }
    }
}

🔍 ServiceFactory 的核心作用 ​

ServiceFactory 定义 ​

csharp
// MediatR/src/MediatR/Mediator.cs
public delegate object ServiceFactory(Type serviceType);

本质:一个委托,接收 Type 参数,返回 object 实例。


在 ASP.NET Core 中的实现 ​

csharp
// 注册时
services.TryAddTransient<ServiceFactory>(sp => sp.GetService);

// 等价于
services.AddTransient<ServiceFactory>(serviceProvider => 
{
    return (Type serviceType) => serviceProvider.GetService(serviceType);
});

工作原理:

csharp
// 使用时
var serviceFactory = serviceProvider.GetRequiredService<ServiceFactory>();

// 调用
var handler = serviceFactory(typeof(IRequestHandler<CreateOrderCommand, OrderResult>));

// 实际执行的是
var handler = serviceProvider.GetService(typeof(IRequestHandler<CreateOrderCommand, OrderResult>));

为什么需要 ServiceFactory? ​

问题:直接依赖 DI 容器 ​

csharp
// ❌ 错误:Mediator 直接依赖 IServiceProvider
public class Mediator : IMediator
{
    private readonly IServiceProvider _serviceProvider;

    public Mediator(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken ct)
    {
        var handler = _serviceProvider.GetService<IRequestHandler<TRequest, TResponse>>();
        // ...
    }
}

问题:

  • ❌ 耦合到特定的 DI 容器
  • ❌ 无法与其他容器集成(Autofac、Unity等)
  • ❌ 测试困难

解决方案:ServiceFactory 抽象 ​

csharp
// ✅ 正确:通过委托解耦
public class Mediator : IMediator
{
    private readonly ServiceFactory _serviceFactory;

    public Mediator(ServiceFactory serviceFactory)
    {
        _serviceFactory = serviceFactory;
    }

    public Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken ct)
    {
        // 不关心具体是哪个 DI 容器
        var handler = (IRequestHandler<TRequest, TResponse>)_serviceFactory(
            typeof(IRequestHandler<TRequest, TResponse>));
        // ...
    }
}

优势:

  • ✅ 与 DI 容器解耦
  • ✅ 可适配任何容器
  • ✅ 易于测试(Mock 委托即可)

🔄 服务生命周期的影响 ​

三种生命周期对比 ​

1. Transient(默认) ​

csharp
builder.Services.AddMediatR(cfg => {
    cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
    cfg.Lifetime = ServiceLifetime.Transient; // 默认
});

行为:

  • 每次 Send() 都创建新的 Mediator 实例
  • 每次解析都创建新的 Handler 实例
  • 管道行为也是每次新建

内存图:

Request 1: Mediator₁ → Handler₁ → Behavior₁
Request 2: Mediator₂ → Handler₂ → Behavior₂
Request 3: Mediator₃ → Handler₃ → Behavior₃

优点:

  • ✅ 无线程安全问题
  • ✅ 状态隔离

缺点:

  • ❌ GC 压力大
  • ❌ 性能略低

2. Scoped(Web应用推荐) ​

csharp
builder.Services.AddMediatR(cfg => {
    cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
    cfg.Lifetime = ServiceLifetime.Scoped;
});

行为:

  • 每个 HTTP 请求共享同一个 Mediator 实例
  • Handler 在请求范围内复用

内存图:

HTTP Request 1: Mediator₁ → Handler₁ (整个请求过程复用)
  ├─ GET /api/orders/1
  └─ POST /api/orders

HTTP Request 2: Mediator₂ → Handler₂ (新的请求,新的实例)
  └─ GET /api/orders/2

优点:

  • ✅ 减少实例创建
  • ✅ 适合 Web 应用
  • ✅ 可共享请求级别的状态

缺点:

  • ⚠️ 需要注意线程安全

3. Singleton ​

csharp
builder.Services.AddMediatR(cfg => {
    cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
    cfg.Lifetime = ServiceLifetime.Singleton;
});

行为:

  • 整个应用只有一个 Mediator 实例
  • 所有请求共享

⚠️ 警告:如果 Handler 注入了 Scoped 服务(如 DbContext),会抛出异常!

InvalidOperationException: Cannot consume scoped service 'DbContext' 
from singleton 'IMediator'.

解决方案:确保所有依赖都是 Singleton 或使用 Scope


📦 自动扫描与注册机制 ​

扫描流程 ​

csharp
private static void RegisterHandlersAndBehaviors(
    IServiceCollection services, 
    MediatRServiceConfiguration configuration)
{
    foreach (var assemblyToScan in configuration.AssembliesToScan)
    {
        // 1. 获取程序集中所有类型
        var types = assemblyToScan.GetTypes();

        // 2. 过滤出实现 IRequestHandler 的类型
        var handlers = types.Where(t => 
            t.GetInterfaces().Any(i => 
                i.IsGenericType && 
                i.GetGenericTypeDefinition() == typeof(IRequestHandler<,>)));

        // 3. 注册到 DI 容器
        foreach (var handler in handlers)
        {
            var interfaces = handler.GetInterfaces()
                .Where(i => i.IsGenericType && 
                       i.GetGenericTypeDefinition() == typeof(IRequestHandler<,>));

            foreach (var @interface in interfaces)
            {
                services.Add(new ServiceDescriptor(@interface, handler, configuration.Lifetime));
            }
        }
    }
}

注册示例 ​

假设有以下 Handler:

csharp
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, OrderResult>
{
    // ...
}

public class GetOrderHandler : IRequestHandler<GetOrderQuery, OrderDto>
{
    // ...
}

注册后的 DI 容器:

csharp
services.AddTransient<IRequestHandler<CreateOrderCommand, OrderResult>, CreateOrderHandler>();
services.AddTransient<IRequestHandler<GetOrderQuery, OrderDto>, GetOrderHandler>();

多程序集扫描 ​

csharp
builder.Services.AddMediatR(cfg => {
    cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
    cfg.RegisterServicesFromAssembly(typeof(OrderHandlersMarker).Assembly);
    cfg.RegisterServicesFromAssembly(typeof(UserHandlersMarker).Assembly);
});

扫描顺序:按添加顺序扫描,后扫描的同名 Handler 会覆盖前面的。


🔌 第三方容器适配 ​

Autofac 集成 ​

bash
dotnet add package Autofac.Extensions.DependencyInjection
csharp
// Program.cs
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder =>
{
    // 注册 MediatR
    containerBuilder.RegisterMediatR(cfg => {
        cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
    });
});

Autofac 内部实现:

csharp
// Autofac.Extensions.DependencyInjection/AutofacRegistration.cs
public static void RegisterMediatR(this ContainerBuilder builder, Action<MediatRServiceConfiguration> configure)
{
    var configuration = new MediatRServiceConfiguration();
    configure(configuration);

    // 注册 ServiceFactory(适配 Autofac)
    builder.Register<ServiceFactory>(ctx =>
    {
        var c = ctx.Resolve<IComponentContext>();
        return t => c.Resolve(t); // ← Autofac 的解析方式
    });

    // 注册其他服务...
}

Unity 集成 ​

csharp
// Unity 适配器
container.RegisterType<ServiceFactory>(
    new InjectionFactory(c => t => c.Resolve(t)));

container.RegisterType<IMediator, Mediator>();

自定义容器适配 ​

csharp
// 假设你有一个自定义 DI 容器
public class CustomContainer
{
    public object Resolve(Type type) { /* ... */ }
}

// 适配 MediatR
var customContainer = new CustomContainer();

// 创建 ServiceFactory
ServiceFactory serviceFactory = type => customContainer.Resolve(type);

// 创建 Mediator
var mediator = new Mediator(serviceFactory);

关键:只需提供 ServiceFactory 委托,无需修改 MediatR 源码!


🎯 性能优化点 ​

1. 避免重复扫描 ​

csharp
// ❌ 错误:每次启动都扫描
services.AddMediatR(cfg => {
    cfg.RegisterServicesFromAssemblies(AppDomain.CurrentDomain.GetAssemblies());
});

// ✅ 正确:只扫描必要的程序集
services.AddMediatR(cfg => {
    cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
});

性能提升:启动时间减少 50-70%


2. 使用 TryAdd 避免重复注册 ​

csharp
// MediatR 内部使用 TryAdd
services.TryAdd(new ServiceDescriptor(typeof(IMediator), typeof(Mediator), lifetime));

// 如果已经注册过,不会重复添加

3. 延迟初始化 ​

csharp
// 仅在首次使用时初始化
private static Lazy<IMediator> _mediator = new Lazy<IMediator>(() =>
{
    var services = new ServiceCollection();
    services.AddMediatR(cfg => 
        cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
    return services.BuildServiceProvider().GetRequiredService<IMediator>();
});

🐛 常见问题排查 ​

问题 1:Handler 未被注册 ​

症状:No service for type 'IRequestHandler'

排查:

csharp
// 调试代码
var assembly = typeof(Program).Assembly;
var types = assembly.GetTypes();

Console.WriteLine($"程序集: {assembly.FullName}");
Console.WriteLine($"总类型数: {types.Length}");

var handlers = types.Where(t => 
    t.GetInterfaces().Any(i => 
        i.IsGenericType && 
        i.GetGenericTypeDefinition() == typeof(IRequestHandler<,>)));

Console.WriteLine($"找到 {handlers.Count()} 个 Handler:");
foreach (var h in handlers)
{
    Console.WriteLine($"  - {h.FullName}");
}

常见原因:

  • Handler 不是 public
  • Handler 是 abstract
  • 程序集路径错误

问题 2:循环依赖 ​

症状:A circular dependency was detected

原因:Handler A 依赖 Handler B,Handler B 依赖 Handler A

解决:提取共享服务

csharp
// ❌ 循环依赖
public class OrderHandler { private readonly PaymentHandler _payment; }
public class PaymentHandler { private readonly OrderHandler _order; }

// ✅ 提取服务
public class OrderProcessingService { /* ... */ }
public class OrderHandler { private readonly OrderProcessingService _service; }
public class PaymentHandler { private readonly OrderProcessingService _service; }

🎓 总结 ​

核心机制 ​

  1. ✅ ServiceFactory 委托:解耦 DI 容器
  2. ✅ 自动扫描:反射查找 Handler 和 Behavior
  3. ✅ 生命周期管理:由 DI 容器控制
  4. ✅ TryAdd:避免重复注册
  5. ✅ 可扩展:轻松适配第三方容器

最佳实践 ​

✅ Web 应用使用 Scoped
✅ 只扫描必要的程序集
✅ 避免在 Singleton 中注入 Scoped 服务
✅ 使用 TryAdd 防止重复注册
✅ 提取共享服务避免循环依赖


💡 提示: 理解 DI 集成原理有助于排查问题和优化性能!

Released under the CC BY-SA 4.0 License.