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

Autofac SingleInstance在ASP.NET WebAPI中每次请求重复实例化问题

问题:Autofac SingleInstance配置未生效,ScriptExecutionHost每次请求都被实例化

我正在开发基于.NET Framework的ASP.NET WebAPI项目,使用Autofac实现依赖注入。需要让ScriptExecutionHost类以单例模式运行——整个应用生命周期内仅存在一个实例,与API请求无关。但目前每次API请求都会创建新的ScriptExecutionHost实例。

控制器构造函数能正常从Autofac获取该实例,但每次都是新对象。唯一例外是配置AutoActivate时:应用启动会创建实例,第一个请求能拿到该实例,但后续请求仍会收到新实例。当前使用Autofac 7.x版本和OWIN框架。

ScriptExecutionHost构造函数仅接收一个字符串参数和另外两个单例对象,相关代码如下:


Startup.cs

public void Configuration(IAppBuilder app)
{
    var container = ContainerConfig.Configure();
    var config = GetHttpConfiguration(container);

    app.UseAutofacMiddleware(container);
    app.UseAutofacLifetimeScopeInjector(container);
    app.UseCors(CorsOptions.AllowAll);
    ConfigureOAuth(app);

#if TESTSYSTEM || DEBUG
    Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
    SwaggerConfig.Register(config);
#endif

    var physicalFileSystem = new PhysicalFileSystem("./app");
    // 将./app设为Web应用的静态文件默认根目录
    app.UseFileServer(new FileServerOptions
    {
        RequestPath = new PathString(string.Empty),
        EnableDirectoryBrowsing = true,
        EnableDefaultFiles = true,
        FileSystem = physicalFileSystem,
        StaticFileOptions = { FileSystem = physicalFileSystem, ServeUnknownFileTypes = true },
        DefaultFilesOptions = { DefaultFileNames = new[] { "index.html" } }
    });

    app.UseStageMarker(PipelineStage.MapHandler);

    app.UseAutofacWebApi(config);
    app.UseWebApi(config);
}

private static HttpConfiguration GetHttpConfiguration(IContainer container)
{
    // 创建HttpConfiguration实例
    var config = new HttpConfiguration
    {
        DependencyResolver = new AutofacWebApiDependencyResolver(container)
    };
    config.Formatters.Remove(config.Formatters.XmlFormatter);

    // 配置日期以UTC格式处理
    config.Formatters.JsonFormatter.SerializerSettings.DateTimeZoneHandling =
        DateTimeZoneHandling.Utc;
    config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling =
        ReferenceLoopHandling.Ignore;
    config.Formatters.JsonFormatter.SerializerSettings.ContractResolver =
        new CamelCasePropertyNamesContractResolver();

    // Web API路由配置
    config.MapHttpAttributeRoutes();

    config.Routes.MapHttpRoute(
        "DefaultApi",
        "api/{controller}/{id}",
        new { id = RouteParameter.Optional }
    );
    return config;
}

private static void ConfigureOAuth(IAppBuilder app)
{
    var oAuthServerOptions = new OAuthAuthorizationServerOptions
    {
        AllowInsecureHttp = true,
        TokenEndpointPath = new PathString("/token"),
        AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
        Provider = new AuthorizationServerProvider()
    };

    // 注册Token生成中间件
    app.UseOAuthAuthorizationServer(oAuthServerOptions);
    app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}

ContainerConfig.Configure()方法

public static IContainer Configure()
{
    var builder = new ContainerBuilder();

    // 注册Web API控制器
    builder.RegisterApiControllers(Assembly.GetExecutingAssembly());

    // 注册Credentials Service
    builder.Register(_ => new CredentialsService(CredentialManagerConfiguration.LoadFromWebConfig()))
        .As<CredentialsService>().SingleInstance();

    // 注册DatabaseContext
    builder.RegisterType<DatabaseContext>().AsSelf().InstancePerRequest();

    // 从web.config注册CredentialManagerConfiguration
    builder.Register(_ => CredentialManagerConfiguration.LoadFromWebConfig()).AsSelf().SingleInstance();
    // 从web.config注册EmailConfiguration
    builder.Register(_ => EmailConfiguration.LoadFromWebConfig()).AsSelf().SingleInstance();

    // 注册EmailClient
    builder.RegisterType<EmailRepo>().As<IEmailRepo>().SingleInstance();

    // 注册UnitOfWork
    builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().AsSelf()
        .WithParameter("templateFileFolder", _fileTemplateBaseDirectory)
        .WithParameter("downloadDir", _fileDownloadBaseDirectory)
        .InstancePerRequest();

    builder.RegisterType<ScriptExecutionHost>().AsSelf().WithParameter("fileDownloadBaseDirectory", _fileDownloadBaseDirectory).SingleInstance().AutoActivate();

    // 可选:注册Autofac模型绑定提供程序
    builder.RegisterWebApiModelBinderProvider();

    return builder.Build();
}

控制器构造函数

public ScriptsController(IUnitOfWork unitOfWork, ScriptExecutionHost scriptExecutionHost) : base(unitOfWork)
{
    _unitOfWork = unitOfWork;
    _adService = new ActiveDirectoryService(new ActiveDirectoryConfiguration());
    _scripExecutionHost = scriptExecutionHost;
}

ScriptExecutionHost构造函数

public class ScriptExecutionHost : IRegisteredObject
{
    // ... 其他代码
    public ScriptExecutionHost(string fileDownloadBaseDirectory, CredentialManagerConfiguration credentialManagerConfiguration, IEmailRepo emailRepo)
    {
        // 注册到宿主环境以实现优雅停机
        // 确保应用池回收前能完成当前任务
        HostingEnvironment.RegisterObject(this);

        _fileDownloadBaseDirectory = fileDownloadBaseDirectory;
        _credentialManagerConfiguration = credentialManagerConfiguration;
        _emailRepo = emailRepo;

        _logger.Debug("ScriptExecutionHost initialized.");
        _logger.Debug("File download base directory: {0}", _fileDownloadBaseDirectory);

        // 用于验证Autofac是否每次创建新实例
        initTime = DateTime.Now;
    }
}

解决方案

1. 调整OWIN中间件顺序

Autofac与WebAPI集成时,中间件顺序会影响作用域解析。修改Startup.cs中的中间件顺序,将UseStageMarker移到WebAPI注册之后:

app.UseAutofacMiddleware(container);
app.UseAutofacLifetimeScopeInjector(container);
app.UseCors(CorsOptions.AllowAll);
ConfigureOAuth(app);

// ... 静态文件配置代码

app.UseAutofacWebApi(config);
app.UseWebApi(config);
// 将阶段标记移到PostAuthenticate,确保作用域正确初始化
app.UseStageMarker(PipelineStage.PostAuthenticate);

2. 使用接口注册ScriptExecutionHost(推荐)

依赖接口而非具体类可以避免Autofac生命周期管理的潜在问题:

  • 定义接口:
public interface IScriptExecutionHost : IRegisteredObject
{
    // 声明必要方法
}

public class ScriptExecutionHost : IScriptExecutionHost
{
    // 实现接口及原有逻辑
}
  • 修改注册代码:
builder.RegisterType<ScriptExecutionHost>()
       .As<IScriptExecutionHost>()
       .WithParameter("fileDownloadBaseDirectory", _fileDownloadBaseDirectory)
       .SingleInstance()
       .AutoActivate();
  • 修改控制器构造函数:
public ScriptsController(IUnitOfWork unitOfWork, IScriptExecutionHost scriptExecutionHost) : base(unitOfWork)
{
    _unitOfWork = unitOfWork;
    _adService = new ActiveDirectoryService(new ActiveDirectoryConfiguration());
    _scripExecutionHost = scriptExecutionHost;
}

3. 验证注册唯一性

检查整个解决方案中是否有其他地方重复注册ScriptExecutionHost,重复注册会覆盖原有生命周期配置。

4. 验证单例实例唯一性

在ScriptExecutionHost中添加静态计数器,确认实例创建次数:

public class ScriptExecutionHost : IRegisteredObject
{
    private static int _instanceCount = 0;
    public int InstanceId { get; }

    public ScriptExecutionHost(string fileDownloadBaseDirectory, CredentialManagerConfiguration credentialManagerConfiguration, IEmailRepo emailRepo)
    {
        InstanceId = Interlocked.Increment(ref _instanceCount);
        _logger.Debug($"ScriptExecutionHost实例 {InstanceId} 初始化于 {DateTime.Now}");
        // ... 原有构造逻辑
    }
}

在控制器中打印InstanceId,确认每次请求是否返回同一ID。

5. 检查应用池回收设置

确保应用程序池没有频繁回收,否则单例实例会被销毁重建,导致出现“每次请求都是新实例”的假象。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 22:39:52