- 全球化与本土化
- 性能
- 高级
使用 ASP.NET Core 中的更改令牌检测更改
作者:Luke Latham
更改令牌 是用于跟踪状态更改的通用、低级别构建基块。
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 || ex.HResult != -2147024864) { throw; } else { 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 || 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 类
要在单个对象中表示一个或多个 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 || ex.HResult != -2147024864) { throw; } else { 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
。 如果发生多个并发更改事件,则调用一次复合更改回调。