使用 ASP.NET Core 中的更改令牌检测更改Detect changes with change tokens in ASP.NET Core

本文内容

更改令牌 是用于跟踪状态更改的通用、低级别构建基块。

查看或下载示例代码如何下载

IChangeToken 接口IChangeToken interface

IChangeToken 传播已发生更改的通知。IChangeToken 驻留在 Microsoft.Extensions.Primitives 命名空间中。Microsoft.Extensions.Primitives NuGet 包隐式提供给 ASP.NET Core 应用。

IChangeToken 具有以下两个属性:

  • ActiveChangeCallbacks,指示令牌是否主动引发回调。如果将 ActiveChangedCallbacks 设置为 false,则不会调用回调,并且应用必须轮询 HasChanged 获取更改。如果未发生任何更改,或者丢弃或禁用基础更改侦听器,还可能永远不会取消令牌。
  • HasChanged,接收一个指示是否发生更改的值。

IChangeToken 接口具有 RegisterChangeCallback(Action<Object>, Object) 方法,用于注册在令牌更改时调用的回调。调用回调之前,必须设置 HasChanged

ChangeToken 类ChangeToken class

ChangeToken 是静态类,用于传播已发生更改的通知。ChangeToken 驻留在 Microsoft.Extensions.Primitives 命名空间中。Microsoft.Extensions.Primitives NuGet 包隐式提供给 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 中更改令牌的使用示例Example uses of change tokens in ASP.NET Core

更改令牌主要用于在 ASP.NET Core 中监视对象更改:

  • 为了监视文件更改,IFileProviderWatch 方法将为要监视的指定文件或文件夹创建 IChangeToken
  • 可以将 IChangeToken 令牌添加到缓存条目,以在更改时触发缓存逐出。
  • 对于 TOptions 更改,IOptionsMonitor 的默认 OptionsMonitor 实现有一个重载,可接受一个或多个 IOptionsChangeTokenSource 实例。每个实例返回 IChangeToken,以注册用于跟踪选项更改的更改通知回调。

监视配置更改Monitor for configuration changes

默认情况下,ASP.NET Core 模板使用 JSON 配置文件(appsettings.json 、appsettings.Development.json 和 appsettings.Production.json )来加载应用配置设置。

使用接受 reloadOnChange 参数的 ConfigurationBuilder 上的 AddJsonFile(IConfigurationBuilder, String, Boolean, Boolean) 扩展方法配置这些文件。reloadOnChange 指示文件更改时是否应该重载配置。此设置出现在 Host 便捷方法 CreateDefaultBuilder 中:

  1. config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
  2. .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true,
  3. reloadOnChange: true);

基于文件的配置由 FileConfigurationSource 表示。FileConfigurationSource 使用 IFileProvider 来监视文件。

默认情况下,由使用 FileSystemWatcher 来监视配置文件更改的 PhysicalFileProvider 提供 IFileMonitor

示例应用演示监视配置更改的两个实现。如果任一 appsettings 文件发生更改,这两个文件监视实现将执行自定义代码:示例应用向控制台写入一条消息。

配置文件的 FileSystemWatcher 可以触发多个令牌回调,以用于单个配置文件更改。为确保自定义代码仅在触发多个令牌回调时运行一次,示例的实现将检查文件哈希。此示例使用 SHA1 文件哈希。使用指数回退实现重试。进行重试是因为可能发生文件锁定,暂时阻止对某个文件计算新的哈希。

Utilities/Utilities.cs :

  1. public static byte[] ComputeHash(string filePath)
  2. {
  3. var runCount = 1;
  4. while(runCount < 4)
  5. {
  6. try
  7. {
  8. if (File.Exists(filePath))
  9. {
  10. using (var fs = File.OpenRead(filePath))
  11. {
  12. return System.Security.Cryptography.SHA1
  13. .Create().ComputeHash(fs);
  14. }
  15. }
  16. else
  17. {
  18. throw new FileNotFoundException();
  19. }
  20. }
  21. catch (IOException ex)
  22. {
  23. if (runCount == 3 || ex.HResult != -2147024864)
  24. {
  25. throw;
  26. }
  27. else
  28. {
  29. Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, runCount)));
  30. runCount++;
  31. }
  32. }
  33. }
  34. return new byte[20];
  35. }

简单启动更改令牌Simple startup change token

将用于更改通知的令牌使用者 Action 回调注册到配置重载令牌。

Startup.Configure中:

  1. ChangeToken.OnChange(
  2. () => config.GetReloadToken(),
  3. (state) => InvokeChanged(state),
  4. env);

config.GetReloadToken() 提供令牌。回调是 InvokeChanged 方法:

  1. private void InvokeChanged(IWebHostEnvironment env)
  2. {
  3. byte[] appsettingsHash = ComputeHash("appSettings.json");
  4. byte[] appsettingsEnvHash =
  5. ComputeHash($"appSettings.{env.EnvironmentName}.json");
  6. if (!_appsettingsHash.SequenceEqual(appsettingsHash) ||
  7. !_appsettingsEnvHash.SequenceEqual(appsettingsEnvHash))
  8. {
  9. _appsettingsHash = appsettingsHash;
  10. _appsettingsEnvHash = appsettingsEnvHash;
  11. WriteConsole("Configuration changed (Simple Startup Change Token)");
  12. }
  13. }

回调的 state 用于在 IWebHostEnvironment 中传递,这可以帮助指定要监视的正确 appsettings 配置文件(例如开发环境中的 appsettings.Development.json )。只更改了一次配置文件时,文件哈希用于防止 WriteConsole 语句由于多个令牌回调而多次运行。

只要应用正在运行,该系统就会运行,并且用户不能禁用。

将配置更改作为服务进行监视Monitor configuration changes as a service

示例实现:

  • 基本启动令牌监视。
  • 作为服务监视。
  • 启用和禁用监视的机制。

示例建立 IConfigurationMonitor 接口。

Extensions/ConfigurationMonitor.cs :

  1. public interface IConfigurationMonitor
  2. {
  3. bool MonitoringEnabled { get; set; }
  4. string CurrentState { get; set; }
  5. }

已实现的类的构造函数 ConfigurationMonitor 注册用于更改通知的回调:

  1. public ConfigurationMonitor(IConfiguration config, IWebHostEnvironment env)
  2. {
  3. _env = env;
  4. ChangeToken.OnChange<IConfigurationMonitor>(
  5. () => config.GetReloadToken(),
  6. InvokeChanged,
  7. this);
  8. }
  9. public bool MonitoringEnabled { get; set; } = false;
  10. public string CurrentState { get; set; } = "Not monitoring";

config.GetReloadToken() 提供令牌。InvokeChanged 是回调方法。此实例中的 state 是对 IConfigurationMonitor 实例的引用,用于访问监视状态。使用了以下两个属性:

  • MonitoringEnabled – 指示回调是否应该运行其自定义代码。
  • CurrentState – 描述在 UI 中使用的当前监视状态。

InvokeChanged 方法类似于前面的方法,不同之处在于:

  • 不运行其代码,除非 MonitoringEnabledtrue
  • 输出其 WriteConsole 输出中的当前 state
  1. private void InvokeChanged(IConfigurationMonitor state)
  2. {
  3. if (MonitoringEnabled)
  4. {
  5. byte[] appsettingsHash = ComputeHash("appSettings.json");
  6. byte[] appsettingsEnvHash =
  7. ComputeHash($"appSettings.{_env.EnvironmentName}.json");
  8. if (!_appsettingsHash.SequenceEqual(appsettingsHash) ||
  9. !_appsettingsEnvHash.SequenceEqual(appsettingsEnvHash))
  10. {
  11. string message = $"State updated at {DateTime.Now}";
  12. _appsettingsHash = appsettingsHash;
  13. _appsettingsEnvHash = appsettingsEnvHash;
  14. WriteConsole("Configuration changed (ConfigurationMonitor Class) " +
  15. $"{message}, state:{state.CurrentState}");
  16. }
  17. }
  18. }

将实例 ConfigurationMonitor 作为服务在 Startup.ConfigureServices 中注册:

  1. services.AddSingleton<IConfigurationMonitor, ConfigurationMonitor>();

索引页提供对配置监视的用户控制。IConfigurationMonitor 的实例注入到 IndexModel 中。

Pages/Index.cshtml.cs:

  1. public IndexModel(
  2. IConfiguration config,
  3. IConfigurationMonitor monitor,
  4. FileService fileService)
  5. {
  6. _config = config;
  7. _monitor = monitor;
  8. _fileService = fileService;
  9. }

配置监视器 (_monitor) 用于启用或禁用监视,并设置 UI 反馈的当前状态:

  1. public IActionResult OnPostStartMonitoring()
  2. {
  3. _monitor.MonitoringEnabled = true;
  4. _monitor.CurrentState = "Monitoring!";
  5. return RedirectToPage();
  6. }
  7. public IActionResult OnPostStopMonitoring()
  8. {
  9. _monitor.MonitoringEnabled = false;
  10. _monitor.CurrentState = "Not monitoring";
  11. return RedirectToPage();
  12. }

触发 OnPostStartMonitoring 时,会启用监视并清除当前状态。触发 OnPostStopMonitoring 时,会禁用监视并设置状态以反映未进行监视。

UI 启用和禁用监视的按钮。

Pages/Index.cshtml:

  1. <button class="btn btn-success" asp-page-handler="StartMonitoring">
  2. Start Monitoring
  3. </button>
  4. <button class="btn btn-danger" asp-page-handler="StopMonitoring">
  5. Stop Monitoring
  6. </button>

监视缓存文件更改Monitor cached file changes

可以使用 IMemoryCache 将文件内容缓存在内存中。内存中缓存主题中介绍了在内存中缓存。无需采取其他步骤(如下面所述的实现),如果源数据更改,将从缓存返回陈旧 (过时)数据。

例如,当续订可调过期时段造成陈旧缓存文件数据时,不考虑所缓存源文件的状态。数据的每个请求续订可调过期时段,但不会将文件重载到缓存中。任何使用文件缓存内容的应用功能都可能会收到陈旧的内容。

在文件缓存方案中使用更改令牌可防止缓存中出现陈旧的文件内容。示例应用演示方法的实现。

示例使用 GetFileContent 来完成以下操作:

  • 返回文件内容。
  • 实现具有指数回退的重试算法,以涵盖文件锁暂时阻止读取文件的情况。

Utilities/Utilities.cs :

  1. public async static Task<string> GetFileContent(string filePath)
  2. {
  3. var runCount = 1;
  4. while(runCount < 4)
  5. {
  6. try
  7. {
  8. if (File.Exists(filePath))
  9. {
  10. using (var fileStreamReader = File.OpenText(filePath))
  11. {
  12. return await fileStreamReader.ReadToEndAsync();
  13. }
  14. }
  15. else
  16. {
  17. throw new FileNotFoundException();
  18. }
  19. }
  20. catch (IOException ex)
  21. {
  22. if (runCount == 3 || ex.HResult != -2147024864)
  23. {
  24. throw;
  25. }
  26. else
  27. {
  28. await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, runCount)));
  29. runCount++;
  30. }
  31. }
  32. }
  33. return null;
  34. }

创建 FileService 以处理缓存文件查找。服务的 GetFileContent 方法调用尝试从内存中缓存获取文件内容并将其返回到调用方 (Services/FileService.cs )。

如果使用缓存键未找到缓存的内容,则将执行以下操作:

  1. public class FileService
  2. {
  3. private readonly IMemoryCache _cache;
  4. private readonly IFileProvider _fileProvider;
  5. private List<string> _tokens = new List<string>();
  6. public FileService(IMemoryCache cache, IWebHostEnvironment env)
  7. {
  8. _cache = cache;
  9. _fileProvider = env.ContentRootFileProvider;
  10. }
  11. public async Task<string> GetFileContents(string fileName)
  12. {
  13. var filePath = _fileProvider.GetFileInfo(fileName).PhysicalPath;
  14. string fileContent;
  15. // Try to obtain the file contents from the cache.
  16. if (_cache.TryGetValue(filePath, out fileContent))
  17. {
  18. return fileContent;
  19. }
  20. // The cache doesn't have the entry, so obtain the file
  21. // contents from the file itself.
  22. fileContent = await GetFileContent(filePath);
  23. if (fileContent != null)
  24. {
  25. // Obtain a change token from the file provider whose
  26. // callback is triggered when the file is modified.
  27. var changeToken = _fileProvider.Watch(fileName);
  28. // Configure the cache entry options for a five minute
  29. // sliding expiration and use the change token to
  30. // expire the file in the cache if the file is
  31. // modified.
  32. var cacheEntryOptions = new MemoryCacheEntryOptions()
  33. .SetSlidingExpiration(TimeSpan.FromMinutes(5))
  34. .AddExpirationToken(changeToken);
  35. // Put the file contents into the cache.
  36. _cache.Set(filePath, fileContent, cacheEntryOptions);
  37. return fileContent;
  38. }
  39. return string.Empty;
  40. }
  41. }

FileService 与内存缓存服务一起注册在服务容器中。

Startup.ConfigureServices中:

  1. services.AddMemoryCache();
  2. services.AddSingleton<FileService>();

页面模型使用服务加载文件内容。

在索引页的 OnGet 方法 (Pages/Index.cshtml.cs ) 中:

  1. var fileContent = await _fileService.GetFileContents("poem.txt");

CompositeChangeToken 类CompositeChangeToken class

要在单个对象中表示一个或多个 IChangeToken 实例,请使用 CompositeChangeToken 类。

  1. var firstCancellationTokenSource = new CancellationTokenSource();
  2. var secondCancellationTokenSource = new CancellationTokenSource();
  3. var firstCancellationToken = firstCancellationTokenSource.Token;
  4. var secondCancellationToken = secondCancellationTokenSource.Token;
  5. var firstCancellationChangeToken = new CancellationChangeToken(firstCancellationToken);
  6. var secondCancellationChangeToken = new CancellationChangeToken(secondCancellationToken);
  7. var compositeChangeToken =
  8. new CompositeChangeToken(
  9. new List<IChangeToken>
  10. {
  11. firstCancellationChangeToken,
  12. secondCancellationChangeToken
  13. });

如果任何表示的令牌 HasChangedtrue,则复合令牌上的 HasChanged 报告 true如果任何表示的令牌 ActiveChangeCallbackstrue,则复合令牌上的 ActiveChangeCallbacks 报告 true如果发生多个并发更改事件,则调用一次复合更改回调。

更改令牌 是用于跟踪状态更改的通用、低级别构建基块。

查看或下载示例代码如何下载

IChangeToken 接口IChangeToken interface

IChangeToken 传播已发生更改的通知。IChangeToken 驻留在 Microsoft.Extensions.Primitives 命名空间中。对于不使用 Microsoft.AspNetCore.App 元包的应用,将为 Microsoft.Extensions.Primitives NuGet 包创建一个包引用。

IChangeToken 具有以下两个属性:

  • ActiveChangeCallbacks,指示令牌是否主动引发回调。如果将 ActiveChangedCallbacks 设置为 false,则不会调用回调,并且应用必须轮询 HasChanged 获取更改。如果未发生任何更改,或者丢弃或禁用基础更改侦听器,还可能永远不会取消令牌。
  • HasChanged,接收一个指示是否发生更改的值。

IChangeToken 接口具有 RegisterChangeCallback(Action<Object>, Object) 方法,用于注册在令牌更改时调用的回调。调用回调之前,必须设置 HasChanged

ChangeToken 类ChangeToken class

ChangeToken 是静态类,用于传播已发生更改的通知。ChangeToken 驻留在 Microsoft.Extensions.Primitives 命名空间中。对于不使用 Microsoft.AspNetCore.App 元包的应用,将为 Microsoft.Extensions.Primitives NuGet 包创建一个包引用。

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 中更改令牌的使用示例Example uses of change tokens in ASP.NET Core

更改令牌主要用于在 ASP.NET Core 中监视对象更改:

  • 为了监视文件更改,IFileProviderWatch 方法将为要监视的指定文件或文件夹创建 IChangeToken
  • 可以将 IChangeToken 令牌添加到缓存条目,以在更改时触发缓存逐出。
  • 对于 TOptions 更改,IOptionsMonitor 的默认 OptionsMonitor 实现有一个重载,可接受一个或多个 IOptionsChangeTokenSource 实例。每个实例返回 IChangeToken,以注册用于跟踪选项更改的更改通知回调。

监视配置更改Monitor for configuration changes

默认情况下,ASP.NET Core 模板使用 JSON 配置文件(appsettings.json 、appsettings.Development.json 和 appsettings.Production.json )来加载应用配置设置。

使用接受 reloadOnChange 参数的 ConfigurationBuilder 上的 AddJsonFile(IConfigurationBuilder, String, Boolean, Boolean) 扩展方法配置这些文件。reloadOnChange 指示文件更改时是否应该重载配置。此设置出现在 WebHost 便捷方法 CreateDefaultBuilder 中:

  1. config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
  2. .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true,
  3. reloadOnChange: true);

基于文件的配置由 FileConfigurationSource 表示。FileConfigurationSource 使用 IFileProvider 来监视文件。

默认情况下,由使用 FileSystemWatcher 来监视配置文件更改的 PhysicalFileProvider 提供 IFileMonitor

示例应用演示监视配置更改的两个实现。如果任一 appsettings 文件发生更改,这两个文件监视实现将执行自定义代码:示例应用向控制台写入一条消息。

配置文件的 FileSystemWatcher 可以触发多个令牌回调,以用于单个配置文件更改。为确保自定义代码仅在触发多个令牌回调时运行一次,示例的实现将检查文件哈希。此示例使用 SHA1 文件哈希。使用指数回退实现重试。进行重试是因为可能发生文件锁定,暂时阻止对某个文件计算新的哈希。

Utilities/Utilities.cs :

  1. public static byte[] ComputeHash(string filePath)
  2. {
  3. var runCount = 1;
  4. while(runCount < 4)
  5. {
  6. try
  7. {
  8. if (File.Exists(filePath))
  9. {
  10. using (var fs = File.OpenRead(filePath))
  11. {
  12. return System.Security.Cryptography.SHA1
  13. .Create().ComputeHash(fs);
  14. }
  15. }
  16. else
  17. {
  18. throw new FileNotFoundException();
  19. }
  20. }
  21. catch (IOException ex)
  22. {
  23. if (runCount == 3 || ex.HResult != -2147024864)
  24. {
  25. throw;
  26. }
  27. else
  28. {
  29. Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, runCount)));
  30. runCount++;
  31. }
  32. }
  33. }
  34. return new byte[20];
  35. }

简单启动更改令牌Simple startup change token

将用于更改通知的令牌使用者 Action 回调注册到配置重载令牌。

Startup.Configure中:

  1. ChangeToken.OnChange(
  2. () => config.GetReloadToken(),
  3. (state) => InvokeChanged(state),
  4. env);

config.GetReloadToken() 提供令牌。回调是 InvokeChanged 方法:

  1. private void InvokeChanged(IHostingEnvironment env)
  2. {
  3. byte[] appsettingsHash = ComputeHash("appSettings.json");
  4. byte[] appsettingsEnvHash =
  5. ComputeHash($"appSettings.{env.EnvironmentName}.json");
  6. if (!_appsettingsHash.SequenceEqual(appsettingsHash) ||
  7. !_appsettingsEnvHash.SequenceEqual(appsettingsEnvHash))
  8. {
  9. _appsettingsHash = appsettingsHash;
  10. _appsettingsEnvHash = appsettingsEnvHash;
  11. WriteConsole("Configuration changed (Simple Startup Change Token)");
  12. }
  13. }

回调的 state 用于在 IHostingEnvironment 中传递,这可以帮助指定要监视的正确 appsettings 配置文件(例如开发环境中的 appsettings.Development.json )。只更改了一次配置文件时,文件哈希用于防止 WriteConsole 语句由于多个令牌回调而多次运行。

只要应用正在运行,该系统就会运行,并且用户不能禁用。

将配置更改作为服务进行监视Monitor configuration changes as a service

示例实现:

  • 基本启动令牌监视。
  • 作为服务监视。
  • 启用和禁用监视的机制。

示例建立 IConfigurationMonitor 接口。

Extensions/ConfigurationMonitor.cs :

  1. public interface IConfigurationMonitor
  2. {
  3. bool MonitoringEnabled { get; set; }
  4. string CurrentState { get; set; }
  5. }

已实现的类的构造函数 ConfigurationMonitor 注册用于更改通知的回调:

  1. public ConfigurationMonitor(IConfiguration config, IHostingEnvironment env)
  2. {
  3. _env = env;
  4. ChangeToken.OnChange<IConfigurationMonitor>(
  5. () => config.GetReloadToken(),
  6. InvokeChanged,
  7. this);
  8. }
  9. public bool MonitoringEnabled { get; set; } = false;
  10. public string CurrentState { get; set; } = "Not monitoring";

config.GetReloadToken() 提供令牌。InvokeChanged 是回调方法。此实例中的 state 是对 IConfigurationMonitor 实例的引用,用于访问监视状态。使用了以下两个属性:

  • MonitoringEnabled – 指示回调是否应该运行其自定义代码。
  • CurrentState – 描述在 UI 中使用的当前监视状态。

InvokeChanged 方法类似于前面的方法,不同之处在于:

  • 不运行其代码,除非 MonitoringEnabledtrue
  • 输出其 WriteConsole 输出中的当前 state
  1. private void InvokeChanged(IConfigurationMonitor state)
  2. {
  3. if (MonitoringEnabled)
  4. {
  5. byte[] appsettingsHash = ComputeHash("appSettings.json");
  6. byte[] appsettingsEnvHash =
  7. ComputeHash($"appSettings.{_env.EnvironmentName}.json");
  8. if (!_appsettingsHash.SequenceEqual(appsettingsHash) ||
  9. !_appsettingsEnvHash.SequenceEqual(appsettingsEnvHash))
  10. {
  11. string message = $"State updated at {DateTime.Now}";
  12. _appsettingsHash = appsettingsHash;
  13. _appsettingsEnvHash = appsettingsEnvHash;
  14. WriteConsole("Configuration changed (ConfigurationMonitor Class) " +
  15. $"{message}, state:{state.CurrentState}");
  16. }
  17. }
  18. }

将实例 ConfigurationMonitor 作为服务在 Startup.ConfigureServices 中注册:

  1. services.AddSingleton<IConfigurationMonitor, ConfigurationMonitor>();

索引页提供对配置监视的用户控制。IConfigurationMonitor 的实例注入到 IndexModel 中。

Pages/Index.cshtml.cs:

  1. public IndexModel(
  2. IConfiguration config,
  3. IConfigurationMonitor monitor,
  4. FileService fileService)
  5. {
  6. _config = config;
  7. _monitor = monitor;
  8. _fileService = fileService;
  9. }

配置监视器 (_monitor) 用于启用或禁用监视,并设置 UI 反馈的当前状态:

  1. public IActionResult OnPostStartMonitoring()
  2. {
  3. _monitor.MonitoringEnabled = true;
  4. _monitor.CurrentState = "Monitoring!";
  5. return RedirectToPage();
  6. }
  7. public IActionResult OnPostStopMonitoring()
  8. {
  9. _monitor.MonitoringEnabled = false;
  10. _monitor.CurrentState = "Not monitoring";
  11. return RedirectToPage();
  12. }

触发 OnPostStartMonitoring 时,会启用监视并清除当前状态。触发 OnPostStopMonitoring 时,会禁用监视并设置状态以反映未进行监视。

UI 启用和禁用监视的按钮。

Pages/Index.cshtml:

  1. <button class="btn btn-success" asp-page-handler="StartMonitoring">
  2. Start Monitoring
  3. </button>
  4. <button class="btn btn-danger" asp-page-handler="StopMonitoring">
  5. Stop Monitoring
  6. </button>

监视缓存文件更改Monitor cached file changes

可以使用 IMemoryCache 将文件内容缓存在内存中。内存中缓存主题中介绍了在内存中缓存。无需采取其他步骤(如下面所述的实现),如果源数据更改,将从缓存返回陈旧 (过时)数据。

例如,当续订可调过期时段造成陈旧缓存文件数据时,不考虑所缓存源文件的状态。数据的每个请求续订可调过期时段,但不会将文件重载到缓存中。任何使用文件缓存内容的应用功能都可能会收到陈旧的内容。

在文件缓存方案中使用更改令牌可防止缓存中出现陈旧的文件内容。示例应用演示方法的实现。

示例使用 GetFileContent 来完成以下操作:

  • 返回文件内容。
  • 实现具有指数回退的重试算法,以涵盖文件锁暂时阻止读取文件的情况。

Utilities/Utilities.cs :

  1. public async static Task<string> GetFileContent(string filePath)
  2. {
  3. var runCount = 1;
  4. while(runCount < 4)
  5. {
  6. try
  7. {
  8. if (File.Exists(filePath))
  9. {
  10. using (var fileStreamReader = File.OpenText(filePath))
  11. {
  12. return await fileStreamReader.ReadToEndAsync();
  13. }
  14. }
  15. else
  16. {
  17. throw new FileNotFoundException();
  18. }
  19. }
  20. catch (IOException ex)
  21. {
  22. if (runCount == 3 || ex.HResult != -2147024864)
  23. {
  24. throw;
  25. }
  26. else
  27. {
  28. await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, runCount)));
  29. runCount++;
  30. }
  31. }
  32. }
  33. return null;
  34. }

创建 FileService 以处理缓存文件查找。服务的 GetFileContent 方法调用尝试从内存中缓存获取文件内容并将其返回到调用方 (Services/FileService.cs )。

如果使用缓存键未找到缓存的内容,则将执行以下操作:

  1. public class FileService
  2. {
  3. private readonly IMemoryCache _cache;
  4. private readonly IFileProvider _fileProvider;
  5. private List<string> _tokens = new List<string>();
  6. public FileService(IMemoryCache cache, IHostingEnvironment env)
  7. {
  8. _cache = cache;
  9. _fileProvider = env.ContentRootFileProvider;
  10. }
  11. public async Task<string> GetFileContents(string fileName)
  12. {
  13. var filePath = _fileProvider.GetFileInfo(fileName).PhysicalPath;
  14. string fileContent;
  15. // Try to obtain the file contents from the cache.
  16. if (_cache.TryGetValue(filePath, out fileContent))
  17. {
  18. return fileContent;
  19. }
  20. // The cache doesn't have the entry, so obtain the file
  21. // contents from the file itself.
  22. fileContent = await GetFileContent(filePath);
  23. if (fileContent != null)
  24. {
  25. // Obtain a change token from the file provider whose
  26. // callback is triggered when the file is modified.
  27. var changeToken = _fileProvider.Watch(fileName);
  28. // Configure the cache entry options for a five minute
  29. // sliding expiration and use the change token to
  30. // expire the file in the cache if the file is
  31. // modified.
  32. var cacheEntryOptions = new MemoryCacheEntryOptions()
  33. .SetSlidingExpiration(TimeSpan.FromMinutes(5))
  34. .AddExpirationToken(changeToken);
  35. // Put the file contents into the cache.
  36. _cache.Set(filePath, fileContent, cacheEntryOptions);
  37. return fileContent;
  38. }
  39. return string.Empty;
  40. }
  41. }

FileService 与内存缓存服务一起注册在服务容器中。

Startup.ConfigureServices中:

  1. services.AddMemoryCache();
  2. services.AddSingleton<FileService>();

页面模型使用服务加载文件内容。

在索引页的 OnGet 方法 (Pages/Index.cshtml.cs ) 中:

  1. var fileContent = await _fileService.GetFileContents("poem.txt");

CompositeChangeToken 类CompositeChangeToken class

要在单个对象中表示一个或多个 IChangeToken 实例,请使用 CompositeChangeToken 类。

  1. var firstCancellationTokenSource = new CancellationTokenSource();
  2. var secondCancellationTokenSource = new CancellationTokenSource();
  3. var firstCancellationToken = firstCancellationTokenSource.Token;
  4. var secondCancellationToken = secondCancellationTokenSource.Token;
  5. var firstCancellationChangeToken = new CancellationChangeToken(firstCancellationToken);
  6. var secondCancellationChangeToken = new CancellationChangeToken(secondCancellationToken);
  7. var compositeChangeToken =
  8. new CompositeChangeToken(
  9. new List<IChangeToken>
  10. {
  11. firstCancellationChangeToken,
  12. secondCancellationChangeToken
  13. });

如果任何表示的令牌 HasChangedtrue,则复合令牌上的 HasChanged 报告 true如果任何表示的令牌 ActiveChangeCallbackstrue,则复合令牌上的 ActiveChangeCallbacks 报告 true如果发生多个并发更改事件,则调用一次复合更改回调。

其他资源Additional resources