Pretty fresh to the ASP.NET / ASP.NET Core environments (typically work in WinForms using DevExpress), and having a bit of a problem trying to understand and implement a SignalR server that calls methods on the client. From my understanding of the "Client Results" section of this Using Hubs in SignalR article, I should be able to call some methods on the client from the server. This works in most cases; however, when I try to call a method on the client that takes in parameters and returns a result, I get an error: System.Exception: 'Client didn't provide a result.'
, which I cannot seem to find much info on.
My server in this case is a basic ASP.NET Core application targeting .NET 7 (so <TargetFramework>net7.0</TargetFramework>
in the .csproj), and my client is a WinForms application targeting .NET 7 Windows (net7.0-windows
) and using the Microsoft.AspNetCore.SignalR.Client
NuGet package (v 7.0.7 as of writing).
My intention is to eventually have a WinForms "SignalR Server" that can communicate with a variety of clients, but use one connected client as the "Primary Client". I haven’t built any of that code yet, so I’m just using my first connected client as the "Primary Client".
Here’s my server-side code (using a strongly-typed hub):
public interface IStrongServer
{
Task<Boolean> TestBoolean();
Task<Boolean> TestBooleanWithParameters(string toSend);
}
public class StrongServerHub : Hub<IStrongServer>
{
private readonly StrongServer _StrongServer;
public StrongServerHub(StrongServer nStrongServer)
{
_StrongServer = nStrongServer;
}
public override async Task OnConnectedAsync()
{
_StrongServer.AssignPrimaryConnection(this.Context.ConnectionId);
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception? exception)
{
_StrongServer.DisconnectPrimaryConnection(this.Context.ConnectionId);
await base.OnDisconnectedAsync(exception);
}
}
public class StrongServer : IStrongServer
{
private string PrimaryConnectionId = "";
private bool PrimaryConnected = false;
private IHubContext<StrongServerHub, IStrongServer> Hub
{
get;
set;
}
public StrongServer(IHubContext<StrongServerHub, IStrongServer> hub)
{
Hub = hub;
}
public void AssignPrimaryConnection(string connectionId)
{
if (String.IsNullOrEmpty(PrimaryConnectionId))
{
PrimaryConnectionId = connectionId;
PrimaryConnected = true;
}
}
public void DisconnectPrimaryConnection(string connectionId)
{
if (!String.IsNullOrEmpty(PrimaryConnectionId) && String.Equals(PrimaryConnectionId, connectionId))
{
PrimaryConnectionId = "";
PrimaryConnected = false;
}
}
public async Task<bool> TestBoolean()
{
return await Hub.Clients.Client(PrimaryConnectionId).TestBoolean();
}
public async Task<bool> TestBooleanWithParameters(string ToSend)
{
return await Hub.Clients.Client(PrimaryConnectionId).TestBooleanWithParameters(ToSend);
}
}
public class SignalRHost
{
public StrongServer Server { get; set; }
public void Run()
{
var builder = WebApplication.CreateBuilder();
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddSignalR();
builder.Services.AddSingleton<StrongServer>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
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.UseAuthorization();
app.MapRazorPages();
app.MapHub<StrongServerHub>("/strong");
Server = app.Services.GetService<StrongServer>();
app.RunAsync();
}
}
// Then in Program.cs, I'd do:
SignalRHost host = new SignalRHost();
host.Run();
bool returnedBoolean = await host.Server.TestBoolean();
bool returnedBooleanWithParameters = await host.Server.TestBooleanWithParameters("Hello World!");
And here’s my client-side code:
public interface ITestClient
{
bool TestBoolean();
bool TestBooleanWithParameters(string test);
}
using System.Diagnostics;
public class SignalRClient : ITestClient
{
HubConnection TestConnection = null;
public async void Init()
{
TestConnection = new HubConnectionBuilder().WithUrl("http://localhost:5035/strong").Build(); // The port 5035 is what my machine used.
TestConnection.On(nameof(TestBoolean), TestBoolean);
TestConnection.On<string>(nameof(TestBooleanWithParameters), (p1) => TestBooleanWithParameters(p1));
await TestConnection.StartAsync();
}
public bool TestBoolean()
{
return true;
}
public bool TestBooleanWithParameters(string test)
{
Debug.WriteLine($"TestBooleanReturn: {test}"); ;
return true;
}
}
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
SignalRClient client = new SignalRClient();
client.Init();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
Again, the differences between ASP.NET and ASP.NET Core are confusing enough to me, so my first thought was that it could be a
TLDR: Is expecting results from a client not possible if the client method has parameters? Am I missing something obvious?
2
Answers
@jdweng's comment about the HTTP Request pointed me in the direction of logging the communication between the client and the server. I added both server-side and client-side logging as found in this Logging and Diagnostics article and found that I was getting an error on the client-side:
Microsoft.AspNetCore.SignalR.Client.HubConnection: Warning: Failed to find a value returning handler for TestBooleanWithParameters
.After seeing some other comments across StackOverflow and Github, I realized that I had to change the following line:
And change it to:
Now everything is working and I'm not longer receiving errors.
The error means that a response was not returned on the connection, so it was not completed. The reasons can be various. However your error should be
TestConnection.On<string>
.More precisely the error is
<string>
After
.On
, you need to delete<string>
. Then continue with your code in the parenthesisSolution