You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Asp.Net程序集内所有OData控制器配置专属作用域服务

为同一程序集下所有OData控制器批量配置专属作用域服务

问题简述

如何在无需为应用内每个控制器单独注册服务的前提下,为同一程序集中的所有(OData)控制器添加作用域服务,即为该程序集配置持有自有服务的固定作用域?

背景说明

当前正在开发基于OData、DDD/Actor模型的Asp.Net Web服务,架构设计如下:

术语定义

  • Native Domain(本地域):指定实体的所有权归属域
  • Foreign Domain(外部域):需要以只读方式访问指定实体的域

每个域拥有独立的DbContext,既阻止对外部域数据表的写入操作、保留只读访问能力,同时降低系统耦合度,DbContext本身仍采用*Scoped(作用域)*生命周期。
若需要访问外部域的数据,需在当前域的DbContext中添加只读数据模型,并提供由对应外部域实现、在外部域中发布的只读Repository(仓储),确保两类仓储共用同一个DbContext实例。
该机制意味着同一项服务需要使用不同的构造函数参数与实例多次注册,每个访问该仓储的外部域都需要单独注册一次。

目前梳理出两种主流实现方案:

  1. 各服务在其Native Domain中提供实现与接口,在Foreign Domain中扩展该接口,新接口标记为internal访问修饰符,该方案不符合预期;
  2. Foreign Domain拥有独立的固定依赖注入作用域,可直接提供原生接口无需编写额外适配代码,但该方案会引发开头提到的作用域配置问题。

第二种方案存在多项实现约束,例如需要在每个命令执行起始阶段创建扩展作用域,但该方案是最简洁清晰的实现路径。

解决方案

不需要为每个控制器单独注册服务,基于Asp.Net Core和OData的原生扩展点即可实现全局批量配置,以下是三个可直接落地的方案,无额外第三方依赖:

方案1:控制器约定+动作过滤器实现专属作用域(侵入性最低)

该方案无需修改任何现有控制器代码,全局配置一次即可覆盖目标程序集下所有OData控制器:

  1. 编写应用模型约定,服务启动时自动扫描目标程序集的所有控制器,统一为其添加作用域标记过滤器:
public class ForeignDomainControllerConvention : IApplicationModelConvention
{
    private readonly Assembly _targetAssembly;
    public ForeignDomainControllerConvention(Assembly targetAssembly)
    {
        _targetAssembly = targetAssembly;
    }

    public void Apply(ApplicationModel application)
    {
        // 筛选出目标程序集中的所有控制器,统一添加作用域过滤器
        foreach (var controller in application.Controllers.Where(c => c.ControllerType.Assembly == _targetAssembly))
        {
            controller.Filters.Add(new ForeignDomainScopeAttribute());
        }
    }
}
  1. 实现对应的动作过滤器,请求进入标记过的控制器时,基于根服务容器创建专属子作用域,替换当前请求上下文的RequestServices为子作用域的服务提供器,后续控制器激活、构造函数注入都会自动从该专属作用域获取实例,请求结束后自动释放作用域资源:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ForeignDomainScopeAttribute : Attribute, IFilterFactory
{
    public bool IsReusable => false;
    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
    {
        return new ForeignDomainScopeFilter();
    }
}

public class ForeignDomainScopeFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        var rootServiceProvider = context.HttpContext.RequestServices;
        // 创建Foreign Domain专属子作用域,可在此处按需注入专属服务
        using var foreignScope = rootServiceProvider.CreateScope();
        // 替换当前请求的服务提供器,所有依赖解析都走专属作用域
        context.HttpContext.RequestServices = foreignScope.ServiceProvider;
        await next();
    }
}
  1. 服务注册阶段将该约定添加到MVC/OData配置中即可,后续该程序集新增任何控制器都会自动套用该规则,无需额外修改配置:
builder.Services.AddControllers()
    .AddOData(opt =>
    {
        // 原有OData路由、EDM模型配置保持不变
    })
    .AddApplicationPart(typeof(任意Foreign Domain控制器).Assembly)
    .AddConventions(new ForeignDomainControllerConvention(typeof(任意Foreign Domain控制器).Assembly));

Foreign Domain专属的DbContext、仓储服务既可以在创建子作用域时动态注册,也可以提前在根容器配置,子作用域会自动继承根容器的所有注册,仅需覆盖需要隔离的服务即可。

方案2:自定义控制器激活器(作用域隔离更彻底)

如果担心过滤器替换RequestServices会影响请求管道中其他中间件的服务解析,可以直接替换默认的控制器激活器:

  • 实现IControllerActivator接口,创建控制器实例前先判断控制器所属程序集,如果是Foreign Domain程序集的OData控制器,就从预配置的专属作用域解析控制器实例,其余控制器走默认解析逻辑
  • 该方案的作用域完全和控制器实例绑定,不会污染请求上下文的全局服务提供器,适合对服务隔离要求较高的场景

方案3:键控服务批量注入(.Net 8+适用,无需手动管理作用域)

如果项目基于.Net 8及以上版本开发,可以直接使用内置的键控服务能力,无需手动创建子作用域:

  • 为每个Foreign Domain的服务、DbContext注册时指定唯一的域标识作为键名
  • 通过控制器约定为目标程序集的所有控制器统一配置注入规则,构造函数注入服务时自动解析对应域键名的实例
  • 该方案完全复用默认的请求作用域,不需要额外管理作用域生命周期,配置成本更低

注意:无论采用哪个方案,都要确保同一个域下的DbContext和关联仓储从同一个作用域解析,避免出现多实例导致的事务、实体跟踪状态不一致问题。

内容的提问来源于stack exchange,提问作者Jonny

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 13:09:10