Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Note
Это не последняя версия этой статьи. В текущей версии см. версию .NET 10 этой статьи.
Warning
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущей версии см. версию .NET 10 этой статьи.
Токен изменений — это низкоуровневый стандартный блок общего назначения, используемый для отслеживания изменений.
Просмотреть или скачать образец кода (описание загрузки)
Интерфейс IChangeToken
IChangeToken распространяет уведомления о том, что произошло изменение.
IChangeToken находится в пространстве имен Microsoft.Extensions.Primitives. Пакет NuGet Microsoft.Extensions.Primitives неявно предоставляется для приложений ASP.NET Core.
IChangeToken имеет два свойства:
-
ActiveChangeCallbacks указывает, инициирует ли токен обратные вызовы заранее. Если для
ActiveChangedCallbacksзадано значениеfalse, обратный вызов не выполняется, а приложению нужно опроситьHasChangedна предмет изменений. Токен также может никогда не быть отменён, если изменения не происходят или базовый обработчик изменений освобождён либо отключён. - HasChanged получает значение, указывающее, произошло ли изменение.
Интерфейс IChangeToken включает метод RegisterChangeCallback(Action<Object>, Object), который регистрирует обратный вызов, выполняемый при изменении токена. Перед выполнением обратного вызова нужно задать HasChanged.
Класс ChangeToken
ChangeToken — это статический класс, который распространяет уведомления о том, что произошло изменение.
ChangeToken находится в пространстве имен Microsoft.Extensions.Primitives. Пакет NuGet Microsoft.Extensions.Primitives неявно включён в приложения ASP.NET Core.
Метод ChangeToken.OnChange(Func<IChangeToken>, Action) регистрирует объект Action, вызываемый при изменении токена:
-
Func<IChangeToken>создает токен. -
Actionвызывается при изменении токена.
Перегрузка ChangeToken.OnChange<TState>(Func<IChangeToken>, Action<TState>, TState) принимает дополнительный параметр TState, который передаётся потребителю токена Action.
OnChange возвращает IDisposable. При вызове Dispose токен прекращает прослушивать дальнейшие изменения и освобождает свои ресурсы.
Примеры использования токенов изменений в ASP.NET Core
Токены изменений используются в ключевых областях ASP.NET Core для отслеживания изменений в объектах:
- Чтобы отслеживать изменения в файлах, метод Watch объекта IFileProvider создает объект
IChangeTokenдля указанных файлов или папки, за которыми нужно следить. -
IChangeTokenтокены можно добавлять к записям кэша, чтобы при изменении инициировать удаление записей из кэша. - Для изменений
TOptionsиспользуемая по умолчанию реализация OptionsMonitor<TOptions> интерфейса IOptionsMonitor<TOptions> имеет перегрузку, которая принимает один или несколько экземпляров IOptionsChangeTokenSource<TOptions>. Каждый экземпляр возвращаетIChangeToken, чтобы зарегистрировать обратный вызов уведомления об изменении для отслеживания изменений параметров.
Отслеживание изменений конфигурации
По умолчанию шаблоны ASP.NET Core используют файлы конфигурации JSON (appsettings.json, appsettings.Development.json и appsettings.Production.json) для загрузки параметров конфигурации приложения.
Эти файлы настраиваются с помощью метода расширения AddJsonFile(IConfigurationBuilder, String, Boolean, Boolean) в ConfigurationBuilder, который принимает параметр reloadOnChange.
reloadOnChange указывает, нужно ли перезагружать конфигурацию при изменении файла. Эта настройка отображается во вспомогательном методе HostCreateDefaultBuilder:
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true,
reloadOnChange: true);
Файловая конфигурация представлена FileConfigurationSource.
FileConfigurationSource использует IFileProvider для отслеживания файлов.
По умолчанию IFileMonitor предоставляется объектом PhysicalFileProvider, который использует FileSystemWatcher для отслеживания изменений в файле конфигурации.
Этот пример приложения демонстрирует две реализации для отслеживания изменений конфигурации. Если любой из appsettings файлов изменяется, обе реализации мониторинга файлов выполняют пользовательский код— пример приложения записывает сообщение в консоль.
FileSystemWatcher файла конфигурации может активировать несколько обратных вызовов токена для одного изменения файла конфигурации. Чтобы пользовательский код выполнялся один раз при активации нескольких обратных вызовов токена, реализация в примере проверяет хэши файлов. В примере используется хэширование файлов SHA1. Повторные попытки выполняются с экспоненциальным увеличением интервала между ними.
Utilities/Utilities.cs:
public static byte[] ComputeHash(string filePath)
{
var runCount = 1;
while(runCount < 4)
{
try
{
if (File.Exists(filePath))
{
using (var fs = File.OpenRead(filePath))
{
return System.Security.Cryptography.SHA1
.Create().ComputeHash(fs);
}
}
else
{
throw new FileNotFoundException();
}
}
catch (IOException ex)
{
if (runCount == 3)
{
throw;
}
Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, runCount)));
runCount++;
}
}
return new byte[20];
}
Токен изменений для простого запуска
Зарегистрируйте обратный вызов Action получателя токена для получения уведомлений об изменениях от токена перезагрузки конфигурации.
В Startup.Configure:
ChangeToken.OnChange(
() => config.GetReloadToken(),
(state) => InvokeChanged(state),
env);
config.GetReloadToken() предоставляет токен. В качестве обратного вызова используется метод InvokeChanged:
private void InvokeChanged(IWebHostEnvironment env)
{
byte[] appsettingsHash = ComputeHash("appSettings.json");
byte[] appsettingsEnvHash =
ComputeHash($"appSettings.{env.EnvironmentName}.json");
if (!_appsettingsHash.SequenceEqual(appsettingsHash) ||
!_appsettingsEnvHash.SequenceEqual(appsettingsEnvHash))
{
_appsettingsHash = appsettingsHash;
_appsettingsEnvHash = appsettingsEnvHash;
WriteConsole("Configuration changed (Simple Startup Change Token)");
}
}
Обратный state вызов используется для передачи IWebHostEnvironment, что полезно для указания правильного appsettings файла конфигурации для мониторинга (например, appsettings.Development.json в среде Development). Хэши файлов используются, чтобы предотвратить многократное выполнение инструкции WriteConsole из-за нескольких срабатываний обратного вызова токена, даже если файл конфигурации изменился только один раз.
Эта система выполняется, пока запущено приложение, и не может быть отключена пользователем.
Отслеживание изменений конфигурации в качестве службы
Этот пример реализует следующее:
- Базовый мониторинг токена запуска.
- Отслеживание как служба.
- Механизм для включения и отключения отслеживания.
Этот пример определяет IConfigurationMonitor интерфейс.
Extensions/ConfigurationMonitor.cs:
public interface IConfigurationMonitor
{
bool MonitoringEnabled { get; set; }
string CurrentState { get; set; }
}
Конструктор реализованного класса ConfigurationMonitor регистрирует обратный вызов для уведомлений об изменениях:
public ConfigurationMonitor(IConfiguration config, IWebHostEnvironment env)
{
_env = env;
ChangeToken.OnChange<IConfigurationMonitor>(
() => config.GetReloadToken(),
InvokeChanged,
this);
}
public bool MonitoringEnabled { get; set; } = false;
public string CurrentState { get; set; } = "Not monitoring";
config.GetReloadToken() предоставляет токен.
InvokeChanged является методом обратного вызова. В данном случае state — это ссылка на экземпляр IConfigurationMonitor, который используется для доступа к состоянию мониторинга. Используются два свойства:
-
MonitoringEnabled: указывает, должна ли функция обратного вызова выполнять свой пользовательский код. -
CurrentState: описывает текущее состояние мониторинга для использования в пользовательском интерфейсе.
Метод InvokeChanged похож на описанный ранее подход, за исключением того, что он:
- Не выполняет свой код, если только
MonitoringEnabledне равноtrue. - выводит текущее значение
stateв своих выходных данныхWriteConsole.
private void InvokeChanged(IConfigurationMonitor state)
{
if (MonitoringEnabled)
{
byte[] appsettingsHash = ComputeHash("appSettings.json");
byte[] appsettingsEnvHash =
ComputeHash($"appSettings.{_env.EnvironmentName}.json");
if (!_appsettingsHash.SequenceEqual(appsettingsHash) ||
!_appsettingsEnvHash.SequenceEqual(appsettingsEnvHash))
{
string message = $"State updated at {DateTime.Now}";
_appsettingsHash = appsettingsHash;
_appsettingsEnvHash = appsettingsEnvHash;
WriteConsole("Configuration changed (ConfigurationMonitor Class) " +
$"{message}, state:{state.CurrentState}");
}
}
}
Экземпляр ConfigurationMonitor регистрируется как служба в Startup.ConfigureServices:
services.AddSingleton<IConfigurationMonitor, ConfigurationMonitor>();
Страница индекса позволяет пользователю управлять отслеживанием конфигурации. Экземпляр IConfigurationMonitor внедряется в IndexModel.
Pages/Index.cshtml.cs:
public IndexModel(
IConfiguration config,
IConfigurationMonitor monitor,
FileService fileService)
{
_config = config;
_monitor = monitor;
_fileService = fileService;
}
Монитор конфигурации (_monitor) позволяет включить или отключить мониторинг и задать текущее состояние для обратной связи пользовательского интерфейса:
public IActionResult OnPostStartMonitoring()
{
_monitor.MonitoringEnabled = true;
_monitor.CurrentState = "Monitoring!";
return RedirectToPage();
}
public IActionResult OnPostStopMonitoring()
{
_monitor.MonitoringEnabled = false;
_monitor.CurrentState = "Not monitoring";
return RedirectToPage();
}
При активации OnPostStartMonitoring отслеживание включается, а текущее состояние сбрасывается. При активации OnPostStopMonitoring отслеживание отключается, а состояние указывает на отсутствие отслеживания.
Кнопки пользовательского интерфейса для включения и отключения отслеживания.
Pages/Index.cshtml:
<button class="btn btn-success" asp-page-handler="StartMonitoring">
Start Monitoring
</button>
<button class="btn btn-danger" asp-page-handler="StopMonitoring">
Stop Monitoring
</button>
Отслеживание изменений кэшированных файлов
Содержимое файла можно кэшировать в памяти с помощью IMemoryCache. Кэширование в памяти описано в разделе Кэш в памяти. Устаревшие (просроченные) данные возвращаются из кэша при изменении источника исходных данных без каких-либо дополнительных действий, таких как описанная ниже реализация.
Например, если не учитывать состояние кэшированного исходного файла при продлении скользящего срока действия, это приведет к появлению в кэше устаревших данных файлов. Каждый запрос к данным продляет скользящий срок действия, но этот файл никогда не загружается в кэш. Любые функции приложения, использующие кэшированное содержимое, могут получить устаревшее содержимое.
Использование токенов изменений в сценарии кэширования файлов предотвращает появление устаревшего содержимого файлов в кэше. Этот пример демонстрирует реализацию данного подхода.
GetFileContent в примере используется для:
- Вернуть содержимое файла.
- Реализуйте алгоритм повторных попыток с экспоненциальной задержкой для обработки случаев, когда проблема доступа к файлу временно препятствует чтению его содержимого.
Utilities/Utilities.cs:
public async static Task<string> GetFileContent(string filePath)
{
var runCount = 1;
while(runCount < 4)
{
try
{
if (File.Exists(filePath))
{
using (var fileStreamReader = File.OpenText(filePath))
{
return await fileStreamReader.ReadToEndAsync();
}
}
else
{
throw new FileNotFoundException();
}
}
catch (IOException ex)
{
if (runCount == 3)
{
throw;
}
Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, runCount)));
runCount++;
}
}
return null;
}
Для поиска кэшированных файлов создаётся FileService. Направленный в службу вызов метода GetFileContent пытается получить содержимое файла из кэша в памяти и вернуть его вызывающему объекту (Services/FileService.cs).
Если кэшированное содержимое не удается найти по ключу кэша, выполняются следующие действия:
- Содержимое файла получается с помощью
GetFileContent. - Токен изменений получается от поставщика файлов с помощью IFileProviders.Watch. При изменении файла активируется обратный вызов токена.
- Содержимое файла кэшируется с использованием скользящего срока действия. Токен изменений подключается к MemoryCacheEntryExtensions.AddExpirationToken для исключения записи кэша, если файл изменяется во время его кэширования.
В следующем примере файлы хранятся в корневом каталоге содержимого приложения.
IWebHostEnvironment.ContentRootFileProvider используется для получения IFileProvider, указывающего на IWebHostEnvironment.ContentRootPath приложения. Для получения filePath используется IFileInfo.PhysicalPath.
public class FileService
{
private readonly IMemoryCache _cache;
private readonly IFileProvider _fileProvider;
private List<string> _tokens = new List<string>();
public FileService(IMemoryCache cache, IWebHostEnvironment env)
{
_cache = cache;
_fileProvider = env.ContentRootFileProvider;
}
public async Task<string> GetFileContents(string fileName)
{
var filePath = _fileProvider.GetFileInfo(fileName).PhysicalPath;
string fileContent;
// Try to obtain the file contents from the cache.
if (_cache.TryGetValue(filePath, out fileContent))
{
return fileContent;
}
// The cache doesn't have the entry, so obtain the file
// contents from the file itself.
fileContent = await GetFileContent(filePath);
if (fileContent != null)
{
// Obtain a change token from the file provider whose
// callback is triggered when the file is modified.
var changeToken = _fileProvider.Watch(fileName);
// Configure the cache entry options for a five minute
// sliding expiration and use the change token to
// expire the file in the cache if the file is
// modified.
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(5))
.AddExpirationToken(changeToken);
// Put the file contents into the cache.
_cache.Set(filePath, fileContent, cacheEntryOptions);
return fileContent;
}
return string.Empty;
}
}
FileService регистрируется в контейнере служб вместе со службой кэширования в памяти.
В Startup.ConfigureServices:
services.AddMemoryCache();
services.AddSingleton<FileService>();
Страничная модель загружает содержимое файла с помощью службы.
В методе OnGet страницы Index (Pages/Index.cshtml.cs):
var fileContent = await _fileService.GetFileContents("poem.txt");
Класс CompositeChangeToken
Чтобы представить один или несколько экземпляров IChangeToken в одном объекте, используйте класс CompositeChangeToken.
var firstCancellationTokenSource = new CancellationTokenSource();
var secondCancellationTokenSource = new CancellationTokenSource();
var firstCancellationToken = firstCancellationTokenSource.Token;
var secondCancellationToken = secondCancellationTokenSource.Token;
var firstCancellationChangeToken = new CancellationChangeToken(firstCancellationToken);
var secondCancellationChangeToken = new CancellationChangeToken(secondCancellationToken);
var compositeChangeToken =
new CompositeChangeToken(
new List<IChangeToken>
{
firstCancellationChangeToken,
secondCancellationChangeToken
});
HasChanged для составного токена указывает true, если HasChanged любой из представленных токенов имеет значение true.
ActiveChangeCallbacks у составного токена указывает true, если любой из представленных токенов ActiveChangeCallbacks имеет значение true. Если возникает несколько одновременных событий изменения, обратный вызов составного изменения выполняется один раз.
Токен изменений — это низкоуровневый стандартный блок общего назначения, используемый для отслеживания изменений.
Просмотреть или скачать образец кода (описание загрузки)
Интерфейс IChangeToken
IChangeToken распространяет уведомления о том, что произошло изменение.
IChangeToken находится в пространстве имен Microsoft.Extensions.Primitives. Для приложений, которые не используют метапакет Microsoft.AspNetCore.App, создайте ссылку на пакет NuGet Microsoft.Extensions.Primitives.
IChangeToken имеет два свойства:
-
ActiveChangeCallbacks указывает, инициирует ли токен обратные вызовы заранее. Если для
ActiveChangedCallbacksзадано значениеfalse, обратный вызов не выполняется, а приложению нужно опроситьHasChangedна предмет изменений. Токен также может никогда не быть отменён, если изменения не происходят или базовый обработчик изменений освобождён либо отключён. - HasChanged получает значение, указывающее, произошло ли изменение.
Интерфейс IChangeToken включает метод RegisterChangeCallback(Action<Object>, Object), который регистрирует обратный вызов, выполняемый при изменении токена. Перед выполнением обратного вызова нужно задать HasChanged.
Класс ChangeToken
ChangeToken — это статический класс, который распространяет уведомления о том, что произошло изменение.
ChangeToken находится в пространстве имен Microsoft.Extensions.Primitives. Для приложений, которые не используют метапакет Microsoft.AspNetCore.App, создайте ссылку на пакет NuGet Microsoft.Extensions.Primitives.
Метод ChangeToken.OnChange(Func<IChangeToken>, Action) регистрирует объект Action, вызываемый при изменении токена:
-
Func<IChangeToken>создает токен. -
Actionвызывается при изменении токена.
Перегрузка ChangeToken.OnChange<TState>(Func<IChangeToken>, Action<TState>, TState) принимает дополнительный параметр TState, который передаётся потребителю токена Action.
OnChange возвращает IDisposable. При вызове Dispose токен прекращает прослушивать дальнейшие изменения и освобождает свои ресурсы.
Примеры использования токенов изменений в ASP.NET Core
Токены изменений используются в ключевых областях ASP.NET Core для отслеживания изменений в объектах:
- Чтобы отслеживать изменения в файлах, метод Watch объекта IFileProvider создает объект
IChangeTokenдля указанных файлов или папки, за которыми нужно следить. -
IChangeTokenтокены можно добавлять к записям кэша, чтобы при изменении инициировать удаление записей из кэша. - Для изменений
TOptionsиспользуемая по умолчанию реализация OptionsMonitor<TOptions> интерфейса IOptionsMonitor<TOptions> имеет перегрузку, которая принимает один или несколько экземпляров IOptionsChangeTokenSource<TOptions>. Каждый экземпляр возвращаетIChangeToken, чтобы зарегистрировать обратный вызов уведомления об изменении для отслеживания изменений параметров.
Отслеживание изменений конфигурации
По умолчанию шаблоны ASP.NET Core используют файлы конфигурации JSON (appsettings.json, appsettings.Development.json и appsettings.Production.json) для загрузки параметров конфигурации приложения.
Эти файлы настраиваются с помощью метода расширения AddJsonFile(IConfigurationBuilder, String, Boolean, Boolean) в ConfigurationBuilder, который принимает параметр reloadOnChange.
reloadOnChange указывает, нужно ли перезагружать конфигурацию при изменении файла. Эта настройка отображается во вспомогательном методе WebHostCreateDefaultBuilder:
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true,
reloadOnChange: true);
Файловая конфигурация представлена FileConfigurationSource.
FileConfigurationSource использует IFileProvider для отслеживания файлов.
По умолчанию IFileMonitor предоставляется объектом PhysicalFileProvider, который использует FileSystemWatcher для отслеживания изменений в файле конфигурации.
Этот пример приложения демонстрирует две реализации для отслеживания изменений конфигурации. Если любой из appsettings файлов изменяется, обе реализации мониторинга файлов выполняют пользовательский код— пример приложения записывает сообщение в консоль.
FileSystemWatcher файла конфигурации может активировать несколько обратных вызовов токена для одного изменения файла конфигурации. Чтобы пользовательский код выполнялся один раз при активации нескольких обратных вызовов токена, реализация в примере проверяет хэши файлов. В примере используется хэширование файлов SHA1. Повторные попытки выполняются с экспоненциальным увеличением интервала между ними.
Utilities/Utilities.cs:
public static byte[] ComputeHash(string filePath)
{
var runCount = 1;
while(runCount < 4)
{
try
{
if (File.Exists(filePath))
{
using (var fs = File.OpenRead(filePath))
{
return System.Security.Cryptography.SHA1
.Create().ComputeHash(fs);
}
}
else
{
throw new FileNotFoundException();
}
}
catch (IOException ex)
{
if (runCount == 3)
{
throw;
}
Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, runCount)));
runCount++;
}
}
return new byte[20];
}
Токен изменений для простого запуска
Зарегистрируйте обратный вызов Action получателя токена для получения уведомлений об изменениях от токена перезагрузки конфигурации.
В Startup.Configure:
ChangeToken.OnChange(
() => config.GetReloadToken(),
(state) => InvokeChanged(state),
env);
config.GetReloadToken() предоставляет токен. В качестве обратного вызова используется метод InvokeChanged:
private void InvokeChanged(IHostingEnvironment env)
{
byte[] appsettingsHash = ComputeHash("appSettings.json");
byte[] appsettingsEnvHash =
ComputeHash($"appSettings.{env.EnvironmentName}.json");
if (!_appsettingsHash.SequenceEqual(appsettingsHash) ||
!_appsettingsEnvHash.SequenceEqual(appsettingsEnvHash))
{
_appsettingsHash = appsettingsHash;
_appsettingsEnvHash = appsettingsEnvHash;
WriteConsole("Configuration changed (Simple Startup Change Token)");
}
}
Обратный state вызов используется для передачи IHostingEnvironment, что полезно для указания правильного appsettings файла конфигурации для мониторинга (например, appsettings.Development.json в среде Development). Хэши файлов используются, чтобы предотвратить многократное выполнение инструкции WriteConsole из-за нескольких срабатываний обратного вызова токена, даже если файл конфигурации изменился только один раз.
Эта система выполняется, пока запущено приложение, и не может быть отключена пользователем.
Отслеживание изменений конфигурации в качестве службы
Этот пример реализует следующее:
- Базовый мониторинг токена запуска.
- Отслеживание как служба.
- Механизм для включения и отключения отслеживания.
Этот пример определяет IConfigurationMonitor интерфейс.
Extensions/ConfigurationMonitor.cs:
public interface IConfigurationMonitor
{
bool MonitoringEnabled { get; set; }
string CurrentState { get; set; }
}
Конструктор реализованного класса ConfigurationMonitor регистрирует обратный вызов для уведомлений об изменениях:
public ConfigurationMonitor(IConfiguration config, IHostingEnvironment env)
{
_env = env;
ChangeToken.OnChange<IConfigurationMonitor>(
() => config.GetReloadToken(),
InvokeChanged,
this);
}
public bool MonitoringEnabled { get; set; } = false;
public string CurrentState { get; set; } = "Not monitoring";
config.GetReloadToken() предоставляет токен.
InvokeChanged является методом обратного вызова. В данном случае state — это ссылка на экземпляр IConfigurationMonitor, который используется для доступа к состоянию мониторинга. Используются два свойства:
-
MonitoringEnabled: указывает, должна ли функция обратного вызова выполнять свой пользовательский код. -
CurrentState: описывает текущее состояние мониторинга для использования в пользовательском интерфейсе.
Метод InvokeChanged похож на описанный ранее подход, за исключением того, что он:
- Не выполняет свой код, если только
MonitoringEnabledне равноtrue. - выводит текущее значение
stateв своих выходных данныхWriteConsole.
private void InvokeChanged(IConfigurationMonitor state)
{
if (MonitoringEnabled)
{
byte[] appsettingsHash = ComputeHash("appSettings.json");
byte[] appsettingsEnvHash =
ComputeHash($"appSettings.{_env.EnvironmentName}.json");
if (!_appsettingsHash.SequenceEqual(appsettingsHash) ||
!_appsettingsEnvHash.SequenceEqual(appsettingsEnvHash))
{
string message = $"State updated at {DateTime.Now}";
_appsettingsHash = appsettingsHash;
_appsettingsEnvHash = appsettingsEnvHash;
WriteConsole("Configuration changed (ConfigurationMonitor Class) " +
$"{message}, state:{state.CurrentState}");
}
}
}
Экземпляр ConfigurationMonitor регистрируется как служба в Startup.ConfigureServices:
services.AddSingleton<IConfigurationMonitor, ConfigurationMonitor>();
Страница индекса позволяет пользователю управлять отслеживанием конфигурации. Экземпляр IConfigurationMonitor внедряется в IndexModel.
Pages/Index.cshtml.cs:
public IndexModel(
IConfiguration config,
IConfigurationMonitor monitor,
FileService fileService)
{
_config = config;
_monitor = monitor;
_fileService = fileService;
}
Монитор конфигурации (_monitor) позволяет включить или отключить мониторинг и задать текущее состояние для обратной связи пользовательского интерфейса:
public IActionResult OnPostStartMonitoring()
{
_monitor.MonitoringEnabled = true;
_monitor.CurrentState = "Monitoring!";
return RedirectToPage();
}
public IActionResult OnPostStopMonitoring()
{
_monitor.MonitoringEnabled = false;
_monitor.CurrentState = "Not monitoring";
return RedirectToPage();
}
При активации OnPostStartMonitoring отслеживание включается, а текущее состояние сбрасывается. При активации OnPostStopMonitoring отслеживание отключается, а состояние указывает на отсутствие отслеживания.
Кнопки пользовательского интерфейса для включения и отключения отслеживания.
Pages/Index.cshtml:
<button class="btn btn-success" asp-page-handler="StartMonitoring">
Start Monitoring
</button>
<button class="btn btn-danger" asp-page-handler="StopMonitoring">
Stop Monitoring
</button>
Отслеживание изменений кэшированных файлов
Содержимое файла можно кэшировать в памяти с помощью IMemoryCache. Кэширование в памяти описано в разделе Кэш в памяти. Устаревшие (просроченные) данные возвращаются из кэша при изменении источника исходных данных без каких-либо дополнительных действий, таких как описанная ниже реализация.
Например, если не учитывать состояние кэшированного исходного файла при продлении скользящего срока действия, это приведет к появлению в кэше устаревших данных файлов. Каждый запрос к данным продляет скользящий срок действия, но этот файл никогда не загружается в кэш. Любые функции приложения, использующие кэшированное содержимое, могут получить устаревшее содержимое.
Использование токенов изменений в сценарии кэширования файлов предотвращает появление устаревшего содержимого файлов в кэше. Этот пример демонстрирует реализацию данного подхода.
GetFileContent в примере используется для:
- Вернуть содержимое файла.
- Реализуйте алгоритм повторных попыток с экспоненциальной задержкой для обработки случаев, когда проблема доступа к файлу временно препятствует чтению его содержимого.
Utilities/Utilities.cs:
public async static Task<string> GetFileContent(string filePath)
{
var runCount = 1;
while(runCount < 4)
{
try
{
if (File.Exists(filePath))
{
using (var fileStreamReader = File.OpenText(filePath))
{
return await fileStreamReader.ReadToEndAsync();
}
}
else
{
throw new FileNotFoundException();
}
}
catch (IOException ex)
{
if (runCount == 3 || ex.HResult != -2147024864)
{
throw;
}
else
{
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, runCount)));
runCount++;
}
}
}
return null;
}
Для поиска кэшированных файлов создаётся FileService. Направленный в службу вызов метода GetFileContent пытается получить содержимое файла из кэша в памяти и вернуть его вызывающему объекту (Services/FileService.cs).
Если кэшированное содержимое не удается найти по ключу кэша, выполняются следующие действия:
- Содержимое файла получается с помощью
GetFileContent. - Токен изменений получается от поставщика файлов с помощью IFileProviders.Watch. При изменении файла активируется обратный вызов токена.
- Содержимое файла кэшируется с использованием скользящего срока действия. Токен изменений подключается к MemoryCacheEntryExtensions.AddExpirationToken для исключения записи кэша, если файл изменяется во время его кэширования.
В следующем примере файлы хранятся в корневом каталоге содержимого приложения.
IHostingEnvironment.ContentRootFileProvider используется для получения объекта IFileProvider, который указывает на ContentRootPath приложения. Для получения filePath используется IFileInfo.PhysicalPath.
public class FileService
{
private readonly IMemoryCache _cache;
private readonly IFileProvider _fileProvider;
private List<string> _tokens = new List<string>();
public FileService(IMemoryCache cache, IHostingEnvironment env)
{
_cache = cache;
_fileProvider = env.ContentRootFileProvider;
}
public async Task<string> GetFileContents(string fileName)
{
var filePath = _fileProvider.GetFileInfo(fileName).PhysicalPath;
string fileContent;
// Try to obtain the file contents from the cache.
if (_cache.TryGetValue(filePath, out fileContent))
{
return fileContent;
}
// The cache doesn't have the entry, so obtain the file
// contents from the file itself.
fileContent = await GetFileContent(filePath);
if (fileContent != null)
{
// Obtain a change token from the file provider whose
// callback is triggered when the file is modified.
var changeToken = _fileProvider.Watch(fileName);
// Configure the cache entry options for a five minute
// sliding expiration and use the change token to
// expire the file in the cache if the file is
// modified.
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(5))
.AddExpirationToken(changeToken);
// Put the file contents into the cache.
_cache.Set(filePath, fileContent, cacheEntryOptions);
return fileContent;
}
return string.Empty;
}
}
FileService регистрируется в контейнере служб вместе со службой кэширования в памяти.
В Startup.ConfigureServices:
services.AddMemoryCache();
services.AddSingleton<FileService>();
Страничная модель загружает содержимое файла с помощью службы.
В методе OnGet страницы Index (Pages/Index.cshtml.cs):
var fileContent = await _fileService.GetFileContents("poem.txt");
Класс CompositeChangeToken
Чтобы представить один или несколько экземпляров IChangeToken в одном объекте, используйте класс CompositeChangeToken.
var firstCancellationTokenSource = new CancellationTokenSource();
var secondCancellationTokenSource = new CancellationTokenSource();
var firstCancellationToken = firstCancellationTokenSource.Token;
var secondCancellationToken = secondCancellationTokenSource.Token;
var firstCancellationChangeToken = new CancellationChangeToken(firstCancellationToken);
var secondCancellationChangeToken = new CancellationChangeToken(secondCancellationToken);
var compositeChangeToken =
new CompositeChangeToken(
new List<IChangeToken>
{
firstCancellationChangeToken,
secondCancellationChangeToken
});
HasChanged у составного токена указывает true, если любой из представленных токенов HasChanged имеет значение true.
ActiveChangeCallbacks у составного токена указывает true, если любой из представленных токенов ActiveChangeCallbacks имеет значение true. Если возникает несколько одновременных событий изменения, обратный вызов составного изменения выполняется один раз.
Дополнительные ресурсы
ASP.NET Core