Global state container as cascading value (blazor wasm) - c#

In blazor wasm (v6), I have an app-wide state container (AppState.razor) as a cascading parameter.
The typical code is:
AppState.razor
<CascadingValue Value="this">
#ChildContent
</CascadingValue>
#code {
[Parameter] public RenderFragment ChildContent { get; set; }
private bool _isFoo;
public bool IsFoo {
get {
return _isFoo;
}
set {
_isFoo = value;
StateHasChanged();
//await SomeAsyncMethod(); // <----------
}
}
//...
}
App.razor
<AppState>
<Router>
...
</Router>
</AppState>
MyComponent.razor
<!--- markup --->
#code {
[CascadingParameter] public AppState AppState { get; set; }
//...
}
After a component sets the AppState.IsFoo property to update the state (and update the UI), I must call an async method (e.g. save to localstorage). But I cannot do that in a sync property setter.
I could change from a cascading parameter to an injectable service, but I prefer not to.
I may need to redesign - what is the typical approach for this use case? (I've seen code with InvokeAsync(SomeAsyncMethod) without an await but I'm wary of that.)

I figured out a workaround.
My requirements:
I wanted a simple state container as advocated by the docs
I didn't want the state container as a service (as shown in the docs), because I wanted to avoid the boilerplate of event handlers and disposal in all consuming components
I wanted the state container as a global cascading parameter, as it's so simple
The trick is to trigger a rerender, and then to perform async work in the OnAfterRenderAsync handler.
AppState.razor
<CascadingValue Value="this">
#ChildContent
</CascadingValue>
#code {
private bool _isDirty; // <-----
[Parameter] public RenderFragment ChildContent { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender) {
await base.OnAfterRenderAsync(firstRender);
if (firstRender) {
await LoadStateFromLocalStorage();
}
if (!firstRender && _isDirty) { // <-----
await SaveStateToLocalStorage();
_isDirty = false;
}
}
private bool _isDarkMode;
public bool IsDarkMode {
get {
return _isDarkMode;
}
set {
if (value == _isDarkMode) return;
_isDarkMode = value;
_isDirty = true; // <-----
StateHasChanged();
}
}
//other properties...
private async Task LoadStateFromLocalStorage() {
Console.WriteLine("LOADED!");
await Task.CompletedTask;
}
private async Task SaveStateToLocalStorage() {
Console.WriteLine("SAVED!");
await Task.CompletedTask;
}
}
The example async code above is for loading / saving to localstorage, but one could do any async work in the same way.
Notes
It could be that concurrency issues would cause rerenders and async invocations to occur in an unexpected order. But it should be fine for most use cases, and is good enough for me. If you want something more robust with a guaranteed order, then use flux (which is overkill for something so simple).
This is meant for very simple app-wide stuff. I wouldn't put all my state in there. For most components I'd use other cascading parameters and injectable services.

Here's a different solution that I think resolves most of the issues in your implementation. It loosely based on the way EditContext works.
First separate out the data from the component. Note the Action delegate that is raised whenever a parameter change takes place. This is basically the StateContainer in the linked MSDocs article.
public class SPAStateContext
{
private bool _darkMode;
public bool DarkMode
{
get => _darkMode;
set
{
if (value != _darkMode)
{
_darkMode = value;
this.NotifyStateChanged();
}
}
}
public Action? StateChanged;
private void NotifyStateChanged()
=> this.StateChanged?.Invoke();
}
Now the State Manager Component.
We cascade the SPAStateContext not the component itself which is far safer (and cheaper).
We register a fire and forget handler on StateChanged. This can be async as the invocation is fire and forget.
#implements IDisposable
<CascadingValue Value=this.data>
#ChildContent
</CascadingValue>
#code {
private readonly SPAStateContext data = new SPAStateContext();
[Parameter] public RenderFragment? ChildContent { get; set; }
protected override void OnInitialized()
=> data.StateChanged += OnStateChanged;
private Action? StateChanged;
// This implements the async void pattern
// it should only be used in specific circumstances such as here in a fire and forget event handler
private async void OnStateChanged()
{
// Do your async work
// In your case do your state management saving
await SaveStateToLocalStorage();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
await LoadStateFromLocalStorage();
}
public void Dispose()
=> data.StateChanged -= OnStateChanged;
}

Related

Blazor - call statehaschanged from service

how can call from OfferteService.cs to get a StateHasChanged function in detailsOfferta.razor?
hello how can I call the StateHasChanged function from a service to update the detailsOfferta.razor page by the function statehaschanged?
Thank you very much
I tried invokeasync but it does not work
(How do I) call StateHasChanged from service
You don't.
What you need to implement is the Notification pattern.
Your data, and it's management, should reside in your service. When something changes in that service, a service level event is raised: this invokes any registeted handlers. Components that display data from the service register event handlers. These call StateHasChanged when they are invoked.
This answer to a similar question describes how to build a notication service for the Blazor WeatherForecast - https://stackoverflow.com/a/69562295/13065781
if you have a BlazorServer, then the state of your components-view is stored on the server. You can do so. Register the service as a scoped, this gives the service lifetime equal to your components, if the lifetime is needed more than the register how singleton.
Declare an event in the service, in my case it is RX observable.
Inject the service into the component and subscribe on event.
public partial class YourComponent : IDisposable
{
private IDisposable _disposable = null;
[Inject] public ITimerService TimerService { get; set; }
public string Time { get; set; }
protected override async Task OnInitializedAsync()
{
_disposable = TimerService.Times.Subscribe(OnTimeMessage);
}
private void OnTimeMessage(string time)
{
Time = time;
StateHasChanged();
}
public void Dispose()
{
_disposable?.Dispose();
}
}
public interface ITimeService
{
IObservable<string> Times { get; }
}
public class TimeService : ITimeService
{
private readonly Subject<string> _subject = new();
private Timer _timer;
public TimeService()
{
_timer = new Timer(() =>
{
_subject.OnNext(DateTime.UtcNow.ToString("G"));
}, null, 1000, 1000);
}
public void Dispose()
{
_timer.Dispose();
_subject.Dispose();
}
public void PublishError(string error)
{
_subject.OnNext(error);
}
public IObservable<string> Times()
{
return _subject;
}
}
// In host initialization
//services.AddSingleton<ITimeService, TimeService>();
services.AddScoped<ITimeService, TimeService>();

Listening to state changes when a property value is changed in a service

I have a class / service in my Blazor MAUI application that regularly manipulates the data that is stored within it. There is an internal schedular on a fixed interval that regenerates the value.
I have a Blazor component that reads the value from this service. When the value changes in my service, I would like my Blazor component to reflect that change.
Just to keep it simple, lets take the following:
public class EmployeeService {
public int NumberOfEmployees { get; private set; }
public EmployeeService() {
// Logic to initialize a fixed scheduled for function
// RecalculateNumberOfEmployees();
}
private async void RecalculateNumberOfEmployees() {
numberOfEmployees += 1;
}
}
#path "/employees"
#inject EmployeeService Service
Number of employees: #Service.NumberOfEmployees
#code {
}
I found a recommendation here that uses a timer to invoke StateHasChanged() but I really, really don't like that approach. It seems like it is a waste of resources and an anti-pattern.
My next step is to make EmployeeService accept EventCallback from the Blazor component and store that in a list. This would allow any component to listen for changes in the EmployeeService class. When a component is unmounted, it will delete the callback.
Something like:
EmployeeService.cs
public List<EventCallback> listeners { get; private set; } = new List<EventCallback>();
public async void RegisterCallback(EventCallback callback) {
listeners.ForEach(...notify listeners);
}
public async void DeregisterCallback(EventCallback callback) {
listeners.Remove ... etc
}
Employees.razor
...
#code {
// register / deregister callback and listen for changes, invoke StateHasChanged()
}
Before I go down this route, are there any better design patterns that I could use for future components that would be better suited for this purpose? I feel like this is something that should already be baked into the framework but haven't seen anything that addresses it.
You could use an event Action:
EmployeeService.cs
public class EmployeeService
{
public int NumberOfEmployees { get; private set; }
public EmployeeService() {
// Logic to initialize a fixed scheduled for function
// RecalculateNumberOfEmployees();
}
private async void RecalculateNumberOfEmployees() {
numberOfEmployees += 1;
NotifyStateChanged();
}
public event Action OnChange;
private void NotifyStateChanged() => OnChange?.Invoke();
}
Employees.razor
#inject EmployeeService EmployeeService
#implements IDisposable
Number of employees: #EmployeeService.NumberOfEmployees
#code {
protected override void OnInitialized()
{
EmployeeService.OnChange += OnChangeHandler;
}
public void Dispose()
{
EmployeeService.OnChange -= OnChangeHandler;
}
private async void OnChangeHandler()
{
await InvokeAsync(StateHasChanged);
}
}
Another possibility is to use the Event Aggregator pattern. Take a look at this library:
https://github.com/mikoskinen/Blazor.EventAggregator

Trigger and handle Event async

I'm currently working on a .net 5 Blazor application.
I use events to pass data from one component to another.
Unfortunately my current logic is synchronous - but I would rather use an asynchronous event handler.
Thus, I need to use the following code to handle my event:
Task.Run(async () => await ChangeNumbers());
Is there a possibility to handle events asynchronously without Task.Run?
My State service looks like this:
public class MyComponentState
{
public int MyNumber { get; set; }
// Is there a way to declare this event Action async??
public event Action OnChange;
public void DoStuff(int myNumber)
{
MyNumber = myNumber;
NotifyStateChanged();
}
private void NotifyStateChanged() => OnChange?.Invoke();
}
The component to handle the state looks like this:
public class MyOtherComponentDisplay : ComponentBase, IDisposable
{
[Inject]
public MyComponentState MyComponentState { get; set; }
protected override void OnInitialized()
{
// this should all be handled async... i could use OnInitializedAsync
MyComponentState.OnChange += OnChangeHandler;
}
private void OnChangeHandler()
{
// Is there a way to handle/declare this without Task.Run(async ...) - but async right away??
Task.Run(async () => await ChangeNumbers());
}
private async Task ChangeNumbers()
{
// Some operations with MyComponentState.MyNumber async needed!!!
StateHasChanged();
}
public void Dispose()
{
MyComponentState.OnChange -= OnChangeHandler;
}
}
Is there a way to declare and handle events async?
Do you know how to solve this problem?
The basic adoptation would be an async void handler:
private async void OnChangeHandler()
{
// Is there a way to handle/declare this without Task.Run(async ...)
// - but async right away??
// Task.Run(async () => await ChangeNumbers());
await ChangeNumbers();
await InvokeAsync(StateHasChanged); // probably needed
}
The way you're doing things looks strange to me. That's not how I do events in Blazor. (Maybe you're coming from Web Forms?)
Generally, a custom event is defined like:
MyControl.razor
[Parameter]
public EventCallback<SomeType> EventName{ get; set; }
#code {
someMethod (){
EventName.InvokeAsync(SomeType data);
}
}
And the handler in the consuming control can be async if you want:
MyPage.razor
<MyControl EventName=OnChangeHandler />
#code {
private async Task OnChangeHandler()
{
await ChangeNumbers();
}
}

WPF MVVM Async event invoke

I am lost in this one, i want my Viewmodel to use a event delegate so i can subscribe to it, open some dialog and wait for the dialog result. Later the ViewModel should do whatever it wants with the dialog result.
Here is how i implemented it (resumed code):
public class MyViewModel()
{
public delegate TributaryDocument SearchDocumentEventHandler();
public event SearchDocumentEventHandler SearchDocument;
//Command for the search button
public CommandRelay SearchDocumentCommand { get; set; }
//Document that i found in the dialog.
public TributaryDocument Document { get; set; }
public MyViewModel()
{
SearchDocumentCommand = new CommandRelay(DoSearchDocument);
}
//The command execution
public void DoSearchDocument()
{
//Event used here !
Document = SearchDocument?.Invoke();
}
}
public class MyUIControl : UserControl
{
public MainWindow MainWindow { get; }
public MyUIControl()
{
MainWindow = Application.Current.Windows[0] as MainWindow;
DataContextChanged += MyUIControl_DataContextChanged;
}
private void MyUIControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var modelView = (MyViewModel)DataContext;
modelView.SearchDocument += MyUIControl_SearchDocument;
}
private TributaryDocument MyUIControl_SearchDocument()
{
//Dont know what to do here... i am lost on this part.
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
}
//The signature for MainWindow.ShowDialog
public async Task<object> ShowDialog(object dialog)
{
return await DialogHost.Show(dialog, "MainDialog");
}
MyDocumentSearcherDialog is just a dialog where i search and return a TributaryDocument object.
The problem to my understanding comes from this part (since i cant compile it):
private TributaryDocument MyUIControl_SearchDocument()
{
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
I cant use await without changing the method signature to async. If i change it to async then i must return a Task<TributaryDocument> and change the event delegate:
public delegate Task<TributaryDocument> SearchDocumentEventHandler();
//On MyUIControl
private Task<TributaryDocument> MyUIControl_SearchDocument()
{
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
//On MyViewModel
public async void DoSearchDocument()
{
//Event used here !
Document = await Task.Run(async () => await SearchDocument?.Invoke());
}
If i do this i get the following exception:
Additional information: The calling thread must be STA, because many
UI components require this.
It seems like all you need to do is to remove the Task.Run (there is no need to Offload to another thread in this situation). The Task.Run will definitely give you a STA Thread Exception if you are doing UI work from within.
However, in short the Async and Await Pattern will create a continuation with the current SynchronisationContext, so there is no need to worry about it.
public async void DoSearchDocument()
{
await SearchDocument?.Invoke();
}
Note : Since this is an event, it's about the only place it's OK to use async void.

Cancellation async in IDisposable class

In my project I use a manager to control a plugin. The main idea is that this plugin must work only in single thread in multythreads WPF application. There is only one instance of plugin in PluginController.
So when I call Start method: it stops plugin (if running) and start it with new argument. Few times a second plugin notificate caller about it's state, and ViewModel shows it in the WPF window.
When I call method Start some times one after one, i see that the previous instance of ViewModel is not destroyed, but only sleeps after Stop. And it calls Update method as god as a new one instance. So my interface twiches becouse two instances are updating it's state. In log is see alternately lines from first one and second one.
But when I call Start(...) then Stop() and then Start(...) again everything works fine.
So
SomeManager.Start(...);
SomeManager.Start(...);
works with errors. And
SomeManager.Start(...);
SomeManager.Stop();
SomeManager.Start(...);
works fine. Can anybody explain me my mistake?
Down lied simplified code.
public static SomeManager
{
public static void Start(SomeArg arg)
{
Stop(); // forgotten code
var vm = GetMainPageVM();
vm.SomeVM = new SomeViewModel(arg);
vm.SomeVM.StartCommand.Execute(null);
}
public static void Stop()
{
var vm = GetMainPageVM();
if (vm.SomeVM != null)
{
vm.SomeVM.Stop();
vm.SomeVM.Dispose();
vm.SomeVM = null;
}
}
}
public sealed SomeViewModel : ViewModelBase, IDisposable
{
private readonly Guid _guid = Guid.NewGuid();
private IPlugin _plugin;
private SomeArg _arg;
public ICommand StartCommand {get; }
public CancellationTokenSource Source {get; }
public SomeViewModel(SomeArg arg)
{
this._arg = arg;
this._plugin = PluginController.GetPluginByName("SomePlugin");
StartCommand = new RelayCommand(StartAsync);
}
~SomeViewModel()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{ ... }
private async Task StartAsync()
{
var progress = new Progress<ISomeProgress>(Update);
try
{
await StartImplementationAsync(progress).ConfigureAwait(false);
}
catch (Exception e) { ... }
}
private async Task StartImplementationAsync(Progress<ISomeProgress> progress)
{
var result = await this._plugin.startAsync(
this._arg,
progress,
this.Source.Token
).ConfigureAwait(false);
}
public void Stop()
{
this._plugin.Stop();
}
private void Update() {log.Debug($"this._guid" ....); }
}
public sealed SomePlugin: IPlugin
{
public async Task<SomeResult> StartAsync(SomeArg args, IProgress<SomeProgress>, CancellationToken cancellationToken)
{ ... }
public void Stop() { ... }
}
UPDATE: I think the problem in simple words is : how to correctly cancel async operation in IDisposable object in normal case with CancellationTokenSource.Cancel() and in unnormal case when Dispose() or Finalizer is called

Categories

Resources