3

I have an ASP.NET Boilerplate v3.6.2 project (.NET Core / Angular) in which I need to call a client function from a backend method, so I'm using the ASP.NET Core SignalR implementation.

I followed the official documentation, so:

In the backend

  1. In my module, I added the dependency to AbpAspNetCoreSignalRModule:

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

    And added this NuGet package to the module's project.

  2. Then I extended the AbpCommonHub class to take advantage of the built-in implementation of the SignalR Hub, adding a SendMessage() method used to send the message:

    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. I changed the mapping of the '/signalr' url to 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. Then I use the hub to send a message in a service implementation:

    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();
            [...]
        }
    }
    

In the client

All the configurations and inclusions are already in place from the original template. When I open the Angular app I can see this console logs:

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

When I try to call the backend service which sends the message to the client, I get this warning in console:

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

I tried many solutions found in the official documentation and on the Internet but none of them worked. I'm not able to define the 'getMessage' handler in the client code.

Some non-working examples I tried:

Implementation 1:

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

Implementation 2:

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

Implementation 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);
    });
});

Have you ever found yourself in this situation? Do you have a simple working example of the handler definition in the Angular client?

UPDATE

I tried this alternative solution, changing the implementation of the SignalRAspNetCoreHelper class (a shared class which is shipped with the base template):

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);
                });
            });
        });
    }
}

Now in the console I can see both the messages:

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

So it is working, but not fully. Somewhere the 'getMessage' handler is not visible. What is the proper way to implement the messages handler in Angular with ASP.NET Boilerplate?

aaron
  • 39,695
  • 6
  • 46
  • 102
gvdm
  • 3,006
  • 5
  • 35
  • 73

3 Answers3

5

You should use Clients.Others.SendAsync or Client.AllExcept.SendAsync instead of Clients.All.SendAsync.

Clients.All.SendAsync is designed for scenarios where the client wants to send AND receive messages (like a chat room). Hence all connected clients are supposed to implement connection.on('getMessage', in order to receive the notifications. If they don't, they raise the warning No client method with the name 'x' found when receiving back the notification they just pushed.

In your scenario, I understand you have one kind of client pushing notifications and another kind receiving them (like a GPS device sending positions to a tracking application). In that scenario, using Clients.Others.SendAsync or Client.AllExcept.SendAsync will ensure pushing clients will not be broadcasted back the notification they (or their kind) just pushed.

ikos23
  • 4,879
  • 10
  • 41
  • 60
tagada
  • 131
  • 1
  • 2
  • @john. tks for the advice and formatting. Could you point out to me the correct way to propose an answer ? Pointing to the API correcting the problem and explaining why is somehow inapropriate? – tagada Oct 04 '18 at 09:59
3

Set autoConnect: false to start your own connection:

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

Better yet...

Don't extend AbpCommonHub. You'll find that real-time notification stops working and you need to replace IRealTimeNotifier because SignalRRealTimeNotifier uses 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?

Follow the documentation and create a separate hub.

Community
  • 1
  • 1
aaron
  • 39,695
  • 6
  • 46
  • 102
1

I was facing the same error in my Angular application where I use the package "@aspnet/signalr": "1.1.4".

enter image description here

The cause of this issue was I wasn't calling the subscribe method after join the channel.

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;
}

So, to fix this I called the subscribe method after join 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;
}
Sibeesh Venu
  • 18,755
  • 12
  • 103
  • 140