Detección de cambios con tokens de cambio en ASP.NET Core
Nota:
Esta no es la versión más reciente de este artículo. Para la versión actual, consulta la versión .NET 8 de este artículo.
Advertencia
Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulta la Directiva de soporte técnico de .NET y .NET Core. Para la versión actual, consulta la versión .NET 8 de este artículo.
Importante
Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.
Para la versión actual, consulte la versión .NET 8 de este artículo.
Un token de cambio es un bloque de creación de bajo nivel y uso general que se usa para realizar el seguimiento de los cambios de estado.
Vea o descargue el código de ejemplo (cómo descargarlo)
Interfaz IChangeToken
IChangeToken propaga notificaciones que indican que se ha producido un cambio. IChangeToken
reside en el espacio de nombres Microsoft.Extensions.Primitives. El paquete de NuGet Microsoft.Extensions.Primitives se proporciona implícitamente con las aplicaciones de ASP.NET Core.
IChangeToken
tiene dos propiedades:
- ActiveChangeCallbacks indica si el token genera devoluciones de llamada de manera proactiva. Si
ActiveChangedCallbacks
se establece enfalse
, nunca se llama a una devolución de llamada y la aplicación debe sondearHasChanged
en busca de cambios. También es posible que un token nunca se cancele si no se producen cambios o si se elimina o deshabilita el agente de escucha de cambios subyacente. - HasChanged recibe un valor que indica si se ha producido un cambio.
La interfaz IChangeToken
incluye el método RegisterChangeCallback(Action<Object>, Object), que registra una devolución de llamada que se invoca cuando el token ha cambiado. HasChanged
se debe establecer antes de que se invoque la devolución de llamada.
Clase ChangeToken
ChangeToken es una clase estática que se usa para propagar notificaciones que indican que se ha producido un cambio. ChangeToken
reside en el espacio de nombres Microsoft.Extensions.Primitives. El paquete de NuGet Microsoft.Extensions.Primitives se proporciona implícitamente con las aplicaciones de ASP.NET Core.
El método ChangeToken.OnChange(Func<IChangeToken>, Action) registra un elemento Action
al que se llama cada vez que cambia el token:
Func<IChangeToken>
genera el token.- Se llama a
Action
cuando cambia el token.
La sobrecarga ChangeToken.OnChange<TState>(Func<IChangeToken>, Action<TState>, TState) toma un parámetro TState
adicional que se pasa al consumidor de tokens Action
.
OnChange
devuelve un valor de IDisposable. Al llamar a Dispose se detiene la escucha del token de futuras modificaciones y se liberan sus recursos.
Ejemplos de uso de tokens de cambio en ASP.NET Core
Los tokens de cambio se usan en áreas principales de ASP.NET Core para supervisar los cambios en los objetos:
- Para supervisar los cambios en los archivos, el método Watch de IFileProvider crea un
IChangeToken
para los archivos especificados o la carpeta que se va a supervisar. - Se pueden agregar tokens
IChangeToken
a las entradas de caché para desencadenar expulsiones de caché al producirse un cambio. - Para los cambios de
TOptions
, la implementación predeterminada OptionsMonitor<TOptions> de IOptionsMonitor<TOptions> tiene una sobrecarga que acepta una o varias instancias de IOptionsChangeTokenSource<TOptions>. Cada instancia devuelve unIChangeToken
para registrar una devolución de llamada de notificación de cambio a fin de realizar el seguimiento de los cambios en las opciones.
Supervisión de los cambios de configuración
De forma predeterminada, las plantillas de ASP.NET Core usan archivos de configuración de JSON (appsettings.json
, appsettings.Development.json
y appsettings.Production.json
) para cargar la configuración de la aplicación.
Estos archivos se configuran mediante el método de extensión AddJsonFile(IConfigurationBuilder, String, Boolean, Boolean) en ConfigurationBuilder que acepta un parámetro reloadOnChange
. reloadOnChange
indica si la configuración se debe recargar en los cambios de archivo. Esta configuración aparece en el método CreateDefaultBuilder de Host:
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true,
reloadOnChange: true);
La configuración basada en archivo se presenta por medio de FileConfigurationSource. FileConfigurationSource
usa IFileProvider para supervisar los archivos.
PhysicalFileProvider proporciona IFileMonitor
de manera predeterminada, que usa FileSystemWatcher para supervisar los cambios del archivo de configuración.
En la aplicación de ejemplo se muestran dos implementaciones para supervisar los cambios de configuración. Si se modifica cualquiera de los archivos appsettings
, ambas implementaciones de supervisión de los archivos ejecutan código personalizado, y la aplicación de ejemplo escribe un mensaje en la consola.
El FileSystemWatcher
de un archivo de configuración puede desencadenar varias devoluciones de llamada de token para un único cambio del archivo de configuración. Para garantizar que el código personalizado se ejecute solo una vez cuando se desencadenan varias devoluciones de llamada de token, la implementación del ejemplo comprueba los hashes de archivo. El ejemplo usa el algoritmo hash seguro 1. Se implementa un reintento con una interrupción 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 cambio de inicio simple
Registra una devolución de llamada de Action
del consumidor de token para las notificaciones de cambio en el token de recarga de configuración.
En Startup.Configure
:
ChangeToken.OnChange(
() => config.GetReloadToken(),
(state) => InvokeChanged(state),
env);
config.GetReloadToken()
proporciona el token. La devolución de llamada es el método 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)");
}
}
El state
de la devolución de llamada se usa para pasar IWebHostEnvironment
, que resulta útil para especificar el archivo de configuración appsettings
correcto que se va a supervisar (por ejemplo, appsettings.Development.json
cuando se está en el entorno de desarrollo). Se usa el hash de archivo para evitar que se ejecute varias veces la instrucción WriteConsole
debido a varias devoluciones de llamada de token cuando el archivo de configuración solo ha cambiado una vez.
Este sistema se ejecuta siempre que la aplicación esté en ejecución y el usuario no lo puede deshabilitar.
Supervisión de los cambios de configuración como servicio
En el ejemplo se implementa lo siguiente:
- La supervisión del token de inicio básico.
- La supervisión como servicio.
- Un mecanismo para habilitar y deshabilitar la supervisión.
El ejemplo establece una interfaz IConfigurationMonitor
.
Extensions/ConfigurationMonitor.cs
:
public interface IConfigurationMonitor
{
bool MonitoringEnabled { get; set; }
string CurrentState { get; set; }
}
El constructor de la clase implementada, ConfigurationMonitor
, registra una devolución de llamada para las notificaciones de cambio:
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()
proporciona el token. InvokeChanged
es el método de devolución de llamada. El elemento state
de esta instancia es una referencia a la instancia de IConfigurationMonitor
que se usa para tener acceso al estado de supervisión. Se usan dos propiedades:
MonitoringEnabled
: indica si la devolución de llamada debe ejecutar su código personalizado.CurrentState
: describe el estado de supervisión actual para su uso en la interfaz de usuario.
El método InvokeChanged
es similar al enfoque anterior, excepto en que:
- No ejecuta su código, a menos que
MonitoringEnabled
seatrue
. - Genera el
state
actual en su salida deWriteConsole
.
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}");
}
}
}
Una instancia de ConfigurationMonitor
se registra como servicio en Startup.ConfigureServices
:
services.AddSingleton<IConfigurationMonitor, ConfigurationMonitor>();
En la página Index se ofrece al usuario el control sobre la supervisión de la configuración. La instancia de IConfigurationMonitor
se inserta en IndexModel
.
Pages/Index.cshtml.cs
:
public IndexModel(
IConfiguration config,
IConfigurationMonitor monitor,
FileService fileService)
{
_config = config;
_monitor = monitor;
_fileService = fileService;
}
El monitor de configuración (_monitor
) se usa para habilitar o deshabilitar la supervisión y establecer el estado actual de los comentarios de la interfaz de usuario:
public IActionResult OnPostStartMonitoring()
{
_monitor.MonitoringEnabled = true;
_monitor.CurrentState = "Monitoring!";
return RedirectToPage();
}
public IActionResult OnPostStopMonitoring()
{
_monitor.MonitoringEnabled = false;
_monitor.CurrentState = "Not monitoring";
return RedirectToPage();
}
Cuando se desencadena OnPostStartMonitoring
, se habilita la supervisión y se borra el estado actual. Cuando se desencadena OnPostStopMonitoring
, se deshabilita la supervisión y se establece el estado para reflejar que no se está realizando la supervisión.
Los botones de la interfaz de usuario habilitan y deshabilitan la supervisión.
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>
Supervisión de los cambios de archivos en caché
El contenido de los archivos se puede almacenar en caché en memoria mediante IMemoryCache. El almacenamiento en caché en memoria se describe en el tema Cache in-memory (Almacenamiento en caché en memoria). Sin realizar pasos adicionales, como la implementación que se describe a continuación, si los datos de origen cambian, se devuelven datos obsoletos (no actualizados) de la caché.
Por ejemplo, si no se tiene en cuenta el estado de un archivo de origen en caché cuando se renueva un período de vencimiento variable, se pueden crear datos de archivo en caché obsoletos. En cada solicitud de los datos se renueva el período de vencimiento variable, pero el archivo nunca se vuelve a cargar en la caché. Las características de la aplicación que usen el contenido en caché del archivo están sujetas a la posible recepción de contenido obsoleto.
El uso de tokens de cambio en un escenario de almacenamiento en caché de archivos evita la presencia de contenido de archivos obsoletos en la caché. En la aplicación de ejemplo se muestra una implementación del enfoque.
En el ejemplo se usa GetFileContent
para:
- Devolver el contenido del archivo.
- Implemente un algoritmo de reintento con retroceso exponencial para cubrir los casos en los que un problema de acceso a archivos retrasa temporalmente la lectura del contenido del archivo.
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;
}
Se crea un FileService
para administrar las búsquedas de archivos en caché. La llamada al método GetFileContent
del servicio intenta obtener el contenido de archivo de la caché en memoria y devolverlo al autor de la llamada (Services/FileService.cs
).
Si el contenido en caché no se encuentra mediante la clave de caché, se realizan las acciones siguientes:
- El contenido del archivo se obtiene mediante
GetFileContent
. - Se obtiene un token de cambio del proveedor de archivos con IFileProviders.Watch. La devolución de llamada del token se desencadena cuando se modifica el archivo.
- El contenido del archivo se almacena en caché con un período de vencimiento variable. El token de cambio se adjunta con MemoryCacheEntryExtensions.AddExpirationToken para expulsar la entrada de caché si el archivo cambia mientras está almacenado en caché.
En el ejemplo siguiente, los archivos se almacenan en la raíz del contenido de la aplicación. IWebHostEnvironment.ContentRootFileProvider
se usa para obtener un IFileProvider que apunte a IWebHostEnvironment.ContentRootPath
de la aplicación. La filePath
se obtiene con 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;
}
}
El FileService
se registra en el contenedor de servicios junto con el servicio de almacenamiento en caché.
En Startup.ConfigureServices
:
services.AddMemoryCache();
services.AddSingleton<FileService>();
El modelo de página carga el contenido del archivo mediante el servicio.
En el método OnGet
de la página de índice (Pages/Index.cshtml.cs
):
var fileContent = await _fileService.GetFileContents("poem.txt");
Clase CompositeChangeToken
Para representar una o varias instancias de IChangeToken
en un solo objeto, use la clase 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
});
En el token compuesto, HasChanged
notifica true
si algún token representado HasChanged
es true
. En el token compuesto, ActiveChangeCallbacks
notifica true
si algún token representado ActiveChangeCallbacks
es true
. Si se producen varios eventos de cambio simultáneos, la devolución de llamada de cambio compuesto se invoca una vez.
Un token de cambio es un bloque de creación de bajo nivel y uso general que se usa para realizar el seguimiento de los cambios de estado.
Vea o descargue el código de ejemplo (cómo descargarlo)
Interfaz IChangeToken
IChangeToken propaga notificaciones que indican que se ha producido un cambio. IChangeToken
reside en el espacio de nombres Microsoft.Extensions.Primitives. En el caso de las aplicaciones que no usan el metapaquete Microsoft.AspNetCore.App, cree una referencia al paquete NuGet Microsoft.Extensions.Primitives.
IChangeToken
tiene dos propiedades:
- ActiveChangeCallbacks indica si el token genera devoluciones de llamada de manera proactiva. Si
ActiveChangedCallbacks
se establece enfalse
, nunca se llama a una devolución de llamada y la aplicación debe sondearHasChanged
en busca de cambios. También es posible que un token nunca se cancele si no se producen cambios o si se elimina o deshabilita el agente de escucha de cambios subyacente. - HasChanged recibe un valor que indica si se ha producido un cambio.
La interfaz IChangeToken
incluye el método RegisterChangeCallback(Action<Object>, Object), que registra una devolución de llamada que se invoca cuando el token ha cambiado. HasChanged
se debe establecer antes de que se invoque la devolución de llamada.
Clase ChangeToken
ChangeToken es una clase estática que se usa para propagar notificaciones que indican que se ha producido un cambio. ChangeToken
reside en el espacio de nombres Microsoft.Extensions.Primitives. En el caso de las aplicaciones que no usan el metapaquete Microsoft.AspNetCore.App, cree una referencia al paquete NuGet Microsoft.Extensions.Primitives.
El método ChangeToken.OnChange(Func<IChangeToken>, Action) registra un elemento Action
al que se llama cada vez que cambia el token:
Func<IChangeToken>
genera el token.- Se llama a
Action
cuando cambia el token.
La sobrecarga ChangeToken.OnChange<TState>(Func<IChangeToken>, Action<TState>, TState) toma un parámetro TState
adicional que se pasa al consumidor de tokens Action
.
OnChange
devuelve un valor de IDisposable. Al llamar a Dispose se detiene la escucha del token de futuras modificaciones y se liberan sus recursos.
Ejemplos de uso de tokens de cambio en ASP.NET Core
Los tokens de cambio se usan en áreas principales de ASP.NET Core para supervisar los cambios en los objetos:
- Para supervisar los cambios en los archivos, el método Watch de IFileProvider crea un
IChangeToken
para los archivos especificados o la carpeta que se va a supervisar. - Se pueden agregar tokens
IChangeToken
a las entradas de caché para desencadenar expulsiones de caché al producirse un cambio. - Para los cambios de
TOptions
, la implementación predeterminada OptionsMonitor<TOptions> de IOptionsMonitor<TOptions> tiene una sobrecarga que acepta una o varias instancias de IOptionsChangeTokenSource<TOptions>. Cada instancia devuelve unIChangeToken
para registrar una devolución de llamada de notificación de cambio a fin de realizar el seguimiento de los cambios en las opciones.
Supervisión de los cambios de configuración
De forma predeterminada, las plantillas de ASP.NET Core usan archivos de configuración de JSON (appsettings.json
, appsettings.Development.json
y appsettings.Production.json
) para cargar la configuración de la aplicación.
Estos archivos se configuran mediante el método de extensión AddJsonFile(IConfigurationBuilder, String, Boolean, Boolean) en ConfigurationBuilder que acepta un parámetro reloadOnChange
. reloadOnChange
indica si la configuración se debe recargar en los cambios de archivo. Esta configuración aparece en el método CreateDefaultBuilder de WebHost:
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true,
reloadOnChange: true);
La configuración basada en archivo se presenta por medio de FileConfigurationSource. FileConfigurationSource
use IFileProvider para supervisar los archivos.
PhysicalFileProvider proporciona IFileMonitor
de manera predeterminada, que usa FileSystemWatcher para supervisar los cambios del archivo de configuración.
En la aplicación de ejemplo se muestran dos implementaciones para supervisar los cambios de configuración. Si se modifica cualquiera de los archivos appsettings
, ambas implementaciones de supervisión de los archivos ejecutan código personalizado, y la aplicación de ejemplo escribe un mensaje en la consola.
El FileSystemWatcher
de un archivo de configuración puede desencadenar varias devoluciones de llamada de token para un único cambio del archivo de configuración. Para garantizar que el código personalizado se ejecute solo una vez cuando se desencadenan varias devoluciones de llamada de token, la implementación del ejemplo comprueba los hashes de archivo. El ejemplo usa el algoritmo hash seguro 1. Se implementa un reintento con una interrupción 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 cambio de inicio simple
Registra una devolución de llamada de Action
del consumidor de token para las notificaciones de cambio en el token de recarga de configuración.
En Startup.Configure
:
ChangeToken.OnChange(
() => config.GetReloadToken(),
(state) => InvokeChanged(state),
env);
config.GetReloadToken()
proporciona el token. La devolución de llamada es el método 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)");
}
}
El state
de la devolución de llamada se usa para pasar IHostingEnvironment
, que resulta útil para especificar el archivo de configuración appsettings
correcto que se va a supervisar (por ejemplo, appsettings.Development.json
cuando se está en el entorno de desarrollo). Se usa el hash de archivo para evitar que se ejecute varias veces la instrucción WriteConsole
debido a varias devoluciones de llamada de token cuando el archivo de configuración solo ha cambiado una vez.
Este sistema se ejecuta siempre que la aplicación esté en ejecución y el usuario no lo puede deshabilitar.
Supervisión de los cambios de configuración como servicio
En el ejemplo se implementa lo siguiente:
- La supervisión del token de inicio básico.
- La supervisión como servicio.
- Un mecanismo para habilitar y deshabilitar la supervisión.
El ejemplo establece una interfaz IConfigurationMonitor
.
Extensions/ConfigurationMonitor.cs
:
public interface IConfigurationMonitor
{
bool MonitoringEnabled { get; set; }
string CurrentState { get; set; }
}
El constructor de la clase implementada, ConfigurationMonitor
, registra una devolución de llamada para las notificaciones de cambio:
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()
proporciona el token. InvokeChanged
es el método de devolución de llamada. El elemento state
de esta instancia es una referencia a la instancia de IConfigurationMonitor
que se usa para tener acceso al estado de supervisión. Se usan dos propiedades:
MonitoringEnabled
: indica si la devolución de llamada debe ejecutar su código personalizado.CurrentState
: describe el estado de supervisión actual para su uso en la interfaz de usuario.
El método InvokeChanged
es similar al enfoque anterior, excepto en que:
- No ejecuta su código, a menos que
MonitoringEnabled
seatrue
. - Genera el
state
actual en su salida deWriteConsole
.
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}");
}
}
}
Una instancia de ConfigurationMonitor
se registra como servicio en Startup.ConfigureServices
:
services.AddSingleton<IConfigurationMonitor, ConfigurationMonitor>();
En la página Index se ofrece al usuario el control sobre la supervisión de la configuración. La instancia de IConfigurationMonitor
se inserta en IndexModel
.
Pages/Index.cshtml.cs
:
public IndexModel(
IConfiguration config,
IConfigurationMonitor monitor,
FileService fileService)
{
_config = config;
_monitor = monitor;
_fileService = fileService;
}
El monitor de configuración (_monitor
) se usa para habilitar o deshabilitar la supervisión y establecer el estado actual de los comentarios de la interfaz de usuario:
public IActionResult OnPostStartMonitoring()
{
_monitor.MonitoringEnabled = true;
_monitor.CurrentState = "Monitoring!";
return RedirectToPage();
}
public IActionResult OnPostStopMonitoring()
{
_monitor.MonitoringEnabled = false;
_monitor.CurrentState = "Not monitoring";
return RedirectToPage();
}
Cuando se desencadena OnPostStartMonitoring
, se habilita la supervisión y se borra el estado actual. Cuando se desencadena OnPostStopMonitoring
, se deshabilita la supervisión y se establece el estado para reflejar que no se está realizando la supervisión.
Los botones de la interfaz de usuario habilitan y deshabilitan la supervisión.
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>
Supervisión de los cambios de archivos en caché
El contenido de los archivos se puede almacenar en caché en memoria mediante IMemoryCache. El almacenamiento en caché en memoria se describe en el tema Cache in-memory (Almacenamiento en caché en memoria). Sin realizar pasos adicionales, como la implementación que se describe a continuación, si los datos de origen cambian, se devuelven datos obsoletos (no actualizados) de la caché.
Por ejemplo, si no se tiene en cuenta el estado de un archivo de origen en caché cuando se renueva un período de vencimiento variable, se pueden crear datos de archivo en caché obsoletos. En cada solicitud de los datos se renueva el período de vencimiento variable, pero el archivo nunca se vuelve a cargar en la caché. Las características de la aplicación que usen el contenido en caché del archivo están sujetas a la posible recepción de contenido obsoleto.
El uso de tokens de cambio en un escenario de almacenamiento en caché de archivos evita la presencia de contenido de archivos obsoletos en la caché. En la aplicación de ejemplo se muestra una implementación del enfoque.
En el ejemplo se usa GetFileContent
para:
- Devolver el contenido del archivo.
- Implemente un algoritmo de reintento con retroceso exponencial para cubrir los casos en los que un problema de acceso a archivos retrasa temporalmente la lectura del contenido del archivo.
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;
}
Se crea un FileService
para administrar las búsquedas de archivos en caché. La llamada al método GetFileContent
del servicio intenta obtener el contenido de archivo de la caché en memoria y devolverlo al autor de la llamada (Services/FileService.cs
).
Si el contenido en caché no se encuentra mediante la clave de caché, se realizan las acciones siguientes:
- El contenido del archivo se obtiene mediante
GetFileContent
. - Se obtiene un token de cambio del proveedor de archivos con IFileProviders.Watch. La devolución de llamada del token se desencadena cuando se modifica el archivo.
- El contenido del archivo se almacena en caché con un período de vencimiento variable. El token de cambio se adjunta con MemoryCacheEntryExtensions.AddExpirationToken para expulsar la entrada de caché si el archivo cambia mientras está almacenado en caché.
En el ejemplo siguiente, los archivos se almacenan en la raíz del contenido de la aplicación. IHostingEnvironment.ContentRootFileProvider se usa para obtener un elemento IFileProvider que apunta a la ContentRootPath de la aplicación. La filePath
se obtiene con 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;
}
}
El FileService
se registra en el contenedor de servicios junto con el servicio de almacenamiento en caché.
En Startup.ConfigureServices
:
services.AddMemoryCache();
services.AddSingleton<FileService>();
El modelo de página carga el contenido del archivo mediante el servicio.
En el método OnGet
de la página de índice (Pages/Index.cshtml.cs
):
var fileContent = await _fileService.GetFileContents("poem.txt");
Clase CompositeChangeToken
Para representar una o varias instancias de IChangeToken
en un solo objeto, use la clase 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
});
En el token compuesto, HasChanged
notifica true
si algún token representado HasChanged
es true
. En el token compuesto, ActiveChangeCallbacks
notifica true
si algún token representado ActiveChangeCallbacks
es true
. Si se producen varios eventos de cambio simultáneos, la devolución de llamada de cambio compuesto se invoca una vez.
Recursos adicionales
- Caché en memoria en ASP.NET Core
- Almacenamiento en caché distribuido en ASP.NET Core
- Almacenamiento en caché de respuestas de ASP.NET Core
- Middleware de almacenamiento en caché de respuestas en ASP.NET Core
- Asistente de etiquetas de caché en ASP.NET Core MVC
- Asistente de etiquetas de caché distribuida en ASP.NET Core