- A change token is a general-purpose, low-level building block used to track changes.
- Change tokens are used in prominent areas of ASP.NET Core monitoring changes to objects:
- For monitoring changes to files,
IFileProvider's Watch method creates an IChangeToken for the specified files or folder to watch.
IChangeToken tokens can be added to cache entries to trigger cache evictions on change.
- For
TOptions changes, the default OptionsMonitor implementation of IOptionsMonitor has an overload that accepts one or more IOptionsChangeTokenSource instances.
- Each instance returns an
IChangeToken to register a change notification callback for tracking options changes.
IChangeToken
IChangeToken propagates notifications that a change has occurred. It has two properties:
ActiveChangedCallbacks indicate if the token proactively raises callbacks. If ActiveChangedCallbacks is set to false, a callback is never called, and the app must poll HasChanged for changes. It's also possible for a token to never be cancelled if no changes occur or the underlying change listener is disposed or disabled.
HasChanged gets a value that indicates if a change has occurred.
- The interface has one method,
RegisterChangeCallback(Action<Object>, Object), which registers a callback that's invoked when the token has changed.
HasChanged must be set before the callback is invoked.
ChangeToken Class
ChangeToken is a static class used to propagate notifications that a change has occurred.
- The
ChangeToken OnChange(Func<IChangeToken>, Action) method registers an Action to call whenever the token changes:
Func<IChangeToken> produces the token.
Action is called when the token changes.
ChangeToken has an OnChange<TState>(Func<IChangeToken>, Action<TState>, TState) overload that takes an additional TState parameter that's passed into the token consumer Action.
OnChange returns an IDisposable.
- Calling
Dispose stops the token from listening for further changes and releases the token's resources.
Monitoring for Configuration Changes
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
- File-based configuration is represented by
FileConfigurationSource.
- The sample app demonstrates two implementations for monitoring configuration changes.
- If either the appsettings.json file changes or the Environment version of the file changes, each implementation executes custom code.
- A configuration file's
FileSystemWatcher can trigger multiple token callbacks for a single configuration file change.
- The sample's implementation guards against this problem by checking file hashes on the configuration files.
- Checking file hashes ensures that at least one of the configuration files has changed before running the custom code.
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);
}
}
}
catch (IOException ex)
{
if (runCount == 3 || ex.HResult != -2147024864)
{
throw;
}
else
{
Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, runCount)));
runCount++;
}
}
}
return new byte[20];
}
- A retry is implemented with an exponential back-off.
- The re-try is present because file locking may occur that temporarily prevents computing a new hash on one of the files.
Simple Startup Change Token