- 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