Monday, October 25, 2021

Communication Between WinForm Application And React JS Application Using SignalR

 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 make 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

Communication Between WinForm Application And React JS Application Using SignalR

Winform Application

Create a new project with Windows Form App (.NET Framework).

Communication Between WinForm Application And React JS Application Using SignalR

Select Framework is .NET Framework 4.5.

Communication Between WinForm Application And React JS Application Using SignalR

Create simple chat form same as below,

Communication Between WinForm Application And React JS Application Using SignalR

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

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

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.

Communication Between WinForm Application And React JS Application Using SignalR

Add reference "Microsoft.ASPNet.SignalR.Core" using NuGet manager for SignalR_Hub project.

Communication Between WinForm Application And React JS Application Using SignalR

Create "ChatHub.cs" class.

Communication Between WinForm Application And React JS Application Using SignalR

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>();
	}
}
C#

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

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

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

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>
YAML

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

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

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
	{

	}
}
C#

Note the SignalR hub running URL from the Project properties. Here, my SignalR hub service is running in "http://localhost:57142/".

Communication Between WinForm Application And React JS Application Using SignalR

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.

Communication Between WinForm Application And React JS Application Using SignalR

<!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>
Markup

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

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

Communication Between WinForm Application And React JS Application Using SignalR

Run the .NET solution. The SignalR Hub and Winform chat application will run.

Communication Between WinForm Application And React JS Application Using SignalR

Communication Between WinForm Application And React JS Application Using SignalR

Run the React web application using the "npm run start" command.

Communication Between WinForm Application And React JS Application Using SignalR

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.

Communication Between WinForm Application And React JS Application Using SignalR

Click the "Connect" button in a Web application.

Communication Between WinForm Application And React JS Application Using SignalR

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.

Communication Between WinForm Application And React JS Application Using SignalR

Same as above, you can send messages from react application also.

Communication Between WinForm Application And React JS Application Using SignalR

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 make 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.

No comments:

Post a Comment

No String Argument Constructor/Factory Method to Deserialize From String Value

  In this short article, we will cover in-depth the   JsonMappingException: no String-argument constructor/factory method to deserialize fro...