I defined 2 separate hub in my project, Which is seem they parse into single proxy... and i'm not against it, let it be.
Next i defined my client, the time i had one client i had no issue with it,
but now that i have two hub, and both come to work at a single page, once the first hub start it work well, request goes to server and back.
but once i call the partial page, which connect and talk to second hub, it goes to connection.start, but it wont break on server, mean server is not notified of this action... . now can any one help me?
Controller1:
var app = angular.module("chatApplication", []);
var myHub = $.connection.chatHub;
app.controller("ChatController", [
"$scope", "$timeout", "$templateCache",
function ($scope, $timeout, $templateCache) {
...
$scope.RegisterClientMethod(myHub);
myHub.connection.start().done(function () {
//Already uses OnConnect Override
//TODO: Link Events To Required Buttons And Actions
//like: $(x).click(fnX);
//TODO: Call Started Events
//myHub.server.hello();
}).fail(function (e) {
$scope.connectionMessage = "Connection Failed" + e.toString();
$scope.$apply();
});
}
]);
Controller2:
var myUserHub = $.connection.userHub;
app.controller("UserController", [
"$scope", "$timeout", "$templateCache",
function($scope, $timeout, $templateCache) {
...
$scope.RegisterClientMethod(myUserHub);
$scope.RegisterWatchers();
myUserHub.server.getUsers();
myUserHub.connection.start().done(function () {
//Since Connection is already open, by chatHub, we cannot relay on that
myUserHub.server.getUsers();
}).fail(function (e) {
$scope.connectionMessage = "Connection Failed" + e.toString();
$scope.$apply();
});
}
]);
Hub1:
namespace SignalRChatSystem.Hubs
{
[ChatAuthorize]
public class ChatHub : Hub
{
...
public override Task OnConnected()
{
//client.doSomething
return base.OnConnected();
}
...
}
}
Hub2:
namespace SignalRChatSystem.Hubs
{
[UserAuthorize]
public class UserHub : Hub
{
...
public override Task OnConnected()
{
//client.doSomething
return base.OnConnected();
}
...
}
}
Mapping
[assembly: OwinStartupAttribute(typeof(SignalRChatSystem.Startup))]
namespace SignalRChatSystem
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
app.MapSignalR();
}
}
}
You should call $.connection.hub.start() only once, even if using multiple hubs. See: http://www.asp.net/signalr/overview/guide-to-the-api/hubs-api-guide-javascript-client#establishconnection
If you are not sure if connection was started earlier on not, you could check before starting it, using the connectionState object.
$.signalR.connectionState
Object {connecting: 0, connected: 1, reconnecting: 2, disconnected: 4}
So your start method could be like this:
if ($.connection.hub && $.connection.hub.state === $.signalR.connectionState.disconnected) {
$.connection.hub.start();
//...
}
If your connection is already open, you can call directly what is in the .done() { body.
Maybe you can check before:
if ($.connection.hub && $.connection.hub.state === $.signalR.connectionState.connected) {
// ... logic for second hub here
}
Related
I need some help knowing if they are doing something wrong with signalR. Let go:
My application is mvc. I followed the example of the tutorials and did so:
I created a RealTime class:
public class RealTime : Hub
{
public async Task SendMessage(string message)
{
await Clients.All.SendAsync("ReceiveMessage", message);
}
}
In StartUp:
public void ConfigureServices(IServiceCollection services)
{
.
.
.
services.AddSignalR();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime appLifetime)
{
.
.
.
app.UseSignalR(routes =>
{
routes.MapHub<RealTime>("/realtime");
});
}
So, I went to my controller and injected IHubContext:
public class MyController : Controller
{
private readonly IHubContext<RealTime> _hub;
public MyController
(
IHubContext<RealTime> hub
)
{
_hub = hub;
}
And at some point I use:
await _hub.Clients.All.SendAsync("ReceiveMessage", "Hello Word!");
On my front end I did the following:
var connection = new signalR.HubConnectionBuilder().withUrl("/realtime").build();
connection.on("ReceiveMessage", function (message) {
Swal.fire(
'The Internet?',
message,
'question'
);
});
connection.start().then(function () {
//
}).catch(function (err) {
//
});
But nothing happens. There is no error in either the back en or the front end. But nothing happens!
Can anyone tell me see I did something wrong?
First thing I noticed was that you injected your hub in a Controller, try injecting it in the service/manager that gets called in that Controller.
The second thing was that you better get used with SendCoreAsync() instead of SendAsync when you aren't in the class that implements Hub.
The third thing i can suggest you to do is to put .configureLogging(signalR.LogLevel.Information) and then .build();
Also, put this to not get an error: import * as signalR from "#aspnet/signalr";
Also start the connection and display a message to know if the connection is established
connection.start().then(function () {
console.log("signalR connected")
}).catch(function (err) {
console.log(err)
});
And I would put the connection.on only after starting the connection
connection.on("ReceiveMessage", (message) =>{
Swal.fire(
'The Internet?',
message,
'question'
);
});
Hope I helped you !
thanks for the help. I found the solution, although ugly it works.
I put before opening the connection the following snippet:
Object.defineProperty(WebSocket, 'OPEN', { value: 1, });
So my js was like this:
var connection = new signalR.HubConnectionBuilder()
.withUrl("/realtime")
.configureLogging(signalR.LogLevel.Debug)
.build();
Object.defineProperty(WebSocket, 'OPEN', { value: 1, });
connection.start().then(function () {
console.log("signalR connected")
}).catch(function (err) {
return console.error(err)
});
connection.on("ReceiveMessage", function (message) {
Swal.fire(
'The Internet?',
message,
'question'
);
});
EDITED: see at the bottom
I'm new to SignalR and trying to implement with it a simple scenario with Angular7 client using this library, and ASP.NET Core web API. All what I need is to use SignalR to notify the client about the progress of some lengthy operations in methods of the API controllers.
After a number of attempts, I got to a point where apparently the connection is established, but then when the long task starts running and sending messages, my client does not seem to receive anything, nor any traffic appears in web sockets (Chrome F12 - Network - WS).
I post here the details, which might also be useful to other newcomers (full source code at https://1drv.ms/u/s!AsHCfliT740PkZh4cHY3r7I8f-VQiQ). Probably I'm just making some obvious error, yet in the docs and googling around I could not find a code fragment essentially different from mine. Could anyone give a hint?
My start point for the server side was https://msdn.microsoft.com/en-us/magazine/mt846469.aspx, plus the docs at https://learn.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-2.2. I tried to create a dummy experimental solution with that.
My code snippets in form of a recipe follow.
(A) Server Side
1.create a new ASP.NET core web API app. No authentication or Docker, just to keep it minimal.
2.add the NuGet package Microsoft.AspNetCore.SignalR.
3.at Startup.cs, ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
// CORS
services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
{
builder.AllowAnyMethod()
.AllowAnyHeader()
// https://github.com/aspnet/SignalR/issues/2110 for AllowCredentials
.AllowCredentials()
.WithOrigins("http://localhost:4200");
}));
// SignalR
services.AddSignalR();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
and the corresponding Configure method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
// CORS
app.UseCors("CorsPolicy");
// SignalR: add to the API at route "/progress"
app.UseSignalR(routes =>
{
routes.MapHub<ProgressHub>("/progress");
});
app.UseHttpsRedirection();
app.UseMvc();
}
4.add a ProgressHub class, which just derives from Hub:
public class ProgressHub : Hub
{
}
5.add a TaskController with a method to start some lengthy operation:
[Route("api/task")]
[ApiController]
public class TaskController : ControllerBase
{
private readonly IHubContext<ProgressHub> _progressHubContext;
public TaskController(IHubContext<ProgressHub> progressHubContext)
{
_progressHubContext = progressHubContext;
}
[HttpGet("lengthy")]
public async Task<IActionResult> Lengthy([Bind(Prefix = "id")] string connectionId)
{
await _progressHubContext
.Clients
.Client(connectionId)
.SendAsync("taskStarted");
for (int i = 0; i < 100; i++)
{
Thread.Sleep(500);
Debug.WriteLine($"progress={i}");
await _progressHubContext
.Clients
.Client(connectionId)
.SendAsync("taskProgressChanged", i);
}
await _progressHubContext
.Clients
.Client(connectionId)
.SendAsync("taskEnded");
return Ok();
}
}
(B) Client Side
1.create a new Angular7 CLI app (without routing, just to keep it simple).
2.npm install #aspnet/signalr --save.
3.my app.component code:
import { Component, OnInit } from '#angular/core';
import { HubConnectionBuilder, HubConnection, LogLevel } from '#aspnet/signalr';
import { TaskService } from './services/task.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
private _connection: HubConnection;
public messages: string[];
constructor(private _taskService: TaskService) {
this.messages = [];
}
ngOnInit(): void {
// https://codingblast.com/asp-net-core-signalr-chat-angular/
this._connection = new HubConnectionBuilder()
.configureLogging(LogLevel.Debug)
.withUrl("http://localhost:44348/signalr/progress")
.build();
this._connection.on("taskStarted", data => {
console.log(data);
});
this._connection.on("taskProgressChanged", data => {
console.log(data);
this.messages.push(data);
});
this._connection.on("taskEnded", data => {
console.log(data);
});
this._connection
.start()
.then(() => console.log('Connection started!'))
.catch(err => console.error('Error while establishing connection: ' + err));
}
public startJob() {
this.messages = [];
this._taskService.startJob('zeus').subscribe(
() => {
console.log('Started');
},
error => {
console.error(error);
}
);
}
}
Its minimalist HTML template:
<h2>Test</h2>
<button type="button" (click)="startJob()">start</button>
<div>
<p *ngFor="let m of messages">{{m}}</p>
</div>
The task service in the above code is just a wrapper for a function which calls HttpClient's get<any>('https://localhost:44348/api/task/lengthy?id=' + id).
EDIT 1
After some more experimenting, I came with these changes:
use .withUrl('https://localhost:44348/progress') as suggested. It seems that now it no more triggers 404. Note the change: I replaced http with https.
do not make the API method async as it seems that the await are not required (i.e. set the return type to IActionResult and remove async and await).
With these changes, I can now see the expected log messages on the client side (Chrome F12). Looking at them, it seems that the connection gets bound to a generated ID k2Swgcy31gjumKtTWSlMLw:
Utils.js:214 [2019-02-28T20:11:48.978Z] Debug: Starting HubConnection.
Utils.js:214 [2019-02-28T20:11:48.987Z] Debug: Starting connection with transfer format 'Text'.
Utils.js:214 [2019-02-28T20:11:48.988Z] Debug: Sending negotiation request: https://localhost:44348/progress/negotiate.
core.js:16828 Angular is running in the development mode. Call enableProdMode() to enable the production mode.
Utils.js:214 [2019-02-28T20:11:49.237Z] Debug: Selecting transport 'WebSockets'.
Utils.js:210 [2019-02-28T20:11:49.377Z] Information: WebSocket connected to wss://localhost:44348/progress?id=k2Swgcy31gjumKtTWSlMLw.
Utils.js:214 [2019-02-28T20:11:49.378Z] Debug: Sending handshake request.
Utils.js:210 [2019-02-28T20:11:49.380Z] Information: Using HubProtocol 'json'.
Utils.js:214 [2019-02-28T20:11:49.533Z] Debug: Server handshake complete.
app.component.ts:39 Connection started!
app.component.ts:47 Task service succeeded
So, it might be the case that I get no notification because my client ID does not match the ID assigned by SignalR (from the paper quoted above I had the impression that it was my duty to provide an ID, given that it is an argument of the API controller). Yet, I cannot see any available method or property in the connection prototype allowing me to retrieve this ID, so that I can pass it to the server when launching the lengthy job. Could this be the reason of my issue? If this is so, there should be a way of getting the ID (or setting it from the client side). What do you think?
It seems I've finally found it. The issue was probably caused by the wrong ID, so I started looking for a solution. A post (https://github.com/aspnet/SignalR/issues/2200) guided me to the usage of groups, which seems the recommended solution in these cases. So, I changed my hub so that it automatically assign the current connection ID to a "progress" group:
public sealed class ProgressHub : Hub
{
public const string GROUP_NAME = "progress";
public override Task OnConnectedAsync()
{
// https://github.com/aspnet/SignalR/issues/2200
// https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/working-with-groups
return Groups.AddToGroupAsync(Context.ConnectionId, "progress");
}
}
Now, my API controller method is:
[HttpGet("lengthy")]
public async Task<IActionResult> Lengthy()
{
await _progressHubContext
.Clients
.Group(ProgressHub.GROUP_NAME)
.SendAsync("taskStarted");
for (int i = 0; i < 100; i++)
{
Thread.Sleep(200);
Debug.WriteLine($"progress={i + 1}");
await _progressHubContext
.Clients
.Group(ProgressHub.GROUP_NAME)
.SendAsync("taskProgressChanged", i + 1);
}
await _progressHubContext
.Clients
.Group(ProgressHub.GROUP_NAME)
.SendAsync("taskEnded");
return Ok();
}
And of course I updated the client code accordingly, so that it does no more have to send an ID when invoking the API method.
Full demo repository available at https://github.com/Myrmex/signalr-notify-progress.
You set the route for the hub as /progress, but then you're attempting to connect to /signalr/progress, which is going to be a 404. If you open the developer console, you should have an connection error there telling you as much.
Just wanted to add that OP was on the right track with the connection ID.
I send it along optionally in the form data.
I'm just reporting progress while uploading to AWS and I handle with SignalR like this:
Controller
[HttpPost("MyPostRoute")]
public async Task<ActionResult> UploadFiles([FromForm] List<IFormFile> files, [FromForm] string? connectionId)
{
await _logic.UploadFiles(files, connectionId);
return Ok();
}
Logic
public async Task<bool> UploadFiles(List<IFormFile> files, string? connectionId)
{
foreach (var file in files)
{
Guid fileGuid = Guid.NewGuid();
string extension = Path.GetExtension(file.FileName);
using (var transferUtility = new TransferUtility(_awsClient))
{
var request = new TransferUtilityUploadRequest()
{
BucketName = _configuration["AwsBucket"],
Key = fileGuid + extension,
InputStream = file.OpenReadStream()
};
if (connectionId != null)
{
AwsFileProgress progress = new()
{
ConnectionId = connectionId,
FileName = file.FileName
};
request.UploadProgressEvent += (sender, e) => Request_UploadProgressEvent(sender, e, progress);
}
await transferUtility.UploadAsync(request);
}
}
return true;
}
private async void Request_UploadProgressEvent(object? sender, UploadProgressArgs e, AwsFileProgress progress)
{
progress.ProgressPercent = e.PercentDone;
await _hub.Clients.Client(progress.ConnectionId).SendAsync("AWSProgress", progress);
}
I made a model for the progress stuff:
public class AwsFileProgress
{
public string ConnectionId { get; set; }
public string FileName { get; set; }
public int ProgressPercent { get; set; } = 0;
}
And finally, the front-end:
import { Injectable } from '#angular/core';
import * as signalR from "#microsoft/signalr";
import { environment } from "#environment";
import { AwsFileProgress } from "#models/signalr/awsFileProgress";
#Injectable({
providedIn: 'root',
})
export class AwsSignalRService {
private hubConnection!: signalR.HubConnection
public data!: AwsFileProgress;
public startConnection = () => {
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl(`${environment.signalRhubRoot}/awsprogresshub`)
.build();
this.hubConnection
.start()
.then(() => { console.log('AWS SignalR Service Connection started') })
.catch(err => console.log('Error while starting AWS SignalR Service WebSocket: ' + err))
}
/////////////////////////////////////////////////////////
//This is all you have to do to get the connection ID:
public getConnectionId() : string | null {
return this.hubConnection.connectionId;
}
/////////////////////////////////////////////////////////
public addAwsProgressListener = () => {
this.hubConnection.on('AWSProgress', (awsFileProgress : AwsFileProgress) => {
this.data = awsFileProgress;
console.log(awsFileProgress.progressPercent);
});
}
}
Then the actual component it's injected into:
import { AwsSignalRService } from "#shared/services/aws-signalr-service";
#Component({
selector: 'settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss'],
})
export class SettingsComponent implements OnInit {
constructor(
private awsSignalRService: AwsSignalRService
) {
awsSignalRService.startConnection();
awsSignalRService.addAwsProgressListener();
}
^ Whatever is in our progress listener is handled here.
Making the request (and reporting client-side upload progress) is handled like this.
Note that I just use a basic (change) handler on a file input (<input type="file" (change)="fileUploadTest($event)"/>)
fileUploadTest(e: any) {
const formData = new FormData();
var cId = this.awsSignalRService.getConnectionId();
for(let file of e.target.files as File[]) {
formData.append('files', file, file.name);
};
if(cId != null)
formData.append('connectionId',cId);
const url: string = `${environment.ApiRoot}/MyPostRoute`;
this.http.post<any>(url,formData, {
reportProgress: true,
observe: 'events'
}).subscribe({
next: (response) => console.log(response),
error: (error) => console.log(error)
});
}
And the results in console of my API uploading data to AWS:
I'm trying to use SignalR for Asp Net Core 2.1 in order to send a message from a controller method which call is triggered from a test button in Angular.
The behavior I'd expect is that when I click the button, my service invokes the controller method, which sends the test message. Then, I will simply log the message.
I want to manage this in a service in order to avoid code duplication in all of the components.
I've read some examples like this question about using SignalR in a service (I've used the second solution) and this article and the official docs but even with applying these concepts it don't seems to work. (So, or I'm absolutely applying them in a wrong way or there's still something missing but I can't find out what...)
The client connects to the Message Hub successfully and if I click the button, the method is getting hit but I don't get any message and instead I get this warning in the Chrome console:
Warning: No client method with the name 'SendAsync' found.
Sending messages works fine, the issue is just with receiving them...
The question is: what am I doing wrong? Is the error on the back-end side or in the Angular side?
I share with you all of my code (the button and the service to call the controller method are not relevant since the call to the service goes fine):
> Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddSignalR();
}
//...
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//...
app.UseSignalR(routes =>
{
//...
routes.MapHub<MessageHub>("/messagehub");
//...
});
}
> MessageHub.cs
public class MessageHub : Hub<ITypedHubClient>
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
public interface ITypedHubClient
{
Task SendAsync(string title, string name, string message);
}
> MessageController.cs
IHubContext<MessageHub, ITypedHubClient> _messageHubContext;
public MessageController(IHubContext<MessageHub, ITypedHubClient> messageHubContext)
{
_messageHubContext = messageHubContext;
}
[HttpPost("Test")]
public async Task<IActionResult> Test()
{
try
{
await _messageHubContext.Clients.All.SendAsync("ReceiveMessage","test", "test");
return Ok(true);
}
catch (Exception e)
{
return BadRequest(e);
}
}
> communication.service.ts
#Injectable()
export class CommunicationService {
private _hubConnection: HubConnection | undefined;
public async: any;
message = '';
messages: string[] = [];
private newmessage = new Subject<string>();
message$ = this.newmessage.asObservable();
constructor() {
this._hubConnection = new signalR.HubConnectionBuilder()
.withUrl('/messagehub')
//.configureLogging(signalR.LogLevel.Information)
.configureLogging(signalR.LogLevel.Debug)
.build();
this._hubConnection.start().catch(err => console.error(err.toString()));
this._hubConnection.on('SendMessage', (user: any, message:any) => {
const received = `Received: ${message}`;
//this.messages.push(received);
this.newmessage.next(received);
console.log("got something new...", received);
});
}
clear() {
this.newmessage.next("");
}
public sendMessage(): void {
const data = `Sent: ${this.message}`;
if (this._hubConnection) {
this._hubConnection.invoke('SendMessage', 'AAA' ,data);
}
this.messages.push(data);
}
}
In signalr core 2.1 you can use strongly typed hubs to declare in an interface what actions can be called on the clients :
public class MessageHub : Hub<ITypedHubClient>
{
public async Task SendMessage(string title, string user, string message)
{
await Clients.All.SendMessageToClient(title, user, message);
}
}
public interface ITypedHubClient
{
Task SendMessageToClient(string title, string name, string message);
}
in the controller :
IHubContext<MessageHub, ITypedHubClient> _messageHubContext;
public async Task<IActionResult> Test()
{
await _messageHubContext.Clients.All.SendMessageToClient("test", "test", "test");
return Ok("ok");
}
in the client :
_hubConnection.on('SendMessageToClient', (title, user, message) => {
const received = `title: ${title}, name: ${user}, message: ${message}`;
console.log(received);
});
If you don't use strongly typed hub, then to call the same method in the client it becomes :
public class MessageHub : Hub
{
public async Task SendMessage(string title, string user, string message)
{
await Clients.All.SendAsync("SendMessageToClient", title, user, message);
}
}
In that case you can use the SendAsync method on the client proxy, it's first parameter is the name of the method you want to call.
Update :
When we define a strongly typed hub with an interface, all interface methods must return a Task. With custom methods, signalr generates methods that call SendCoreAsync. That allow us to call these methods asynchronously.
If the return type of the interface methods is not a Task we get the error : All client proxy methods must return 'System.Threading.Tasks.Task'
I tried to implement SignalR for ASP.NET Core as documented here: https://aspnetboilerplate.com/Pages/Documents/SignalR-AspNetCore-Integration
But I always get an error. Browser console output:
DEBUG:
Starting connection using WebSockets transport
Information: Normalizing '/signalr-myChatHub' to 'http://localhost:62114/signalr-myChatHub'
SCRIPT12008: WebSocket Error: Incorrect HTTP response. Status code 400, Bad Request
I'm using the latest version of all packages 3.6.2 and
Abp.AspNetCore.SignalR 3.6.2-preview6.
Web.Host/Startup.cs:
#elif FEATURE_SIGNALR_ASPNETCORE
app.UseSignalR(routes =>
{
routes.MapHub<AbpCommonHub>("/signalr");
routes.MapHub<MyChatHub>("/signalr-myChatHub"); // Prefix with '/signalr'
});
#endif
Copy-pasted MyChatHub.cs:
public class MyChatHub : Hub, ITransientDependency
{
public IAbpSession AbpSession { get; set; }
public ILogger Logger { get; set; }
public MyChatHub()
{
AbpSession = NullAbpSession.Instance;
Logger = NullLogger.Instance;
}
public async Task SendMessage(string message)
{
await Clients.All.SendAsync("getMessage", string.Format("User {0}: {1}", AbpSession.UserId, message));
}
public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
Logger.Debug("A client connected to MyChatHub: " + Context.ConnectionId);
}
public override async Task OnDisconnectedAsync(Exception exception)
{
await base.OnDisconnectedAsync(exception);
Logger.Debug("A client disconnected from MyChatHub: " + Context.ConnectionId);
}
}
More-or-less copy-pasted View:
#using EMetall.Web.Startup
#section scripts
{
<environment names="Development">
<script src="~/lib/signalr-client/signalr.min.js"></script>
<script src="~/lib/abp-web-resources/Abp/Framework/scripts/libs/abp.signalr-client.js"></script>
</environment>
<script type="text/javascript">
$(function () {
var chatHub = null;
abp.signalr.startConnection('/signalr-myChatHub', function (connection) {
chatHub = connection; // Save a reference to the hub
connection.on('getMessage', function (message) { // Register for incoming messages
console.log('received message: ' + message);
});
}).then(function (connection) {
console.log('Connected to myChatHub server!');
abp.event.trigger('myChatHub.connected');
});
abp.event.on('myChatHub.connected', function() { // Register for connect event
chatHub.invoke('sendMessage', "Hi everybody, I'm connected to the chat!"); // Send a message to the server
});
});
</script>
}
You need to use compatible versions of:
Abp.AspNetCore.SignalR (currently 3.6.2-preview6),
signalr.min.js (currently 1.0.0-rc1-update1) from #aspnet/signalr, and
abp.signalr-client.js (currently 3.6.2) to be released on abp-web-resources.
As of Version 4.2 it works out of the box.
You just have to download it and then create the chathup, register the route and call it in the view and it works. Great.
See for example for the chathup, route and view:
https://aspnetboilerplate.com/Pages/Documents/SignalR-AspNetCore-Integration
In our application I want to have a "realtime" grid of notifications that the user can monitor. That grid is a KendoUI grid with SignalR-transport and is read-only. So the only hub method defined is a Read method.
This is my hub that is hooked up to the KendoUI grid.:
[HubName( "NotificationsHub" )]
public class NotificationsHub : Hub
{
public IApplicationSupportService Service { get; set; } //injeccted with Autofac
public NotificationsHub()
{
}
public IEnumerable<EventViewModel> Read()
{
var events = from e in Service.FindEvents()
select new EventViewModel
{
EventId = e.EventId,
Text = e.Text,
CreatedOn = e.CreatedOn
};
return events;
}
}
I also have a NServiceBus message handler that is supposed to call a client side method called "addEventToPage". The notification handler receives a nevent from the service bus and is supposed to call all clients to update their grid.
This is the message handler on the server side, that uses a singleton helper to call the hub's clients via context.Clients.All.addEventToPage():
//NServiceBus message handler subscribes to an event, then calls clients
public class NotificationsHandler : IHandleMessages<IBusinessEventReceived>
{
public void Handle( INewEventWasSaved message )
{
NotifyTicker.Instance.UpdateClient( message.EventId, message.Text );
}
}
public class NotifyTicker
{
// Singleton instance
private static readonly Lazy<NotifyTicker> _instance = new Lazy<NotifyTicker>(
() => new NotifyTicker( GlobalHost.ConnectionManager.GetHubContext<NotificationsHub>() ) );
private IHubContext _context;
private NotifyTicker( IHubContext context )
{
_context = context;
}
public static NotifyTicker Instance
{
get { return _instance.Value; }
}
public void UpdateClient( int eventId, string text )
{
_context.Clients.All.addNewEventToPage( eventId, text );
}
}
The client code:
<script>
var hubProxy;
var hubStart;
$(function () {
var connection = $.connection;
$.connection.hub.logging = true;
hubProxy = connection.NotificationsHub;
//this function is called by message handler on server
hubProxy.client.addNewEventToPage = function (eventId, text) {
console.log(eventId + ' ' + text);
};
hubStart = $.connection.hub.start()
.done(function () { console.log('Now connected, connection ID=' + $.connection.hub.id); })
.fail(function () { console.log('Could not Connect!'); });
$("#grid").kendoGrid({
editable: false,
columns: [
{ field: "Text" }
],
dataSource: {
type: "signalr",
autoSync: true,
schema: {
model: {
id: "EventId",
fields: {
"EventId": { editable: false, nullable: true },
"Text": { type: "string" }
}
}
},
transport: {
signalr: {
promise: hubStart,
hub: hubProxy,
server: {
read: "read"
},
client: {
read: "read"
}
}
}
}
});
});
</script>
As you can see I am adding the "addEventToPage" method BEFORE the hub proxy starts. But the method is not called, period.
Originally, that method was supposed to add a EventViewModel to the KendoUI grid's datasource like so:
dataSource.add({
EventId: eventId,
Text: text
});
But that didn't work. It can't even write to the console.
The connection is established successfully, with web sockets. I can confirm that in the Chrome console output.
What am I missing?
Is there maybe a better way to update the grid without a customer client -side function? Maybe I can tell teh grid to Re-Read?