未使用 SignalR 找到名称为 'x' 的客户端方法

No client method with the name 'x' found with SignalR

我有一个 ASP.NET Boilerplate v3.6.2 项目 (.NET Core / Angular),我需要在其中从后端方法调用客户端函数,所以我使用 ASP.NET Core SignalR implementation.

我按照官方文档做了,所以:

在后端

  1. 在我的模块中,我将依赖添加到 AbpAspNetCoreSignalRModule:

    [DependsOn(typeof(AbpAspNetCoreSignalRModule))]
    public class MyModule : AbpModule
    

    并将 this NuGet 包添加到模块的项目中。

  2. 然后我扩展了 AbpCommonHub class 以利用 SignalR Hub 的内置实现,添加了一个 SendMessage() 方法用于发送留言:

    public interface IMySignalRHub :  ITransientDependency
    {
        Task SendMessage(string message);
    }
    
    public class MySignalRHub: AbpCommonHub, IMySignalRHub {
        protected IHubContext<MySignalRHub> _context;
        protected IOnlineClientManager onlineClientManager;
        protected IClientInfoProvider clientInfoProvider;
    
        public MySignalRHub(
            IHubContext<MySignalRHub> context, 
            IOnlineClientManager onlineClientManager,
            IClientInfoProvider clientInfoProvider)
        : base(onlineClientManager, clientInfoProvider) {
            AbpSession = NullAbpSession.Instance;
            _context = context;
        }
    
        public async Task SendMessage(string message) {
            await _context.Clients.All.SendAsync("getMessage", string.Format("User {0}: {1}", AbpSession.UserId, message));
        }
    }
    
  3. 我将 '/signalr' url 的映射更改为 MySignalRHub:

    public class Startup {
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) {
            [...]
            # if FEATURE_SIGNALR
                app.UseAppBuilder(ConfigureOwinServices);
            # elif FEATURE_SIGNALR_ASPNETCORE
                app.UseSignalR(routes => {
                    routes.MapHub<MySignalRHub>("/signalr");
                });
            # endif
            [...]
        }
    }
    
  4. 然后我在服务实现中使用集线器发送消息:

    public class MyAppService: AsyncCrudAppService<MyEntity, MyDto, int, PagedAndSortedResultRequestDto, MyCreateDto, MyDto>, IMyAppService {
    
        private readonly IMySignalRHub _hub;
    
        public MyAppService(IRepository<MyEntity> repository, IMySignalRHub hub) : base(repository) {
            _hub = hub;
        }
    
        public override Task<MyDto> Create(MyCreateDto input) {
            _hub.SendMessage("test string").Wait();
            [...]
        }
    }
    

在客户端

原始模板中的所有配置和内含物都已到位。当我打开 Angular 应用程序时,我可以看到此控制台日志:

DEBUG: Connected to SignalR server!
DEBUG: Registered to the SignalR server!

当我尝试调用向客户端发送消息的后端服务时,我在控制台中收到此警告:

Warning: No client method with the name 'getMessage' found.

我尝试了在 official documentation 和互联网上找到的许多解决方案,但其中 none 有效。我无法在客户端代码中定义 'getMessage' 处理程序。

一些 无效的 我试过的例子:

实施 1:

// This point is reached
abp.event.on('getMessage', userNotification => {
    debugger; // Never reaches this point
});

实施 2:

// This point is reached
abp.event.on('abp.notifications.received', userNotification => {
    debugger; // Never reaches this point
});

实施 3:

// This is taken from the official documentation and triggers the error:
// ERROR TypeError: abp.signalr.startConnection is not a function
abp.signalr.startConnection('/signalr', function (connection) {
    connection.on('getMessage', function (message) {
        console.log('received message: ' + message);
    });
});

你遇到过这种情况吗? Angular 客户端中是否有处理程序定义的简单工作示例?

更新

我尝试了这个替代解决方案,更改了 SignalRAspNetCoreHelper class(基本模板附带的共享 class)的实现:

export class SignalRAspNetCoreHelper {
    static initSignalR(): void {

        var encryptedAuthToken = new UtilsService().getCookieValue(AppConsts.authorization.encrptedAuthTokenName);

        abp.signalr = {
            autoConnect: true,
            connect: undefined,
            hubs: undefined,
            qs: AppConsts.authorization.encrptedAuthTokenName + "=" + encodeURIComponent(encryptedAuthToken),
            remoteServiceBaseUrl: AppConsts.remoteServiceBaseUrl,
            startConnection: undefined,
            url: '/signalr'
        };

        jQuery.getScript(AppConsts.appBaseUrl + '/assets/abp/abp.signalr-client.js', () => {
            // ADDED THIS
            abp.signalr.startConnection(abp.signalr.url, function (connection) {
                connection.on('getMessage', function (message) { // Register for incoming messages
                    console.log('received message: ' + message);
                });
            });
        });
    }
}

现在在控制台中我可以看到两条消息:

Warning: No client method with the name 'getMessage' found.
SignalRAspNetCoreHelper.ts:22 received message: User 2: asd

所以它正在工作,但不完全。 'getMessage' 处理程序在某处不可见。 使用 ASP.NET 样板在 Angular 中实现消息处理程序的正确方法是什么?

设置autoConnect: false开始你自己的连接:

abp.signalr = {
    autoConnect: false,
    // ...
};

更好...

不要扩展 AbpCommonHub。你会发现实时通知停止工作,你需要替换 IRealTimeNotifier 因为 SignalRRealTimeNotifier 使用 AbpCommonHub.

Do you have a simple working example of the handler definition in the Angular client?

What is the proper way to implement the messages handler in Angular with ASPNet Boilerplate?

按照文档创建一个单独的中心。

您应该使用 Clients.Others.SendAsyncClient.AllExcept.SendAsync 而不是 Clients.All.SendAsync

Clients.All.SendAsync 专为客户端想要发送和接收消息的场景(如聊天室)而设计。因此,所有连接的客户端都应该实现 connection.on('getMessage', 以接收通知。如果他们不这样做,他们会发出警告 在收到他们刚刚推送的通知时找不到名称为 'x' 的客户端方法。

在您的场景中,我了解到您有一种客户端推送通知,另一种客户端接收通知(例如 GPS 设备将位置发送到跟踪应用程序)。在这种情况下,使用 Clients.Others.SendAsyncClient.AllExcept.SendAsync 将确保推送客户端不会广播回他们(或他们的同类)刚刚推送的通知。

我在使用包 "@aspnet/signalr": "1.1.4" 的 Angular 应用程序中遇到了同样的错误。

出现这个问题的原因是我加入频道后没有调用订阅方法

public async getWorkSheetById(worksheetId: string): Promise < Worksheet > {
    const worksheet: Worksheet = await this._worksheetService.getWorksheet(worksheetId);
    this.displayWorksheet(worksheet);
    await this._worksheetEventsService.joinWorksheetChannel(this._loadedWorksheet.id);
    return worksheet;
}

所以,为了解决这个问题,我在加入后调用了订阅方法 await this.subscribeToSendMethod(this._loadedWorksheet))

public subscribeToSendMethod(loadedWorksheet: Worksheet): Worksheet {
    let newWorksheet: Worksheet;
    this._hubConnection.on('Send', (groupId: string, payloadType: string, payload: string, senderUserId: string)=> {
        newWorksheet=this._worksheetHandler.handlePayload(payloadType, payload, loadedWorksheet);
        this.displayWorksheet(newWorksheet);
    }
    );
    return newWorksheet;
}