Hệ thống cấu hình trong .NET (Configuration) hỗ trợ tính năng tự động cập nhật khi các tệp tin nguồn bị thay đổi. Nếu thuộc tính ReloadOnChange được thiết lập là true, hệ thống sẽ tự động tải lại dữ liệu mà không cần khởi động lại ứng dụng. Cơ chế này được xây dựng dựa trên interface IChangeToken.
Hầu hết các trình cung cấp cấu hình dựa trên tệp tin đều kế thừa từ lớp trừu tượng FileConfigurationProvider và sử dụng FileConfigurationSource để định nghĩa các thiết lập. Trong FileConfigurationSource, thuộc tính ReloadOnChange đóng vai trò quyết định việc có theo dõi tệp tin hay không. Khi kích hoạt, Provider sẽ sử dụng phương thức ChangeToken.OnChange() để đăng ký theo dõi sự kiện thay đổi.
if (configSource.ReloadOnChange && configSource.FileProvider != null)
{
ChangeToken.OnChange(
() => configSource.FileProvider.Watch(configSource.Path),
() =>
{
Thread.Sleep(configSource.ReloadDelay);
this.ReloadData(forceReload: true);
});
}
Phương thức ChangeToken.OnChange() hoạt động theo mô hình Producer-Consumer. Trong đó, Producer là một hàm delegate trả về IChangeToken, và Consumer là hành động sẽ thực thi khi có thông báo thay đổi. Interface IChangeToken cung cấp phương thức RegisterChangeCallback để đăng ký các hàm callback này.
public interface IChangeToken
{
bool HasChanged { get; }
bool ActiveChangeCallbacks { get; }
IDisposable RegisterChangeCallback(Action<object?> callback, object? state);
}
Đối với việc giám sát tệp tin, FileProvider.Watch() thường trả về một CancellationChangeToken. Bên dưới lớp thực thi, một FileSystemWatcher sẽ lắng nghe sự kiện từ hệ điều hành. Khi tệp tin bị chỉnh sửa, CancellationTokenSource.Cancel() được gọi, kích hoạt chuỗi callback và cuối cùng là phương thức Load() để cập nhật giá trị mới vào bộ nhớ.
Thông tin về việc thay đổi không chỉ dừng lại ở Provider mà còn được truyền ngược lên các lớp cao hơn như ConfigurationRoot hoặc ConfigurationManager. Để làm được điều này, .NET sử dụng ConfigurationReloadToken.
public class ConfigurationReloadToken : IChangeToken
{
private CancellationTokenSource _cts = new CancellationTokenSource();
public bool ActiveChangeCallbacks => true;
public bool HasChanged => _cts.IsCancellationRequested;
public IDisposable RegisterChangeCallback(Action<object?> callback, object? state)
=> _cts.Token.Register(callback, state);
public void OnReload() => _cts.Cancel();
}
Khi phương thức Load của một Provider hoàn tất việc đọc dữ liệu mới, nó sẽ gọi OnReload() để thông báo rằng dữ liệu đã thay đổi. Quá trình này sẽ hủy bỏ token cũ và khởi tạo một token mới cho lần thay đổi tiếp theo.
// Trong FileConfigurationProvider
private void ReloadData(bool forceReload)
{
// Thực hiện logic tải dữ liệu từ tệp tin...
NotifyChanged();
}
// Trong ConfigurationProvider
protected void NotifyChanged()
{
ConfigurationReloadToken oldToken = Interlocked.Exchange(ref _reloadToken,
new ConfigurationReloadToken());
oldToken.OnReload();
}
Tại cấp độ cao nhất, ConfigurationRoot khi khởi tạo sẽ lặp qua tất cả các Provider và đăng ký lắng nghe token của từng Provider đó thông qua p.GetReloadToken(). Khi bất kỳ Provider nào thông báo thay đổi, ConfigurationRoot cũng sẽ phát ra tín hiệu thay đổi chung cho toàn bộ hệ thống cấu hình của ứng dụng.
public class ConfigurationRoot : IConfigurationRoot, IDisposable
{
private readonly IList<IConfigurationProvider> _providers;
private readonly List<IDisposable> _changeRegistrations;
public ConfigurationRoot(IList<IConfigurationProvider> providers)
{
_providers = providers;
_changeRegistrations = new List<IDisposable>(providers.Count);
foreach (var p in providers)
{
p.Load();
_changeRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), () => TriggerGlobalChange()));
}
}
private void TriggerGlobalChange()
{
ConfigurationReloadToken oldToken = Interlocked.Exchange(ref _changeToken,
new ConfigurationReloadToken());
oldToken.OnReload();
}
}
Nhờ vào cấu trúc này, nhà phát triển có thể sử dụng IConfiguration.GetReloadToken() kết hợp với ChangeToken.OnChange để thực hiện các logic nghiệp vụ cụ thể (như làm mới cache hoặc cấu trúc lại DI container) ngay khi tệp cấu hình JSON hoặc XML được lưu lại.