In modern websites, some pieces of information may appear in multiple places, such as the profile avatar, the website language, theme, etc. One way to ensure data consistency and accuracy is to implement the Single Source of Truth (SSOT) pattern, which centralises all data instead of distributing it across multiple components. In this tutorial, we will go through:
CascadingValueSource
approach.In Blazor, maintaining consistent data across components is essential, particularly in features like profile management. Consider the scenario profile management as in the image:
When the user inputs data and the change is accepted, it should update the display name on the menu bar and the profile page. Without SSOT, developers might risk:
With data centralised with SSOT, developers can easily find the data needed to display. Furthermore, when changes are applied to the data source, all components will be automatically updated. This ensures data consistency and accuracy throughout the app and reduces the effort to maintain such data.
There are 2 ways to implement the SSOT pattern in Blazor:
CascadingValueSource
: This method leverages a cascading parameter with a source that automatically notifies and updates all dependent components when the central data changes. It’s ideal for real-time updates, such as reflecting a new display name across the app, with minimal manual intervention.For new projects, CascadingValueSource
is typically the better option due to its simplicity and automatic updates. Use a scoped service when integrating with older codebases.
CascadingValueSource
ApproachCascadingValueSource<T>
property, where T
is the class itself:public class CascadingParameterSharedData { public int Value { get; set; } public CascadingValueSource<CascadingParameterSharedData> Source { get; set; } }
Program.cs
: While registering the class, set the source value.builder.Services.AddCascadingValue(sp => { var value = new CascadingParameterSharedData(); value.Source = new(value, false); // false indicates no fixed value return value.Source; });
<div>Value: @SharedData.Value</div> <button type="button" @onclick="ChangeValue">Change Value</button> @code { [CascadingParameter] public CascadingParameterSharedData SharedData { get; set; } public async Task ChangeValue() { var random = new Random(); SharedData.Value = random.Next(1, 100); await SharedData.Source.NotifyChangedAsync(); } }
After modifying the shared data (e.g.,SharedData.Value
), always invokeNotifyChangedAsync
to propagate the change. Failing to do so will leave other components using the old value, breaking the SSOT principle and causing rendering discrepancies.
public class BlazorSchoolTransferService { public string Message { get; set; } = ""; public event EventHandler MessageChanged = (sender, args) => { }; public void NotifyChanged() { MessageChanged.Invoke(this, EventArgs.Empty); } }
Program.cs
:builder.Services.AddScoped<BlazorSchoolTransferService>();
@inject BlazorSchoolTransferService TransferService @implements IDisposable <div class="bg-primary p-5"> <h3>Component1</h3> <div>Value: @TransferService.Message</div> <button type="button" @onclick="ChangeValue">Change Value</button> <Component2 /> </div> @code { protected override void OnInitialized() { TransferService.MessageChanged += OnTransferServiceChanged; } public void OnTransferServiceChanged(object? sender, EventArgs e) { StateHasChanged(); } public void ChangeValue() { var random = new Random(); TransferService.Message = "Message updated from Component1"; TransferService.NotifyChanged(); } public void Dispose() { TransferService.MessageChanged -= OnTransferServiceChanged; } }
- Event Subscription: Every component accessing data must subscribe to change event (e.g., in
OnInitialized
) and callStateHasChanged
to update the UI when notified. The number of events depends on the requirement. It could be one event for multiple properties or one event per property.- Notification: After modifying data, always call notify method to propagate the change to all subscribers.
- Unsubscription: Unsubscribe from the event in
Dispose
to prevent memory leaks.