Jeazyee
Jeazyee

Reputation: 73

C# Blazor Server: Display live data using INotifyPropertyChanged

I have a little problem, I try to display live data on a page on my Blazor Server Project. After reading several stuff I think this should be possible to make with using INotifyPropertyChanged. Unfortunately I am a noob on this, here's what I tried:

My Model Price with INotifyPropertyChanged (generated using Rider):

public class Price : INotifyPropertyChanged
{
    private double _askPrice;
    private double _bidPrice;
    private double _spread;

    public Price(double askPrice, double bidPrice)
    {
        _askPrice = askPrice;
        _bidPrice = bidPrice;
    }

    public double AskPrice {
        get => _askPrice;
        set
        {
            _askPrice = value; 
            OnPropertyChanged("AskPrice");
            OnPropertyChanged("Spread");
        }
    }

    public double BidPrice
    {
        get => _bidPrice;
        set
        {
            _bidPrice = value; 
            OnPropertyChanged("BidPrice");
            OnPropertyChanged("Spread");
        }
    }

    public double Spread => _askPrice - _bidPrice;

    public event PropertyChangedEventHandler PropertyChanged;
    
    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

How I get the live data:

public class PriceService
{
    public static Price BinancePrice;

    public static void StartBinanceStream()
    {
        var client = new BinanceSocketClient();
        // subscribe to updates on the spot API
        client.Spot.SubscribeToBookTickerUpdates("BTCEUR", data =>
        {
            BinancePrice = new Price((double)data.BestAskPrice, (double)data.BestBidPrice);
        });
    }
}

And finally the content of my razorfile:

<h5>BID: @($"{PriceService.BinancePrice.BidPrice:F2} EUR")</h5>
<h5>ASK: @($"{PriceService.BinancePrice.AskPrice:F2} EUR")</h5>
<h5>Spread: @($"{PriceService.BinancePrice.Spread:F2} EUR")</h5>

@code {
protected override async Task OnInitializedAsync()
{
    PriceService.BinancePrice.PropertyChanged += async (sender, e) => { await InvokeAsync(StateHasChanged); };
    await base.OnInitializedAsync();
}

async void OnPropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
    await InvokeAsync(StateHasChanged);
}

public void Dispose()
{
    PriceService.BinancePrice.PropertyChanged -= OnPropertyChangedHandler;
}
}

It does show the data but doesn't show it live with changes, I need to reopen the tab or refresh the page to see the current data. The goal is that the UI refreshes every time the Price is changing. Would be awesome if you could help me out on this!:)

Upvotes: 4

Views: 6211

Answers (1)

enet
enet

Reputation: 45704

I couldn't really verify the source of the issue, though it seems to me to be related to the Price class and the invocation of PropertyChangedEventHandler

However, here's code sample that should work:

Price.cs

public class Price : INotifyPropertyChanged
{
    private double _askPrice;
    private double _bidPrice;
    private double _spread;

    public Price(double askPrice, double bidPrice)
    {
        _askPrice = askPrice;
        _bidPrice = bidPrice;
    }

    public double AskPrice
    {
        get => _askPrice;
        set => SetProperty(ref _askPrice, value);
    }

    public double BidPrice
    {
        get => _bidPrice;
        set => SetProperty(ref _bidPrice, value);
    }

    public double Spread => _askPrice - _bidPrice;

    public event PropertyChangedEventHandler PropertyChanged;

    void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new 
                         PropertyChangedEventArgs(propertyName));
    }

    bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string 
                                                     propertyName = null)
    {
        if (Equals(storage, value))
        {
            return false;
        }

        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }
 } 

PriceService.cs

 public class PriceService
 {
    public Price BinancePrice { get; set; }
    double count;
    double BestAskPrice;
    double BestBidPrice;
    public PriceService()
    {
        BlazorTimer t = new BlazorTimer();
        t.SetTimer(3000, 1, 100);
        t.CountCompleted += NotifyCompleted;
        BestAskPrice = 102.09;
        BestBidPrice = 101.03;
        BinancePrice = new Price((double)BestAskPrice, (double)BestBidPrice);

    }

    private void NotifyCompleted(object sender, TimerEventArgs args)
    {
        count = (double) args.Count;

        BestAskPrice += count;
        BestBidPrice += count;
        BinancePrice.AskPrice = (double)BestAskPrice;
        BinancePrice.BidPrice =  (double)BestBidPrice;
    }
 }

Note: PriceService is a service, and should be added to the DI Container:

services.AddSingleton<PriceService>();

Note also that I avoid using static stuffs. Note: In order to verify that the UI is changing, I am using a Timer. It is really crude, and you only have to use it to see that things are fine, and then apply your code...

BlazorTimer.cs

public class BlazorTimer
{
    private Timer _timer;
    private int count;
    private int end;
    internal void SetTimer(double interval, int start, int _end)
    {
        _timer = new Timer(interval);
        _timer.Elapsed += Counter;
        _timer.Enabled = true;
        count = start;
        end = _end;
        _timer.Start();
    }

     
    private void Counter(object sender, ElapsedEventArgs e)
    {
        count++;
        TimerEventArgs args = new TimerEventArgs { Count = count };
        OnCountCompleted(args);
      
    }
       
    protected virtual void OnCountCompleted(TimerEventArgs args)
    {
        EventHandler<TimerEventArgs> handler = CountCompleted;
        if (handler != null)
        {
            handler(this, args);
        }
    }

    public event EventHandler<TimerEventArgs> CountCompleted;
 }

 public class TimerEventArgs : EventArgs
 {
    public int Count { get; set; }
 } 

Usage

@*@implements IDisposable*@
    
@inject PriceService PriceService

<h5>BID: @($"{PriceService.BinancePrice.BidPrice:F2} EUR")</h5>
<h5>ASK: @($"{PriceService.BinancePrice.AskPrice:F2} EUR")</h5>
<h5>Spread: @($"{PriceService.BinancePrice.Spread:F2} EUR")</h5>

@code {
    protected override async Task OnInitializedAsync()
    {
        PriceService.BinancePrice.PropertyChanged += async (sender, e) => { await InvokeAsync(StateHasChanged); };
        await base.OnInitializedAsync();
    }

    //async void OnPropertyChangedHandler(object sender, PropertyChangedEventArgs e)
    //{
    //    await InvokeAsync(StateHasChanged);
    //}

    //public void Dispose()
    //{
    //    PriceService.BinancePrice.PropertyChanged -= OnPropertyChangedHandler;
    //}
}   

Upvotes: 7

Related Questions