对 ASP.NET / ASP.NET Core 环境非常陌生(通常使用 DevExpress 在 WinForms 中工作),并且在尝试理解和实现调用客户端方法的 SignalR 服务器时遇到一些问题。根据我对这篇在 SignalR 文章中使用集线器的“客户端结果”部分的理解,我应该能够从服务器调用客户端上的一些方法。这在大多数情况下都有效;但是,当我尝试在客户端上调用接受参数并返回结果的方法时,我收到错误:
System.Exception: 'Client didn't provide a result.'
,我似乎找不到太多信息。
在本例中,我的服务器是一个面向 .NET 7 的基本 ASP.NET Core 应用程序(因此 .csproj 中的
<TargetFramework>net7.0</TargetFramework>
),我的客户端是一个面向 .NET 7 Windows (net7.0-windows
) 并使用 的 WinForms 应用程序Microsoft.AspNetCore.SignalR.Client
NuGet 包(撰写本文时版本为 7.0.7)。
我的目的是最终拥有一个 WinForms“SignalR 服务器”,它可以与各种客户端通信,但使用一个连接的客户端作为“主客户端”。我还没有构建任何代码,所以我只是使用我的第一个连接的客户端作为“主客户端”。
这是我的服务器端代码(使用强类型集线器):
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!");
这是我的客户端代码:
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());
}
}
同样,ASP.NET 和 ASP.NET Core 之间的差异对我来说已经足够令人困惑了,所以我的第一个想法是它可能是一个
TLDR: 如果客户端方法有参数,是否不可能从客户端获取结果?我错过了一些明显的东西吗?
@jdweng 关于 HTTP 请求的评论为我指明了记录客户端和服务器之间通信的方向。我添加了服务器端和客户端日志记录如本日志记录和诊断文章中所示,并发现我在客户端遇到错误:
Microsoft.AspNetCore.SignalR.Client.HubConnection: Warning: Failed to find a value returning handler for TestBooleanWithParameters
。
在 StackOverflow 和 Github 上看到其他一些评论后,我意识到我必须更改以下行:
TestConnection.On<string>(nameof(TestBooleanWithParameters), (p1) => TestBooleanWithParameters(p1));
并将其更改为:
TestConnection.On(nameof(TestBooleanWithParameters), (String p1) => TestBooleanWithParameters(p1));
现在一切正常,我不再收到错误。
我想提供一个不同的答案,您可以在其中保留键入的参数,并在其中传递
Action<T>
而不是 Func<T>
。
所以而不是
TestConnection.On<string>(nameof(TestBooleanWithParameters), (p1) => TestBooleanWithParameters(p1));
您需要使用
Action
参数而不是 Func
TestConnection.On<string>(nameof(TestBooleanWithParameters), TestBooleanWithParameters);
作为参考,
HubConnection.On()
中的Microsoft.AspNetCore.SignalR.Client.HubConnectionExtensions
有不同的重载。
您正在使用这个:
public static IDisposable On(this HubConnection hubConnection, string methodName, Func<Task> handler)
{
ArgumentNullThrowHelper.ThrowIfNull(hubConnection);
return hubConnection.On(methodName, Type.EmptyTypes, args => handler());
}
我的解决方案使用此重载
public static IDisposable On<T1>(this HubConnection hubConnection, string methodName, Action<T1> handler)
{
ArgumentNullThrowHelper.ThrowIfNull(hubConnection);
return hubConnection.On(methodName,
new[] { typeof(T1) },
args => handler((T1)args[0]!));
}