Blazor - call statehaschanged from service - c#

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>();

Related

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

Listening to raised event in .NET Core Web API service

I will keep it simple. I have two services in my ASP.NET Core WebAPI project.
ServiceA and ServiceB.
Service A is responsible for sending emails when Service B raises an event. Service B has an event handler and a delegate and the event is raised correctly. However, the problem is in Service A when trying to handle the event via += EventHandlingMethod();
The handler method EventHandlingMethod() is never called. I have placed a breakpoint inside the method but it never triggers, event after method EventRaised() has been called from Service B correctly.
Is it possible because Service A is set up in Startup.cs as services.AddTransient<IServiceA, ServiceA>();?
I have provided a simple example of publish-subscribe pattern. This is synchronous but if you are looking for an asynchronous version you can use channels or any other message broker such as RabbitMQ / NServiceBus etc.
public class PublishSubscribeMiddleMan: IPubSub
{
Dictionary<Type, List<ISubscriber>> pubSub = new Dictionary<Type, List<ISubscriber>>();
public void PublishEvent<Publisher>(Publisher publisher)
{
Type t = publisher.GetType();
if (pubSub.TryGetValue(t, out var subscribers))
{
subscribers.ForEach(subscriber => subscriber.EventHandlingMethod());
}
}
public void Subscribe<Publisher>(ISubscriber subscriber)
{
Type t = typeof(Publisher);
if (pubSub.TryGetValue(t, out var subscribers))
{
subscribers.Add(subscriber);
}
else pubSub.Add(t, new List<ISubscriber> { subscriber });
}
}
public interface ISubscriber
{
void EventHandlingMethod();
}
public interface IPubSub
{
void Subscribe<Publisher>(ISubscriber subscriber);
void PublishEvent<Publisher>(Publisher publisher);
}
public class ServiceA : IServiceA
{
private readonly IPubSub publishSubscribe;
public ServiceA(IPubSub publishSubscribe)
{
this.publishSubscribe = publishSubscribe;
}
public void RaiseEvent()
{
publishSubscribe.PublishEvent(this);
}
}
public interface IServiceA
{
void RaiseEvent();
}
public class ServiceB : ISubscriber
{
public ServiceB(IPubSub publishSubscribe)
{
publishSubscribe.Subscribe<ServiceA>(this);
}
public void EventHandlingMethod()
{
//throw new NotImplementedException();
}
}
You would need to register the PubSub inside ConfigureServices as shown:
services.AddScoped<IPubSub, PublishSubscribeMiddleMan>();
services.AddTransient<IServiceA, ServiceA>();

How to unit test methods in windows form class?

I have a developing a c# windows form application and I have a method that exists inside the main form class.
Imagine methodA as part of the main form class.
public void methodA() {
A.someMethod();
B.someMethod();
// some more code
if (someCondition) {
// execute some code
}
// initialize timer and set event handler for timer
// run new thread
}
class A {
someMethod() {...}
}
class B {
someMethod() {...}
}
How would I run tests to test the branch logic of this methodA (isCondition)? since it involves initializing timer and running threads. Can i only verify the logic while doing system test ? I dont think it is possible to mock the timer and threading function.
Thank you !
Of course you can mock the timer. This is by creating a new interface, say, ITimerWrapper and implement it by using the concrete Timer class. Basically a wrapper of the Timer class. Then use that instead of the concrete Timer class you have.
Something in the tune of:
public partial class Form1 : Form
{
private readonly ITimerWrapper _timerWrapper;
public Form1(ITimerWrapper timerWrapper)
{
InitializeComponent();
this._timerWrapper = timerWrapper; // of course this is done via dependency injection
this._timerWrapper.Interval = 1000;
}
private void Form1_Load(object sender, EventArgs e)
{
// now you can mock this interface
this._timerWrapper.AddTickHandler(this.Tick_Event);
this._timerWrapper.Start();
}
private void Tick_Event(object sender, EventArgs e)
{
Console.WriteLine("tick tock");
}
}
public interface ITimerWrapper
{
void AddTickHandler(EventHandler eventHandler);
void Start();
void Stop();
int Interval { get; set; }
}
public class TimerWrapper : ITimerWrapper
{
private readonly Timer _timer;
public TimerWrapper()
{
this._timer = new Timer();
}
public int Interval
{
get
{
return this._timer.Interval;
}
set
{
this._timer.Interval = value;
}
}
public void AddTickHandler(EventHandler eventHandler)
{
this._timer.Tick += eventHandler;
}
public void Start()
{
this._timer.Start();
}
public void Stop()
{
this._timer.Stop();
}
}
Then for the spinning of a new thread, that's also testable by doing the same thing.
Bottomline is to have an interface to separate concerns and mock the interface on your unit test.

Is it safe to call StateHasChanged() from an arbitrary thread?

Is it safe to call StateHasChanged() from an arbitrary thread?
Let me give you some context. Imagine a Server-side Blazor/Razor Components application where you have:
A singleton service NewsProvider that raises BreakingNews events from an arbitrary thread.
A component News.cshtml that gets the service injected and subscribes to BreakingNews event. When the event is raised, the component updates the model and calls StateHashChanged()
NewsProvider.cs
using System;
using System.Threading;
namespace BlazorServer.App
{
public class BreakingNewsEventArgs: EventArgs
{
public readonly string News;
public BreakingNewsEventArgs(string news)
{
this.News = news;
}
}
public interface INewsProvider
{
event EventHandler<BreakingNewsEventArgs> BreakingNews;
}
public class NewsProvider : INewsProvider, IDisposable
{
private int n = 0;
public event EventHandler<BreakingNewsEventArgs> BreakingNews;
private Timer timer;
public NewsProvider()
{
timer = new Timer(BroadCastBreakingNews, null, 10, 2000);
}
void BroadCastBreakingNews(object state)
{
BreakingNews?.Invoke(this, new BreakingNewsEventArgs("Noticia " + ++n));
}
public void Dispose()
{
timer.Dispose();
}
}
}
News.cshtml
#page "/news"
#inject INewsProvider NewsProvider
#implements IDisposable
<h1>News</h1>
#foreach (var n in this.news)
{
<p>#n</p>
}
#functions {
EventHandler<BreakingNewsEventArgs> breakingNewsEventHandler;
List<string> news = new List<string>();
protected override void OnInit()
{
base.OnInit();
breakingNewsEventHandler = new EventHandler<BreakingNewsEventArgs>(OnBreakingNews);
this.NewsProvider.BreakingNews += breakingNewsEventHandler;
}
void OnBreakingNews(object sender, BreakingNewsEventArgs e)
{
this.news.Add(e.News);
StateHasChanged();
}
public void Dispose()
{
this.NewsProvider.BreakingNews -= breakingNewsEventHandler;
}
}
Startup.cs
using Microsoft.AspNetCore.Blazor.Builder;
using Microsoft.Extensions.DependencyInjection;
using BlazorServer.App.Services;
namespace BlazorServer.App
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Since Blazor is running on the server, we can use an application service
// to read the forecast data.
services.AddSingleton<WeatherForecastService>();
services.AddSingleton<INewsProvider, NewsProvider>();
}
public void Configure(IBlazorApplicationBuilder app)
{
app.AddComponent<App>("app");
}
}
}
it apparently works, but I don't know if StateHasChanged() is thread safe. If it isn't, how can I call StateHashChanged() safely?. Is there something similar to Control.BeginInvoke? Should I use SyncrhonizationContext.Post?
No, calling StateHasChanged() from an arbitrary thread is not safe.
The correct way to call StateHasChanged() is by using InvokeAsync()
void OnBreakingNews(object sender, BreakingNewsEventArgs e)
{
InvokeAsync(() => {
news.Add(e.News);
StateHasChanged();
});
}

Consuming a restful web service

I am trying to consume an interface but I am having some difficulty here.
I am trying to set it to a xamrin list view in behind a content page
public class xxxApiClient : IApi
{
readonly string url = "http://localhost:81/ ";
readonly IHttpService httpService;
public xxxApiClient(IHttpService httpService)
{
this.httpService = httpService;
}
public Task<List<JobsList>> GetJobs() => httpService.Get<List<JobsList>>($"{url}JobsLists");
}
How ever I am not to sure how I cosume getjobs correclty I am trying the following
public partial class JobsPage : ContentPage ,xxxWC.Interface.IApi
{
public xxxWC.Interface.IApi api = new ful;
public JobsPage ()
{
InitializeComponent ();
}
private Task SetItemSource()
. {
. JobListing.ItemsSource = FuelAp
}
How do I use the get jobs correctly above in the method setItemSource?.
The bit I am having hard time to understand is here.
How do I call the base GetJobs method I have already created in API Client.
Task<List<JobsList>> IApi.GetJobs()
{
throw new NotImplementedException();
}
private Task SetItemSource()
{
JobListings.ItemsSource =await GetJobs();
}
}
Edit 2
Ok based on suggestions below I updated My Code as such
IHttpService httpService;
xxxApiClient _api = newxxxApiClient(httpService);
public JobsPage ()
{
InitializeComponent ();
}
private Task SetItemSource()
{
JobListings.ItemsSource =await GetJobs();
}
But i get the below error
Severity Code Description Project File Line Suppression State
Error CS0236 A field initializer cannot reference the non-static
field, method, or property
'JobsPage.httpService' xxxCallManagmentAppMobile C:\Work\xxxCallAppDev\XamForms\xxxCallManagmentApp\xxxCallManagmentAppMobile\FuelCallManagmentAppMobile\Views\JobsPage.xaml.cs 17 Active
Can someone explain why
Edit 3
Ok i got a bit further but still having some issues. as the main method is not awaited how do I call set SetItemSource.
xxxApiClient _api ;
public JobsPage ()
{
InitializeComponent ()
SetItemSource();
}
private async Task SetItemSource()
{
JobListings.ItemsSource = await client.GetJobs();
}
Assuming that IApi has been mapped to xxxApiClient implementation
Try resolving the service using the DependencyService so that it is available to be used in the view
public partial class JobsPage : ContentPage {
public readonly IApi client;
public JobsPage () {
InitializeComponent ();
client = DependencyService.Get<IApi>();
}
private async Task SetItemSource() {
JobListing.ItemsSource = await client.GetJobs();
//...
}
}
As for calling the SetItemSource, it is async so should be awaited. That can't be done in the constructor.
Consider creating a event that can be raised and its handler used to await the desired behavior.
private event EventHandler loadingData = delegate { };
private async void onLoadingData(object sender, Eventargs args) {
JobListing.ItemsSource = await client.GetJobs();
}
Full code
public partial class JobsPage : ContentPage {
public readonly IApi client;
public JobsPage () {
InitializeComponent ();
//resolving client
client = DependencyService.Get<IApi>();
//subscribing to event
loadingData += onLoadingData;
//raising event
loadingData(this, EventArgs.Empty);
}
private async Task SetItemSource() {
JobListing.ItemsSource = await client.GetJobs();
//...
}
private event EventHandler loadingData = delegate { };
private async void onLoadingData(object sender, Eventargs args) {
JobListing.ItemsSource = await client.GetJobs();
}
}
Although a custom event was created, you could just as easily used on of the event/eventhandler of the view.
All of that code should actually live inside of a view model and then bound to the view in a binding context.

Categories

Resources