RForge.Blazor.Events
1.0.0
dotnet add package RForge.Blazor.Events --version 1.0.0
NuGet\Install-Package RForge.Blazor.Events -Version 1.0.0
<PackageReference Include="RForge.Blazor.Events" Version="1.0.0" />
<PackageVersion Include="RForge.Blazor.Events" Version="1.0.0" />
<PackageReference Include="RForge.Blazor.Events" />
paket add RForge.Blazor.Events --version 1.0.0
#r "nuget: RForge.Blazor.Events, 1.0.0"
#:package RForge.Blazor.Events@1.0.0
#addin nuget:?package=RForge.Blazor.Events&version=1.0.0
#tool nuget:?package=RForge.Blazor.Events&version=1.0.0
RForge.Blazor.Events
Typed events and reactive state for Blazor components, wherever they sit in the tree.
Status: new in RForge.Blazor 1.0.0. Tested with 96 unit/generator tests on each of net8, net9 and net10, plus a browser-driven pass over every sample page. See the design specification.
Blazor makes parent ↔ child communication easy. Communicating across the tree usually means a hand-written service with C# events, plus subscribing, unsubscribing, InvokeAsync, StateHasChanged and error handling in every component that uses it.
RForge.Blazor.Events handles that for you. It's inspired by Svelte's stores and context and adapted to Blazor's DI and rendering model.
Features
- Events: typed, fire-and-forget messages (
IEvent) that any component or service can publish and handle. - State: store-like values (
IState<T>) with a current value, change notification, and no-op suppression through record equality. - Automatic lifecycle: derive from
RfEventComponentand subscriptions, re-renders, thread marshaling,ErrorBoundaryintegration and disposal are handled for you. - Selectors: re-render only when the part of the state you use changes.
- Scopes:
<EventScope>and<StateScope>isolate events and give subtrees their own state instances. Routes includeBubble(default),Local,Broadcast,Root,GlobalandNamed. - Source generator:
[Subscribe]and[UseState]attributes, with no reflection, so it's trim/AOT safe. Analyzers (RFEVT001–RFEVT014) catch mistakes at build time. - Every render mode: static SSR, Interactive Server, WebAssembly and Auto, with or without prerendering, plus opt-in state persistence from prerender to interactive.
- Works with plain components too: opt-in cascading values let third-party components read state through
[CascadingParameter].
Install
dotnet add package RForge.Blazor.Events
One package contains the runtime, the source generator and the analyzers. Add it to every project that contains event-aware components (in a Blazor Web App, both the server and the .Client project), because NuGet doesn't pass analyzers through project references. See Setup for the full walkthrough.
A quick look
Register (in every Program.cs that hosts interactive components, including the .Client project for WebAssembly/Auto):
builder.Services.AddRForgeEvents();
Define events and state:
public sealed record ItemAdded(int ProductId, int Quantity) : IEvent;
public sealed record CartState(int Count, decimal Total) : IState<CartState>
{
public static CartState Initial => new(0, 0m);
}
Publish and write state from any component:
@* ProductCard.razor *@
@inherits RfEventComponent
<button @onclick="AddToCart">Add</button>
// ProductCard.razor.cs
public partial class ProductCard
{
[Parameter] public Product Product { get; set; } = default!;
private async Task AddToCart()
{
State<CartState>().Update(s => s with { Count = s.Count + 1, Total = s.Total + Product.Price });
await PublishAsync(new ItemAdded(Product.Id, 1));
}
}
React anywhere else in the tree, with no shared parent needed:
@* CartBadge.razor *@
@inherits RfEventComponent
<span class="badge @(_pulse ? "pulse" : "")">@Count</span>
// CartBadge.razor.cs
public partial class CartBadge
{
[UseState(Select = nameof(SelectCount))]
private partial int Count { get; } // re-renders only when Count changes
private static int SelectCount(CartState s) => s.Count;
[Subscribe]
private void OnItemAdded(ItemAdded e) => _pulse = true;
private bool _pulse;
}
Scope a subtree so each instance gets its own state and its own event channel:
<EventScope Name="checkout">
<StateScope TState="CheckoutForm">
<AddressForm />
<PaymentForm />
</StateScope>
<OrderSummary />
</EventScope>
Render-mode support
| Mode | Support |
|---|---|
| Static SSR | Full within the request: one hub per request. Nothing updates after the HTML is sent. |
| Interactive Server | Full. One hub per circuit (per user). |
| Interactive WebAssembly | Full. One hub per browser app instance. |
| Interactive Auto | Full, within whichever runtime the component is running in. |
| Prerendering | Prerendered HTML reflects state loaded during the render. Opt-in PersistState(...) carries root state into the interactive session. |
| Server ↔ WebAssembly islands on the same page | ❌ Not supported. Separate DI containers mean separate hubs. |
Details: docs/events/design/render-modes.md.
Requirements
- .NET 8, 9 or 10.
[Subscribe], state, scopes and the analyzers work with the .NET 8 SDK and newer. [UseState]needs C# 13, because it is declared as a partial property. net9 and net10 projects already compile at C# 13 or later. A net8 project defaults to C# 12, so it needs the .NET 9 SDK or newer and<LangVersion>13</LangVersion>(orlatest).- Async lambdas at C# 12. In a net8 project at the default C# 12,
builder.On<MyEvent>(async e => ...)is ambiguous between theTaskandValueTaskoverloads (CS0121). Pass a method group, cast the lambda toFunc<MyEvent, Task>, or setLangVersionto 13 or later, which resolves it. - Members annotated with
[Subscribe]/[UseState]must be in a code-behind (.razor.cs) partial class, not in a@codeblock. Source generators can't see the Razor compiler's output, andRFEVT001reports it if you forget (ADR-004). - Event-aware
.razorfiles start with@inherits RfEventComponent. Razor otherwise declares: ComponentBase, which conflicts with a base class declared in the code-behind (ADR-017).
Samples
samples/ contains a Blazor Web App (Server + WebAssembly + Auto) with seven example pages:
| Page | Level | Render mode | Shows |
|---|---|---|---|
| Shared state | Simple | Auto | IState, [UseState], selectors, record-equality no-ops |
| Notifications | Simple | Server | IEvent, [Subscribe], deep publishers, events vs. state |
| Plain components | Simple | WebAssembly | CascadeRootState, StateScope CascadeValue, nearest provider |
| Scoped widgets | Complex | Auto | StateScope per widget, Route.Local / Bubble / Global, tuple selectors, scope disposal |
| Checkout | Complex | Server (prerendered) | PersistState, named routes, async handlers + cancellation, ErrorBoundary isolation |
| Live dashboard | Complex | Server | Off-thread publishing from a service, manual registration, Render = false |
| Render modes | Complex | Static + Server + WASM | Which islands share a hub, static reads, the cascade boundary |
dotnet run --project samples/RForge.Blazor.Events.Sample
Building and testing
dotnet build src/RForge/RForge.sln
dotnet test src/RForge/RForge.sln
| Project | Contents |
|---|---|
src/RForge/RForge.Blazor.Events |
Runtime library (net8.0, net9.0, net10.0). Packs the generator into analyzers/dotnet/cs. |
src/RForge/RForge.Blazor.Events.Generators |
Incremental source generator + analyzers (netstandard2.0) |
tests/RForge.Blazor.Events.Tests |
Routing, delivery, state, persistence and bUnit component tests |
tests/RForge.Blazor.Events.Generators.Tests |
Generated-code, incremental-caching and diagnostic tests |
samples/RForge.Blazor.Events.Sample(.Client) |
Example app |
For contributors: the samples and tests in this repository reference the source projects instead of the package. A project reference to the runtime doesn't bring the generator along, so they reference both:
<ProjectReference Include="path/to/src/RForge.Blazor.Events/RForge.Blazor.Events.csproj" />
<ProjectReference Include="path/to/src/RForge.Blazor.Events.Generators/RForge.Blazor.Events.Generators.csproj"
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
Tutorials
Step-by-step guides with complete, compile-checked code and the recommended file layout: docs/events/tutorials.
| Start here | Then |
|---|---|
| 01 · Setup: package, registration, project layout | 02 · State · 03 · State with cascades · 04 · Events |
| 13 · Best practices: one-page checklist | 05 · Scopes and routing · 06 · Scoped state · 07 · Services · 08 · Persistence · 09 · Render modes · 10 · Manual registration · 11 · Testing · 12 · Troubleshooting |
Design documentation
| Document | Covers |
|---|---|
| Design overview | Goals, non-goals, glossary, architecture, open questions, verified framework behaviors |
| API | Public API surface |
| Events | Publishing, routing, delivery semantics |
| State | Stores, selectors, providers, persistence |
| Scoping | EventScope, StateScope, resolution rules |
| Components | RfEventComponent lifecycle, manual registration |
| Source generator | Attributes, generated code, diagnostics |
| Render modes | Behavior per render mode, limitations |
| Decisions | Architecture decision log |
| Comparison | vs. Fluxor, CascadingValueSource<T>, BlazorComponentBus, Svelte |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.AspNetCore.Components.Web (>= 10.0.12)
-
net8.0
- Microsoft.AspNetCore.Components.Web (>= 8.0.3)
-
net9.0
- Microsoft.AspNetCore.Components.Web (>= 9.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on RForge.Blazor.Events:
| Package | Downloads |
|---|---|
|
RForge.Blazor
All of RForge.Blazor in one package: the light weight Bulma component library, typed events and state, and the auto API generator. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 0 | 11/4/2026 |
| 1.0.0-rc.30 | 0 | 11/4/2026 |