In this article I will show you how to connect application written in angular with core backend using SignalR.
The main purpose of using this tool is to send notifications, when client interact with backend.
We start from create api application in dotnet. To use signalR you doesn’t need to install nugetpack, because signalR is a part od core framework – at least core 2.2
In startup.cs / ConfigureServices add this line
services.AddCors(o => o.AddPolicy("CorsPolicy", builder => { builder .AllowAnyMethod() .AllowAnyHeader() .WithOrigins("http://localhost:4200") .AllowCredentials(); })); services.AddSignalR();Notice that we must use CORS method, in simple words is it security tool, that tells our application who can connect with us using HTTP headers.
I added url address in to method WithOrigins. From this site we will be connecting to backend app.
startup.cs / Configure
app.UseCors("CorsPolicy"); app.UseSignalR(route => { route.MapHub<NotificationsHub>("/notifications"); });Then we have to create NotificationsHub class:
public class NotificationsHub : Hub<INotificationsHub>, INotificationsHub { public async Task SendNotification(string message) { await Clients.Caller.SendNotification(message); } public override Task OnConnectedAsync() { Console.WriteLine("Who is connected: " + Context.ConnectionId); return base.OnConnectedAsync(); } } public interface INotificationsHub { Task SendNotification(string message); }We have to derived from Hub class. It is recommended to inherit from the generic hub class to strongly typed it. Thanks to this, our methods included in the interface will be reflected in the hub context. Actually, it would be enough, we still have a controller to write.
SendController:
private readonly IHubContext<NotificationsHub, INotificationsHub> _hubContext; public SendController(IHubContext<NotificationsHub, INotificationsHub> hubContext) { _hubContext = hubContext; } [HttpGet("signalr/{id}")] public string Signalr(string id) { _hubContext.Clients.Client(id).SendNotification("Hello world!"); return "message send to: " + id; }I will not start showing you how to create project in angular. I assume that you can do this
So install dependency using ng cli:
app.components.ts should look like this:
import { Component, OnInit } from '@angular/core'; import { HubConnection, HubConnectionBuilder } from '@aspnet/signalr'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { private _hubConnection: HubConnection; ngOnInit(): void { this._hubConnection = new HubConnectionBuilder().withUrl("http://localhost:5201/notifications").build(); this._hubConnection .start() .then(() => console.log('Connection started!')) .catch(err => console.log('Error while establishing connection :(')); this._hubConnection.on("SendNotification", (message: string) => { console.log("message: " + message) }); } title = 'Web'; }Important is to point correctly to back end /notifications endpoint. Pay special attention to the name of the method „SendNotification” it is identical like a method in our interface INotificationsHub.
Now we must run instance of angular app, open terminal in angular root directory and write command:
ng serve --port 4200Open two cards in chrome using address with angular app and open console. You will see this:
Copy ID from address in console localhost:5201/notifications?id={here is id}, and paste it in the address to our SendController endpoint.
http://localhost:5201/api/send/Signalr/{id what you copy}After hitting enter, you notice that the message only reached the angular application with the same ID.
Now we can do something more effective We will try to display message in browser in beauty full box.
Install required dependency to angular:
Once you have installed the above packages, open the angular.json file and include the toastr CSS.
"styles": [ "src/styles.css", "node_modules/ngx-toastr/toastr.css" ]Include the BrowserAnimationsModule and ToastrModule in the app.module.ts file and import both the modules.
Here is how the app.module.ts file looks:
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ToastrModule } from 'ngx-toastr'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, BrowserAnimationsModule, ToastrModule.forRoot() ], providers: [ToastrModule], bootstrap: [AppComponent] }) export class AppModule { }And then we need to modify app.component.ts to looks this:
import { Component, OnInit } from '@angular/core'; import { HubConnection, HubConnectionBuilder } from '@aspnet/signalr'; import { ToastrService } from 'ngx-toastr'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { title = 'CitizenBudget-Web'; private _hubConnection: HubConnection; private _toast: ToastrService; constructor(private toastr: ToastrService) { this._toast = toastr; } ngOnInit(): void { this._hubConnection = new HubConnectionBuilder().withUrl("http://localhost:5201/notifications").build(); this._hubConnection .start() .then(() => console.log('Connection started!')) .catch(err => console.log('Error while establishing connection :(')); this._hubConnection.on("SendNotification", (id: string, message: string) => { this.toastr.show(message); }); } }Do the trick once again and see what happen
This is a small problem to solve. After re-loading the angular application or moving to another subpage, you will receive a new identification number. So you have to switch to a new one. It’s annoying.
We will write a mechanism dealing with the receipt of the current customer’s Id.
ClientInfoInMemory.cs
public class ClientInfoInMemory : IClientInfoInMemory { private ConcurrentDictionary<string, ClientInfo> _onlineClients { get; } = new ConcurrentDictionary<string, ClientInfo>(); public bool AddUpdate(string clientGuid, string connectionId) { var clientAlreadyExists = _onlineClients.ContainsKey(clientGuid); var clientInfo = new ClientInfo { ClientGuid = clientGuid, ConnectionId = connectionId }; _onlineClients.AddOrUpdate(clientGuid, clientInfo, (key, value) => clientInfo); return clientAlreadyExists; } public void Remove(string clientGuid) { ClientInfo clientInfo; _onlineClients.TryRemove(clientGuid, out clientInfo); } public ClientInfo GetUserInfo(string clientName) { ClientInfo client; _onlineClients.TryGetValue(clientName, out client); return client; } }We will add one more method to the NotificationsHub class
public async Task JoinAsync(string clientGuid) { await Task.Run(() => _clientInfoInMemory.AddUpdate(clientGuid, Context.ConnectionId)); }Do not forget to add a new method to the interface INotificationsHub.
Next, we’ll add the method in the client’s generating GUID number, which will be kept in the cookie and sent when the SignalR client connects to the server.
For this we need an npm package.
Now the class app.component.ts should look like this:
OnInit(): void { this._hubConnection = new HubConnectionBuilder().withUrl('http://localhost:5202/notifications').build(); this._hubConnection .start() .then(() => { console.log('Connedddction started!'); this.Join(this.GenerateGuid()); }) .catch(err => console.log('Error while establishing connection :(')); this._hubConnection.on("SendNotification", (id: string, message: string) => { this.toastr.show(message); }); } private Join(guid: string): void { this._hubConnection.invoke('JoinAsync', guid); }The rest is up to you, you have to do a little counter-game. To first get the Guid from the ClientInfoInMemory class and do something like this:
public async Task HandleAsync(string guid) { var clientId = _clientInfoInMemory.GetUserInfo(guid).ConnectionId; await _hubContext.Clients.Client(clientId) .SendNotificationAsync("Message send."); }Get me on twitter if you have some questions Thanks for attention.