- 使用 ASP.NET Core 中的更改令牌检测更改Detect changes with change tokens in ASP.NET Core
- IChangeToken 接口IChangeToken interface
- ChangeToken 类ChangeToken class
- ASP.NET Core 中更改令牌的使用示例Example uses of change tokens in ASP.NET Core
- 监视配置更改Monitor for configuration changes
- 监视缓存文件更改Monitor cached file changes
- CompositeChangeToken 类CompositeChangeToken class
- IChangeToken 接口IChangeToken interface
- ChangeToken 类ChangeToken class
- ASP.NET Core 中更改令牌的使用示例Example uses of change tokens in ASP.NET Core
- 监视配置更改Monitor for configuration changes
- 监视缓存文件更改Monitor cached file changes
- CompositeChangeToken 类CompositeChangeToken class
- 其他资源Additional resources
使用 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 中监视对象更改:
- 为了监视文件更改,IFileProvider 的 Watch 方法将为要监视的指定文件或文件夹创建
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 中:
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true,
reloadOnChange: true);
基于文件的配置由 FileConfigurationSource 表示。FileConfigurationSource
使用 IFileProvider 来监视文件。
默认情况下,由使用 FileSystemWatcher 来监视配置文件更改的 PhysicalFileProvider 提供 IFileMonitor
。
示例应用演示监视配置更改的两个实现。如果任一 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 || ex.HResult != -2147024864)
{
throw;
}
else
{
Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, runCount)));
runCount++;
}
}
}
return new byte[20];
}
简单启动更改令牌Simple startup change token
将用于更改通知的令牌使用者 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 )。只更改了一次配置文件时,文件哈希用于防止 WriteConsole
语句由于多个令牌回调而多次运行。
只要应用正在运行,该系统就会运行,并且用户不能禁用。
将配置更改作为服务进行监视Monitor configuration changes as a service
示例实现:
- 基本启动令牌监视。
- 作为服务监视。
- 启用和禁用监视的机制。
示例建立 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
– 描述在 UI 中使用的当前监视状态。
InvokeChanged
方法类似于前面的方法,不同之处在于:
- 不运行其代码,除非
MonitoringEnabled
为true
。 - 输出其
WriteConsole
输出中的当前state
。
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
) 用于启用或禁用监视,并设置 UI 反馈的当前状态:
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
时,会禁用监视并设置状态以反映未进行监视。
UI 启用和禁用监视的按钮。
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>
监视缓存文件更改Monitor cached file changes
可以使用 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 连接在一起,以逐出缓存条目。
在下面的示例中,文件存储在应用的内容根目录中。IWebHostEnvironment.ContentRootFileProvider
用于获取指向应用的IWebHostEnvironment.ContentRootPath
的 IFileProvider。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
方法 (Pages/Index.cshtml.cs ) 中:
var fileContent = await _fileService.GetFileContents("poem.txt");
CompositeChangeToken 类CompositeChangeToken class
要在单个对象中表示一个或多个 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 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 中监视对象更改:
- 为了监视文件更改,IFileProvider 的 Watch 方法将为要监视的指定文件或文件夹创建
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 中:
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true,
reloadOnChange: true);
基于文件的配置由 FileConfigurationSource 表示。FileConfigurationSource
使用 IFileProvider 来监视文件。
默认情况下,由使用 FileSystemWatcher 来监视配置文件更改的 PhysicalFileProvider 提供 IFileMonitor
。
示例应用演示监视配置更改的两个实现。如果任一 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 || ex.HResult != -2147024864)
{
throw;
}
else
{
Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, runCount)));
runCount++;
}
}
}
return new byte[20];
}
简单启动更改令牌Simple startup change token
将用于更改通知的令牌使用者 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 )。只更改了一次配置文件时,文件哈希用于防止 WriteConsole
语句由于多个令牌回调而多次运行。
只要应用正在运行,该系统就会运行,并且用户不能禁用。
将配置更改作为服务进行监视Monitor configuration changes as a service
示例实现:
- 基本启动令牌监视。
- 作为服务监视。
- 启用和禁用监视的机制。
示例建立 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
– 描述在 UI 中使用的当前监视状态。
InvokeChanged
方法类似于前面的方法,不同之处在于:
- 不运行其代码,除非
MonitoringEnabled
为true
。 - 输出其
WriteConsole
输出中的当前state
。
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
) 用于启用或禁用监视,并设置 UI 反馈的当前状态:
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
时,会禁用监视并设置状态以反映未进行监视。
UI 启用和禁用监视的按钮。
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>
监视缓存文件更改Monitor cached file changes
可以使用 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 用于获取指向应用的 ContentRootPath 的 IFileProvider。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
方法 (Pages/Index.cshtml.cs ) 中:
var fileContent = await _fileService.GetFileContents("poem.txt");
CompositeChangeToken 类CompositeChangeToken class
要在单个对象中表示一个或多个 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
。如果发生多个并发更改事件,则调用一次复合更改回调。