使用 ASP.NET Core 中的更改令牌检测更改
注意
此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .NET 8 版本。
更改令牌是用于跟踪状态更改的通用、低级别构建基块。
IChangeToken 接口
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 是静态类,用于传播已发生更改的通知。 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 中更改令牌的使用示例
更改令牌主要用于在 ASP.NET Core 中监视对象更改:
- 为了监视文件更改,IFileProvider 的 Watch 方法将为要监视的指定文件或文件夹创建
IChangeToken
。 - 可以将
IChangeToken
令牌添加到缓存条目,以在更改时触发缓存逐出。 - 对于
TOptions
更改,IOptionsMonitor<TOptions> 的默认 OptionsMonitor<TOptions> 实现有一个重载,可接受一个或多个 IOptionsChangeTokenSource<TOptions> 实例。 每个实例返回IChangeToken
,以注册用于跟踪选项更改的更改通知回调。
监视配置更改
默认情况下,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)
{
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
)。 只更改了一次配置文件时,文件哈希用于防止 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
:描述在 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>
监视缓存文件更改
可以使用 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
用于获取指向应用的 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 类
要在单个对象中表示一个或多个 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 元包的应用,将为 Microsoft.Extensions.Primitives NuGet 包创建一个包引用。
IChangeToken
具有以下两个属性:
- ActiveChangeCallbacks,指示令牌是否主动引发回调。 如果将
ActiveChangedCallbacks
设置为false
,则不会调用回调,并且应用必须轮询HasChanged
获取更改。 如果未发生任何更改,或者丢弃或禁用基础更改侦听器,还可能永远不会取消令牌。 - HasChanged,接收一个指示是否发生更改的值。
IChangeToken
接口具有 RegisterChangeCallback(Action<Object>, Object) 方法,用于注册在令牌更改时调用的回调。 调用回调之前,必须设置 HasChanged
。
ChangeToken 类
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 中更改令牌的使用示例
更改令牌主要用于在 ASP.NET Core 中监视对象更改:
- 为了监视文件更改,IFileProvider 的 Watch 方法将为要监视的指定文件或文件夹创建
IChangeToken
。 - 可以将
IChangeToken
令牌添加到缓存条目,以在更改时触发缓存逐出。 - 对于
TOptions
更改,IOptionsMonitor<TOptions> 的默认 OptionsMonitor<TOptions> 实现有一个重载,可接受一个或多个 IOptionsChangeTokenSource<TOptions> 实例。 每个实例返回IChangeToken
,以注册用于跟踪选项更改的更改通知回调。
监视配置更改
默认情况下,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)
{
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
)。 只更改了一次配置文件时,文件哈希用于防止 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
:描述在 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>
监视缓存文件更改
可以使用 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 类
要在单个对象中表示一个或多个 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
。 如果发生多个并发更改事件,则调用一次复合更改回调。