'SignalR no longer works since upgrading to .NET Core 3.1
I have a websocket server application that I recently upgraded from .NET Core 2.1 to 3.1, however, since then the negotiation seems to fail. This is the error that my console shows.
I tried my best to follow all the Microsoft docs on how best to upgrade to 3.1. In the code below you can find all the packages my C# server currently uses. I know that with 3.1 you don't have to reference the SignalR packages since Microsoft.AspNetCore.App is enough, but I saw on someone else's post that it helped them so I tried to add it to mine (with no luck).
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Core" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="3.1.24" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.1.2" PrivateAssets="All" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.0.2105168" />
<PackageReference Include="RabbitMQ.Client" Version="5.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.10" />
</ItemGroup>
This is the code for my Startup class:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<ConnectionOptions>(Configuration.GetSection("MQConfig"));
services.AddHostedService<LiveUpdaterService>();
services.AddCors(options =>
{
options.AddPolicy(MyAllowSpecificOrigins,
builder =>
{
builder
.AllowAnyMethod()
.AllowAnyHeader()
.AllowAnyOrigin()
.AllowCredentials();
});
});
//I also tried services.AddSignalR();
services.AddSignalRCore();
services.AddMvc(options => options.EnableEndpointRouting = false)
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
//app.UseHsts();
}
app.UseCors(cors =>
{
cors.AllowAnyHeader();
cors.AllowAnyOrigin();
cors.AllowAnyMethod();
cors.AllowCredentials();
});
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<LiveUpdateHub>("/liveupdatehub", options =>
{
options.Transports =
HttpTransportType.WebSockets |
HttpTransportType.LongPolling;
});
endpoints.MapControllers();
});
app.UseMvc();
}
}
This is the code for my JavaScript client:
import { HubConnectionBuilder, LogLevel } from "@microsoft/signalr";
var connection;
var connected;
export default class LiveDataHub {
connectWebSocket() {
connection = new HubConnectionBuilder()
.withUrl("http://localhost:5003/liveupdatehub")
.configureLogging(LogLevel.Information)
.withAutomaticReconnect()
.build();
//Disable button until connection is established
document.getElementById("runTestButton").disabled = true;
connection.onclose(function (error) {
document.getElementById("runTestButton").disabled = true;
connected = false;
console.log(error)
alert("There was a problem connecting to the backend, please try again");
});
connection
.start()
.then(function () {
document.getElementById("runTestButton").disabled = false;
console.log(connection);
connected = true;
})
.catch(function (err) {
alert("No connection could be made to the backend, please refresh: " + err.toString());
connected = false;
return console.error(err.toString());
});
return connection;
}
So far for the server, I've tried moving the Configure and ConfigureServices methods around because I know their order matters in 3.1, but maybe there's a position I've missed. I've also tried adding a lot of extra Configure methods that I read from other posts like (options => options.EnableEndpointRouting = false) or AddNewtonSoftJson().
For the client I've tried to skip the negotiation part by adding this:
connectWebSocket() {
connection = new HubConnectionBuilder()
.withUrl("ws://localhost:5003/liveupdatehub", {
skipNegotiation: true,
transport: HttpTransportType.WebSockets
})
.configureLogging(LogLevel.Information)
.withAutomaticReconnect()
.build();
But the console error log then changes to this. Which is what leads me to believe that SignalR is the main issue here.
If anyone can help me with this I'd really appreciate it!
Solution 1:[1]
So after some time I've discovered what the underlying issue was. It turns out that calling services.AddHostedService() causes the hosted service to be called up before the SignalR connection can be established, and because my LiveUpdaterService relies on SignalR it crashes the whole connection.
After commenting that method out, the negotiation works along with the rest of my app (besides the LiveUpdaterService). I found the answer through this link if anyone is interested: https://github.com/Azure/azure-signalr/issues/909. I'm currently trying to find a workaround fix for this, but at least the connection is working now.
I'll marked this as solved since the AddHostedService is more of a compatibility issue. Hopefully this might help someone else with the same problem.
Final code for my Startup:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddSignalR();
services.Configure<ConnectionOptions>(Configuration.GetSection("MQConfig"));
services.AddCors(options =>
{
options.AddPolicy("AllowSetOrigins",
builder =>
{
builder
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.WithOrigins("http://localhost:5003")
.WithOrigins("http://localhost:8080");
});
});
//services.AddHostedService<LiveUpdaterService>();
}
// 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.UseCors("AllowSetOrigins");
app.UseAuthorization();
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapHub<LiveUpdateHub>("/liveupdatehub");
});
}
}
Final code for my packages:
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="RabbitMQ.Client" Version="5.2.0" />
</ItemGroup>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | ShunLao |