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
相关产品推荐
相关产品推荐

