Introduction
Hello Friends!!!! Nowadays most of the applications are built with the latest tech stack, but at the same time, those applications want to communicate with the legacy applications. In this article, I am going to explain how to making communication between legacy Winforms (builds using .NET framework) application with a Modern ReactJS web application using SingalR. SignalR is an open-source library for Microsoft ASP.NET that allows servers to send asynchronous messages to client-side web applications and it includes server-side and client-side JavaScript components.
Demo - Simple Chat Application
The chat application contains three parts,
- Winform application (.NET Framework 4.5)
- SignalR Hub (.NET Framework 4.5.2)
- React JS application
Winform Application
Create a new project with Windows Form App (.NET Framework).
Select Framework is .NET Framework 4.5.
Create simple chat form same as below,
Add below code for connecting the SingalR Hub.
private void btnConnect_Click(object sender, EventArgs e)
{
EstablishConnection();
}
private void EstablishConnection()
{
connection = new HubConnection("http://localhost:57142/");
chatHub = connection.CreateHubProxy("chatHub");
chatHub.On<string, object>("SendMessageToWinForm", (name, data) =>
{
if (txtChatHistory.InvokeRequired)
{
txtChatHistory.Invoke(new Action(() => txtChatHistory.AppendText($"{name} : {data}" + Environment.NewLine)));
}
else
{
txtChatHistory.AppendText($"{name} : {data}" + Environment.NewLine);
}
});
try
{
connection.Start().Wait();
MessageBox.Show("Connected Successfully");
}
catch (Exception ex)
{
MessageBox.Show("Error while connecting the SignalR Hub. Message: " + ex.Message);
}
}
Add the below code to send the message to the ReactJS application.
private void btnSend_Click(object sender, EventArgs e)
{
try
{
txtChatHistory.AppendText($"WinForms : {txtChatMessage.Text}" + Environment.NewLine);
chatHub
.Invoke(
"SendToWebApp",
"WinForms",
txtChatMessage.Text)
.Wait();
}
catch (Exception ex)
{
throw ex;
}
}
SignalR Hub
Add a new ASP.NET Web Application(.NET Framework) project in the solution with the name of SignalR_Hub. If you want, create a separate solution for SignalR Hub.
Add reference "Microsoft.ASPNet.SignalR.Core" using NuGet manager for SignalR_Hub project.
Create "ChatHub.cs" class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
namespace SignalR_Hub
{
[HubName("chatHub")]
public class ChatHub : Hub
{
}
public static class ConnectedUser
{
public static List<string> connections = new List<string>();
}
}
Add below methods to add and remove the Connection Ids during connecting and disconnect the clients from SignarR Hub.
public override Task OnConnected()
{
ConnectedUser.connections.Add(Context.ConnectionId);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
ConnectedUser.connections.Remove(Context.ConnectionId);
return base.OnDisconnected(stopCalled);
}
Add below the method for sending messages to the WinForm application. Here "SendMessageToWinForm" is a dynamic object which is subscribed to in the Winform Chat application.
public void SendToWinForm(string eventName, object data)
{
Clients.All.SendMessageToWinForm(eventName, data);
}
Add below the method for sending messages to Web applications. Here "SendMessageToWebApp" is a dynamic object which is subscribed in the ReactJS Web application.
public void SendToWebApp(string eventName, object data)
{
Clients.All.SendMessageToWebApp(eventName, data);
}
In the above two methods, "Clients.All" means send a message to all connected clients with the SignalR hub. If you want to send a message to a particular client then use Clients.Client(<conectionId of the client>).
Add below code in Web.config file to avoid CORS issue from web application. Here "Access-Control-Allow-Origin" value is "http://localhost:3000" because my ReactJS web application is running in "http://localhost:3000".
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="http://localhost:3000" />
<add name="Access-Control-Allow-Headers" value="Content-Type,x-requested-with,x-signalr-user-agent" />
<add name="Access-Control-Allow-Methods" value="OPTIONS,TRACE,GET,HEAD,POST" />
<add name="Access-Control-Allow-Credentials" value="true" />
</customHeaders>
</httpProtocol>
</system.webServer>
Create your own "Startup.cs" class and add the below code to map and run the signal hub.
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(SignalR_Hub.Startup))]
namespace SignalR_Hub
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Map("/signalr", map =>
{
var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableDetailedErrors = true;
hubConfiguration.EnableJavaScriptProxies = true;
map.RunSignalR(hubConfiguration);
});
}
}
}
If you want to add any authorization, then create a custom AuthorizeAttribute handler (example: SignalRAuthorizeAttribute) for validating the bearer token and add it to HubPipeline in the Startup class. I didn't use any Authorization in my demo project.
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
namespace SignalR_Hub.Authorization
{
public class SignalRAuthorizeAttribute : AuthorizeAttribute
{
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
string token;
var path = request.LocalPath;
bool isTokenPresent = !string.IsNullOrEmpty(request.QueryString.Get("token"));
if (isTokenPresent)
{
//Validate the token
return true;
}
return false;
}
}
}
app.Map("/signalr", map =>
{
var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableDetailedErrors = true;
var authorizer = new SignalRAuthorizeAttribute();
var module = new AuthorizeModule(authorizer, authorizer);
GlobalHost.HubPipeline.AddModule(module);
map.RunSignalR(hubConfiguration);
});
Add "SignalRAuthorize" to your "ChatHub' class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using SignalR_Hub.Authorization;
namespace SignalR_Hub
{
[SignalRAuthorize]
[HubName("chatHub")]
public class ChatHub : Hub
{
}
}
Note the SignalR hub running URL from the Project properties. Here, my SignalR hub service is running in "http://localhost:57142/".
ReactJS Web Application
Create an empty react application. Please refer to the link to know about creating the reactjs application. Here, I am using JS and Hooks concepts in my reactjs application and used the VS Code editor for web application development.
Add "jquery-3.4.1.min.js" and "jquery.signalR-2.4.2.min.js". Download the js files from the below link,
Add reference of above two files in the index.html file using <script> tag.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>React App</title>
<script src="./Scripts/jquery-3.4.1.min.js"></script>
<script src="./Scripts/jquery.signalR-2.4.2.min.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
Modify the App.js file with the below code. There are 3 functions in the App.js file.
- Connect() - Connect the SignalR hub
- Send() - Send the message to the Winform Application
- messageChange() - Store the textbox value into the local state.
import './App.css';
import { useEffect, useState } from 'react';
function App() {
const [chatHubProxy, SetChatHubProxy] = useState("");
const [connection, SetConnection] = useState(null);
const [chatMessage, SetChatMessage] = useState("");
const [chatHistory, SetChatHistory] = useState("");
function Send() {
chatHubProxy.invoke('SendToWinForm', "Web", chatMessage).done(function () {
SetChatHistory(prevChat => prevChat !== "" ? (prevChat + "\nWeb :" + chatMessage) : ("\nWeb :" + chatMessage));
}).fail(function (error) {
console.log('Invocation failed. Error: ' + error);
});
}
function Connect() {
try {
let localConnection = window.$.hubConnection("http://localhost:57142");
localConnection.qs = { 'version': '1.0' };
var hubProxy = localConnection.createHubProxy('chathub');
hubProxy.on('SendMessageToWebApp', function (eventName, data) {
SetChatHistory(prevChat => prevChat !== "" ? (prevChat + "\n" + eventName + ":" + data) : (eventName + ":" + data));
});
localConnection.start()
.done(function () { alert("Connected Successfully"); console.log('Now connected, connection ID=' + localConnection.id); })
.fail(function () { console.log('Could not connect'); });
SetChatHubProxy(hubProxy);
SetConnection(localConnection);
} catch (error) {
console.log(error);
}
}
function messageChange(e) {
SetChatMessage(e.target.value);
}
useEffect(() => {
return (()=> {
connection.close();
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return (
<div className="App">
<div className="container">
<button onClick={Connect}>Connect</button>
<br />
<br />
<label>Message:</label>
<br />
<input type="text" id="message" onChange={messageChange} />
<button className="btn-Send" onClick={Send}>Send</button>
<br />
<br />
<label>Chat History:</label>
<br />
<textarea defaultValue={chatHistory} rows={10} cols={50}>
</textarea>
<br />
</div>
</div>
);
}
export default App;
How to Run
- In the .NET solution, Go to Debug -> Set Startup Projects -> Common Properties -> Startup Project,
- Select Multiple startup projects check box
- Change Action to Start for both project
Run the .NET solution. The SignalR Hub and Winform chat application will run.
Run the React web application using the "npm run start" command.
Click the "Connect" button in a WinForm application to connect the SignalR Hub. Once connected to the SignalR hub, will get the "Connected Successfully" message box.
Click the "Connect" button in a Web application.
In the WinForm application, type a message in the text box and click send button. The Web application will receive the message. You can see this in the "Chat History" text box.
Same as above, you can send messages from react application also.
SignalR hub can be hosted in the below ways,
- Cloud
- On-premises network
- Localhost in the user machine
- Windows service in the user machine
In this article, I discussed How to making communication between legacy .NET applications with a modern ReactJS Web application using SignalR. Hope you liked it. If you have any doubts or comments about this, please let me know in the comments.