c# - 在自定义 ASP.NET Core 配置提供程序中停止 SqlDependency
问题描述
我已经编写了一个自定义配置提供程序,按照此处的说明从数据库表中加载 ASP.NET Core 配置:
如果数据库中的值发生更改,我的提供程序将SqlDependency
用于重新加载配置。
的文档SqlDependency
指出:
必须为每个 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
此类监视输入changeToken
并将DbConfigurationDataProvider
新配置发布到应用程序。
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>();
}
总结一下:如何确保Dispose()
在类中调用该方法DbConfigurationDataProvider
?
到目前为止,我发现的唯一信息来自这里: 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
解决方案
推荐阅读
- javascript - 使用 onInput() 在选择元素中获取所选 html 选项的值
- c++ - 热门问题的答案让移位操作员错了?
- cygwin - Cygwin安装没有共享库,或者我应该如何激活共享库?
- reactjs - 为什么 react-frame-component iframe 的 body 变空了?
- types - Haskell 之外是否还有 GADT 理论?
- firebase - 更改 Firebase 面向公众的名称
- ios - Core Data+CloudKit 未连接到 CloudKit
- ios - 错误 HE0046。Rider 无法安装 iOS 应用程序
- angular - 如何根据 quizId 路由到特定测验?
- php - 无法在 php 中正确使用 sql 查询的结果