惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LangChain Blog
N
News and Events Feed by Topic
T
Tor Project blog
AI
AI
S
Schneier on Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Hacker News: Ask HN
Hacker News: Ask HN
Spread Privacy
Spread Privacy
The Last Watchdog
The Last Watchdog
B
Blog
G
GRAHAM CLULEY
Recent Commits to openclaw:main
Recent Commits to openclaw:main
W
WeLiveSecurity
GbyAI
GbyAI
Hacker News - Newest:
Hacker News - Newest: "LLM"
C
Check Point Blog
SecWiki News
SecWiki News
Y
Y Combinator Blog
I
Intezer
S
Securelist
WordPress大学
WordPress大学
小众软件
小众软件
Google DeepMind News
Google DeepMind News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
美团技术团队
Schneier on Security
Schneier on Security
F
Full Disclosure
D
Darknet – Hacking Tools, Hacker News & Cyber Security
P
Proofpoint News Feed
Jina AI
Jina AI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Help Net Security
Help Net Security
P
Privacy International News Feed
N
News and Events Feed by Topic
人人都是产品经理
人人都是产品经理
The Register - Security
The Register - Security
Application and Cybersecurity Blog
Application and Cybersecurity Blog
MyScale Blog
MyScale Blog
Vercel News
Vercel News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
Threatpost
H
Help Net Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
G
Google Developers Blog
T
Tailwind CSS Blog
L
Lohrmann on Cybersecurity
Forbes - Security
Forbes - Security
C
Cisco Blogs
The GitHub Blog
The GitHub Blog
博客园 - 司徒正美

Duende Software Official Site

Duende Software Duende Software Duende Software Duende Software Duende Software Duende Software Stop AI Bots from Wasting Your Server How Duende IdentityServer Filters Claims (And Why It Matters) Core vs Extended Protocols in Duende IdentityServer v8: What You Get and When You Need More Your IdentityServer v8 Upgrade Checklist: A Quick Pre-Flight Guide Setting Up SAML Single Sign-On in ASP.NET with Duende IdentityServer Your Identity, Your Terms: Duende's Modular Identity Infrastructure and v8.x Release Duende Spring Launch '26: Identity Infrastructure That Expands With You SAML and OpenID Connect (OIDC): Coexistence, Not Competition The 9 Components of SAML You Need to Know, Ranked by Importance The Cost of NOT Implementing Financial-Grade Security Token Issuer Isolation: Why It Matters for Security and Compliance The Composable Identity Pattern: Build What You Need, Skip What You Don't Multi-Brand Identity: When Your Company Needs More Than One Face Post-Quantum Cryptography in .NET 10: A Practical Guide The field Keyword in C# 14: Write Less, Validate More The Real Cost of Build vs. Buy for Identity OAuth 2.1 Made Simple: The Only Flows You Need Beyond localhost: Multi-Instance ASP.NET Core Deployment with .NET 10 Harden Your .NET JSON Deserialization with System.Text.Json and JsonSerializerOptions.Strict ASP.NET Core Cookie Size Limits in Production: Causes and Fixes The Emergency Stop Button - Implementing Immediate Token Revocation in .NET 10 The 2025 OWASP Top 10 and IdentityServer Update Guidance for CVE-2026-40372 - ASP.NET Data Protection Why a Standard JWT Access Token Matters The Identity Governance Checklist You Wish You Had Six Months Ago The History and Future of SAML: Why a 20-Year-Old Protocol Still Matters The Cookie Apocalypse Already Happened Verify - Open Source Sponsorship Why Identity Is Infrastructure, Not a Feature Extending Duende IdentityServer Server-Side Sessions with Dynamic User Metadata Give Your AI Coding Assistant Duende Expertise with Agent Skills and MCP Server Triggering User Registration via OpenID Connect with Duende IdentityServer Improving .NET Security Code with C# 14 Property Extensions Developing Audit Logs with Duende IdentityServer Events Patch Releases: Addressing CVE-2026-26127 in Microsoft.BCL.Memory Client-Initiated Backchannel Authentication (CIBA) in ASP.NET Core 10 with Duende Identity Server Rate Limiting IdentityServer Endpoints It's Probably DNS - Can You Dig It? Security Lingo Explained: Encode vs Encrypt vs Hash Implementing Zero Trust with Resource Isolation Security Lingo Explained: JWT DPoP Security for .NET APIs with JwtBearer Extensions v1.0.0 Announcing the Duende IdentityServer4 Migration Analysis Tool BenchmarkDotNet - Open Source Sponsorship Security Lingo Explained: PAR Why Signing Key Rotation Matters in OpenID Connect and Duende IdentityServer Security Lingo Explained: OP Duende Year-End Review 2025 Security Lingo Explained: BCP Security Lingo Explained: DPoP Security Lingo Explained: Auth Secure frontend apps with the BFF Pattern Scaling with Duende IdentityServer, MCP, and AI Duende IdentityServer v7.4 is now available Duende BFFv4 is now available Securing OpenAPI and Swagger UI with OAuth in .NET 10 Building a Federation Gateway with Duende IdentityServer: Strategies and Considerations for Identity Orchestration
Duende Software
Khalid Abuhakmeh, AL Rodriguez · 2026-07-14 · via Duende Software Official Site
Summary: ChangeToken, a core primitive in the .NET Framework's Microsoft.Extensions.Primitives package, enables reactive patterns like configuration hot-reloading, options monitoring, and cache invalidation. The one-shot IChangeToken interface is the foundation, while the ChangeToken.OnChange() method simplifies continuous change monitoring by handling the automatic re-registration loop. Understanding this abstraction allows developers to build custom, framework-consistent change notification sources.

You've written reloadOnChange: true more times than you can count. You've used IOptionsMonitor<T> to pick up fresh configuration without restarting your app. Maybe you've tied a cache entry's lifetime to a file on disk. All of those features share a common mechanism under the hood: ChangeToken, a type in Microsoft.Extensions.Primitives package that has been quietly running your reactive patterns since Microsoft introduced ASP.NET Core.

Most developers never look at it directly. That changes today. Once you understand how ChangeToken works, you can build your own hot-reload, cache invalidation, and change-notification patterns with the same reliability that the framework uses internally.

Where ChangeToken Is Already in Your Code

ChangeToken shows up in four places you almost certainly use every day.

Configuration reloading. When you call AddJsonFile with reloadOnChange: true, the configuration system uses IFileProvider.Watch() to get an IChangeToken for appsettings.json. When the file changes on disk, that token signals a change, and your configuration reloads automatically.

The options monitor. IOptionsMonitor<T> tracks changes through IOptionsChangeTokenSource<T>. Every time you call monitor.CurrentValue and get fresh values without restarting; a change token is fired, triggering a reload behind the scenes.

Memory cache expiration. MemoryCacheEntryOptions has an AddExpirationToken(IChangeToken) method. Pass it any change token, and the cache entry evicts itself the moment that token signals. The expiration timer you set is just one option; change tokens are another.

File watching. IFileProvider.Watch("*.json") returns an IChangeToken directly. It fires when any matching file changes, moves, or is deleted.

These four APIs all speak the same language. The abstraction is IChangeToken, and because they share it, you can compose them, swap implementations, and slot your own logic in anywhere the framework expects one. You've probably written lines like these before:

Csharp

// Configuration reloading: ChangeToken fires when appsettings.json changes
builder.Configuration.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);

// Options monitor: CurrentValue is always fresh because ChangeToken triggered a reload
app.MapGet("/config", (IOptionsMonitor<MyOptions> monitor) => monitor.CurrentValue);

// Cache expiration tied to a change token: evicts when the token signals
var entry = cache.CreateEntry("key");
entry.AddExpirationToken(fileProvider.Watch("data.json"));

// File watching: returns an IChangeToken you can subscribe to
IChangeToken token = fileProvider.Watch("*.json");
// Each of these is abbreviated. The point is recognition, not a runnable program.

The IChangeToken Interface

The IChangeToken interface is intentionally small. Three members, nothing more:

Csharp

public interface IChangeToken
{
    bool HasChanged { get; }
    bool ActiveChangeCallbacks { get; }
    IDisposable RegisterChangeCallback(Action<object?> callback, object? state);
}

HasChanged flips to true when the source signals a change. Once it's true, it stays true. There's no reset. This approach is the one-shot design: a token represents a single change event rather than an ongoing subscription.

ActiveChangeCallbacks tells you whether the token will proactively invoke your callback. When this is true, the token fires your callback as soon as the change happens. When it's false, the token is polling-only, and you have to check HasChanged yourself. Most tokens you'll encounter in the framework have ActiveChangeCallbacks set to true.

RegisterChangeCallback wires up your handler. It returns an IDisposable you can use to unsubscribe. Because tokens are one-shot, you also need a new token after each change if you want to keep watching. That's the most common source of confusion with this API, and the reason ChangeToken.OnChange() exists.

ChangeToken.OnChange(): The Method That Does the Work

If you use RegisterChangeCallback directly, you're responsible for requesting a new token after every change and re-registering your callback. Miss that step once, and your watcher goes silent.

ChangeToken.OnChange() handles that loop for you:

Csharp

public static IDisposable OnChange(
    Func<IChangeToken?> changeTokenProducer,
    Action changeTokenConsumer);

The first argument is a factory function. Every time OnChange needs a token, it calls this. The second argument is your callback, invoked whenever a change is detected.

Here's what happens internally on each cycle:

sequenceDiagram
    participant OnChange as ChangeToken.OnChange()
    participant Producer as Token Producer (your code)
    participant Source as Change Source (e.g. file system)
    participant Callback as Your Callback

    OnChange->>Producer: Get IChangeToken
    Producer-->>OnChange: Token #1
    OnChange->>OnChange: Register on Token #1
    Source->>OnChange: Token #1 signals HasChanged = true
    OnChange->>Producer: Get IChangeToken (auto re-register)
    Producer-->>OnChange: Token #2
    OnChange->>Callback: Invoke callback
    OnChange->>OnChange: Register on Token #2
    Note over OnChange,Callback: Cycle repeats automatically

The re-registration step is the whole point. When a change fires, OnChange first fetches a new token from your producer, then invokes your callback, and then registers on the new token. Your watch continues without any extra code on your part. OnChange returns an IDisposable. Hold onto it and dispose of it when you want to stop watching, such as when your service shuts down.

Building a Custom Change Notification

Here's a concrete example. The program below monitors a directory and invokes a callback whenever any file in it changes. The whole thing is ChangeToken.OnChange() plus PhysicalFileProvider.

Add the NuGet package first:

Shell

dotnet add package Microsoft.Extensions.FileProviders.Physical

Then the code:

Csharp

using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;

var watchPath = Path.Combine(Path.GetTempPath(), "file-drop");
Directory.CreateDirectory(watchPath);

Console.WriteLine($"Watching: {watchPath}");
Console.WriteLine("Drop or modify files, then press Enter to stop.\n");

// PhysicalFileProvider wraps the directory we want to watch.
using var fileProvider = new PhysicalFileProvider(watchPath);

// The first argument is a factory: OnChange calls it every time it needs a fresh token.
// The second argument is your callback: it runs on every detected change.
// Re-registration is automatic. You don't need to call this again after each event.
using var watcher = ChangeToken.OnChange(
    () => fileProvider.Watch("*.*"),
    () => Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Change detected in {watchPath}")
);

Console.ReadLine();

That's the complete program. Drop a file into the temp directory, and you'll see the timestamp print. The producer lambda (() => fileProvider.Watch("*.*")) is called after each change, producing a fresh token and keeping the cycle alive automatically.

If you need to build a token from scratch without a file provider, CancellationChangeToken is the right tool. It wraps a CancellationTokenSource and signals a change when you cancel it:

Csharp

using Microsoft.Extensions.Primitives;

var cts = new CancellationTokenSource();
var changeToken = new CancellationChangeToken(cts.Token);

// Subscribe to the change
changeToken.RegisterChangeCallback(_ => Console.WriteLine("Change signaled!"), null);

// Later, when you want to signal:
cts.Cancel(); // prints "Change signaled!"

Because tokens are one-shot, you'd create a new CancellationTokenSource and a new CancellationChangeToken each time you want to signal again. That's the same re-registration pattern from before, and ChangeToken.OnChange() handles it the same way if you pass a factory that creates a fresh pair each time.

For watching multiple independent sources at once, CompositeChangeToken combines any number of IChangeToken instances into a single token that fires when any of them signal.

When to Use ChangeToken (and When Not To)

ChangeToken is a good fit when you need framework-consistent change notifications. If your code integrates with the configuration, options, or caching systems, it's the natural choice because those systems already understand IChangeToken. It's also useful when you want composable change sources: combine a file token, a timeout token, and a config token to get a single signal.

Reach for something else when the use case is different. High-frequency event streams belong in System.Threading.Channels or System.Reactive. Guaranteed delivery with retry and durability belongs in a message queue. Simple in-process notifications between two objects, where you control both sides, are fine as a plain C# event. ChangeToken is a notification primitive. It tells you something changed; it doesn't guarantee exactly once delivery, ordering, or backpressure.

Wrap-Up

ChangeToken is the observer pattern, standardized and shipped as part of the .NET Framework. It's been running your config reloads, options monitors, and cache invalidations from day one. The one-shot design keeps implementations simple and thread-safe, and ChangeToken.OnChange() handles the re-registration loop, so you don't have to.

Now that you know how it works, you can plug into the same abstraction. Any code that accepts an IChangeToken can work with your custom signal source, and any change source that produces an IChangeToken can drive your logic.

What's Next

For a hands-on challenge: find a spot in your codebase that polls for changes on a timer and replace it with a ChangeToken. Configuration files, feature flag stores, certificate rotation, and database-backed settings. Anything that follows the pattern "check if something changed, then react" is a candidate.