Partilhar via


Detetar alterações com tokens de alteração no ASP.NET Core

Note

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 10 deste artigo.

Warning

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 10 deste artigo.

Um token de alteração é um bloco de construção de baixo nível de uso geral usado para controlar alterações de estado.

Visualizar ou descarregar amostra de código (como descarregar)

Interface IChangeToken

IChangeToken propaga notificações de que ocorreu uma alteração. IChangeToken reside no espaço de nomes Microsoft.Extensions.Primitives. O pacote NuGet Microsoft.Extensions.Primitives é fornecido implicitamente aos aplicativos ASP.NET Core.

IChangeToken tem duas propriedades:

  • ActiveChangeCallbacks Indique se o token gera retornos de chamada proativamente. Se ActiveChangedCallbacks estiver definido como false, um callback nunca será chamado, e o aplicativo deverá interrogar HasChanged para alterações. Também é possível que um token nunca seja cancelado se nenhuma alteração ocorrer ou se o ouvinte de alterações subjacente for descartado ou desativado.
  • HasChanged Recebe um valor que indica se ocorreu uma alteração.

A IChangeToken interface inclui o método RegisterChangeCallback(Action<Object>, Object), que registra um retorno de chamada que é invocado quando o token é alterado. HasChanged deve ser definido antes que o callback seja invocado.

Classe ChangeToken

ChangeToken é uma classe estática usada para propagar notificações de que ocorreu uma alteração. ChangeToken reside no espaço de nomes Microsoft.Extensions.Primitives. O pacote NuGet Microsoft.Extensions.Primitives é fornecido implicitamente aos aplicativos ASP.NET Core.

O método ChangeToken.OnChange(Func<IChangeToken>, Action) registra um Action para chamar sempre que o token muda:

  • Func<IChangeToken> produz o token.
  • Action é chamado quando o token muda.

A sobrecarga ChangeToken.OnChange<TState>(Func<IChangeToken>, Action<TState>, TState) usa um parâmetro adicional TState que é passado para o consumidor Action do token.

OnChange retorna um IDisposable. A chamada Dispose impede que o token escute novas alterações e libera os recursos do token.

Exemplos de usos de tokens de alteração no ASP.NET Core

Os tokens de alteração são usados em áreas proeminentes do ASP.NET Core para monitorar alterações em objetos:

  • Para monitorizar alterações em arquivos, o método IFileProvider de Watch cria um IChangeToken para observar os arquivos ou as pastas especificadas.
  • IChangeToken Os tokens podem ser adicionados às entradas de cache para acionar expulsões de cache ao ocorrer uma alteração.
  • Para TOptions alterações, a implementação padrão OptionsMonitor<TOptions> do IOptionsMonitor<TOptions> tem uma sobrecarga que aceita uma ou mais IOptionsChangeTokenSource<TOptions> instâncias. Cada instância retorna um IChangeToken retorno de chamada para registrar uma notificação de alteração para controlar as alterações de opções.

Monitorar alterações de configuração

Por padrão, os modelos ASP.NET Core usam arquivos de configuração JSON (appsettings.json, appsettings.Development.jsone appsettings.Production.json) para carregar as definições de configuração do aplicativo.

Estes ficheiros são configurados usando o método de extensão AddJsonFile(IConfigurationBuilder, String, Boolean, Boolean) em ConfigurationBuilder que aceita um parâmetro reloadOnChange. reloadOnChange Indica se a configuração deve ser recarregada em alterações de arquivo. Essa configuração aparece no Host método CreateDefaultBuilderde conveniência :

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

A configuração baseada em arquivo é representada pelo FileConfigurationSource. FileConfigurationSource usa IFileProvider para monitorar arquivos.

Por padrão, o IFileMonitor é fornecido por um PhysicalFileProvider, que usa FileSystemWatcher para monitorar alterações no arquivo de configuração.

O aplicativo de exemplo demonstra duas implementações para monitorar alterações de configuração. Se algum dos appsettings arquivos for alterado, ambas as implementações de monitoramento de arquivos executarão código personalizado — o aplicativo de exemplo grava uma mensagem no console.

Um arquivo de FileSystemWatcher configuração pode disparar vários retornos de chamada de token para uma única alteração no arquivo de configuração. Para garantir que o código personalizado seja executado apenas uma vez quando vários retornos de chamada de token são acionados, a implementação do exemplo verifica os hashes do arquivo. O exemplo usa hashing de arquivo SHA1. Uma nova tentativa é implementada com um recuo exponencial.

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

Token de alteração de inicialização simples

Registe um callback de consumidor de token Action para notificações de alteração para o token de recarregamento de configuração.

Em Startup.Configure:

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

config.GetReloadToken() fornece o token. O método de retorno de chamada é o 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)");
    }
}

O state do callback é usado para passar no IWebHostEnvironment, o que é útil para especificar o ficheiro de configuração correto appsettings para monitorizar (por exemplo, appsettings.Development.json quando estiver no ambiente Development). Os hashes de ficheiro são usados para impedir que a instrução WriteConsole seja executada várias vezes devido a múltiplos retornos de chamada de token quando o ficheiro de configuração só foi alterado uma vez.

Este sistema é executado enquanto o aplicativo estiver em execução e não puder ser desativado pelo usuário.

Monitorar alterações de configuração como um serviço

O exemplo implementa:

  • Monitoramento básico de token de inicialização.
  • Monitorização como serviço.
  • Um mecanismo para ativar e desativar a monitorização.

O exemplo estabelece uma IConfigurationMonitor interface.

Extensions/ConfigurationMonitor.cs:

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

O construtor da classe implementada, ConfigurationMonitor, registra um retorno de chamada para notificações de alteração:

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() fornece o token. InvokeChanged é o método de retorno de chamada. O state neste caso é uma referência à IConfigurationMonitor instância que é usada para acessar o estado de monitoramento. Duas propriedades são usadas:

  • MonitoringEnabled: Indica se o retorno de chamada deve executar o seu código propriamente personalizado.
  • CurrentState: Descreve o estado de monitoramento atual para uso na interface do usuário.

O InvokeChanged método é semelhante à abordagem anterior, exceto que:

  • Não executa seu código, a menos que MonitoringEnabled seja true.
  • Produz a corrente state em sua WriteConsole saída.
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}");
        }
    }
}

Uma instância ConfigurationMonitor é registrada como um serviço em Startup.ConfigureServices:

services.AddSingleton<IConfigurationMonitor, ConfigurationMonitor>();

A página Índice oferece ao usuário controle sobre o monitoramento da configuração. A instância de IConfigurationMonitor é injetada no IndexModel.

Pages/Index.cshtml.cs:

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

O monitor de configuração (_monitor) é usado para habilitar ou desabilitar o monitoramento e definir o estado atual para comentários da interface do usuário:

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

    return RedirectToPage();
}

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

    return RedirectToPage();
}

Quando OnPostStartMonitoring é acionado, o monitoramento é habilitado e o estado atual é limpo. Quando OnPostStopMonitoring é acionado, o monitoramento é desativado e o estado é definido para refletir que o monitoramento não está ocorrendo.

Os botões na interface do usuário habilitam e desabilitam o monitoramento.

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>

Monitorar alterações de arquivos armazenados em cache

O conteúdo do arquivo pode ser armazenado em cache na memória usando IMemoryCache. O cache na memória é descrito no tópico Cache na memória . Sem executar etapas adicionais, como a implementação descrita abaixo, os dados obsoletos (desatualizados ) são retornados de um cache se os dados de origem forem alterados.

Por exemplo, não levar em conta o status de um arquivo de origem armazenado em cache ao renovar um período de expiração deslizante leva a dados de arquivo em cache obsoletos. Cada solicitação de dados renova o período de expiração deslizante, mas o arquivo nunca é recarregado no cache. Todos os recursos do aplicativo que usam o conteúdo armazenado em cache do arquivo estão sujeitos a possivelmente receber conteúdo obsoleto.

O uso de tokens de alteração em um cenário de cache de arquivos impede a presença de conteúdo de arquivo obsoleto no cache. O aplicativo de exemplo demonstra uma implementação da abordagem.

O exemplo usa GetFileContent para:

  • Retornar conteúdo do arquivo.
  • Implemente um algoritmo de repetição com back-off exponencial para cobrir casos em que um problema de acesso ao arquivo atrasa temporariamente a leitura do conteúdo do arquivo.

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

A FileService é criado para lidar com consultas de ficheiros em cache. A GetFileContent chamada de método do serviço tenta obter o conteúdo do arquivo do cache na memória e devolvê-lo ao chamador (Services/FileService.cs).

Se o conteúdo armazenado em cache não for encontrado usando a chave de cache, as seguintes ações serão executadas:

  1. O conteúdo do arquivo é obtido usando GetFileContent.
  2. Um token de alteração é obtido do provedor de arquivos com IFileProviders.Watch. O Callback do token é ativado quando o ficheiro é modificado.
  3. O conteúdo do arquivo é armazenado em cache com um período de expiração deslizante . O token de alteração é anexado com MemoryCacheEntryExtensions.AddExpirationToken para remover a entrada de cache se o arquivo for alterado enquanto estiver armazenado em cache.

No exemplo a seguir, os arquivos são armazenados na raiz de conteúdo do aplicativo. IWebHostEnvironment.ContentRootFileProvider é usado para obter um IFileProvider apontando para o IWebHostEnvironment.ContentRootPath. O filePath é obtido com 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;
    }
}

O FileService é registrado no contêiner de serviço junto com o serviço de cache de memória.

Em Startup.ConfigureServices:

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

O modelo de página carrega o conteúdo do arquivo usando o serviço.

No método OnGet da página de índice (Pages/Index.cshtml.cs):

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

CompositeChangeToken classe

Para representar uma ou mais IChangeToken instâncias em um único objeto, use a CompositeChangeToken classe.

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

Relatórios HasChanged sobre o token composto true caso algum token HasChanged representado seja true. Relatórios ActiveChangeCallbacks sobre o token composto true caso algum token ActiveChangeCallbacks representado seja true. Se ocorrerem vários eventos de alteração simultânea, o retorno de chamada de alteração composta será invocado uma vez.

Um token de alteração é um bloco de construção de baixo nível de uso geral usado para controlar alterações de estado.

Visualizar ou descarregar amostra de código (como descarregar)

Interface IChangeToken

IChangeToken propaga notificações de que ocorreu uma alteração. IChangeToken reside no espaço de nomes Microsoft.Extensions.Primitives. Para aplicativos que não usam o metapacote Microsoft.AspNetCore.App, crie uma referência de pacote para o pacote NuGet Microsoft.Extensions.Primitives .

IChangeToken tem duas propriedades:

  • ActiveChangeCallbacks Indique se o token gera retornos de chamada proativamente. Se ActiveChangedCallbacks estiver definido como false, um callback nunca será chamado, e o aplicativo deverá interrogar HasChanged para alterações. Também é possível que um token nunca seja cancelado se nenhuma alteração ocorrer ou se o ouvinte de alterações subjacente for descartado ou desativado.
  • HasChanged Recebe um valor que indica se ocorreu uma alteração.

A IChangeToken interface inclui o método RegisterChangeCallback(Action<Object>, Object), que registra um retorno de chamada que é invocado quando o token é alterado. HasChanged deve ser definido antes que o callback seja invocado.

Classe ChangeToken

ChangeToken é uma classe estática usada para propagar notificações de que ocorreu uma alteração. ChangeToken reside no espaço de nomes Microsoft.Extensions.Primitives. Para aplicativos que não usam o metapacote Microsoft.AspNetCore.App, crie uma referência de pacote para o pacote NuGet Microsoft.Extensions.Primitives .

O método ChangeToken.OnChange(Func<IChangeToken>, Action) registra um Action para chamar sempre que o token muda:

  • Func<IChangeToken> produz o token.
  • Action é chamado quando o token muda.

A sobrecarga ChangeToken.OnChange<TState>(Func<IChangeToken>, Action<TState>, TState) usa um parâmetro adicional TState que é passado para o consumidor Action do token.

OnChange retorna um IDisposable. A chamada Dispose impede que o token escute novas alterações e libera os recursos do token.

Exemplos de usos de tokens de alteração no ASP.NET Core

Os tokens de alteração são usados em áreas proeminentes do ASP.NET Core para monitorar alterações em objetos:

  • Para monitorizar alterações em arquivos, o método IFileProvider de Watch cria um IChangeToken para observar os arquivos ou as pastas especificadas.
  • IChangeToken Os tokens podem ser adicionados às entradas de cache para acionar expulsões de cache ao ocorrer uma alteração.
  • Para TOptions alterações, a implementação padrão OptionsMonitor<TOptions> do IOptionsMonitor<TOptions> tem uma sobrecarga que aceita uma ou mais IOptionsChangeTokenSource<TOptions> instâncias. Cada instância retorna um IChangeToken retorno de chamada para registrar uma notificação de alteração para controlar as alterações de opções.

Monitorar alterações de configuração

Por padrão, os modelos ASP.NET Core usam arquivos de configuração JSON (appsettings.json, appsettings.Development.jsone appsettings.Production.json) para carregar as definições de configuração do aplicativo.

Estes ficheiros são configurados usando o método de extensão AddJsonFile(IConfigurationBuilder, String, Boolean, Boolean) em ConfigurationBuilder que aceita um parâmetro reloadOnChange. reloadOnChange Indica se a configuração deve ser recarregada em alterações de arquivo. Essa configuração aparece no WebHost método CreateDefaultBuilderde conveniência :

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

A configuração baseada em arquivo é representada pelo FileConfigurationSource. FileConfigurationSource usa IFileProvider para monitorar arquivos.

Por padrão, o IFileMonitor é fornecido por um PhysicalFileProvider, que usa FileSystemWatcher para monitorar alterações no arquivo de configuração.

O aplicativo de exemplo demonstra duas implementações para monitorar alterações de configuração. Se algum dos appsettings arquivos for alterado, ambas as implementações de monitoramento de arquivos executarão código personalizado — o aplicativo de exemplo grava uma mensagem no console.

Um arquivo de FileSystemWatcher configuração pode disparar vários retornos de chamada de token para uma única alteração no arquivo de configuração. Para garantir que o código personalizado seja executado apenas uma vez quando vários retornos de chamada de token são acionados, a implementação do exemplo verifica os hashes do arquivo. O exemplo usa hashing de arquivo SHA1. Uma nova tentativa é implementada com um recuo exponencial.

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

Token de alteração de inicialização simples

Registe um callback de consumidor de token Action para notificações de alteração para o token de recarregamento de configuração.

Em Startup.Configure:

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

config.GetReloadToken() fornece o token. O método de retorno de chamada é o 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)");
    }
}

O state do callback é usado para passar o IHostingEnvironment, o que é útil para especificar o ficheiro de configuração appsettings correto a monitorizar (por exemplo, appsettings.Development.json quando estiver no ambiente Development). Os hashes de ficheiro são usados para impedir que a instrução WriteConsole seja executada várias vezes devido a múltiplos retornos de chamada de token quando o ficheiro de configuração só foi alterado uma vez.

Este sistema é executado enquanto o aplicativo estiver em execução e não puder ser desativado pelo usuário.

Monitorar alterações de configuração como um serviço

O exemplo implementa:

  • Monitoramento básico de token de inicialização.
  • Monitorização como serviço.
  • Um mecanismo para ativar e desativar a monitorização.

O exemplo estabelece uma IConfigurationMonitor interface.

Extensions/ConfigurationMonitor.cs:

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

O construtor da classe implementada, ConfigurationMonitor, registra um retorno de chamada para notificações de alteração:

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() fornece o token. InvokeChanged é o método de retorno de chamada. O state neste caso é uma referência à IConfigurationMonitor instância que é usada para acessar o estado de monitoramento. Duas propriedades são usadas:

  • MonitoringEnabled: Indica se o retorno de chamada deve executar o seu código propriamente personalizado.
  • CurrentState: Descreve o estado de monitoramento atual para uso na interface do usuário.

O InvokeChanged método é semelhante à abordagem anterior, exceto que:

  • Não executa seu código, a menos que MonitoringEnabled seja true.
  • Produz a corrente state em sua WriteConsole saída.
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}");
        }
    }
}

Uma instância ConfigurationMonitor é registrada como um serviço em Startup.ConfigureServices:

services.AddSingleton<IConfigurationMonitor, ConfigurationMonitor>();

A página Índice oferece ao usuário controle sobre o monitoramento da configuração. A instância de IConfigurationMonitor é injetada no IndexModel.

Pages/Index.cshtml.cs:

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

O monitor de configuração (_monitor) é usado para habilitar ou desabilitar o monitoramento e definir o estado atual para comentários da interface do usuário:

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

    return RedirectToPage();
}

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

    return RedirectToPage();
}

Quando OnPostStartMonitoring é acionado, o monitoramento é habilitado e o estado atual é limpo. Quando OnPostStopMonitoring é acionado, o monitoramento é desativado e o estado é definido para refletir que o monitoramento não está ocorrendo.

Os botões na interface do usuário habilitam e desabilitam o monitoramento.

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>

Monitorar alterações de arquivos armazenados em cache

O conteúdo do arquivo pode ser armazenado em cache na memória usando IMemoryCache. O cache na memória é descrito no tópico Cache na memória . Sem executar etapas adicionais, como a implementação descrita abaixo, os dados obsoletos (desatualizados ) são retornados de um cache se os dados de origem forem alterados.

Por exemplo, não levar em conta o status de um arquivo de origem armazenado em cache ao renovar um período de expiração deslizante leva a dados de arquivo em cache obsoletos. Cada solicitação de dados renova o período de expiração deslizante, mas o arquivo nunca é recarregado no cache. Todos os recursos do aplicativo que usam o conteúdo armazenado em cache do arquivo estão sujeitos a possivelmente receber conteúdo obsoleto.

O uso de tokens de alteração em um cenário de cache de arquivos impede a presença de conteúdo de arquivo obsoleto no cache. O aplicativo de exemplo demonstra uma implementação da abordagem.

O exemplo usa GetFileContent para:

  • Retornar conteúdo do arquivo.
  • Implemente um algoritmo de repetição com back-off exponencial para cobrir casos em que um problema de acesso ao arquivo atrasa temporariamente a leitura do conteúdo do arquivo.

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

A FileService é criado para lidar com consultas de ficheiros em cache. A GetFileContent chamada de método do serviço tenta obter o conteúdo do arquivo do cache na memória e devolvê-lo ao chamador (Services/FileService.cs).

Se o conteúdo armazenado em cache não for encontrado usando a chave de cache, as seguintes ações serão executadas:

  1. O conteúdo do arquivo é obtido usando GetFileContent.
  2. Um token de alteração é obtido do provedor de arquivos com IFileProviders.Watch. O Callback do token é ativado quando o ficheiro é modificado.
  3. O conteúdo do arquivo é armazenado em cache com um período de expiração deslizante . O token de alteração é anexado com MemoryCacheEntryExtensions.AddExpirationToken para remover a entrada de cache se o arquivo for alterado enquanto estiver armazenado em cache.

No exemplo a seguir, os arquivos são armazenados na raiz de conteúdo do aplicativo. IHostingEnvironment.ContentRootFileProvider é utilizado para obter um IFileProvider que aponta para o diretório da raiz de conteúdo do aplicativo ContentRootPath. O filePath é obtido com 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;
    }
}

O FileService é registrado no contêiner de serviço junto com o serviço de cache de memória.

Em Startup.ConfigureServices:

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

O modelo de página carrega o conteúdo do arquivo usando o serviço.

No método OnGet da página de índice (Pages/Index.cshtml.cs):

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

CompositeChangeToken classe

Para representar uma ou mais IChangeToken instâncias em um único objeto, use a CompositeChangeToken classe.

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

Relatórios HasChanged sobre o token composto true caso algum token HasChanged representado seja true. Relatórios ActiveChangeCallbacks sobre o token composto true caso algum token ActiveChangeCallbacks representado seja true. Se ocorrerem vários eventos de alteração simultânea, o retorno de chamada de alteração composta será invocado uma vez.

Recursos adicionais