Install
$ agentstack add skill-dotnet-skills-coordinate-components ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Coordinate Components
Step 1 — Read AGENTS.md
Read AGENTS.md at the workspace root to learn the project's conventions before making changes.
Step 2 — Decide the scope
| Need | Mechanism | When to use | |------|-----------|-------------| | Subtree (same render mode) | CascadingValue component | Theme, layout config within a layout | | App-wide (all render modes) | CascadingValueSource via DI | Current user, feature flags, theme shared globally | | Mutable shared state within a circuit | Scoped service + Action event | Shopping cart, notification count, selected filters |
For parent→child one level: use [Parameter] / EventCallback (see author-component skill). For persisting state across prerender→interactive: see support-prerendering skill.
Workflow (quick reference)
- Choose the mechanism from the table in Step 2
- If crossing render mode boundaries → use
CascadingValueSource(Step 4) - Register in
Program.cswithAddCascadingValue(...)andisFixed: false - Consume via
[CascadingParameter]in child components - Update via
NotifyChangedAsync(newValue)— never page reload - For additional mutable state within a circuit → add scoped service (Step 5)
- Wrap any
StateHasChangedfrom background threads inInvokeAsync - Implement
IDisposable— dispose timers, cancel tokens, unsubscribe events
Step 3 — CascadingValue for subtree state
Wrap a subtree with `` to flow data to all descendants without passing it through every intermediate component.
@* In a layout or parent component *@
@Body
@code {
private ThemeInfo theme = new() { ButtonClass = "btn-primary" };
}
Consume in any descendant:
[CascadingParameter]
private ThemeInfo? Theme { get; set; }
Rules:
- Matched by type, not name. To cascade multiple values of the same type, add
Name:
``razor ... ` `csharp [CascadingParameter(Name = "PrimaryTheme")] private ThemeInfo? Primary { get; set; } ``
- Set
IsFixed="true"when the value never changes — avoids subscription overhead. - Does NOT cross render mode boundaries. A `` in a static SSR parent is invisible to interactive children. See Step 6.
Step 4 — CascadingValueSource<T> for app-wide state
Register a CascadingValueSource in DI when the value must be available to all components regardless of render mode.
// Program.cs
builder.Services.AddCascadingValue(sp =>
{
var theme = new ThemeInfo { ButtonClass = "btn-primary" };
return new CascadingValueSource(theme, isFixed: false);
});
Consume identically to Step 3:
[CascadingParameter]
private ThemeInfo? Theme { get; set; }
To update and notify subscribers, either mutate the existing object or replace it:
@* Component that changes the theme *@
@inject CascadingValueSource ThemeSource
Toggle theme
@code {
private bool isDark;
private async Task ToggleDarkMode()
{
isDark = !isDark;
// Replace the value entirely:
var newTheme = new ThemeInfo { ButtonClass = isDark ? "btn-dark" : "btn-primary" };
await ThemeSource.NotifyChangedAsync(newTheme);
}
}
NotifyChangedAsync() (no argument) also works — mutate the object and then call it. NotifyChangedAsync(newValue) replaces the value and notifies in one step.
Update protocol: Whenever shared state changes, the component that changes it MUST inject CascadingValueSource and call NotifyChangedAsync(). This is the only mechanism that triggers re-rendering in all [CascadingParameter] subscribers. Without this call, no subscribers update. Do not use NavigationManager.Refresh() or page reloads as a substitute.
Rules:
isFixed: falseenables change notifications.isFixed: trueis better for truly static values (feature flags).- Crosses render mode boundaries — works for per-page interactivity, global interactivity, and WebAssembly. Key advantage over ``.
- Keep cascaded types granular. Every
NotifyChangedAsyncre-renders ALL subscribers regardless of which property changed. Don't put all app state into one cascaded type. - For Auto/WebAssembly apps, register in both server and
.ClientProgram.cs. The type must be in a shared assembly.
Step 5 — Scoped state service with change events
For mutable shared state that multiple components read and write (shopping cart, notification count, filters), use a scoped service with an event for change notification.
Define the service:
public class CartState
{
private readonly List _items = [];
public IReadOnlyList Items => _items;
public int Count => _items.Count;
public event Action? OnChange;
public void Add(CartItem item)
{
_items.Add(item);
OnChange?.Invoke();
}
public void Remove(CartItem item)
{
_items.Remove(item);
OnChange?.Invoke();
}
}
Register as scoped:
builder.Services.AddScoped();
Subscribe in components:
@inject CartState Cart
@implements IDisposable
@Cart.Count
@code {
protected override void OnInitialized()
{
Cart.OnChange += StateHasChanged;
}
public void Dispose()
{
Cart.OnChange -= StateHasChanged;
}
}
The simple Action OnChange pattern works when the event fires from the Blazor sync context (button click → Cart.Add(…)). If the event fires from outside the sync context (timer, background task, SignalR hub), wrap in InvokeAsync:
private Action? _handler;
protected override void OnInitialized()
{
_handler = () => InvokeAsync(StateHasChanged);
Cart.OnChange += _handler;
}
public void Dispose() => Cart.OnChange -= _handler;
Store the delegate in a field so you can unsubscribe the exact same instance.
Step 6 — Render mode and service lifetime rules
Cascading values don't cross render mode boundaries
A ` placed in a static SSR layout (MainLayout.razor when the layout renders statically) will **not** reach interactive children. The interactive component sees null` for the cascading parameter.
Fix: Use CascadingValueSource registered in DI (Step 4) or a scoped service (Step 5). Both cross boundaries because DI services are resolved per-circuit, not from the component tree.
Service lifetime on Server vs WebAssembly
| Lifetime | Server | WebAssembly | |----------|--------|-------------| | Scoped | Per circuit (per user connection) | Per browser tab | | Singleton | Shared across ALL users | Per browser tab (safe) | | Transient | New instance per injection | New instance per injection |
On Server, never store user-specific state in a singleton — every user's circuit shares the same singleton. One user's cart leaks into another's. Use AddScoped().
On WebAssembly, singletons are per-tab and safe. But code meant for both Server and WebAssembly (Auto mode) must use scoped.
Auto/WebAssembly with prerendering
State services must be defined in the .Client project or a shared assembly — they cannot reference server-only types. Register the service in both Program.cs files. State created during prerender does not survive the switch to the interactive runtime. Use the support-prerendering skill's [PersistentState] pattern to carry state across.
Don'ts
- Don't use a singleton for per-user state on Server — all circuits share it, leaking state between users.
- Don't put all app state into one cascaded object —
NotifyChangedAsyncre-renders ALL subscribers on every change. Separate concerns into distinct types (ThemeState,CartState,UserPreferences). - Don't forget to unsubscribe — omitting
Disposeon event subscriptions causes memory leaks that grow per-circuit. - **Don't use `
in a static layout expecting it to reach interactive children** — it won't cross render mode boundaries. Use DI-registeredCascadingValueSource` or scoped services. - Don't use
NavigationManager.Refresh(forceReload: true)to propagate cascading value changes — this destroys the circuit and forces a full page reload. Instead, injectCascadingValueSourceand callNotifyChangedAsync(newValue)to push updates to all[CascadingParameter]subscribers without a page reload. - Don't call
StateHasChangedfrom a non-Blazor thread — wrap inInvokeAsync. The framework throwsInvalidOperationException: The current thread is not associated with the Dispatcher.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: dotnet
- Source: dotnet/skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.