Aspnet SignalR and React

Have you ever needed to communicate from server to the react client? We normally use an api to call the server from client and we learned to live with that, but some time you need the opposite. This article is about how to use Aspnet SignalR to send notifications to the React app. On the other words server to client communication!

Scenario : We are making it easy, send the current date time from one client (by clicking a button) to the SignalR server and send the same message back to all clients (you can open multi browser pages to see the effect)

Get the code here

Aspnet SignalR

SignalR is a open-source library for Microsoft ASP.NET for server to send asynchronous notifications to client-side web applications. It comes with libraries both for server-side as well as client-side JavaScript. The JavaScript library of course can be used in a react web application. There is a good getting started document for the JavaScript library at Microsoft: Use ASP.NET Core SignalR with TypeScript. It is a very good beginning ,but if you want to mix it with react, things can easily go out of control.

Reactjs 

React is a front-end JavaScript library. Although, React is very simple to learn, it take time to be well understood and mastered. Using React you normally don’t have variables but you have constant; so whenever you define a variable with var or let it means that you probably are making a mistake. Instead of variables you have state and your job as the programmer is basically to manage the state of the react application.

So since you don’t have the variable if you have something that changes, you can not just assign a new value to it. This makes things a little bit more interesting while you are introducing some of external libraries.

Redux or not

Redux is a library for managing application state, it mixes very well with React. I personally love Redux, so normally for bigger projects I already have redux or similar things like easy peasy. But here I am making a simple project for you to get the idea, if your project is small you can use this sample , if your project is bigger you can get the idea expand it to redux or whatever you like.

Get the backend ready

So, I am going to make this very short as we are focusing on the react side. If you feel you can’t follow please visit the microsoft getting started (like at the beginning of the article) and get more familiar with donet.

We are doing it in dotnet 6, with a WebApi template. Just make sure you have dotnet 6 installed! Open your favorit terminal and then run this command to make new webapi project. The command creates a dotnet project the name of SignalrProject (or create one in visual studio):

dotnet new webapi -n SignalrProject
Code language: JavaScript (javascript)

Open your project in visual studio and add a class by the name of SignalrHub.cs and paste the content below. It receives the message and the user from the client and send it back to all clients. Put your break point on line 5 if you want to understand it better.

using Microsoft.AspNetCore.SignalR; public class SignalrHub : Hub { public async Task NewMessage(string user, string message) { await Clients.All.SendAsync("messageReceived", user, message); } }
Code language: C# (cs)

Open Program.cs, we need to add a few lines to get SignalR up and running (add highlighted lines ) :

var builder = WebApplication.CreateBuilder(args); builder.Services.AddSignalR(); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.MapHub<SignalrHub>("/hub"); app.Run();
Code language: C# (cs)

And that is basically it! For the backend (I did not removed REST API capabilities as in real project you probably going to need REST API too)

Now when you run your hub, should be ready to send and receive message at https://localhost:port/hab

Note : in some environments the casing of the URL letter matters ex /Hub vs /hub

Dealing with CORS ( Cross-Origin Resource Sharing )

In case you run your react app from different address, for example from https://localhost:3000 while dotnet signalR backend served from another port like https://localhost:5001 then you most likely are going to see error

Error: Failed to complete negotiation with the server: TypeError: Failed to fetch

And if you open the browser’s console you are going to see something like:

Access to fetch at /negotiate?negotiateVersion=1 from origin has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’.

The error comes from the dotnet side, and not react side! The reason is that you have 2 deferent origins (domain if you want to simplify it). Of course, it is a development setup problem; because for your production setup, you most likely use some kind of application gateway and serve both backend and frontend from the same domain.

To solve CORS error development problem (in easiest way) in dotnet you can allow all origins for your dotnet backend while you are in development mode. Just add highlighted lines to Program.cs in IsDevelopment section.

if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); app.UseCors(x => x .AllowAnyMethod() .AllowAnyHeader() .SetIsOriginAllowed(origin => true) // allow any origin .AllowCredentials()); // allow credentials }
Code language: C# (cs)

For a more granular setting that gives you a better security please read Enable Cross-Origin Requests

React side with typescript

I make the example with typescript, so if you want javascript, just remove the extra!

To start with react you need NodeJS installed. It comes equipped with npm.

I makes sense that your react app sit in another folder than back end. Something like this :

-- root folder |----SignalrProject |- ... |- SignalrProject.csproj |-----frontend |-- ... |-- package.json

to create this cd to root folder and run below command

npx create-react-app frontend --template typescript

We just need to install the client side library to our react. CD to the frontend folder you just created and run command below to add the SignalR client side library to your React project:

npm i @microsoft/signalr @types/node
Code language: CSS (css)

Open your frontend directory in visual studio code (if you want to !) and create a file named signalRConnection.ts under ./src/signalr-connection.ts:

import * as signalR from "@microsoft/signalr"; const URL = process.env.HUB_ADDRESS ?? "https://localhost:5001/hub"; //or whatever your backend port is class Connector { private connection: signalR.HubConnection; public events: (onMessageReceived: (username: string, message: string) => void) => void; static instance: Connector; constructor() { this.connection = new signalR.HubConnectionBuilder() .withUrl(URL) .withAutomaticReconnect() .build(); this.connection.start().catch(err => document.write(err)); this.events = (onMessageReceived) => { this.connection.on("messageReceived", (username, message) => { onMessageReceived(username, message); }); }; } public newMessage = (messages: string) => { this.connection.send("newMessage", "foo", messages).then(x => console.log("sent")) } public static getInstance(): Connector { if (!Connector.instance) Connector.instance = new Connector(); return Connector.instance; } } export default Connector.getInstance;
Code language: JavaScript (javascript)

So Let’s talk about the code above. We need to have a single connection that is why we use Singleton pattern. When you want to write your code make more functions like public newMessage for sending information to the server and also more delegates like onMessageReceived . The functions are straight forward but the delegates might need more elaborations

Receiving message from the server

On line 19 we told our app to listen for an incoming message by the name of messageReceived. So whenever server sends this type of message line 19 is going to call its inner function and with arguments username and message . Here we use delegation (that is very common in react) to call a function that is defined in our components.

Usage

Here is how you use this Connector. If you have more events you can create them as public functions and import it. Also I used useState to keep the state of the message, and useEffect to update the state whenever server sends a new message.

import React, { useEffect, useState } from 'react'; import './App.css'; import Connector from './signalr-connection.ts' function App() { const { newMessage, events } = Connector(); const [message, setMessage] = useState("initial value"); useEffect(() => { events((_, message) => setMessage(message)); }); return ( <div className="App"> <span>message from signalR: <span style={{ color: "green" }}>{message}</span> </span> <br /> <button onClick={() => newMessage((new Date()).toISOString())}>send date </button> </div> ); } export default App;
Code language: JavaScript (javascript)

Here I just wanted to keep it simple so instead of adding an input and getting the value, I just sent the current date to the server, then get it back and display it. You can open 2 browsers and and see that this value is updates simultaneously.

Run the backend and front end and try the application. You can get the code all together from GitHub.

Make more events (server calls):

Essentially we are done here, but if you want to get some idea about how to add more events (how to receive other type of messages). First update the signature definition of your delidage (typescript only )

public events: (onMessageReceived: (username: string, message: string) => void, onSomeOtherServerEventReceived: (payload: Payload) => void ) => void; then you add the this.events = (onMessageReceived, onSomeOtherServerEventReceived) => { this.connection.on("messageReceived", (username, message) => { onMessageReceived(username, message); }); this.connection.on("somethingDefinedOnServer", (payload) => { somethingDefinedOnServer(payload); }); };
Code language: JavaScript (javascript)

then use it in your component :

useEffect(() => { const handleMessageReceived = (_, message) => setMessage(message); const handleSomeOtherServerEventReceived = (payload) => { // do something... } events(handleMessageReceived ,handleSomeOtherServerEventReceived ); });
Code language: Java (java)

Get the code here

More reading

now that you learned about react and signalR, maybe you wan to learn about :


Posted

in

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *