ASP.NET Core で変更トークンを使用して変更を検出する
Note
これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
警告
このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
重要
この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。
現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
"変更トークン" は、状態の変更を追跡するために使用される汎用の低レベル構成ブロックです。
サンプル コードを表示またはダウンロードします (ダウンロード方法)。
IChangeToken インターフェイス
IChangeToken により、変更が発生したという通知が伝達されます。 IChangeToken
は Microsoft.Extensions.Primitives 名前空間内にあります。 Microsoft.Extensions.Primitives NuGet パッケージは、ASP.NET Core アプリに暗黙的に提供されます。
IChangeToken
に次の 2 つのプロパティがあります。
- ActiveChangeCallbacks は、トークンによってコールバックがプロアクティブに生成されるかどうかを示します。
ActiveChangedCallbacks
がfalse
に設定されている場合、コールバックは呼び出されず、アプリは変更のためにHasChanged
をポーリングする必要があります。 変更がまったく発生しない場合、または基になる変更リスナーが破棄されるか無効になっている場合、トークンがまったくキャンセルされない可能性もあります。 - HasChanged は、変更が発生したかどうかを示す値を受け取ります。
IChangeToken
インターフェイスには、RegisterChangeCallback(Action<Object>, Object) というメソッドが含まれています。これを使うと、トークンが変更されたときに呼び出されるコールバックを登録できます。 コールバックが呼び出される前に HasChanged
を設定する必要があります。
ChangeToken クラス
ChangeToken は、変更が発生したことの通知を伝達するために使用される静的クラスです。 ChangeToken
は Microsoft.Extensions.Primitives 名前空間内にあります。 Microsoft.Extensions.Primitives NuGet パッケージは、ASP.NET Core アプリに暗黙的に提供されます。
ChangeToken.OnChange(Func<IChangeToken>, Action) メソッドを使うと、トークンが変更されるたびに呼び出される Action
を登録できます。
Func<IChangeToken>
はトークンを生成します。Action
は、トークンが変更されたときに呼び出されます。
ChangeToken.OnChange<TState>(Func<IChangeToken>, Action<TState>, TState) オーバーロードには、トークン コンシューマー Action
に渡される追加の TState
パラメーターを指定できます。
OnChange
では IDisposable が返されます。 Dispose を呼び出すと、トークンによるその後の変更のリッスンが停止され、トークンのリソースが解放されます。
ASP.NET Core での変更のトークンの使用例
変更トークンは、オブジェクトの変更を監視するために ASP.NET Core の主要な領域で使用されます。
- ファイルへの変更を監視するために、IFileProvider の Watch メソッドでは、指定された監視対象のファイルまたはフォルダーの
IChangeToken
が作成されます。 IChangeToken
トークンをキャッシュ エントリに追加して、変更時にキャッシュの削除をトリガーすることができます。TOptions
の変更のために、OptionsMonitor<TOptions> の既定の実装である IOptionsMonitor<TOptions> には、1 つ以上の IOptionsChangeTokenSource<TOptions> インスタンスを受け取るオーバーロードが含まれています。 各インスタンスは、オプションの変更を追跡するために変更通知のコールバックを登録するIChangeToken
を返します。
構成の変更の監視
既定では、ASP.NET Core テンプレートは、JSON 構成ファイル (appsettings.json
、appsettings.Development.json
、appsettings.Production.json
) を使用して、アプリの構成設定を読み込みます。
これらのファイルは、reloadOnChange
パラメーターを受け取る、ConfigurationBuilder 上の AddJsonFile(IConfigurationBuilder, String, Boolean, Boolean) 拡張メソッドを使用して構成されます。 reloadOnChange
は、ファイルの変更時に構成を再読み込みするかどうかを示します。 この設定は、Host の便利なメソッド CreateDefaultBuilder 内に現れます。
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true,
reloadOnChange: true);
ファイル ベースの構成は、FileConfigurationSource によって表されます。 FileConfigurationSource
ではファイルを監視するために IFileProvider が使用されます。
既定では、IFileMonitor
は PhysicalFileProvider によって提供されます。これは、FileSystemWatcher を使用して、構成ファイルの変更を監視します。
サンプル アプリは、構成の変更を監視するための 2 つの実装を示します。 appsettings
ファイルのいずれかが変更されると、ファイルを監視する両方の実装によってカスタム コードが実行されます。サンプル アプリによりコンソールにメッセージが書き込まれます。
構成ファイルの FileSystemWatcher
は、1 つの構成ファイルの変更に対して複数のトークンのコールバックをトリガーできます。 複数のトークンのコールバックがトリガーされたときにカスタム コードが一度だけ実行されるようにするために、サンプルの実装ではファイル ハッシュがチェックされます。 サンプルでは、SHA1 ファイル ハッシュが使用されています。 再試行は、指数バックオフを使用して実装されます。
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];
}
シンプルなスタートアップ変更トークン
構成再読み込みトークンへの変更通知のために、トークン コンシューマー Action
のコールバックを登録します。
Startup.Configure
の場合:
ChangeToken.OnChange(
() => config.GetReloadToken(),
(state) => InvokeChanged(state),
env);
config.GetReloadToken()
はトークンを提供します。 コールバックは、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)");
}
}
コールバックの state
は、IWebHostEnvironment
を渡すためのに使われます。これは監視する適切な appsettings
構成ファイルを指定するのに便利です (たとえば、開発環境の場合は appsettings.Development.json
)。 ファイル ハッシュは、構成ファイルが 1 回のみ変更されたときの複数のトークンのコールバックのために WriteConsole
ステートメントが複数回実行されるのを防ぐために使用されます。
このシステムは、アプリが実行されている限り実行され、ユーザーが無効にすることはできません。
サービスとしての構成の変更の監視
サンプルは次のものを実装します。
- 基本的なスタートアップ トークンの監視
- サービスとしての監視
- 監視を有効および無効にするメカニズム。
サンプルでは IConfigurationMonitor
インターフェイスが設定されています。
Extensions/ConfigurationMonitor.cs
:
public interface IConfigurationMonitor
{
bool MonitoringEnabled { get; set; }
string CurrentState { get; set; }
}
実装済みのクラスのコンストラクター ConfigurationMonitor
は、変更通知のコールバックを登録します。
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()
はトークンを提供します。 InvokeChanged
はコールバック メソッドです。 このインスタンスの state
は、監視の状態にアクセスするために使用される IConfigurationMonitor
インスタンスへの参照です。 次の 2 つのプロパティが使用されます。
MonitoringEnabled
: コールバックでそのカスタム コードを実行する必要があるかどうかを示します。CurrentState
: UI で使用するための現在の監視状態を説明します。
InvokeChanged
メソッドは、次の点を除けば、前の方法に似ています。
MonitoringEnabled
がtrue
ではない限りコードを実行しません。- 現在の
state
をその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}");
}
}
}
インスタンス ConfigurationMonitor
は、Startup.ConfigureServices
でサービスとして登録されます。
services.AddSingleton<IConfigurationMonitor, ConfigurationMonitor>();
インデックス ページは、ユーザーに構成監視の制御を提供します。 IConfigurationMonitor
のインスタンスは、IndexModel
に挿入されます。
Pages/Index.cshtml.cs
:
public IndexModel(
IConfiguration config,
IConfigurationMonitor monitor,
FileService fileService)
{
_config = config;
_monitor = monitor;
_fileService = fileService;
}
構成監視 (_monitor
) は、監視を有効または無効にし、UI フィードバックの現在の状態を設定するために使用されます。
public IActionResult OnPostStartMonitoring()
{
_monitor.MonitoringEnabled = true;
_monitor.CurrentState = "Monitoring!";
return RedirectToPage();
}
public IActionResult OnPostStopMonitoring()
{
_monitor.MonitoringEnabled = false;
_monitor.CurrentState = "Not monitoring";
return RedirectToPage();
}
OnPostStartMonitoring
がトリガーされると、監視が有効になり、現在の状態がクリアされます。 OnPostStopMonitoring
がトリガーされると、監視が無効になり、監視が発生していないことを反映するように状態が設定されます。
UI のボタンを使って監視を有効および無効にできます。
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>
キャッシュされたファイルの変更の監視
IMemoryCache を使用して、ファイルの内容をメモリ内にキャッシュすることができます。 メモリ内キャッシュの説明については、「メモリ内キャッシュ」トピックを参照してください。 以下に示す実装など、追加の手順を実行しないと、ソース データが変更された場合に期限切れの (古い) データがキャッシュから返されます。
たとえば、スライド式有効期限期間の更新時にキャッシュされたソース ファイルの状態を考慮しないと、キャッシュされたファイル データが古くなります。 データの各要求が、スライド式有効期限を更新しますが、ファイルがキャッシュに再読み込みされることはありません。 ファイルのキャッシュされた内容を使用するアプリの機能は、古い内容を受け取る可能性があります。
ファイル キャッシュのシナリオで変更トークンを使用すると、キャッシュに古いファイルの内容が残ることを防止できます。 サンプル アプリは、このアプローチの実装を示しています。
このサンプルでは、GetFileContent
を使用して次の操作を実行します。
- ファイルの内容を返します。
- 指数バックオフを使用する再試行アルゴリズムを実装し、ファイル アクセスの問題によりファイルのコンテンツの読み取りが一時的に妨げられるケースに対処します。
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;
}
FileService
はキャッシュされたファイルの参照の処理するために作成されます。 サービスの GetFileContent
メソッド呼び出しは、メモリ内キャッシュからファイルの内容を取得して、呼び出し元 (Services/FileService.cs
) に戻そうとします。
キャッシュ キーを使用してキャッシュされた内容が見つからない場合、次の操作が実行されます。
GetFileContent
を使用してファイルの内容が取得されます。- 変更トークンは、IFileProviders.Watch を使用してファイル プロバイダーから取得します。 ファイルが変更されたときに、トークンのコールバックがトリガーされます。
- ファイルの内容は、スライド式有効期限の期間キャシュされます。 ファイルがキャッシュされている間に変更された場合、キャッシュ エントリを削除するために、MemoryCacheEntryExtensions.AddExpirationToken を使用して変更トークンがアタッチされます。
次の例では、ファイルはアプリのコンテンツ ルートに格納されます。 IWebHostEnvironment.ContentRootFileProvider
は、アプリの IWebHostEnvironment.ContentRootPath
を指す IFileProvider を取得するために使用されます。 IFileInfo.PhysicalPath を使って filePath
を取得します。
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;
}
}
FileService
はメモリ キャッシュ サービスと共にサービス コンテナーに登録されます。
Startup.ConfigureServices
の場合:
services.AddMemoryCache();
services.AddSingleton<FileService>();
ページ モデルでは、このサービスを使ってファイルの内容が読み込まれます。
Index ページの OnGet
メソッド内 (Pages/Index.cshtml.cs
):
var fileContent = await _fileService.GetFileContents("poem.txt");
CompositeChangeToken クラス
1 つのオブジェクト内で 1 つまたは複数の IChangeToken
インスタンスを表すためには、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
});
複合トークンの HasChanged
は、いずれかの表現されているトークン HasChanged
が true
である場合に、true
を報告します。 複合トークンの ActiveChangeCallbacks
は、いずれかの表現されているトークン ActiveChangeCallbacks
が true
である場合に、true
を報告します。 複数の同時変更イベントが発生すると、複合変更コールバックが 1 回呼び出されます。
"変更トークン" は、状態の変更を追跡するために使用される汎用の低レベル構成ブロックです。
サンプル コードを表示またはダウンロードします (ダウンロード方法)。
IChangeToken インターフェイス
IChangeToken により、変更が発生したという通知が伝達されます。 IChangeToken
は Microsoft.Extensions.Primitives 名前空間内にあります。 Microsoft.AspNetCore.App メタパッケージを使用しないアプリの場合は、Microsoft.Extensions.Primitives NuGet パッケージのパッケージ参照を作成します。
IChangeToken
に次の 2 つのプロパティがあります。
- ActiveChangeCallbacks は、トークンによってコールバックがプロアクティブに生成されるかどうかを示します。
ActiveChangedCallbacks
がfalse
に設定されている場合、コールバックは呼び出されず、アプリは変更のためにHasChanged
をポーリングする必要があります。 変更がまったく発生しない場合、または基になる変更リスナーが破棄されるか無効になっている場合、トークンがまったくキャンセルされない可能性もあります。 - HasChanged は、変更が発生したかどうかを示す値を受け取ります。
IChangeToken
インターフェイスには、RegisterChangeCallback(Action<Object>, Object) というメソッドが含まれています。これを使うと、トークンが変更されたときに呼び出されるコールバックを登録できます。 コールバックが呼び出される前に HasChanged
を設定する必要があります。
ChangeToken クラス
ChangeToken は、変更が発生したことの通知を伝達するために使用される静的クラスです。 ChangeToken
は Microsoft.Extensions.Primitives 名前空間内にあります。 Microsoft.AspNetCore.App メタパッケージを使用しないアプリの場合は、Microsoft.Extensions.Primitives NuGet パッケージのパッケージ参照を作成します。
ChangeToken.OnChange(Func<IChangeToken>, Action) メソッドを使うと、トークンが変更されるたびに呼び出される Action
を登録できます。
Func<IChangeToken>
はトークンを生成します。Action
は、トークンが変更されたときに呼び出されます。
ChangeToken.OnChange<TState>(Func<IChangeToken>, Action<TState>, TState) オーバーロードには、トークン コンシューマー Action
に渡される追加の TState
パラメーターを指定できます。
OnChange
では IDisposable が返されます。 Dispose を呼び出すと、トークンによるその後の変更のリッスンが停止され、トークンのリソースが解放されます。
ASP.NET Core での変更のトークンの使用例
変更トークンは、オブジェクトの変更を監視するために ASP.NET Core の主要な領域で使用されます。
- ファイルへの変更を監視するために、IFileProvider の Watch メソッドでは、指定された監視対象のファイルまたはフォルダーの
IChangeToken
が作成されます。 IChangeToken
トークンをキャッシュ エントリに追加して、変更時にキャッシュの削除をトリガーすることができます。TOptions
の変更のために、OptionsMonitor<TOptions> の既定の実装である IOptionsMonitor<TOptions> には、1 つ以上の IOptionsChangeTokenSource<TOptions> インスタンスを受け取るオーバーロードが含まれています。 各インスタンスは、オプションの変更を追跡するために変更通知のコールバックを登録するIChangeToken
を返します。
構成の変更の監視
既定では、ASP.NET Core テンプレートは、JSON 構成ファイル (appsettings.json
、appsettings.Development.json
、appsettings.Production.json
) を使用して、アプリの構成設定を読み込みます。
これらのファイルは、reloadOnChange
パラメーターを受け取る、ConfigurationBuilder 上の AddJsonFile(IConfigurationBuilder, String, Boolean, Boolean) 拡張メソッドを使用して構成されます。 reloadOnChange
は、ファイルの変更時に構成を再読み込みするかどうかを示します。 この設定は、WebHost の便利なメソッド CreateDefaultBuilder 内に現れます。
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true,
reloadOnChange: true);
ファイル ベースの構成は、FileConfigurationSource によって表されます。 FileConfigurationSource
ではファイルを監視するために IFileProvider が使用されます。
既定では、IFileMonitor
は PhysicalFileProvider によって提供されます。これは、FileSystemWatcher を使用して、構成ファイルの変更を監視します。
サンプル アプリは、構成の変更を監視するための 2 つの実装を示します。 appsettings
ファイルのいずれかが変更されると、ファイルを監視する両方の実装によってカスタム コードが実行されます。サンプル アプリによりコンソールにメッセージが書き込まれます。
構成ファイルの FileSystemWatcher
は、1 つの構成ファイルの変更に対して複数のトークンのコールバックをトリガーできます。 複数のトークンのコールバックがトリガーされたときにカスタム コードが一度だけ実行されるようにするために、サンプルの実装ではファイル ハッシュがチェックされます。 サンプルでは、SHA1 ファイル ハッシュが使用されています。 再試行は、指数バックオフを使用して実装されます。
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];
}
シンプルなスタートアップ変更トークン
構成再読み込みトークンへの変更通知のために、トークン コンシューマー Action
のコールバックを登録します。
Startup.Configure
の場合:
ChangeToken.OnChange(
() => config.GetReloadToken(),
(state) => InvokeChanged(state),
env);
config.GetReloadToken()
はトークンを提供します。 コールバックは、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)");
}
}
コールバックの state
は、IHostingEnvironment
を渡すためのに使われます。これは監視する適切な appsettings
構成ファイルを指定するのに便利です (たとえば、開発環境の場合は appsettings.Development.json
)。 ファイル ハッシュは、構成ファイルが 1 回のみ変更されたときの複数のトークンのコールバックのために WriteConsole
ステートメントが複数回実行されるのを防ぐために使用されます。
このシステムは、アプリが実行されている限り実行され、ユーザーが無効にすることはできません。
サービスとしての構成の変更の監視
サンプルは次のものを実装します。
- 基本的なスタートアップ トークンの監視
- サービスとしての監視
- 監視を有効および無効にするメカニズム。
サンプルでは IConfigurationMonitor
インターフェイスが設定されています。
Extensions/ConfigurationMonitor.cs
:
public interface IConfigurationMonitor
{
bool MonitoringEnabled { get; set; }
string CurrentState { get; set; }
}
実装済みのクラスのコンストラクター ConfigurationMonitor
は、変更通知のコールバックを登録します。
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()
はトークンを提供します。 InvokeChanged
はコールバック メソッドです。 このインスタンスの state
は、監視の状態にアクセスするために使用される IConfigurationMonitor
インスタンスへの参照です。 次の 2 つのプロパティが使用されます。
MonitoringEnabled
: コールバックでそのカスタム コードを実行する必要があるかどうかを示します。CurrentState
: UI で使用するための現在の監視状態を説明します。
InvokeChanged
メソッドは、次の点を除けば、前の方法に似ています。
MonitoringEnabled
がtrue
ではない限りコードを実行しません。- 現在の
state
をその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}");
}
}
}
インスタンス ConfigurationMonitor
は、Startup.ConfigureServices
でサービスとして登録されます。
services.AddSingleton<IConfigurationMonitor, ConfigurationMonitor>();
インデックス ページは、ユーザーに構成監視の制御を提供します。 IConfigurationMonitor
のインスタンスは、IndexModel
に挿入されます。
Pages/Index.cshtml.cs
:
public IndexModel(
IConfiguration config,
IConfigurationMonitor monitor,
FileService fileService)
{
_config = config;
_monitor = monitor;
_fileService = fileService;
}
構成監視 (_monitor
) は、監視を有効または無効にし、UI フィードバックの現在の状態を設定するために使用されます。
public IActionResult OnPostStartMonitoring()
{
_monitor.MonitoringEnabled = true;
_monitor.CurrentState = "Monitoring!";
return RedirectToPage();
}
public IActionResult OnPostStopMonitoring()
{
_monitor.MonitoringEnabled = false;
_monitor.CurrentState = "Not monitoring";
return RedirectToPage();
}
OnPostStartMonitoring
がトリガーされると、監視が有効になり、現在の状態がクリアされます。 OnPostStopMonitoring
がトリガーされると、監視が無効になり、監視が発生していないことを反映するように状態が設定されます。
UI のボタンを使って監視を有効および無効にできます。
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>
キャッシュされたファイルの変更の監視
IMemoryCache を使用して、ファイルの内容をメモリ内にキャッシュすることができます。 メモリ内キャッシュの説明については、「メモリ内キャッシュ」トピックを参照してください。 以下に示す実装など、追加の手順を実行しないと、ソース データが変更された場合に期限切れの (古い) データがキャッシュから返されます。
たとえば、スライド式有効期限期間の更新時にキャッシュされたソース ファイルの状態を考慮しないと、キャッシュされたファイル データが古くなります。 データの各要求が、スライド式有効期限を更新しますが、ファイルがキャッシュに再読み込みされることはありません。 ファイルのキャッシュされた内容を使用するアプリの機能は、古い内容を受け取る可能性があります。
ファイル キャッシュのシナリオで変更トークンを使用すると、キャッシュに古いファイルの内容が残ることを防止できます。 サンプル アプリは、このアプローチの実装を示しています。
このサンプルでは、GetFileContent
を使用して次の操作を実行します。
- ファイルの内容を返します。
- 指数バックオフを使用する再試行アルゴリズムを実装し、ファイル アクセスの問題によりファイルのコンテンツの読み取りが一時的に妨げられるケースに対処します。
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;
}
FileService
はキャッシュされたファイルの参照の処理するために作成されます。 サービスの GetFileContent
メソッド呼び出しは、メモリ内キャッシュからファイルの内容を取得して、呼び出し元 (Services/FileService.cs
) に戻そうとします。
キャッシュ キーを使用してキャッシュされた内容が見つからない場合、次の操作が実行されます。
GetFileContent
を使用してファイルの内容が取得されます。- 変更トークンは、IFileProviders.Watch を使用してファイル プロバイダーから取得します。 ファイルが変更されたときに、トークンのコールバックがトリガーされます。
- ファイルの内容は、スライド式有効期限の期間キャシュされます。 ファイルがキャッシュされている間に変更された場合、キャッシュ エントリを削除するために、MemoryCacheEntryExtensions.AddExpirationToken を使用して変更トークンがアタッチされます。
次の例では、ファイルはアプリのコンテンツ ルートに格納されます。 アプリの ContentRootPath を指す IFileProvider を取得するために、IHostingEnvironment.ContentRootFileProvider が使われます。 IFileInfo.PhysicalPath を使って filePath
を取得します。
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;
}
}
FileService
はメモリ キャッシュ サービスと共にサービス コンテナーに登録されます。
Startup.ConfigureServices
の場合:
services.AddMemoryCache();
services.AddSingleton<FileService>();
ページ モデルでは、このサービスを使ってファイルの内容が読み込まれます。
Index ページの OnGet
メソッド内 (Pages/Index.cshtml.cs
):
var fileContent = await _fileService.GetFileContents("poem.txt");
CompositeChangeToken クラス
1 つのオブジェクト内で 1 つまたは複数の IChangeToken
インスタンスを表すためには、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
});
複合トークンの HasChanged
は、いずれかの表現されているトークン HasChanged
が true
である場合に、true
を報告します。 複合トークンの ActiveChangeCallbacks
は、いずれかの表現されているトークン ActiveChangeCallbacks
が true
である場合に、true
を報告します。 複数の同時変更イベントが発生すると、複合変更コールバックが 1 回呼び出されます。
その他の技術情報
ASP.NET Core