Udostępnij za pośrednictwem


Wykrywanie zmian za pomocą tokenów zmian w programie ASP.NET Core

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.

Ostrzeżenie

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.

Ważne

Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.

Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.

Token zmiany jest blokiem konstrukcyjnym ogólnego przeznaczenia niskiego poziomu używanym do śledzenia zmian stanu.

Wyświetl lub pobierz przykładowy kod (jak pobrać)

IChangeToken, interfejs

IChangeToken propaguje powiadomienia o wystąpieniu zmiany. IChangeToken znajduje się w Microsoft.Extensions.Primitives przestrzeni nazw. Pakiet NuGet Microsoft.Extensions.Primitives jest niejawnie udostępniany aplikacjom ASP.NET Core.

IChangeToken ma dwie właściwości:

  • ActiveChangeCallbacks wskaż, czy token aktywnie wywołuje wywołania zwrotne. Jeśli ActiveChangedCallbacks ustawiono wartość false, wywołanie zwrotne nigdy nie jest wywoływane, a aplikacja musi sondować HasChanged pod kątem zmian. Istnieje również możliwość, że token nigdy nie zostanie anulowany, jeśli nie wystąpią żadne zmiany lub podstawowy odbiornik zmian zostanie usunięty lub wyłączony.
  • HasChanged otrzymuje wartość wskazującą, czy nastąpiła zmiana.

Interfejs IChangeToken zawiera metodę RegisterChangeCallback(Action<Object>, Object), która rejestruje wywołanie zwrotne wywoływane po zmianie tokenu. HasChanged należy ustawić przed wywołaniem wywołania zwrotnego.

ChangeToken, klasa

ChangeToken jest klasą statyczną używaną do propagowania powiadomień o wystąpieniu zmiany. ChangeToken znajduje się w Microsoft.Extensions.Primitives przestrzeni nazw. Pakiet NuGet Microsoft.Extensions.Primitives jest niejawnie udostępniany aplikacjom ASP.NET Core.

Metoda ChangeToken.OnChange(Func<IChangeToken>, Action) rejestruje metodę Action do wywołania przy każdym zmianie tokenu:

  • Func<IChangeToken> tworzy token.
  • Action jest wywoływany po zmianie tokenu.

Przeciążenie ChangeToken.OnChange TState>(Func<IChangeToken<>, Action<TState, TState>) przyjmuje dodatkowy TState parametr przekazany do użytkownika Actiontokenu .

OnChange zwraca wartość IDisposable. Wywołanie Dispose uniemożliwia nasłuchiwanie tokenu w celu uzyskania dalszych zmian i zwalnia zasoby tokenu.

Przykładowe zastosowania tokenów zmian w programie ASP.NET Core

Tokeny zmian są używane w widocznych obszarach ASP.NET Core do monitorowania zmian w obiektach:

  • W celu monitorowania zmian w plikach IFileProvidermetoda tworzy Watch IChangeToken element dla określonych plików lub folderu do obejrzenia.
  • IChangeToken tokeny można dodawać do wpisów pamięci podręcznej w celu wyzwalania eksmisji pamięci podręcznej po zmianie.
  • W przypadku TOptions zmian domyślna OptionsMonitor<TOptions> implementacja IOptionsMonitor<TOptions> ma przeciążenie, które akceptuje co najmniej jedno IOptionsChangeTokenSource<TOptions> wystąpienie. Każde wystąpienie zwraca element , IChangeToken aby zarejestrować wywołanie zwrotne powiadomienia o zmianie w celu śledzenia zmian opcji.

Monitorowanie zmian konfiguracji

Domyślnie szablony ASP.NET Core używają plików konfiguracji JSON (appsettings.json, appsettings.Development.jsoni appsettings.Production.json) do ładowania ustawień konfiguracji aplikacji.

Te pliki są konfigurowane przy użyciu metody rozszerzenia AddJsonFile(IConfigurationBuilder, String, Boolean, Boolean), która ConfigurationBuilder akceptuje reloadOnChange parametr. reloadOnChange wskazuje, czy konfiguracja powinna zostać ponownie załadowana na zmiany w pliku. To ustawienie jest wyświetlane w metodzie Host CreateDefaultBuilderwygody :

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

Konfiguracja oparta na plikach jest reprezentowana przez FileConfigurationSource. FileConfigurationSource program używa IFileProvider do monitorowania plików.

Domyślnie element IFileMonitor jest dostarczany przez PhysicalFileProviderelement , który służy FileSystemWatcher do monitorowania zmian plików konfiguracji.

Przykładowa aplikacja demonstruje dwie implementacje monitorowania zmian konfiguracji. Jeśli którykolwiek z appsettings plików ulegnie zmianie, obie implementacje monitorowania plików wykonują kod niestandardowy — przykładowa aplikacja zapisuje komunikat w konsoli.

Plik FileSystemWatcher konfiguracji może wyzwalać wiele wywołań zwrotnych tokenów dla jednej zmiany pliku konfiguracji. Aby upewnić się, że kod niestandardowy jest uruchamiany tylko raz po wyzwoleniu wielu wywołań zwrotnych tokenów, implementacja przykładu sprawdza skróty plików. W przykładzie użyto skrótu pliku SHA1. Ponawianie jest implementowane przy użyciu wycofywania wykładniczego.

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];
}

Prosty token zmiany uruchamiania

Zarejestruj wywołanie zwrotne użytkownika Action tokenu, aby otrzymywać powiadomienia o zmianie w tokenie ponownego ładowania konfiguracji.

W pliku Startup.Configure:

ChangeToken.OnChange(
    () => config.GetReloadToken(),
    (state) => InvokeChanged(state),
    env);

config.GetReloadToken() udostępnia token. Wywołanie zwrotne jest InvokeChanged metodą:

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)");
    }
}

Wywołanie state zwrotne służy do przekazywania IWebHostEnvironmentelementu , który jest przydatny do określenia poprawnego appsettings pliku konfiguracji do monitorowania (na przykład appsettings.Development.json w środowisku deweloperskiego). Skróty plików są używane do zapobiegania wielokrotnemu uruchamianiu WriteConsole instrukcji z powodu wielu wywołań zwrotnych tokenów, gdy plik konfiguracji zmienił się tylko raz.

Ten system działa tak długo, jak aplikacja jest uruchomiona i nie może być wyłączona przez użytkownika.

Monitorowanie zmian konfiguracji jako usługi

Przykład implementuje:

  • Podstawowe monitorowanie tokenu uruchamiania.
  • Monitorowanie jako usługa.
  • Mechanizm włączania i wyłączania monitorowania.

Przykład ustanawia IConfigurationMonitor interfejs.

Extensions/ConfigurationMonitor.cs:

public interface IConfigurationMonitor
{
    bool MonitoringEnabled { get; set; }
    string CurrentState { get; set; }
}

Konstruktor zaimplementowanej klasy , ConfigurationMonitorrejestruje wywołanie zwrotne dla powiadomień o zmianie:

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() dostarcza token. InvokeChanged to metoda wywołania zwrotnego. W state tym wystąpieniu jest odwołaniem do IConfigurationMonitor wystąpienia używanego do uzyskiwania dostępu do stanu monitorowania. Używane są dwie właściwości:

  • MonitoringEnabled: wskazuje, czy wywołanie zwrotne powinno uruchamiać kod niestandardowy.
  • CurrentState: opisuje bieżący stan monitorowania do użycia w interfejsie użytkownika.

Metoda jest podobna InvokeChanged do wcześniejszego podejścia, z tą różnicą, że:

  • Nie uruchamia kodu, chyba że MonitoringEnabled ma wartość true.
  • Zwraca bieżącą wartość state w danych wyjściowych WriteConsole .
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}");
        }
    }
}

Wystąpienie ConfigurationMonitor jest zarejestrowane jako usługa w programie Startup.ConfigureServices:

services.AddSingleton<IConfigurationMonitor, ConfigurationMonitor>();

Strona Indeks oferuje użytkownikowi kontrolę nad monitorowaniem konfiguracji. Wystąpienie IConfigurationMonitor klasy jest wstrzykiwane do obiektu IndexModel.

Pages/Index.cshtml.cs:

public IndexModel(
    IConfiguration config, 
    IConfigurationMonitor monitor, 
    FileService fileService)
{
    _config = config;
    _monitor = monitor;
    _fileService = fileService;
}

Monitor konfiguracji (_monitor) służy do włączania lub wyłączania monitorowania i ustawiania bieżącego stanu opinii interfejsu użytkownika:

public IActionResult OnPostStartMonitoring()
{
    _monitor.MonitoringEnabled = true;
    _monitor.CurrentState = "Monitoring!";

    return RedirectToPage();
}

public IActionResult OnPostStopMonitoring()
{
    _monitor.MonitoringEnabled = false;
    _monitor.CurrentState = "Not monitoring";

    return RedirectToPage();
}

Po OnPostStartMonitoring wyzwoleniu monitorowanie jest włączone, a bieżący stan zostanie wyczyszczone. Po OnPostStopMonitoring wyzwoleniu monitorowanie jest wyłączone, a stan jest ustawiony tak, aby odzwierciedlał, że monitorowanie nie występuje.

Przyciski w interfejsie użytkownika włączają i wyłączają monitorowanie.

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>

Monitorowanie buforowanych zmian plików

Zawartość pliku może być buforowana w pamięci przy użyciu polecenia IMemoryCache. Buforowanie w pamięci zostało opisane w temacie Pamięć podręczna w pamięci . Bez wykonywania dodatkowych kroków, takich jak implementacja opisana poniżej, nieaktualne (nieaktualne) dane są zwracane z pamięci podręcznej, jeśli dane źródłowe się zmienią.

Na przykład nie biorąc pod uwagę stanu buforowanego pliku źródłowego podczas odnawiania przesuwanego okresu wygaśnięcia prowadzi do nieaktualnych danych plików w pamięci podręcznej. Każde żądanie dotyczące danych odnawia okres wygaśnięcia, ale plik nigdy nie jest ponownie ładowany do pamięci podręcznej. Wszystkie funkcje aplikacji korzystające z buforowanej zawartości pliku mogą otrzymywać nieaktualną zawartość.

Używanie tokenów zmian w scenariuszu buforowania plików zapobiega obecności nieaktualnej zawartości pliku w pamięci podręcznej. Przykładowa aplikacja demonstruje implementację podejścia.

W przykładzie użyto metody GetFileContent do:

  • Zwracanie zawartości pliku.
  • Zaimplementuj algorytm ponawiania z wykładniczym wycofywaniem, aby uwzględnić przypadki, w których problem z dostępem do pliku tymczasowo opóźnia odczytywanie zawartości pliku.

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;
}

Zostanie FileService utworzony element do obsługi buforowanych odnośników plików. Wywołanie GetFileContent metody usługi próbuje uzyskać zawartość pliku z pamięci podręcznej w pamięci i zwrócić ją do obiektu wywołującego (Services/FileService.cs).

Jeśli zawartość buforowana nie zostanie znaleziona przy użyciu klucza pamięci podręcznej, zostaną wykonane następujące akcje:

  1. Zawartość pliku jest uzyskiwana przy użyciu polecenia GetFileContent.
  2. Token zmiany jest uzyskiwany od dostawcy plików za pomocą elementu IFileProviders.Watch. Wywołanie zwrotne tokenu jest wyzwalane po zmodyfikowaniu pliku.
  3. Zawartość pliku jest buforowana z przesuwanym okresem wygaśnięcia . Token zmiany jest dołączany z elementem MemoryCacheEntryExtensions.AddExpirationToken , aby wykluczyć wpis pamięci podręcznej, jeśli plik ulegnie zmianie w pamięci podręcznej.

W poniższym przykładzie pliki są przechowywane w katalogu głównym zawartości aplikacji. IWebHostEnvironment.ContentRootFileProvider służy do uzyskiwania wskaźnika IFileProvider w aplikacji IWebHostEnvironment.ContentRootPath. Element filePath jest uzyskiwany za pomocą elementu 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;
    }
}

Element FileService jest zarejestrowany w kontenerze usługi wraz z usługą buforowania pamięci.

W pliku Startup.ConfigureServices:

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

Model strony ładuje zawartość pliku przy użyciu usługi.

W metodzie strony OnGet Indeks (Pages/Index.cshtml.cs):

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

CompositeChangeToken, klasa

Aby reprezentować co najmniej jedno IChangeToken wystąpienie w jednym obiekcie, użyj CompositeChangeToken klasy .

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 w raportach true tokenu złożonego, jeśli jakikolwiek reprezentowany token HasChanged to true. ActiveChangeCallbacks w raportach true tokenu złożonego, jeśli jakikolwiek reprezentowany token ActiveChangeCallbacks to true. Jeśli wystąpi wiele współbieżnych zdarzeń zmiany, wywołanie zwrotne zmiany złożonej jest wywoływane jednorazowo.

Token zmiany jest blokiem konstrukcyjnym ogólnego przeznaczenia niskiego poziomu używanym do śledzenia zmian stanu.

Wyświetl lub pobierz przykładowy kod (jak pobrać)

IChangeToken, interfejs

IChangeToken propaguje powiadomienia o wystąpieniu zmiany. IChangeToken znajduje się w Microsoft.Extensions.Primitives przestrzeni nazw. W przypadku aplikacji, które nie korzystają z Microsoft.AspNetCore.App metapakiet, utwórz odwołanie do pakietu Microsoft.Extensions.Primitives NuGet.

IChangeToken ma dwie właściwości:

  • ActiveChangeCallbacks wskaż, czy token aktywnie wywołuje wywołania zwrotne. Jeśli ActiveChangedCallbacks ustawiono wartość false, wywołanie zwrotne nigdy nie jest wywoływane, a aplikacja musi sondować HasChanged pod kątem zmian. Istnieje również możliwość, że token nigdy nie zostanie anulowany, jeśli nie wystąpią żadne zmiany lub podstawowy odbiornik zmian zostanie usunięty lub wyłączony.
  • HasChanged otrzymuje wartość wskazującą, czy nastąpiła zmiana.

Interfejs IChangeToken zawiera metodę RegisterChangeCallback(Action<Object>, Object), która rejestruje wywołanie zwrotne wywoływane po zmianie tokenu. HasChanged należy ustawić przed wywołaniem wywołania zwrotnego.

ChangeToken, klasa

ChangeToken jest klasą statyczną używaną do propagowania powiadomień o wystąpieniu zmiany. ChangeToken znajduje się w Microsoft.Extensions.Primitives przestrzeni nazw. W przypadku aplikacji, które nie korzystają z Microsoft.AspNetCore.App metapakiet, utwórz odwołanie do pakietu Microsoft.Extensions.Primitives NuGet.

Metoda ChangeToken.OnChange(Func<IChangeToken>, Action) rejestruje metodę Action do wywołania przy każdym zmianie tokenu:

  • Func<IChangeToken> tworzy token.
  • Action jest wywoływany po zmianie tokenu.

Przeciążenie ChangeToken.OnChange TState>(Func<IChangeToken<>, Action<TState, TState>) przyjmuje dodatkowy TState parametr przekazany do użytkownika Actiontokenu .

OnChange zwraca wartość IDisposable. Wywołanie Dispose uniemożliwia nasłuchiwanie tokenu w celu uzyskania dalszych zmian i zwalnia zasoby tokenu.

Przykładowe zastosowania tokenów zmian w programie ASP.NET Core

Tokeny zmian są używane w widocznych obszarach ASP.NET Core do monitorowania zmian w obiektach:

  • W celu monitorowania zmian w plikach IFileProvidermetoda tworzy Watch IChangeToken element dla określonych plików lub folderu do obejrzenia.
  • IChangeToken tokeny można dodawać do wpisów pamięci podręcznej w celu wyzwalania eksmisji pamięci podręcznej po zmianie.
  • W przypadku TOptions zmian domyślna OptionsMonitor<TOptions> implementacja IOptionsMonitor<TOptions> ma przeciążenie, które akceptuje co najmniej jedno IOptionsChangeTokenSource<TOptions> wystąpienie. Każde wystąpienie zwraca element , IChangeToken aby zarejestrować wywołanie zwrotne powiadomienia o zmianie w celu śledzenia zmian opcji.

Monitorowanie zmian konfiguracji

Domyślnie szablony ASP.NET Core używają plików konfiguracji JSON (appsettings.json, appsettings.Development.jsoni appsettings.Production.json) do ładowania ustawień konfiguracji aplikacji.

Te pliki są konfigurowane przy użyciu metody rozszerzenia AddJsonFile(IConfigurationBuilder, String, Boolean, Boolean), która ConfigurationBuilder akceptuje reloadOnChange parametr. reloadOnChange wskazuje, czy konfiguracja powinna zostać ponownie załadowana na zmiany w pliku. To ustawienie jest wyświetlane w metodzie WebHost CreateDefaultBuilderwygody :

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

Konfiguracja oparta na plikach jest reprezentowana przez FileConfigurationSource. FileConfigurationSource program używa IFileProvider do monitorowania plików.

Domyślnie element IFileMonitor jest dostarczany przez PhysicalFileProviderelement , który służy FileSystemWatcher do monitorowania zmian plików konfiguracji.

Przykładowa aplikacja demonstruje dwie implementacje monitorowania zmian konfiguracji. Jeśli którykolwiek z appsettings plików ulegnie zmianie, obie implementacje monitorowania plików wykonują kod niestandardowy — przykładowa aplikacja zapisuje komunikat w konsoli.

Plik FileSystemWatcher konfiguracji może wyzwalać wiele wywołań zwrotnych tokenów dla jednej zmiany pliku konfiguracji. Aby upewnić się, że kod niestandardowy jest uruchamiany tylko raz po wyzwoleniu wielu wywołań zwrotnych tokenów, implementacja przykładu sprawdza skróty plików. W przykładzie użyto skrótu pliku SHA1. Ponawianie jest implementowane przy użyciu wycofywania wykładniczego.

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];
}

Prosty token zmiany uruchamiania

Zarejestruj wywołanie zwrotne użytkownika Action tokenu, aby otrzymywać powiadomienia o zmianie w tokenie ponownego ładowania konfiguracji.

W pliku Startup.Configure:

ChangeToken.OnChange(
    () => config.GetReloadToken(),
    (state) => InvokeChanged(state),
    env);

config.GetReloadToken() udostępnia token. Wywołanie zwrotne jest InvokeChanged metodą:

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)");
    }
}

Wywołanie state zwrotne służy do przekazywania IHostingEnvironmentelementu , który jest przydatny do określenia poprawnego appsettings pliku konfiguracji do monitorowania (na przykład appsettings.Development.json w środowisku deweloperskiego). Skróty plików są używane do zapobiegania wielokrotnemu uruchamianiu WriteConsole instrukcji z powodu wielu wywołań zwrotnych tokenów, gdy plik konfiguracji zmienił się tylko raz.

Ten system działa tak długo, jak aplikacja jest uruchomiona i nie może być wyłączona przez użytkownika.

Monitorowanie zmian konfiguracji jako usługi

Przykład implementuje:

  • Podstawowe monitorowanie tokenu uruchamiania.
  • Monitorowanie jako usługa.
  • Mechanizm włączania i wyłączania monitorowania.

Przykład ustanawia IConfigurationMonitor interfejs.

Extensions/ConfigurationMonitor.cs:

public interface IConfigurationMonitor
{
    bool MonitoringEnabled { get; set; }
    string CurrentState { get; set; }
}

Konstruktor zaimplementowanej klasy , ConfigurationMonitorrejestruje wywołanie zwrotne dla powiadomień o zmianie:

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() dostarcza token. InvokeChanged to metoda wywołania zwrotnego. W state tym wystąpieniu jest odwołaniem do IConfigurationMonitor wystąpienia używanego do uzyskiwania dostępu do stanu monitorowania. Używane są dwie właściwości:

  • MonitoringEnabled: wskazuje, czy wywołanie zwrotne powinno uruchamiać kod niestandardowy.
  • CurrentState: opisuje bieżący stan monitorowania do użycia w interfejsie użytkownika.

Metoda jest podobna InvokeChanged do wcześniejszego podejścia, z tą różnicą, że:

  • Nie uruchamia kodu, chyba że MonitoringEnabled ma wartość true.
  • Zwraca bieżącą wartość state w danych wyjściowych WriteConsole .
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}");
        }
    }
}

Wystąpienie ConfigurationMonitor jest zarejestrowane jako usługa w programie Startup.ConfigureServices:

services.AddSingleton<IConfigurationMonitor, ConfigurationMonitor>();

Strona Indeks oferuje użytkownikowi kontrolę nad monitorowaniem konfiguracji. Wystąpienie IConfigurationMonitor klasy jest wstrzykiwane do obiektu IndexModel.

Pages/Index.cshtml.cs:

public IndexModel(
    IConfiguration config, 
    IConfigurationMonitor monitor, 
    FileService fileService)
{
    _config = config;
    _monitor = monitor;
    _fileService = fileService;
}

Monitor konfiguracji (_monitor) służy do włączania lub wyłączania monitorowania i ustawiania bieżącego stanu opinii interfejsu użytkownika:

public IActionResult OnPostStartMonitoring()
{
    _monitor.MonitoringEnabled = true;
    _monitor.CurrentState = "Monitoring!";

    return RedirectToPage();
}

public IActionResult OnPostStopMonitoring()
{
    _monitor.MonitoringEnabled = false;
    _monitor.CurrentState = "Not monitoring";

    return RedirectToPage();
}

Po OnPostStartMonitoring wyzwoleniu monitorowanie jest włączone, a bieżący stan zostanie wyczyszczone. Po OnPostStopMonitoring wyzwoleniu monitorowanie jest wyłączone, a stan jest ustawiony tak, aby odzwierciedlał, że monitorowanie nie występuje.

Przyciski w interfejsie użytkownika włączają i wyłączają monitorowanie.

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>

Monitorowanie buforowanych zmian plików

Zawartość pliku może być buforowana w pamięci przy użyciu polecenia IMemoryCache. Buforowanie w pamięci zostało opisane w temacie Pamięć podręczna w pamięci . Bez wykonywania dodatkowych kroków, takich jak implementacja opisana poniżej, nieaktualne (nieaktualne) dane są zwracane z pamięci podręcznej, jeśli dane źródłowe się zmienią.

Na przykład nie biorąc pod uwagę stanu buforowanego pliku źródłowego podczas odnawiania przesuwanego okresu wygaśnięcia prowadzi do nieaktualnych danych plików w pamięci podręcznej. Każde żądanie dotyczące danych odnawia okres wygaśnięcia, ale plik nigdy nie jest ponownie ładowany do pamięci podręcznej. Wszystkie funkcje aplikacji korzystające z buforowanej zawartości pliku mogą otrzymywać nieaktualną zawartość.

Używanie tokenów zmian w scenariuszu buforowania plików zapobiega obecności nieaktualnej zawartości pliku w pamięci podręcznej. Przykładowa aplikacja demonstruje implementację podejścia.

W przykładzie użyto metody GetFileContent do:

  • Zwracanie zawartości pliku.
  • Zaimplementuj algorytm ponawiania z wykładniczym wycofywaniem, aby uwzględnić przypadki, w których problem z dostępem do pliku tymczasowo opóźnia odczytywanie zawartości pliku.

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;
}

Zostanie FileService utworzony element do obsługi buforowanych odnośników plików. Wywołanie GetFileContent metody usługi próbuje uzyskać zawartość pliku z pamięci podręcznej w pamięci i zwrócić ją do obiektu wywołującego (Services/FileService.cs).

Jeśli zawartość buforowana nie zostanie znaleziona przy użyciu klucza pamięci podręcznej, zostaną wykonane następujące akcje:

  1. Zawartość pliku jest uzyskiwana przy użyciu polecenia GetFileContent.
  2. Token zmiany jest uzyskiwany od dostawcy plików za pomocą elementu IFileProviders.Watch. Wywołanie zwrotne tokenu jest wyzwalane po zmodyfikowaniu pliku.
  3. Zawartość pliku jest buforowana z przesuwanym okresem wygaśnięcia . Token zmiany jest dołączany z elementem MemoryCacheEntryExtensions.AddExpirationToken , aby wykluczyć wpis pamięci podręcznej, jeśli plik ulegnie zmianie w pamięci podręcznej.

W poniższym przykładzie pliki są przechowywane w katalogu głównym zawartości aplikacji. Element IHostingEnvironment.ContentRootFileProvider służy do uzyskiwania wskaźnika IFileProvider wskazującego na element ContentRootPath. Element filePath jest uzyskiwany za pomocą elementu 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;
    }
}

Element FileService jest zarejestrowany w kontenerze usługi wraz z usługą buforowania pamięci.

W pliku Startup.ConfigureServices:

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

Model strony ładuje zawartość pliku przy użyciu usługi.

W metodzie strony OnGet Indeks (Pages/Index.cshtml.cs):

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

CompositeChangeToken, klasa

Aby reprezentować co najmniej jedno IChangeToken wystąpienie w jednym obiekcie, użyj CompositeChangeToken klasy .

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 w raportach true tokenu złożonego, jeśli jakikolwiek reprezentowany token HasChanged to true. ActiveChangeCallbacks w raportach true tokenu złożonego, jeśli jakikolwiek reprezentowany token ActiveChangeCallbacks to true. Jeśli wystąpi wiele współbieżnych zdarzeń zmiany, wywołanie zwrotne zmiany złożonej jest wywoływane jednorazowo.

Dodatkowe zasoby