在 ASP.NET Core 中使用變更權杖來偵測變更

「變更權杖」是用來追蹤狀態變更的一般用途低階建置組塊。

檢視或下載範例程式碼 \(英文\) (如何下載)

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) 多載接受傳遞到權杖取用者 Action 的額外 TState 參數。

OnChange 會傳回 IDisposable。 呼叫 Dispose 將阻止權杖接聽其他變更和釋出權杖的資源。

ASP.NET Core 中變更權杖的範例用法

變更權杖用於 ASP.NET Core 監視物件變更的重要區域:

監視設定變更

根據預設,ASP.NET Core 範本會使用 JSON 設定檔 (appsettings.jsonappsettings.Development.jsonappsettings.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 來監視檔案。

根據預設,IFileMonitorPhysicalFileProvider 提供,它會使用 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)。 檔案雜湊用來防止 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 方法類似於之前的方法,不同之處在於:

  • 不會執行其程式碼,除非 MonitoringEnabledtrue
  • 在其 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)。

如果使用快取索引鍵找不到快取的內容,則會採取下列動作:

  1. 使用 GetFileContent 取得檔案內容。
  2. 使用 IFileProviders.Watch 從檔案提供者取得變更權杖。 修改檔案時,就會觸發權杖的回呼。
  3. 使用滑動期限快取檔案內容。 變更權杖附有 MemoryCacheEntryExtensions.AddExpirationToke,可在快取的檔案變更時收回快取項目。

在下列範例中,檔案是儲存在應用程式的內容根路徑IWebHostEnvironment.ContentRootFileProvider 是用來取得指向應用程式 IWebHostEnvironment.ContentRootPathIFileProviderfilePath 是使用 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
        });

如果任何表示的權杖 HasChangedtrue,複合權杖上的 HasChanged 會報告 true。 如果任何表示的權杖 ActiveChangeCallbackstrue,複合權杖上的 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) 多載接受傳遞到權杖取用者 Action 的額外 TState 參數。

OnChange 會傳回 IDisposable。 呼叫 Dispose 將阻止權杖接聽其他變更和釋出權杖的資源。

ASP.NET Core 中變更權杖的範例用法

變更權杖用於 ASP.NET Core 監視物件變更的重要區域:

監視設定變更

根據預設,ASP.NET Core 範本會使用 JSON 設定檔 (appsettings.jsonappsettings.Development.jsonappsettings.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 來監視檔案。

根據預設,IFileMonitorPhysicalFileProvider 提供,它會使用 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)。 檔案雜湊用來防止 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 方法類似於之前的方法,不同之處在於:

  • 不會執行其程式碼,除非 MonitoringEnabledtrue
  • 在其 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)。

如果使用快取索引鍵找不到快取的內容,則會採取下列動作:

  1. 使用 GetFileContent 取得檔案內容。
  2. 使用 IFileProviders.Watch 從檔案提供者取得變更權杖。 修改檔案時,就會觸發權杖的回呼。
  3. 使用滑動期限快取檔案內容。 變更權杖附有 MemoryCacheEntryExtensions.AddExpirationToke,可在快取的檔案變更時收回快取項目。

在下列範例中,檔案是儲存在應用程式的內容根路徑IHostingEnvironment.ContentRootFileProvider 是用來取得指向應用程式 ContentRootPathIFileProviderfilePath 是使用 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
        });

如果任何表示的權杖 HasChangedtrue,複合權杖上的 HasChanged 會報告 true。 如果任何表示的權杖 ActiveChangeCallbackstrue,複合權杖上的 ActiveChangeCallbacks 會報告 true。 如果發生多個同時變更事件,會叫用複合變更回呼一次。

其他資源