gpt4 book ai didi

c# - 停止自定义 ASP.NET Core 配置提供程序中的 SqlDependency

转载 作者:行者123 更新时间:2023-12-02 03:05:16 25 4
gpt4 key购买 nike

我编写了一个自定义配置提供程序,按照此处的说明从数据库表加载 ASP.NET Core 配置:

ASP.Net Custom Configuration Provider

我的提供商使用 SqlDependency如果数据库中的值发生更改,则重新加载配置。

documentation对于 SqlDependency 指出:

The Stop method must be called for each Start call. A given listener only shuts down fully when it receives the same number of Stop requests as Start requests.

我不确定如何在 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 并从数据库加载数据。这也是我想要 Stop() SqlDependency 调用 Dispose() 的地方。当前未调用 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/

其中涵盖了如何处置对象:

  1. 在带有 using 语句的代码块内(不适用)
  2. 请求结束时(不适用)
  3. 使用 DI 容器(不适用 - 我认为不适用?)
  4. 申请结束时<-- 听起来很有希望

选项 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 接口(interface)的更多信息:

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/web-host?view=aspnetcore-2.2

编辑
这个例子甚至没有调用SqlDependency.Stop(),也许它并不那么重要?

https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/sqldependency-in-an-aspnet-app

最佳答案

执行此操作的“正确”方法是让您的配置提供程序是一次性的,然后将所有 SqlDependency 对象作为配置提供程序处置的一部分进行处置。

不幸的是,在 2.x 中,配置框架不支持一次性提供程序。然而,作为 aspnet/Extensions#786 的一部分,这可能会发生变化。和 aspnet/Extensions#861 .

由于我参与了此开发,我可以自豪地宣布从 3.0 开始,将支持一次性配置提供程序

使用 Microsoft.Extensions.Configuration 3.0,当配置根被处置时,一次性提供程序将被正确处置。当(Web)主机被释放时,配置根将在 ASP.NET Core 3.0 中被释放。因此,最终,您的一次性配置提供程序将被正确处理,并且不应再泄漏任何内容。

关于c# - 停止自定义 ASP.NET Core 配置提供程序中的 SqlDependency,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54072129/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com