首页
/ MediatR在Blazor Server中的服务作用域管理实践

MediatR在Blazor Server中的服务作用域管理实践

2025-05-20 18:10:46作者:宣聪麟

在Blazor Server应用中使用MediatR时,服务作用域管理是一个需要特别注意的技术点。本文将深入探讨这一问题的本质,分析现有解决方案的优缺点,并提出更优雅的实践方案。

问题背景

Blazor Server应用采用电路(Circuit)模型,每个用户连接会建立一个持久化的电路。在这个模型中,Scoped服务的生命周期与电路绑定,而非传统ASP.NET Core中的请求级别。这与MediatR通常的使用模式产生了根本性的冲突。

当我们在MediatR处理器(Handler)中注入Scoped服务时,这些服务实例会在整个电路生命周期内保持存活。这可能导致:

  1. 数据库上下文等资源长时间不释放
  2. 状态意外共享
  3. 并发问题
  4. 资源泄漏

传统解决方案分析

开发者通常采用以下两种方式解决这个问题:

1. 处理器内创建作用域

public class SomeHandler : IRequestHandler<SomeQuery>
{
    private readonly IServiceScopeFactory _scopeFactory;

    public SomeHandler(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    public async Task Handle(SomeQuery request, CancellationToken ct)
    {
        using var scope = _scopeFactory.CreateScope();
        var service = scope.ServiceProvider.GetRequiredService<ISomeService>();
        // 使用服务...
    }
}

优点

  • 明确控制服务生命周期
  • 每个请求获得全新服务实例

缺点

  • 代码重复
  • 手动解析服务不够优雅
  • 容易忘记创建作用域

2. 管道行为(Pipeline Behavior)

public class ScopedBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    private readonly IServiceScopeFactory _scopeFactory;

    public ScopedBehavior(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    public async Task<TResponse> Handle(TRequest request, CancellationToken ct, RequestHandlerDelegate<TResponse> next)
    {
        using var scope = _scopeFactory.CreateScope();
        // 如何将作用域传递给处理器?
        return await next();
    }
}

优点

  • 集中管理作用域
  • 减少重复代码

缺点

  • 难以将作用域传递给处理器
  • 通知处理器(Notification Handler)难以共享同一作用域

进阶解决方案

自定义作用域标记属性

我们可以设计一个自定义属性来标记需要从请求作用域解析的服务:

[AttributeUsage(AttributeTargets.Parameter)]
public class FromRequestScopeAttribute : Attribute { }

public class SomeHandler : IRequestHandler<SomeQuery>
{
    private readonly ISomeService _service;

    public SomeHandler([FromRequestScope] ISomeService service)
    {
        _service = service;
    }
    // ...
}

实现方案

  1. 自定义服务提供者工厂
public class RequestScopedServiceProvider : IServiceProvider
{
    private readonly IServiceProvider _circuitProvider;
    private readonly IServiceScope _requestScope;

    public RequestScopedServiceProvider(IServiceProvider circuitProvider)
    {
        _circuitProvider = circuitProvider;
        _requestScope = circuitProvider.CreateScope();
    }

    public object GetService(Type serviceType)
    {
        // 优先从请求作用域解析
        return _requestScope.ServiceProvider.GetService(serviceType) 
            ?? _circuitProvider.GetService(serviceType);
    }

    public void Dispose() => _requestScope.Dispose();
}
  1. 集成到MediatR管道
public class ScopedBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    private readonly IServiceScopeFactory _scopeFactory;

    public ScopedBehavior(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    public async Task<TResponse> Handle(TRequest request, CancellationToken ct, RequestHandlerDelegate<TResponse> next)
    {
        using var scope = _scopeFactory.CreateScope();
        var requestServices = new RequestScopedServiceProvider(scope.ServiceProvider);
        
        // 将requestServices传递给处理器...
        return await next();
    }
}

通知处理器的特殊考虑

通知处理器(Notification Handler)需要特别注意,因为它们可能由请求处理器触发,但运行在不同的上下文中。为确保一致性:

  1. 在发布通知时传递当前作用域
  2. 使用自定义的发布策略确保通知处理器使用正确的作用域
public class ScopedPublisher : INotificationPublisher
{
    public async Task Publish(IEnumerable<NotificationHandlerExecutor> handlers, INotification notification, CancellationToken ct)
    {
        // 从当前上下文获取作用域
        var scope = GetCurrentScope();
        
        foreach (var handler in handlers)
        {
            using var handlerScope = scope.CreateScope();
            var handlerInstance = handlerScope.ServiceProvider.GetService(handler.HandlerType);
            await handler.HandlerCallback(handlerInstance, notification, ct);
        }
    }
}

最佳实践建议

  1. 明确区分服务作用域

    • 电路级服务:用户会话状态等
    • 请求级服务:数据库上下文等
  2. 采用分层架构

    • 业务逻辑层使用请求级作用域
    • 表示层可以使用电路级作用域
  3. 文档化服务生命周期

    • 为每个服务明确标注其预期生命周期
    • 在DI注册时添加注释
  4. 自动化测试验证

    • 编写测试验证服务作用域行为
    • 特别是并发场景下的行为

结论

在Blazor Server中使用MediatR时,正确处理服务作用域是保证应用稳定性的关键。通过自定义作用域管理策略,我们可以在保持代码整洁的同时,确保资源得到正确管理。本文提出的解决方案既考虑了请求处理器的需求,也兼顾了通知处理器的特殊情况,为开发者提供了一套完整的实践方案。

登录后查看全文
热门项目推荐
相关项目推荐