SignalR Core中Hub调用含事件客户端接口报错及解决咨询
SignalR Hub包含事件接口导致调用失败的问题
问题场景
基于ASP.Net Core搭建基础Web应用并初始化SignalR Hub,Hub代码如下:
internal class SignalRTransceiverServer : Hub<ITransceiver<DataRecord, List<DataRecord>>>, ITransmitter<DataRecord> { public async Task SendDataPackage(DataRecord package) { Console.WriteLine(package); try { await Clients.All.GetDataPackage(new List<DataRecord>() { package }); } catch (Exception ex) { Console.WriteLine(ex.Message); } } }
相关接口定义:
public interface ITransceiver<T, R> : IReceiver<R>, ITransmitter<T> { }
public interface IReceiver<T> { /// <summary> /// Gets a data package of <typeparamref name="T"/>. The parameter is for API's, which pass the arguments directly /// (via RPC e.g.) into the method. Invokes the DataReceived event. /// </summary> /// <param name="package">An optional package parameter, if the underlaying mechanism needs to pass the data directly into /// the function.</param> public Task GetDataPackage(T? package); public event EventHandler<T> DataReceived; }
调用Hub的SendDataPackage方法时抛出错误:
Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher[8] Failed to invoke hub method 'SendDataPackage'. System.InvalidOperationException: Type must not contain events. at Microsoft.AspNetCore.SignalR.Internal.TypedClientBuilder`1.VerifyInterface(Type interfaceType) at Microsoft.AspNetCore.SignalR.Internal.TypedClientBuilder`1.VerifyInterface(Type interfaceType) at Microsoft.AspNetCore.SignalR.Internal.TypedClientBuilder`1.GenerateClientBuilder() at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode) --- End of stack trace from previous location --- at System.Lazy`1.CreateValue() at Microsoft.AspNetCore.SignalR.Internal.TypedClientBuilder`1.Build(IClientProxy proxy) at TestHub.SignalRTransceiverServer.SendDataPackage(DataRecord package) in D:\dat.eE\Praktikum.RH\TestHub\SignalRTransceiverServer.cs:line 25 at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher`1.ExecuteMethod(ObjectMethodExecutor methodExecutor, Hub hub, Object[] arguments) at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher`1.<Invoke>g__ExecuteInvocation|18_0(DefaultHubDispatcher`1 dispatcher, ObjectMethodExecutor methodExecutor, THub hub, Object[] arguments, AsyncServiceScope scope, IHubActivator`1 hubActivator, HubConnectionContext connection, HubMethodInvocationMessage hubMethodInvocationMessage, Boolean isStreamCall)
已知移除IReceiver中的事件可解决问题,但需明确两个核心问题:
- SignalR Core为何不允许目标类型包含事件?
- 如何在保留事件的前提下绕过该限制?
问题解答
1. SignalR不允许目标类型包含事件的原因
SignalR的类型化客户端(Hub<T>中的泛型参数T)是用来定义服务端向客户端发起RPC调用的契约,仅支持声明可远程调用的方法。
事件是CLR进程内的成员,依赖本地委托绑定实现,但SignalR的通信是跨进程/跨网络的,服务端与客户端运行在完全独立的环境中,不存在直接的内存或委托传递机制,无法将服务端的事件绑定到客户端的事件处理程序。
SignalR在构建类型化客户端代理时,会校验接口成员类型,一旦检测到事件就会抛出错误——因为它无法生成对应的远程调用逻辑,事件不符合类型化客户端的契约设计目标。
2. 保留事件的绕过方案
方案一:拆分接口
将供SignalR使用的RPC方法契约与包含事件的本地接口分离:
// 仅用于SignalR的客户端RPC契约,不含事件 public interface ISignalRReceiver<T> { Task GetDataPackage(T? package); } // 原接口保留事件,用于本地代码的事件订阅逻辑 public interface IReceiver<T> : ISignalRReceiver<T> { event EventHandler<T> DataReceived; } // Hub改为使用无事件的SignalR专用接口 internal class SignalRTransceiverServer : Hub<ITransceiverSignalR<DataRecord, List<DataRecord>>>, ITransmitter<DataRecord> { public async Task SendDataPackage(DataRecord package) { Console.WriteLine(package); try { await Clients.All.GetDataPackage(new List<DataRecord>() { package }); } catch (Exception ex) { Console.WriteLine(ex.Message); } } } // 对应SignalR的收发契约接口 public interface ITransceiverSignalR<T, R> : ISignalRReceiver<R>, ITransmitter<T> { }
此方案下,SignalR基于无事件的ISignalRReceiver生成客户端代理,本地代码依然可通过IReceiver接口订阅事件,两者互不干扰。
方案二:使用非类型化客户端调用
若不想修改接口结构,可直接使用非类型化的SendAsync方法调用客户端方法,绕开类型化客户端的校验:
internal class SignalRTransceiverServer : Hub, ITransmitter<DataRecord> { public async Task SendDataPackage(DataRecord package) { Console.WriteLine(package); try { // 直接指定方法名和参数,无需依赖类型化接口 await Clients.All.SendAsync("GetDataPackage", new List<DataRecord>() { package }); } catch (Exception ex) { Console.WriteLine(ex.Message); } } }
该方式无需类型化接口,自然不会触发事件校验,但缺点是失去类型安全,需手动维护方法名与参数的一致性。
内容的提问来源于stack exchange,提问作者Roy Mustang
相关产品推荐
相关产品推荐

