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

ASP.NET MVC&OWIN中InstancePerRequest服务构造注入的Dispose问题

问题描述

我在ASP.NET MVC & OWIN中通过Autofac注册了InstancePerRequest生命周期的FooRequest服务:

builder.RegisterType<FooRequest>().AsSelf().InstancePerRequest();

之后在两处解析FooRequest:

  1. 在global.asax的Application_BeginRequest()方法中:
protected void Application_BeginRequest(object sender, EventArgs e)
{
  var fooRequest = DependencyResolver.Current.GetService<FooRequest>();
}
  1. 在标记为InstancePerLifetimeScope的FooService构造函数中注入:
public class FooService
{
  public FooService(FooRequest fooRequest)
  {
    ...
  }
}

遇到的问题:

  • 这两处解析得到的FooRequest实例不相同
  • 构造注入到FooService中的FooRequest实例,在请求结束时不会调用Dispose[Async]方法
  • 补充:在FooService构造函数外使用DependencyResolver.Current.GetService<FooRequest>()能获取到正确的同实例FooRequest

补充配置信息

OWIN & 容器配置

[assembly: OwinStartup(typeof(Project.Web.Startup))]
namespace Project.Web
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var builder = new ContainerBuilder();

            // 注册控制器以支持构造函数注入
            builder.RegisterControllers(typeof(MvcApplication).Assembly);
            builder.RegisterApiControllers(typeof(MvcApplication).Assembly);

            // 注册依赖项
            builder.RegisterModule(new ProjectWebModule());
            builder.Register(c => new IdentityFactoryOptions<ApplicationUserManager> { DataProtectionProvider = app.GetDataProtectionProvider() });

            // 注册过滤器以支持属性注入
            builder.RegisterFilterProvider();

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

            // 替换MVC依赖解析器为Autofac
            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

            // 替换WebAPI依赖解析器为Autofac
            GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);

            // 向OWIN注册Autofac中间件
            app.UseAutofacMiddleware(container);
            app.UseAutofacMvc();

            app.Use((context, next) =>
            {
                var httpContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
                httpContext.SetSessionStateBehavior(SessionStateBehavior.Required);
                return next();
            });

            // 标准MVC配置
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); // 始终在RouteConfig.RegisterRoutes之后执行!
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            // 在RegisterGlobalFilters之后配置认证
            ConfigureAuth(app);
        }
    }
}

ProjectWebModule

namespace Project.Web.Autofac
{
    public class ProjectWebModule : Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterType<FooRequest>().AsSelf().InstancePerRequest();
            builder.RegisterType<FooService>().AsSelf().InstancePerLifetimeScope();

            builder.Register(c => HttpContext.Current.GetOwinContext().Authentication).As<IAuthenticationManager>();
            
            builder.Register(c =>
                                 // 当HttpContext不可用时注册FakeHttpContext
                                 HttpContext.Current != null
                                     ? new HttpContextWrapper(HttpContext.Current) as HttpContextBase
                                     : new FakeHttpContext("~/") as HttpContextBase)
                   .As<HttpContextBase>()
                   .InstancePerLifetimeScope()
                   .OnActivated(e =>
                   {
                       Toolbox.HttpContext = e.Instance;
                   });

            builder.Register(c => c.Resolve<HttpContextBase>().Request)
                .As<HttpRequestBase>()
                .InstancePerLifetimeScope();
            builder.Register(c => c.Resolve<HttpContextBase>().Response)
                .As<HttpResponseBase>()
                .InstancePerLifetimeScope();
            builder.Register(c => c.Resolve<HttpContextBase>().Server)
                .As<HttpServerUtilityBase>()
                .InstancePerLifetimeScope();
            builder.Register(c => c.Resolve<HttpContextBase>().Session)
                .As<HttpSessionStateBase>()
                .InstancePerLifetimeScope();

            base.Load(builder);
        }
    }
}

问题原因与解决方案

核心原因

问题本质是请求生命周期范围的创建时机不匹配,以及FooService的生命周期范围与请求范围脱节:

  1. Application_BeginRequest中解析时,Autofac会自动创建绑定到当前HttpContext的请求级生命周期范围,这个范围会在请求结束时自动释放,因此能拿到正确的FooRequest实例。
  2. FooService注册为InstancePerLifetimeScope,如果它在Autofac OWIN中间件创建请求范围前被解析,或者从根容器直接解析,会被绑定到根容器的子范围(而非请求范围)。此时注入的FooRequest属于这个子范围,既和请求范围的实例不同,也不会在请求结束时被自动释放。
  3. 构造函数外调用DependencyResolver.Current.GetService<FooRequest>()能拿到正确实例,是因为此时请求范围已存在,解析操作会使用当前请求范围。

解决方案

方案1:将FooService改为InstancePerRequest(推荐)

如果FooService本身是请求级服务,直接修改注册方式,让它绑定到请求范围,注入的FooRequest会自动归属请求范围并在请求结束时释放:

// 在ProjectWebModule中修改
builder.RegisterType<FooService>().AsSelf().InstancePerRequest();

方案2:确保FooService在请求范围内解析

若必须保留InstancePerLifetimeScope,要保证FooService的解析操作始终在请求范围内进行:

  • 避免从根容器直接解析FooService,统一通过DependencyResolver.Current(MVC场景)或Request.GetOwinContext().GetAutofacLifetimeScope()(OWIN场景)获取当前请求范围,再从该范围解析服务。
  • 检查FooService的触发时机,禁止在Application_Start等请求前阶段,或自定义非请求范围中解析。

方案3:调整OWIN中间件顺序(关键配置修正)

你的OWIN中间件顺序错误,AutofacMvc中间件应在所有MVC相关配置前执行,确保请求范围在MVC流程启动前创建:
修改Startup.Configuration中的顺序:

public void Configuration(IAppBuilder app)
{
    var builder = new ContainerBuilder();

    // ... 注册控制器、依赖项、过滤器等代码保持不变 ...

    var container = builder.Build();

    // 替换依赖解析器代码保持不变 ...

    // 先注册Autofac中间件
    app.UseAutofacMiddleware(container);
    app.UseAutofacMvc();

    // 再设置Session行为
    app.Use((context, next) =>
    {
        var httpContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
        httpContext.SetSessionStateBehavior(SessionStateBehavior.Required);
        return next();
    });

    // 最后执行MVC标准配置
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    BundleConfig.RegisterBundles(BundleTable.Bundles);

    ConfigureAuth(app);
}

方案4:替换HttpContextBase的注册方式(可选)

将HttpContextBase的生命周期改为InstancePerRequest,确保它始终绑定到请求范围,避免意外的子范围问题:

builder.Register(c =>
                     HttpContext.Current != null
                         ? new HttpContextWrapper(HttpContext.Current) as HttpContextBase
                         : new FakeHttpContext("~/") as HttpContextBase)
       .As<HttpContextBase>()
       .InstancePerRequest()
       .OnActivated(e =>
       {
           Toolbox.HttpContext = e.Instance;
       });

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 01:15:37