在自定义 ASP.NET Core 配置提供程序中停止 SqlDependency
Posted
技术标签:
【中文标题】在自定义 ASP.NET Core 配置提供程序中停止 SqlDependency【英文标题】:Stop SqlDependency in custom ASP.NET Core Configuration Provider 【发布时间】:2019-06-01 23:37:46 【问题描述】:我编写了一个自定义配置提供程序,按照此处的说明从数据库表中加载 ASP.NET Core 配置:
ASP.Net Custom Configuration Provider
如果数据库中的值发生变化,我的提供商使用SqlDependency
重新加载配置。
SqlDependency
的 documentation 声明:
必须为每个 Start 调用调用 Stop 方法。给定的侦听器只有在收到与启动请求相同数量的停止请求时才会完全关闭。
我不确定如何在 ASP.NET Core 的自定义配置提供程序中执行此操作。
下面是代码:
DbConfigurationSource
基本上是IDbProvider
的容器,用于处理从数据库中检索数据
public class DbConfigurationSource : IConfigurationSource
/// <summary>
/// Used to access the contents of the file.
/// </summary>
public virtual IDbProvider DbProvider get; set;
/// <summary>
/// Determines whether the source will be loaded if the underlying data changes.
/// </summary>
public virtual bool ReloadOnChange get; set;
/// <summary>
/// Will be called if an uncaught exception occurs in FileConfigurationProvider.Load.
/// </summary>
public Action<DbLoadExceptionContext> OnLoadException get; set;
public IConfigurationProvider Build(IConfigurationBuilder builder)
return new DbConfigurationProvider(this);
DbConfigurationDataProvider
这是创建和监视SqlDependency
并从数据库加载数据的类。这也是Dispose()
呼叫是我想要Stop()
SqlDependency
的地方。 Dispose()
当前未被调用。
public class DbConfigurationDataProvider : IDbProvider, IDisposable
private readonly string _applicationName;
private readonly string _connectionString;
private ConfigurationReloadToken _reloadToken;
public DbConfigurationDataProvider(string applicationName, string connectionString)
if (string.IsNullOrWhiteSpace(applicationName))
throw new ArgumentNullException(nameof(applicationName));
if (string.IsNullOrWhiteSpace(connectionString))
throw new ArgumentNullException(nameof(connectionString));
_applicationName = applicationName;
_connectionString = connectionString;
_reloadToken = new ConfigurationReloadToken();
SqlDependency.Start(_connectionString);
void OnDependencyChange(object sender, SqlNotificationEventArgs e)
var dependency = (SqlDependency)sender;
dependency.OnChange -= OnDependencyChange;
var previousToken = Interlocked.Exchange(
ref _reloadToken,
new ConfigurationReloadToken());
previousToken.OnReload();
public IChangeToken Watch()
return _reloadToken;
public List<ApplicationSettingDto> GetData()
var settings = new List<ApplicationSettingDto>();
var sql = "select parameter, value from dbo.settingsTable where application = @application";
using (var connection = new SqlConnection(_connectionString))
using (var command = new SqlCommand(sql, connection))
command.Parameters.AddWithValue("application", _applicationName);
var dependency = new SqlDependency(command);
// Subscribe to the SqlDependency event.
dependency.OnChange += OnDependencyChange;
connection.Open();
using (var reader = command.ExecuteReader())
var keyIndex = reader.GetOrdinal("parameter");
var valueIndex = reader.GetOrdinal("value");
while (reader.Read())
settings.Add(new ApplicationSettingDto
Key = reader.GetString(keyIndex), Value = reader.GetString(valueIndex));
Debug.WriteLine($"DateTime.Now: settings.Count settings loaded");
return settings;
public void Dispose()
SqlDependency.Stop(_connectionString);
Debug.WriteLine($"nameof(WhsConfigurationProvider) Disposed");
DbConfigurationProvider
该类监控DbConfigurationDataProvider
中的changeToken
并将新配置发布到应用程序。
public class DbConfigurationProvider : ConfigurationProvider
private DbConfigurationSource Source get;
public DbConfigurationProvider(DbConfigurationSource source)
Source = source ?? throw new ArgumentNullException(nameof(source));
if (Source.ReloadOnChange && Source.DbProvider != null)
ChangeToken.OnChange(
() => Source.DbProvider.Watch(),
() =>
Load(reload: true);
);
private void Load(bool reload)
// Always create new Data on reload to drop old keys
if (reload)
Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var settings = Source.DbProvider.GetData();
try
Load(settings);
catch (Exception e)
HandleException(e);
OnReload();
public override void Load()
Load(reload: false);
public void Load(List<ApplicationSettingDto> settings)
Data = settings.ToDictionary(s => s.Key, s => s.Value, StringComparer.OrdinalIgnoreCase);
private void HandleException(Exception e)
// Removed for brevity
DbConfigurationExtensions
为设置所有内容而调用的扩展方法。
public static class DbConfigurationExtensions
public static IConfigurationBuilder AddDbConfiguration(this IConfigurationBuilder builder, IConfiguration config, string applicationName = "")
if (string.IsNullOrWhiteSpace(applicationName))
applicationName = config.GetValue<string>("ApplicationName");
// DB Server and Catalog loaded from Environment Variables for now
var server = config.GetValue<string>("DbConfigurationServer");
var database = config.GetValue<string>("DbConfigurationDatabase");
if (string.IsNullOrWhiteSpace(server))
// Removed for brevity
if (string.IsNullOrWhiteSpace(database))
// Removed for brevity
var sqlBuilder = new SqlConnectionStringBuilder
DataSource = server,
InitialCatalog = database,
IntegratedSecurity = true
;
return builder.Add(new DbConfigurationSource
DbProvider = new DbConfigurationDataProvider(applicationName, sqlBuilder.ToString()),
ReloadOnChange = true
);
最后,设置整个事情的调用:
public class Program
public static void Main(string[] args)
CreateWebHostBuilder(args).Build().Run();
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
config.AddDbConfiguration(hostingContext.Configuration, "TestApp");
).UseStartup<Startup>();
总结一下:如何确保在DbConfigurationDataProvider
类中调用Dispose()
方法?
到目前为止,我发现的唯一信息来自这里: https://andrewlock.net/four-ways-to-dispose-idisposables-in-asp-net-core/
其中包括如何处理对象:
-
在带有 using 语句的代码块内(不适用)
请求结束时(不适用)
使用 DI 容器(不适用 - 我不认为?)
当应用程序结束时
选项 4 如下所示:
public void Configure(IApplicationBuilder app, IApplicationLifetime applicationLifetime,
SingletonAddedManually toDispose)
applicationLifetime.ApplicationStopping.Register(OnShutdown, toDispose);
// configure middleware etc
private void OnShutdown(object toDispose)
((IDisposable)toDispose).Dispose();
SingletonAddedManually
在我的例子中是 DbConfigurationDataProvider
类,但这远远超出了 Startup
类的范围。
更多关于IApplicationLifetime
接口的信息:
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/web-host?view=aspnetcore-2.2
编辑
这个例子甚至都懒得打SqlDependency.Stop()
,也许没那么重要?
https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/sqldependency-in-an-aspnet-app
【问题讨论】:
嘿,我只是想通知您,对一次性提供程序的支持正在 3.0 中提供,并且应该已经可以在当前的 .NET Core 3.0 预览版 5 中进行测试。 【参考方案1】:执行此操作的“正确”方法是让您的配置提供程序是一次性的,然后将您的所有SqlDependency
对象作为配置提供程序处置的一部分进行处置。
不幸的是,在 2.x 中,配置框架不支持一次性提供程序。但是,作为aspnet/Extensions#786 和aspnet/Extensions#861 的一部分,这可能会发生变化。
由于我参与了此项目的开发,我可以自豪地宣布,从 3.0 开始,将支持一次性配置提供程序。
在Microsoft.Extensions.Configuration
3.0 中,一次性提供程序将在配置根被处置时被正确处置。当(Web)主机被释放时,配置根将在 ASP.NET Core 3.0 中释放。所以最终,您的一次性配置提供程序将被妥善处理,并且不应再泄漏任何东西。
【讨论】:
以上是关于在自定义 ASP.NET Core 配置提供程序中停止 SqlDependency的主要内容,如果未能解决你的问题,请参考以下文章