Gray_Rhino
Gray_Rhino

Reputation: 1283

SignalR client-server connection issue

I have been trying to find out the issue for the whole day and couldn't figure it out. This is my SignalR Hub (BlazorServerAppHub.cs)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.SignalR;
    using BlazorServerApp.Data;
    
    namespace BlazorServerApp
    {
        public class BlazorServerAppHub: Hub
        {
            public const string HubUrl = "/chat";
      
            public async Task Broadcast(WeatherForecast[] forecasts)
            {
                await Clients.All.SendAsync("ReceiveMessage", forecasts);
            }
    
            public override Task OnConnectedAsync()
            {
                return base.OnConnectedAsync();
            }
        }
    }

This is the business logic page (WeatherForecastService.cs)

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using BlazorServerApp.Data;
using Microsoft.Extensions.Hosting;
using System.Threading;
using System.Timers;

namespace BlazorServerApp.Data
{
    public class WeatherForecastService  
    {
        private readonly IHubContext<BlazorServerAppHub> _hubContext;

        public WeatherForecastService(IHubContext<BlazorServerAppHub> hubContext)
        {
            _hubContext = hubContext;
        }
        
        protected  async Task Broadcast()
        {
            var forecasts = await GetForecastAsync();
            await _hubContext.Clients.All.SendAsync("Broadcast", forecasts);
        }

        public Task<WeatherForecast[]> GetForecastAsync()
        {
            var rng = new Random();
        }    
                
    }

 public class WeatherForecast
    {
        public int A { get; set; }

    }
}

Finally, this is my razor page (FetchData.razor)

@page "/fetchdata"

@using BlazorServerApp.Data;
@inject NavigationManager navigationManager
@using Microsoft.AspNetCore.SignalR.Client;

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{

    <table class="table">
        <thead>
            <tr>
                <th>A</th>
               
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.A</td>
                    
                </tr>
            }
        </tbody>
    </table>
}

@code {

    private  WeatherForecast[] forecasts;
    private HubConnection _hubConnection;

    protected override async Task OnInitializedAsync()
    {
        string baseUri = navigationManager.BaseUri;
        string hubUrl = baseUri.TrimEnd('/') + BlazorServerAppHub.HubUrl;

        _hubConnection = new HubConnectionBuilder().WithUrl(hubUrl).Build();
        _hubConnection.On< WeatherForecast[]>("Broadcast", forcast => { forecasts = forcast; StateHasChanged(); });

        await _hubConnection.StartAsync();
    }
}

Basically, I am trying to get data from the server and push it to the client periodically. The Problem is my page when I run the app forecasts in FetchData.razor is empty so the page shows "Loading". Why is that? My guess is I am missing something in SignalR communication.

Upvotes: 1

Views: 288

Answers (1)

devNull
devNull

Reputation: 4219

In order for the server to periodically push the data to the client, you'll need to run some sort of background service. There are a number of ways to do that, for example:

However, since you said you just want to send the data once for testing purposes, you can hook into Hub.OnConnectedAsync() to send the data to the client when they initially connect. First, I'd suggest creating a separate interface/repository for Task<WeatherForecast[]> GetForecastAsync():

public interface IWeatherForecastRepository
{
    Task<WeatherForecast[]> GetForecastAsync();
}

public class WeatherForecastRepository
{
    private static readonly string[] Tickers = new[]
    {
        "10", "20", "30", "44", "77"
    };

    private static readonly int[] Ones = new[]
    {
        1000, 15000, 7000, 500, 2200
    };

    public Task<WeatherForecast[]> GetForecastAsync()
    {
        var rng = new Random();
        return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            A = index,
            B = Tickers[index - 1],
            C = NextFloat(rng),
            D = Ones[index - 1],
            E = rng.Next(0, 10000),
        }).ToArray());
    }

    static float NextFloat(Random random)
    {
        double decimalPart = random.NextDouble();
        double intPart = random.Next(0, 1000);

        return (float)Math.Round(intPart + decimalPart, 3); ;
    }
}

Then, once you register that repository with DI, you can inject it into your BlazorServerAppHub and use it in OnConnectedAsync():

public class BlazorServerAppHub : Hub
{
    public const string HubUrl = "/chat";

    private readonly IWeatherForecastRepository _weatherForecastRepository;

    public BlazorServerAppHub(IWeatherForecastRepository weatherForecastRepository)
    {
        _weatherForecastRepository = weatherForecastRepository;
    }

    public async Task Broadcast(WeatherForecast[] forecasts)
    {
        await Clients.All.SendAsync("ReceiveMessage", forecasts);
    }

    public override async Task OnConnectedAsync()
    {
        await base.OnConnectedAsync();

        var forecasts = await _weatherForecastRepository.GetForecastAsync();

        // Clients.Caller will only send the data to the client that just connected
        await Clients.Caller.SendAsync("Broadcast", forecasts);
    }
}

Upvotes: 1

Related Questions