Carlos Henrique
Carlos Henrique

Reputation: 373

Blazor not receiving signalr created on server side

Following many tutorials, examples, this example below I call on the server side, but the client side does not receive, sometimes it works but sometimes it doesn’t (more doesn’t work than it works)

It was supposed to be very simple, but it's not, any suggest will help me so much!

Server side

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers().AddNewtonsoftJson(options =>
        {
            options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
        });

        var connection = @"data source=comandai.database.windows.net;initial catalog=HojeTaPago;persist security info=True;user id=Comandai;password=Ck@21112009;MultipleActiveResultSets=True;";
        services.AddDbContext<ComandaiContext>(options => options.UseSqlServer(connection));

        services.AddSignalR(options => options.KeepAliveInterval = TimeSpan.FromSeconds(5));

        services.Configure<CookiePolicyOptions>(options =>
        {
            options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });

        services.AddResponseCompression(opts =>
        {
            opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
                new[] { "application/octet-stream" });
        });

        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { Title = "HojeTaPago API", Version = "v1" });
            c.AddSecurityDefinition("basic", new OpenApiSecurityScheme
            {
                Name = "Authorization",
                Type = SecuritySchemeType.Http,
                Scheme = "basic",
                In = ParameterLocation.Header,
                Description = "Basic Authorization header using the Bearer scheme."
            });

            c.AddSecurityRequirement(new OpenApiSecurityRequirement
            {
                {
                      new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference
                            {
                                Type = ReferenceType.SecurityScheme,
                                Id = "basic"
                            }
                        },
                        new string[] {}
                }
            });
        });

        services.AddCors(options => options.AddPolicy("CorsPolicy",
        builder =>
        {
            builder.AllowAnyMethod().AllowAnyHeader()
                   .AllowCredentials();
        }));
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseResponseCompression();

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        // Enable middleware to serve generated Swagger as a JSON endpoint.
        app.UseSwagger();

        // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
        // specifying the Swagger JSON endpoint.
        app.UseSwaggerUI(c =>
        {
            c.SwaggerEndpoint("/swagger/v1/swagger.json", "HojeTaPago API V1");
            c.RoutePrefix = string.Empty;
        });

        app.UseHttpsRedirection();

        app.UseRouting();

        app.UseCors("CorsPolicy");

        app.UseAuthentication();

        app.UseAuthorization();

        app.UseMiddleware<AuthenticationMiddleware>();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapHub<NovoPedidoHub>("/novopedidohub");
            endpoints.MapControllers();
        });
    }
}

Where im using the signalr

await _novoPedidoContext.Clients.All.SendAsync("NovoPedido", ListaComandaItem);

NovoPedidoHub

Client side - Blazor

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
        services.AddServerSideBlazor();
        services.AddBlazoredLocalStorage();
        services.AddBootstrapCss();
        services.AddTransient<HubConnectionBuilder>();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapBlazorHub();
            endpoints.MapFallbackToPage("/_Host");
        });
    }
}

Where i call..

protected override async Task OnInitializedAsync()
{
    DataService dataService = new DataService();
    PedidosParaAceitar = new List<Comanda>(await dataService.BuscarComandasAbertas());

    connection = _hubConnectionBuilder.WithUrl(dataService.servidor + "novopedidohub",
        opt =>
        {
            opt.Transports = HttpTransportType.WebSockets;
            opt.SkipNegotiation = true;
        }).Build();

    connection.On<List<ComandaItem>>("NovoPedido", async lista =>
    {
        var idEstabelecimento = await localStorage.GetItemAsync<int>("IdEstabelecimento");

        if (lista.FirstOrDefault().Comanda.IdEstabelecimento == idEstabelecimento)
        {
            if (PedidosParaAceitar == null)
                PedidosParaAceitar = new List<Comanda>();

            if (PedidosParaAceitar.Count(x => x.Id == lista.FirstOrDefault().IdComanda) > 0)
                foreach (var comandaitem in lista)
                {
                    PedidosParaAceitar.FirstOrDefault(x => x.Id == lista.FirstOrDefault().IdComanda).ComandaItem.Add(comandaitem);
                }
            else
                PedidosParaAceitar.Add(await dataService.BuscarComandaAberta(lista.FirstOrDefault().IdComanda));

            StateHasChanged();
        }
    });

    await connection.StartAsync();
}

Console output

Upvotes: 1

Views: 2072

Answers (2)

Johan Gov
Johan Gov

Reputation: 1352

I had a similar issue to you and found the problem by adding error handling to the signalR connection. When I did I found out that I had an error in how I handled my messages and the error was just swallowed by the connection.

I'd suggest adding .ConfigureLogging(x => x.AddApplicationInsights()) when you're createing the connection like this:

connection = _hubConnectionBuilder.WithUrl(dataService.servidor + "novopedidohub",
    opt =>
    {
        opt.Transports = HttpTransportType.WebSockets;
        opt.SkipNegotiation = true;
    })
.ConfigureLogging(x => x.AddApplicationInsights())
.Build();

Upvotes: 1

Quango
Quango

Reputation: 13448

You didn't specify in the tags if this was client-side (WASM) or server-side Blazor.

Looking at the question I noticed this line in ConfigureServices:

services.AddServerSideBlazor();

So you're attempting to use SignalR, a client-side communication library from the Server. In server-side Blazor all the C# code runs on the server. In this respect, SignalR is redundant since it's already being used by Blazor for communicating between the clients and the server.

By a very fortunate coincidence, I actually wrote an app to test this out recently. I created a server-side Blazor app, and wrote this service:

    public class TalkService
    {
        public TalkService()
        {
            history = new List<string>();
        }

        public Action<string> OnChange { get; set; }


        // inform all users of new message
        public Task SendAsync(string message)
        {
            // add to history
            history.Add(message);
            // ensure only last 10 shown
            if (history.Count > 10) history.RemoveAt(0);
            OnChange.Invoke(message);
            return Task.FromResult(0);
        }

        private readonly List<string> history;

        public IReadOnlyList<string> GetHistory() => history;
    }

I then registered it as a Singleton on the server (all clients use the same service) in Startup.cs in the ConfigureServices() method:


    services.AddSingleton<TalkService>();

Then rewrote Index.razor as follows:

@page "/"
@inject TalkService service

<p>Talk App started</p>

<p>Send a message: <input type="text"@bind="@message" />
    <button class="btn btn-sm btn-primary" @onclick="Send"  >Send</button>
    </p>

@foreach (var m in messages)
{
    <p>@m</p>
}
@code {
    string message;

    async Task Send()
    {
        if(!string.IsNullOrWhiteSpace(message))
            await service.SendAsync(message);
        message = string.Empty;
    }

    List<string> messages;

    protected override void OnParametersSet()
    {
        // load history
        messages = service.GetHistory().ToList();

        // register for updates
        service.OnChange += ChangeHandler;
    }

    protected void ChangeHandler(string message)
    {
        messages.Add(message);
        InvokeAsync(StateHasChanged);
    }
}

The talk service is a basic "chat" example of course. It's a Singleton so all pages/clients that reference it use the same instance. The service has a simple event OnChange that clients (like the Index page) can listen to for changes elsewhere.

SignalR isn't needed for this app since it's already "there" for server-side.

Demo App

The demo app also has a background service that generates time messages as well. I've pushed this to GitHub to help as a guide:

https://github.com/conficient/BlazorServerWithSignalR

Upvotes: 2

Related Questions