Tutorial: Get started with ASP.NET Core SignalR using TypeScript and Webpack
Note
This isn't the latest version of this article. For the current release, see the .NET 8 version of this article.
Warning
This version of ASP.NET Core is no longer supported. For more information, see .NET and .NET Core Support Policy. For the current release, see the .NET 8 version of this article.
Important
This information relates to a pre-release product that may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.
For the current release, see the .NET 8 version of this article.
This tutorial demonstrates using Webpack in an ASP.NET Core SignalR web app to bundle and build a client written in TypeScript. Webpack enables developers to bundle and build the client-side resources of a web app.
In this tutorial, you learn how to:
- Create an ASP.NET Core SignalR app
- Configure the SignalR server
- Configure a build pipeline using Webpack
- Configure the SignalR TypeScript client
- Enable communication between the client and the server
View or download sample code (how to download)
Prerequisites
Visual Studio 2022 with the ASP.NET and web development workload.
Create the ASP.NET Core web app
By default, Visual Studio uses the version of npm found in its installation directory. To configure Visual Studio to look for npm in the PATH
environment variable:
Launch Visual Studio. At the start window, select Continue without code.
Navigate to Tools > Options > Projects and Solutions > Web Package Management > External Web Tools.
Select the
$(PATH)
entry from the list. Select the up arrow to move the entry to the second position in the list, and select OK:.
To create a new ASP.NET Core web app:
- Use the File > New > Project menu option and choose the ASP.NET Core Empty template. Select Next.
- Name the project
SignalRWebpack
, and select Create. - Select .NET 8.0 (Long Term Support) from the Framework drop-down. Select Create.
Add the Microsoft.TypeScript.MSBuild NuGet package to the project:
- In Solution Explorer, right-click the project node and select Manage NuGet Packages. In the Browse tab, search for
Microsoft.TypeScript.MSBuild
and then select Install on the right to install the package.
Visual Studio adds the NuGet package under the Dependencies node in Solution Explorer, enabling TypeScript compilation in the project.
Configure the server
In this section, you configure the ASP.NET Core web app to send and receive SignalR messages.
In
Program.cs
, call AddSignalR:var builder = WebApplication.CreateBuilder(args); builder.Services.AddSignalR();
Again, in
Program.cs
, call UseDefaultFiles and UseStaticFiles:var app = builder.Build(); app.UseDefaultFiles(); app.UseStaticFiles();
The preceding code allows the server to locate and serve the
index.html
file. The file is served whether the user enters its full URL or the root URL of the web app.Create a new directory named
Hubs
in the project root,SignalRWebpack/
, for the SignalR hub class.Create a new file,
Hubs/ChatHub.cs
, with the following code:using Microsoft.AspNetCore.SignalR; namespace SignalRWebpack.Hubs; public class ChatHub : Hub { public async Task NewMessage(long username, string message) => await Clients.All.SendAsync("messageReceived", username, message); }
The preceding code broadcasts received messages to all connected users once the server receives them. It's unnecessary to have a generic
on
method to receive all the messages. A method named after the message name is enough.In this example:
- The TypeScript client sends a message identified as
newMessage
. - The C#
NewMessage
method expects the data sent by the client. - A call is made to SendAsync on Clients.All.
- The received messages are sent to all clients connected to the hub.
- The TypeScript client sends a message identified as
Add the following
using
statement at the top ofProgram.cs
to resolve theChatHub
reference:using SignalRWebpack.Hubs;
In
Program.cs
, map the/hub
route to theChatHub
hub. Replace the code that displaysHello World!
with the following code:app.MapHub<ChatHub>("/hub");
Configure the client
In this section, you create a Node.js project to convert TypeScript to JavaScript and bundle client-side resources, including HTML and CSS, using Webpack.
Run the following command in the project root to create a
package.json
file:npm init -y
Add the highlighted property to the
package.json
file and save the file changes:{ "name": "SignalRWebpack", "version": "1.0.0", "private": true, "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
Setting the
private
property totrue
prevents package installation warnings in the next step.Install the required npm packages. Run the following command from the project root:
npm i -D -E clean-webpack-plugin css-loader html-webpack-plugin mini-css-extract-plugin ts-loader typescript webpack webpack-cli
The
-E
option disables npm's default behavior of writing semantic versioning range operators topackage.json
. For example,"webpack": "5.76.1"
is used instead of"webpack": "^5.76.1"
. This option prevents unintended upgrades to newer package versions.For more information, see the npm-install documentation.
Replace the
scripts
property ofpackage.json
file with the following code:"scripts": { "build": "webpack --mode=development --watch", "release": "webpack --mode=production", "publish": "npm run release && dotnet publish -c Release" },
The following scripts are defined:
build
: Bundles the client-side resources in development mode and watches for file changes. The file watcher causes the bundle to regenerate each time a project file changes. Themode
option disables production optimizations, such as tree shaking and minification. usebuild
in development only.release
: Bundles the client-side resources in production mode.publish
: Runs therelease
script to bundle the client-side resources in production mode. It calls the .NET CLI's publish command to publish the app.
Create a file named
webpack.config.js
in the project root, with the following code:const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const { CleanWebpackPlugin } = require("clean-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { entry: "./src/index.ts", output: { path: path.resolve(__dirname, "wwwroot"), filename: "[name].[chunkhash].js", publicPath: "/", }, resolve: { extensions: [".js", ".ts"], }, module: { rules: [ { test: /\.ts$/, use: "ts-loader", }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"], }, ], }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ template: "./src/index.html", }), new MiniCssExtractPlugin({ filename: "css/[name].[chunkhash].css", }), ], };
The preceding file configures the Webpack compilation process:
- The
output
property overrides the default value ofdist
. The bundle is instead emitted in thewwwroot
directory. - The
resolve.extensions
array includes.js
to import the SignalR client JavaScript.
- The
Create a new directory named
src
in the project root,SignalRWebpack/
, for the client code.Copy the
src
directory and its contents from the sample project into the project root. Thesrc
directory contains the following files:index.html
, which defines the homepage's boilerplate markup:<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>ASP.NET Core SignalR with TypeScript and Webpack</title> </head> <body> <div id="divMessages" class="messages"></div> <div class="input-zone"> <label id="lblMessage" for="tbMessage">Message:</label> <input id="tbMessage" class="input-zone-input" type="text" /> <button id="btnSend">Send</button> </div> </body> </html>
css/main.css
, which provides CSS styles for the homepage:*, *::before, *::after { box-sizing: border-box; } html, body { margin: 0; padding: 0; } .input-zone { align-items: center; display: flex; flex-direction: row; margin: 10px; } .input-zone-input { flex: 1; margin-right: 10px; } .message-author { font-weight: bold; } .messages { border: 1px solid #000; margin: 10px; max-height: 300px; min-height: 300px; overflow-y: auto; padding: 5px; }
tsconfig.json
, which configures the TypeScript compiler to produce ECMAScript 5-compatible JavaScript:{ "compilerOptions": { "target": "es5" } }
index.ts
:import * as signalR from "@microsoft/signalr"; import "./css/main.css"; const divMessages: HTMLDivElement = document.querySelector("#divMessages"); const tbMessage: HTMLInputElement = document.querySelector("#tbMessage"); const btnSend: HTMLButtonElement = document.querySelector("#btnSend"); const username = new Date().getTime(); const connection = new signalR.HubConnectionBuilder() .withUrl("/hub") .build(); connection.on("messageReceived", (username: string, message: string) => { const m = document.createElement("div"); m.innerHTML = `<div class="message-author">${username}</div><div>${message}</div>`; divMessages.appendChild(m); divMessages.scrollTop = divMessages.scrollHeight; }); connection.start().catch((err) => document.write(err)); tbMessage.addEventListener("keyup", (e: KeyboardEvent) => { if (e.key === "Enter") { send(); } }); btnSend.addEventListener("click", send); function send() { connection.send("newMessage", username, tbMessage.value) .then(() => (tbMessage.value = "")); }
The preceding code retrieves references to DOM elements and attaches two event handlers:
keyup
: Fires when the user types in thetbMessage
textbox and calls thesend
function when the user presses the Enter key.click
: Fires when the user selects the Send button and callssend
function is called.
The
HubConnectionBuilder
class creates a new builder for configuring the server connection. ThewithUrl
function configures the hub URL.SignalR enables the exchange of messages between a client and a server. Each message has a specific name. For example, messages with the name
messageReceived
can run the logic responsible for displaying the new message in the messages zone. Listening to a specific message can be done via theon
function. Any number of message names can be listened to. It's also possible to pass parameters to the message, such as the author's name and the content of the message received. Once the client receives a message, a newdiv
element is created with the author's name and the message content in itsinnerHTML
attribute. It's added to the maindiv
element displaying the messages.Sending a message through the WebSockets connection requires calling the
send
method. The method's first parameter is the message name. The message data inhabits the other parameters. In this example, a message identified asnewMessage
is sent to the server. The message consists of the username and the user input from a text box. If the send works, the text box value is cleared.
Run the following command at the project root:
npm i @microsoft/signalr @types/node
The preceding command installs:
- The SignalR TypeScript client, which allows the client to send messages to the server.
- The TypeScript type definitions for Node.js, which enables compile-time checking of Node.js types.
Test the app
Confirm that the app works with the following steps:
Run Webpack in
release
mode. Using the Package Manager Console window, run the following command in the project root.npm run release
This command generates the client-side assets to be served when running the app. The assets are placed in the
wwwroot
folder.Webpack completed the following tasks:
- Purged the contents of the
wwwroot
directory. - Converted the TypeScript to JavaScript in a process known as transpilation.
- Mangled the generated JavaScript to reduce file size in a process known as minification.
- Copied the processed JavaScript, CSS, and HTML files from
src
to thewwwroot
directory. - Injected the following elements into the
wwwroot/index.html
file:- A
<link>
tag, referencing thewwwroot/main.<hash>.css
file. This tag is placed immediately before the closing</head>
tag. - A
<script>
tag, referencing the minifiedwwwroot/main.<hash>.js
file. This tag is placed immediately after the closing</title>
tag.
- A
- Purged the contents of the
Select Debug > Start without debugging to launch the app in a browser without attaching the debugger. The
wwwroot/index.html
file is served athttps://localhost:<port>
.If there are compile errors, try closing and reopening the solution.
Open another browser instance (any browser) and paste the URL in the address bar.
Choose either browser, type something in the Message text box, and select the Send button. The unique user name and message are displayed on both pages instantly.
Next steps
- Strongly typed hubs
- Authentication and authorization in ASP.NET Core SignalR
- MessagePack Hub Protocol in SignalR for ASP.NET Core
Additional resources
This tutorial demonstrates using Webpack in an ASP.NET Core SignalR web app to bundle and build a client written in TypeScript. Webpack enables developers to bundle and build the client-side resources of a web app.
In this tutorial, you learn how to:
- Create an ASP.NET Core SignalR app
- Configure the SignalR server
- Configure a build pipeline using Webpack
- Configure the SignalR TypeScript client
- Enable communication between the client and the server
View or download sample code (how to download)
Prerequisites
Visual Studio 2022 with the ASP.NET and web development workload.
Create the ASP.NET Core web app
By default, Visual Studio uses the version of npm found in its installation directory. To configure Visual Studio to look for npm in the PATH
environment variable:
Launch Visual Studio. At the start window, select Continue without code.
Navigate to Tools > Options > Projects and Solutions > Web Package Management > External Web Tools.
Select the
$(PATH)
entry from the list. Select the up arrow to move the entry to the second position in the list, and select OK:.
To create a new ASP.NET Core web app:
- Use the File > New > Project menu option and choose the ASP.NET Core Empty template. Select Next.
- Name the project
SignalRWebpack
, and select Create. - Select
.NET 7.0 (Standard Term Support)
from the Framework drop-down. Select Create.
Add the Microsoft.TypeScript.MSBuild NuGet package to the project:
- In Solution Explorer, right-click the project node and select Manage NuGet Packages. In the Browse tab, search for
Microsoft.TypeScript.MSBuild
and then select Install on the right to install the package.
Visual Studio adds the NuGet package under the Dependencies node in Solution Explorer, enabling TypeScript compilation in the project.
Configure the server
In this section, you configure the ASP.NET Core web app to send and receive SignalR messages.
In
Program.cs
, call AddSignalR:var builder = WebApplication.CreateBuilder(args); builder.Services.AddSignalR();
Again, in
Program.cs
, call UseDefaultFiles and UseStaticFiles:var app = builder.Build(); app.UseDefaultFiles(); app.UseStaticFiles();
The preceding code allows the server to locate and serve the
index.html
file. The file is served whether the user enters its full URL or the root URL of the web app.Create a new directory named
Hubs
in the project root,SignalRWebpack/
, for the SignalR hub class.Create a new file,
Hubs/ChatHub.cs
, with the following code:using Microsoft.AspNetCore.SignalR; namespace SignalRWebpack.Hubs; public class ChatHub : Hub { public async Task NewMessage(long username, string message) => await Clients.All.SendAsync("messageReceived", username, message); }
The preceding code broadcasts received messages to all connected users once the server receives them. It's unnecessary to have a generic
on
method to receive all the messages. A method named after the message name is enough.In this example:
- The TypeScript client sends a message identified as
newMessage
. - The C#
NewMessage
method expects the data sent by the client. - A call is made to SendAsync on Clients.All.
- The received messages are sent to all clients connected to the hub.
- The TypeScript client sends a message identified as
Add the following
using
statement at the top ofProgram.cs
to resolve theChatHub
reference:using SignalRWebpack.Hubs;
In
Program.cs
, map the/hub
route to theChatHub
hub. Replace the code that displaysHello World!
with the following code:app.MapHub<ChatHub>("/hub");
Configure the client
In this section, you create a Node.js project to convert TypeScript to JavaScript and bundle client-side resources, including HTML and CSS, using Webpack.
Run the following command in the project root to create a
package.json
file:npm init -y
Add the highlighted property to the
package.json
file and save the file changes:{ "name": "SignalRWebpack", "version": "1.0.0", "private": true, "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
Setting the
private
property totrue
prevents package installation warnings in the next step.Install the required npm packages. Run the following command from the project root:
npm i -D -E clean-webpack-plugin css-loader html-webpack-plugin mini-css-extract-plugin ts-loader typescript webpack webpack-cli
The
-E
option disables npm's default behavior of writing semantic versioning range operators topackage.json
. For example,"webpack": "5.76.1"
is used instead of"webpack": "^5.76.1"
. This option prevents unintended upgrades to newer package versions.For more information, see the npm-install documentation.
Replace the
scripts
property ofpackage.json
file with the following code:"scripts": { "build": "webpack --mode=development --watch", "release": "webpack --mode=production", "publish": "npm run release && dotnet publish -c Release" },
The following scripts are defined:
build
: Bundles the client-side resources in development mode and watches for file changes. The file watcher causes the bundle to regenerate each time a project file changes. Themode
option disables production optimizations, such as tree shaking and minification. usebuild
in development only.release
: Bundles the client-side resources in production mode.publish
: Runs therelease
script to bundle the client-side resources in production mode. It calls the .NET CLI's publish command to publish the app.
Create a file named
webpack.config.js
in the project root, with the following code:const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const { CleanWebpackPlugin } = require("clean-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { entry: "./src/index.ts", output: { path: path.resolve(__dirname, "wwwroot"), filename: "[name].[chunkhash].js", publicPath: "/", }, resolve: { extensions: [".js", ".ts"], }, module: { rules: [ { test: /\.ts$/, use: "ts-loader", }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"], }, ], }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ template: "./src/index.html", }), new MiniCssExtractPlugin({ filename: "css/[name].[chunkhash].css", }), ], };
The preceding file configures the Webpack compilation process:
- The
output
property overrides the default value ofdist
. The bundle is instead emitted in thewwwroot
directory. - The
resolve.extensions
array includes.js
to import the SignalR client JavaScript.
- The
Copy the
src
directory and its contents from the sample project into the project root. Thesrc
directory contains the following files:index.html
, which defines the homepage's boilerplate markup:<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>ASP.NET Core SignalR with TypeScript and Webpack</title> </head> <body> <div id="divMessages" class="messages"></div> <div class="input-zone"> <label id="lblMessage" for="tbMessage">Message:</label> <input id="tbMessage" class="input-zone-input" type="text" /> <button id="btnSend">Send</button> </div> </body> </html>
css/main.css
, which provides CSS styles for the homepage:*, *::before, *::after { box-sizing: border-box; } html, body { margin: 0; padding: 0; } .input-zone { align-items: center; display: flex; flex-direction: row; margin: 10px; } .input-zone-input { flex: 1; margin-right: 10px; } .message-author { font-weight: bold; } .messages { border: 1px solid #000; margin: 10px; max-height: 300px; min-height: 300px; overflow-y: auto; padding: 5px; }
tsconfig.json
, which configures the TypeScript compiler to produce ECMAScript 5-compatible JavaScript:{ "compilerOptions": { "target": "es5" } }
index.ts
:import * as signalR from "@microsoft/signalr"; import "./css/main.css"; const divMessages: HTMLDivElement = document.querySelector("#divMessages"); const tbMessage: HTMLInputElement = document.querySelector("#tbMessage"); const btnSend: HTMLButtonElement = document.querySelector("#btnSend"); const username = new Date().getTime(); const connection = new signalR.HubConnectionBuilder() .withUrl("/hub") .build(); connection.on("messageReceived", (username: string, message: string) => { const m = document.createElement("div"); m.innerHTML = `<div class="message-author">${username}</div><div>${message}</div>`; divMessages.appendChild(m); divMessages.scrollTop = divMessages.scrollHeight; }); connection.start().catch((err) => document.write(err)); tbMessage.addEventListener("keyup", (e: KeyboardEvent) => { if (e.key === "Enter") { send(); } }); btnSend.addEventListener("click", send); function send() { connection.send("newMessage", username, tbMessage.value) .then(() => (tbMessage.value = "")); }
The preceding code retrieves references to DOM elements and attaches two event handlers:
keyup
: Fires when the user types in thetbMessage
textbox and calls thesend
function when the user presses the Enter key.click
: Fires when the user selects the Send button and callssend
function is called.
The
HubConnectionBuilder
class creates a new builder for configuring the server connection. ThewithUrl
function configures the hub URL.SignalR enables the exchange of messages between a client and a server. Each message has a specific name. For example, messages with the name
messageReceived
can run the logic responsible for displaying the new message in the messages zone. Listening to a specific message can be done via theon
function. Any number of message names can be listened to. It's also possible to pass parameters to the message, such as the author's name and the content of the message received. Once the client receives a message, a newdiv
element is created with the author's name and the message content in itsinnerHTML
attribute. It's added to the maindiv
element displaying the messages.Sending a message through the WebSockets connection requires calling the
send
method. The method's first parameter is the message name. The message data inhabits the other parameters. In this example, a message identified asnewMessage
is sent to the server. The message consists of the username and the user input from a text box. If the send works, the text box value is cleared.
Run the following command at the project root:
npm i @microsoft/signalr @types/node
The preceding command installs:
- The SignalR TypeScript client, which allows the client to send messages to the server.
- The TypeScript type definitions for Node.js, which enables compile-time checking of Node.js types.
Test the app
Confirm that the app works with the following steps:
Run Webpack in
release
mode. Using the Package Manager Console window, run the following command in the project root.npm run release
This command generates the client-side assets to be served when running the app. The assets are placed in the
wwwroot
folder.Webpack completed the following tasks:
- Purged the contents of the
wwwroot
directory. - Converted the TypeScript to JavaScript in a process known as transpilation.
- Mangled the generated JavaScript to reduce file size in a process known as minification.
- Copied the processed JavaScript, CSS, and HTML files from
src
to thewwwroot
directory. - Injected the following elements into the
wwwroot/index.html
file:- A
<link>
tag, referencing thewwwroot/main.<hash>.css
file. This tag is placed immediately before the closing</head>
tag. - A
<script>
tag, referencing the minifiedwwwroot/main.<hash>.js
file. This tag is placed immediately after the closing</title>
tag.
- A
- Purged the contents of the
Select Debug > Start without debugging to launch the app in a browser without attaching the debugger. The
wwwroot/index.html
file is served athttps://localhost:<port>
.If there are compile errors, try closing and reopening the solution.
Open another browser instance (any browser) and paste the URL in the address bar.
Choose either browser, type something in the Message text box, and select the Send button. The unique user name and message are displayed on both pages instantly.
Next steps
- Strongly typed hubs
- Authentication and authorization in ASP.NET Core SignalR
- MessagePack Hub Protocol in SignalR for ASP.NET Core
Additional resources
This tutorial demonstrates using Webpack in an ASP.NET Core SignalR web app to bundle and build a client written in TypeScript. Webpack enables developers to bundle and build the client-side resources of a web app.
In this tutorial, you learn how to:
- Create an ASP.NET Core SignalR app
- Configure the SignalR server
- Configure a build pipeline using Webpack
- Configure the SignalR TypeScript client
- Enable communication between the client and the server
View or download sample code (how to download)
Prerequisites
- Visual Studio 2022 with the ASP.NET and web development workload.
- .NET 6.0 SDK
Create the ASP.NET Core web app
By default, Visual Studio uses the version of npm found in its installation directory. To configure Visual Studio to look for npm in the PATH
environment variable:
Launch Visual Studio. At the start window, select Continue without code.
Navigate to Tools > Options > Projects and Solutions > Web Package Management > External Web Tools.
Select the
$(PATH)
entry from the list. Select the up arrow to move the entry to the second position in the list, and select OK:.
To create a new ASP.NET Core web app:
- Use the File > New > Project menu option and choose the ASP.NET Core Empty template. Select Next.
- Name the project
SignalRWebpack
, and select Create. - Select
.NET 6.0 (Long Term Support)
from the Framework drop-down. Select Create.
Add the Microsoft.TypeScript.MSBuild NuGet package to the project:
- In Solution Explorer, right-click the project node and select Manage NuGet Packages. In the Browse tab, search for
Microsoft.TypeScript.MSBuild
and then select Install on the right to install the package.
Visual Studio adds the NuGet package under the Dependencies node in Solution Explorer, enabling TypeScript compilation in the project.
Configure the server
In this section, you configure the ASP.NET Core web app to send and receive SignalR messages.
In
Program.cs
, call AddSignalR:var builder = WebApplication.CreateBuilder(args); builder.Services.AddSignalR();
Again, in
Program.cs
, call UseDefaultFiles and UseStaticFiles:var app = builder.Build(); app.UseDefaultFiles(); app.UseStaticFiles();
The preceding code allows the server to locate and serve the
index.html
file. The file is served whether the user enters its full URL or the root URL of the web app.Create a new directory named
Hubs
in the project root,SignalRWebpack/
, for the SignalR hub class.Create a new file,
Hubs/ChatHub.cs
, with the following code:using Microsoft.AspNetCore.SignalR; namespace SignalRWebpack.Hubs; public class ChatHub : Hub { public async Task NewMessage(long username, string message) => await Clients.All.SendAsync("messageReceived", username, message); }
The preceding code broadcasts received messages to all connected users once the server receives them. It's unnecessary to have a generic
on
method to receive all the messages. A method named after the message name is enough.In this example, the TypeScript client sends a message identified as
newMessage
. The C#NewMessage
method expects the data sent by the client. A call is made to SendAsync on Clients.All. The received messages are sent to all clients connected to the hub.Add the following
using
statement at the top ofProgram.cs
to resolve theChatHub
reference:using SignalRWebpack.Hubs;
In
Program.cs
, map the/hub
route to theChatHub
hub. Replace the code that displaysHello World!
with the following code:app.MapHub<ChatHub>("/hub");
Configure the client
In this section, you create a Node.js project to convert TypeScript to JavaScript and bundle client-side resources, including HTML and CSS, using Webpack.
Run the following command in the project root to create a
package.json
file:npm init -y
Add the highlighted property to the
package.json
file and save the file changes:{ "name": "SignalRWebpack", "version": "1.0.0", "private": true, "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
Setting the
private
property totrue
prevents package installation warnings in the next step.Install the required npm packages. Run the following command from the project root:
npm i -D -E clean-webpack-plugin css-loader html-webpack-plugin mini-css-extract-plugin ts-loader typescript webpack webpack-cli
The
-E
option disables npm's default behavior of writing semantic versioning range operators topackage.json
. For example,"webpack": "5.70.0"
is used instead of"webpack": "^5.70.0"
. This option prevents unintended upgrades to newer package versions.For more information, see the npm-install documentation.
Replace the
scripts
property ofpackage.json
file with the following code:"scripts": { "build": "webpack --mode=development --watch", "release": "webpack --mode=production", "publish": "npm run release && dotnet publish -c Release" },
The following scripts are defined:
build
: Bundles the client-side resources in development mode and watches for file changes. The file watcher causes the bundle to regenerate each time a project file changes. Themode
option disables production optimizations, such as tree shaking and minification. usebuild
in development only.release
: Bundles the client-side resources in production mode.publish
: Runs therelease
script to bundle the client-side resources in production mode. It calls the .NET CLI's publish command to publish the app.
Create a file named
webpack.config.js
in the project root, with the following code:const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const { CleanWebpackPlugin } = require("clean-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { entry: "./src/index.ts", output: { path: path.resolve(__dirname, "wwwroot"), filename: "[name].[chunkhash].js", publicPath: "/", }, resolve: { extensions: [".js", ".ts"], }, module: { rules: [ { test: /\.ts$/, use: "ts-loader", }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"], }, ], }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ template: "./src/index.html", }), new MiniCssExtractPlugin({ filename: "css/[name].[chunkhash].css", }), ], };
The preceding file configures the Webpack compilation process:
- The
output
property overrides the default value ofdist
. The bundle is instead emitted in thewwwroot
directory. - The
resolve.extensions
array includes.js
to import the SignalR client JavaScript.
- The
Copy the
src
directory and its contents from the sample project into the project root. Thesrc
directory contains the following files:index.html
, which defines the homepage's boilerplate markup:<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>ASP.NET Core SignalR with TypeScript and Webpack</title> </head> <body> <div id="divMessages" class="messages"></div> <div class="input-zone"> <label id="lblMessage" for="tbMessage">Message:</label> <input id="tbMessage" class="input-zone-input" type="text" /> <button id="btnSend">Send</button> </div> </body> </html>
css/main.css
, which provides CSS styles for the homepage:*, *::before, *::after { box-sizing: border-box; } html, body { margin: 0; padding: 0; } .input-zone { align-items: center; display: flex; flex-direction: row; margin: 10px; } .input-zone-input { flex: 1; margin-right: 10px; } .message-author { font-weight: bold; } .messages { border: 1px solid #000; margin: 10px; max-height: 300px; min-height: 300px; overflow-y: auto; padding: 5px; }
tsconfig.json
, which configures the TypeScript compiler to produce ECMAScript 5-compatible JavaScript:{ "compilerOptions": { "target": "es5" } }
index.ts
:import * as signalR from "@microsoft/signalr"; import "./css/main.css"; const divMessages: HTMLDivElement = document.querySelector("#divMessages"); const tbMessage: HTMLInputElement = document.querySelector("#tbMessage"); const btnSend: HTMLButtonElement = document.querySelector("#btnSend"); const username = new Date().getTime(); const connection = new signalR.HubConnectionBuilder() .withUrl("/hub") .build(); connection.on("messageReceived", (username: string, message: string) => { const m = document.createElement("div"); m.innerHTML = `<div class="message-author">${username}</div><div>${message}</div>`; divMessages.appendChild(m); divMessages.scrollTop = divMessages.scrollHeight; }); connection.start().catch((err) => document.write(err)); tbMessage.addEventListener("keyup", (e: KeyboardEvent) => { if (e.key === "Enter") { send(); } }); btnSend.addEventListener("click", send); function send() { connection.send("newMessage", username, tbMessage.value) .then(() => (tbMessage.value = "")); }
The preceding code retrieves references to DOM elements and attaches two event handlers:
keyup
: Fires when the user types in thetbMessage
textbox and calls thesend
function when the user presses the Enter key.click
: Fires when the user selects the Send button and callssend
function is called.
The
HubConnectionBuilder
class creates a new builder for configuring the server connection. ThewithUrl
function configures the hub URL.SignalR enables the exchange of messages between a client and a server. Each message has a specific name. For example, messages with the name
messageReceived
can run the logic responsible for displaying the new message in the messages zone. Listening to a specific message can be done via theon
function. Any number of message names can be listened to. It's also possible to pass parameters to the message, such as the author's name and the content of the message received. Once the client receives a message, a newdiv
element is created with the author's name and the message content in itsinnerHTML
attribute. It's added to the maindiv
element displaying the messages.Sending a message through the WebSockets connection requires calling the
send
method. The method's first parameter is the message name. The message data inhabits the other parameters. In this example, a message identified asnewMessage
is sent to the server. The message consists of the username and the user input from a text box. If the send works, the text box value is cleared.Run the following command at the project root:
npm i @microsoft/signalr @types/node
The preceding command installs:
- The SignalR TypeScript client, which allows the client to send messages to the server.
- The TypeScript type definitions for Node.js, which enables compile-time checking of Node.js types.
Test the app
Confirm that the app works with the following steps:
Run Webpack in
release
mode. Using the Package Manager Console window, run the following command in the project root. If you aren't in the project root, entercd SignalRWebpack
before entering the command.npm run release
This command generates the client-side assets to be served when running the app. The assets are placed in the
wwwroot
folder.Webpack completed the following tasks:
- Purged the contents of the
wwwroot
directory. - Converted the TypeScript to JavaScript in a process known as transpilation.
- Mangled the generated JavaScript to reduce file size in a process known as minification.
- Copied the processed JavaScript, CSS, and HTML files from
src
to thewwwroot
directory. - Injected the following elements into the
wwwroot/index.html
file:- A
<link>
tag, referencing thewwwroot/main.<hash>.css
file. This tag is placed immediately before the closing</head>
tag. - A
<script>
tag, referencing the minifiedwwwroot/main.<hash>.js
file. This tag is placed immediately after the closing</title>
tag.
- A
- Purged the contents of the
Select Debug > Start without debugging to launch the app in a browser without attaching the debugger. The
wwwroot/index.html
file is served athttps://localhost:<port>
.If you get compile errors, try closing and reopening the solution.
Open another browser instance (any browser) and paste the URL in the address bar.
Choose either browser, type something in the Message text box, and select the Send button. The unique user name and message are displayed on both pages instantly.
Next steps
- Strongly typed hubs
- Authentication and authorization in ASP.NET Core SignalR
- MessagePack Hub Protocol in SignalR for ASP.NET Core
Additional resources
This tutorial demonstrates using Webpack in an ASP.NET Core SignalR web app to bundle and build a client written in TypeScript. Webpack enables developers to bundle and build the client-side resources of a web app.
In this tutorial, you learn how to:
- Scaffold a starter ASP.NET Core SignalR app
- Configure the SignalR TypeScript client
- Configure a build pipeline using Webpack
- Configure the SignalR server
- Enable communication between client and server
View or download sample code (how to download)
Prerequisites
- Visual Studio 2019 with the ASP.NET and web development workload
- .NET Core SDK 3.0 or later
- Node.js with npm
Create the ASP.NET Core web app
Configure Visual Studio to look for npm in the PATH environment variable. By default, Visual Studio uses the version of npm found in its installation directory. Follow these instructions in Visual Studio:
Launch Visual Studio. At the start window, select Continue without code.
Navigate to Tools > Options > Projects and Solutions > Web Package Management > External Web Tools.
Select the $(PATH) entry from the list. Select the up arrow to move the entry to the second position in the list, and select OK.
.
Visual Studio configuration is complete.
- Use the File > New > Project menu option and choose the ASP.NET Core Web Application template. Select Next.
- Name the project *SignalRWebPac``, and select Create.
- Select .NET Core from the target framework drop-down, and select ASP.NET Core 3.1 from the framework selector drop-down. Select the Empty template, and select Create.
Add the Microsoft.TypeScript.MSBuild
package to the project:
- In Solution Explorer (right pane), right-click the project node and select Manage NuGet Packages. In the Browse tab, search for
Microsoft.TypeScript.MSBuild
, and then click Install on the right to install the package.
Visual Studio adds the NuGet package under the Dependencies node in Solution Explorer, enabling TypeScript compilation in the project.
Configure Webpack and TypeScript
The following steps configure the conversion of TypeScript to JavaScript and the bundling of client-side resources.
Run the following command in the project root to create a
package.json
file:npm init -y
Add the highlighted property to the
package.json
file and save the file changes:{ "name": "SignalRWebPack", "version": "1.0.0", "private": true, "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
Setting the
private
property totrue
prevents package installation warnings in the next step.Install the required npm packages. Run the following command from the project root:
npm i -D -E clean-webpack-plugin@3.0.0 css-loader@3.4.2 html-webpack-plugin@3.2.0 mini-css-extract-plugin@0.9.0 ts-loader@6.2.1 typescript@3.7.5 webpack@4.41.5 webpack-cli@3.3.10
Some command details to note:
- A version number follows the
@
sign for each package name. npm installs those specific package versions. - The
-E
option disables npm's default behavior of writing semantic versioning range operators to *packagejson
. For example,"webpack": "4.41.5"
is used instead of"webpack": "^4.41.5"
. This option prevents unintended upgrades to newer package versions.
See the npm-install docs for more detail.
- A version number follows the
Replace the
scripts
property of thepackage.json
file with the following code:"scripts": { "build": "webpack --mode=development --watch", "release": "webpack --mode=production", "publish": "npm run release && dotnet publish -c Release" },
Some explanation of the scripts:
build
: Bundles the client-side resources in development mode and watches for file changes. The file watcher causes the bundle to regenerate each time a project file changes. Themode
option disables production optimizations, such as tree shaking and minification. Only usebuild
in development.release
: Bundles the client-side resources in production mode.publish
: Runs therelease
script to bundle the client-side resources in production mode. It calls the .NET CLI's publish command to publish the app.
Create a file named
webpack.config.js
, in the project root, with the following code:const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const { CleanWebpackPlugin } = require("clean-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { entry: "./src/index.ts", output: { path: path.resolve(__dirname, "wwwroot"), filename: "[name].[chunkhash].js", publicPath: "/" }, resolve: { extensions: [".js", ".ts"] }, module: { rules: [ { test: /\.ts$/, use: "ts-loader" }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] } ] }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ template: "./src/index.html" }), new MiniCssExtractPlugin({ filename: "css/[name].[chunkhash].css" }) ] };
The preceding file configures the Webpack compilation. Some configuration details to note:
- The
output
property overrides the default value ofdist
. The bundle is instead emitted in thewwwroot
directory. - The
resolve.extensions
array includes.js
to import the SignalR client JavaScript.
- The
Create a new src directory in the project root to store the project's client-side assets.
Create
src/index.html
with the following markup.<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>ASP.NET Core SignalR</title> </head> <body> <div id="divMessages" class="messages"> </div> <div class="input-zone"> <label id="lblMessage" for="tbMessage">Message:</label> <input id="tbMessage" class="input-zone-input" type="text" /> <button id="btnSend">Send</button> </div> </body> </html>
The preceding HTML defines the homepage's boilerplate markup.
Create a new src/css directory. Its purpose is to store the project's
.css
files.Create
src/css/main.css
with the following CSS:*, *::before, *::after { box-sizing: border-box; } html, body { margin: 0; padding: 0; } .input-zone { align-items: center; display: flex; flex-direction: row; margin: 10px; } .input-zone-input { flex: 1; margin-right: 10px; } .message-author { font-weight: bold; } .messages { border: 1px solid #000; margin: 10px; max-height: 300px; min-height: 300px; overflow-y: auto; padding: 5px; }
The preceding
main.css
file styles the app.Create
src/tsconfig.json
with the following JSON:{ "compilerOptions": { "target": "es5" } }
The preceding code configures the TypeScript compiler to produce ECMAScript 5-compatible JavaScript.
Create
src/index.ts
with the following code:import "./css/main.css"; const divMessages: HTMLDivElement = document.querySelector("#divMessages"); const tbMessage: HTMLInputElement = document.querySelector("#tbMessage"); const btnSend: HTMLButtonElement = document.querySelector("#btnSend"); const username = new Date().getTime(); tbMessage.addEventListener("keyup", (e: KeyboardEvent) => { if (e.key === "Enter") { send(); } }); btnSend.addEventListener("click", send); function send() { }
The preceding TypeScript retrieves references to DOM elements and attaches two event handlers:
keyup
: This event fires when the user types in thetbMessage
textbox. Thesend
function is called when the user presses the Enter key.click
: This event fires when the user selects the Send button. Thesend
function is called.
Configure the app
In
Startup.Configure
, add calls to UseDefaultFiles(IApplicationBuilder) and UseStaticFiles(IApplicationBuilder).public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseDefaultFiles(); app.UseStaticFiles(); app.UseEndpoints(endpoints => { endpoints.MapHub<ChatHub>("/hub"); }); }
The preceding code allows the server to locate and serve the
index.html
file. The file is served whether the user enters its full URL or the root URL of the web app.At the end of
Startup.Configure
, map a /hub route to theChatHub
hub. Replace the code that displays Hello World! with the following line:app.UseEndpoints(endpoints => { endpoints.MapHub<ChatHub>("/hub"); });
In
Startup.ConfigureServices
, call AddSignalR.services.AddSignalR();
Create a new directory named Hubs in the project root SignalRWebPack/ to store the SignalR hub.
Create hub
Hubs/ChatHub.cs
with the following code:using Microsoft.AspNetCore.SignalR; using System.Threading.Tasks; namespace SignalRWebPack.Hubs { public class ChatHub : Hub { } }
Add the following
using
statement at the top of theStartup.cs
file to resolve theChatHub
reference:using SignalRWebPack.Hubs;
Enable client and server communication
The app currently displays a basic form to send messages, but isn't yet functional. The server is listening to a specific route but does nothing with sent messages.
Run the following command at the project root:
npm i @microsoft/signalr @types/node
The preceding command installs:
- The SignalR TypeScript client, which allows the client to send messages to the server.
- The TypeScript type definitions for Node.js, which enables compile-time checking of Node.js types.
Add the highlighted code to the
src/index.ts
file:import "./css/main.css"; import * as signalR from "@microsoft/signalr"; const divMessages: HTMLDivElement = document.querySelector("#divMessages"); const tbMessage: HTMLInputElement = document.querySelector("#tbMessage"); const btnSend: HTMLButtonElement = document.querySelector("#btnSend"); const username = new Date().getTime(); const connection = new signalR.HubConnectionBuilder() .withUrl("/hub") .build(); connection.on("messageReceived", (username: string, message: string) => { let m = document.createElement("div"); m.innerHTML = `<div class="message-author">${username}</div><div>${message}</div>`; divMessages.appendChild(m); divMessages.scrollTop = divMessages.scrollHeight; }); connection.start().catch(err => document.write(err)); tbMessage.addEventListener("keyup", (e: KeyboardEvent) => { if (e.key === "Enter") { send(); } }); btnSend.addEventListener("click", send); function send() { }
The preceding code supports receiving messages from the server. The
HubConnectionBuilder
class creates a new builder for configuring the server connection. ThewithUrl
function configures the hub URL.SignalR enables the exchange of messages between a client and a server. Each message has a specific name. For example, messages with the name
messageReceived
can run the logic responsible for displaying the new message in the messages zone. Listening to a specific message can be done via theon
function. Any number of message names can be listened to. It's also possible to pass parameters to the message, such as the author's name and the content of the message received. Once the client receives a message, a newdiv
element is created with the author's name and the message content in itsinnerHTML
attribute. It's added to the maindiv
element displaying the messages.Now that the client can receive a message, configure it to send messages. Add the highlighted code to the
src/index.ts
file:import "./css/main.css"; import * as signalR from "@microsoft/signalr"; const divMessages: HTMLDivElement = document.querySelector("#divMessages"); const tbMessage: HTMLInputElement = document.querySelector("#tbMessage"); const btnSend: HTMLButtonElement = document.querySelector("#btnSend"); const username = new Date().getTime(); const connection = new signalR.HubConnectionBuilder() .withUrl("/hub") .build(); connection.on("messageReceived", (username: string, message: string) => { let messages = document.createElement("div"); messages.innerHTML = `<div class="message-author">${username}</div><div>${message}</div>`; divMessages.appendChild(messages); divMessages.scrollTop = divMessages.scrollHeight; }); connection.start().catch(err => document.write(err)); tbMessage.addEventListener("keyup", (e: KeyboardEvent) => { if (e.key === "Enter") { send(); } }); btnSend.addEventListener("click", send); function send() { connection.send("newMessage", username, tbMessage.value) .then(() => tbMessage.value = ""); }
Sending a message through the WebSockets connection requires calling the
send
method. The method's first parameter is the message name. The message data inhabits the other parameters. In this example, a message identified asnewMessage
is sent to the server. The message consists of the username and the user input from a text box. If the send works, the text box value is cleared.Add the
NewMessage
method to theChatHub
class:using Microsoft.AspNetCore.SignalR; using System.Threading.Tasks; namespace SignalRWebPack.Hubs { public class ChatHub : Hub { public async Task NewMessage(long username, string message) { await Clients.All.SendAsync("messageReceived", username, message); } } }
The preceding code broadcasts received messages to all connected users once the server receives them. It's unnecessary to have a generic
on
method to receive all the messages. A method named after the message name suffices.In this example, the TypeScript client sends a message identified as
newMessage
. The C#NewMessage
method expects the data sent by the client. A call is made to SendAsync on Clients.All. The received messages are sent to all clients connected to the hub.
Test the app
Confirm that the app works with the following steps.
Run Webpack in release mode. Using the Package Manager Console window, run the following command in the project root. If you aren't in the project root, enter
cd SignalRWebPack
before entering the command.npm run release
This command generates the client-side assets to be served when running the app. The assets are placed in the
wwwroot
folder.Webpack completed the following tasks:
- Purged the contents of the
wwwroot
directory. - Converted the TypeScript to JavaScript in a process known as transpilation.
- Mangled the generated JavaScript to reduce file size in a process known as minification.
- Copied the processed JavaScript, CSS, and HTML files from
src
to thewwwroot
directory. - Injected the following elements into the
wwwroot/index.html
file:- A
<link>
tag, referencing thewwwroot/main.<hash>.css
file. This tag is placed immediately before the closing</head>
tag. - A
<script>
tag, referencing the minifiedwwwroot/main.<hash>.js
file. This tag is placed immediately after the closing</title>
tag.
- A
- Purged the contents of the
Select Debug > Start without debugging to launch the app in a browser without attaching the debugger. The
wwwroot/index.html
file is served athttp://localhost:<port_number>
.If you get compile errors, try closing and reopening the solution.
Open another browser instance (any browser). Paste the URL in the address bar.
Choose either browser, type something in the Message text box, and select the Send button. The unique user name and message are displayed on both pages instantly.
Additional resources
This tutorial demonstrates using Webpack in an ASP.NET Core SignalR web app to bundle and build a client written in TypeScript. Webpack enables developers to bundle and build the client-side resources of a web app.
In this tutorial, you learn how to:
- Scaffold a starter ASP.NET Core SignalR app
- Configure the SignalR TypeScript client
- Configure a build pipeline using Webpack
- Configure the SignalR server
- Enable communication between client and server
View or download sample code (how to download)
Prerequisites
- Visual Studio 2019 with the ASP.NET and web development workload
- .NET Core SDK 2.2 or later
- Node.js with npm
Create the ASP.NET Core web app
Configure Visual Studio to look for npm in the PATH environment variable. By default, Visual Studio uses the version of npm found in its installation directory. Follow these instructions in Visual Studio:
Navigate to Tools > Options > Projects and Solutions > Web Package Management > External Web Tools.
Select the $(PATH) entry from the list. Select the up arrow to move the entry to the second position in the list.
Visual Studio configuration is completed. It's time to create the project.
- Use the File > New > Project menu option and choose the ASP.NET Core Web Application template.
- Name the project *SignalRWebPack`, and select Create.
- Select .NET Core from the target framework drop-down, and select ASP.NET Core 2.2 from the framework selector drop-down. Select the Empty template, and select Create.
Configure Webpack and TypeScript
The following steps configure the conversion of TypeScript to JavaScript and the bundling of client-side resources.
Run the following command in the project root to create a
package.json
file:npm init -y
Add the highlighted property to the
package.json
file:{ "name": "SignalRWebPack", "version": "1.0.0", "private": true, "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
Setting the
private
property totrue
prevents package installation warnings in the next step.Install the required npm packages. Run the following command from the project root:
npm install -D -E clean-webpack-plugin@1.0.1 css-loader@2.1.0 html-webpack-plugin@4.0.0-beta.5 mini-css-extract-plugin@0.5.0 ts-loader@5.3.3 typescript@3.3.3 webpack@4.29.3 webpack-cli@3.2.3
Some command details to note:
- A version number follows the
@
sign for each package name. npm installs those specific package versions. - The
-E
option disables npm's default behavior of writing semantic versioning range operators to *packagejson
. For example,"webpack": "4.29.3"
is used instead of"webpack": "^4.29.3"
. This option prevents unintended upgrades to newer package versions.
See the npm-install docs for more detail.
- A version number follows the
Replace the
scripts
property of thepackage.json
file with the following code:"scripts": { "build": "webpack --mode=development --watch", "release": "webpack --mode=production", "publish": "npm run release && dotnet publish -c Release" },
Some explanation of the scripts:
build
: Bundles the client-side resources in development mode and watches for file changes. The file watcher causes the bundle to regenerate each time a project file changes. Themode
option disables production optimizations, such as tree shaking and minification. Only usebuild
in development.release
: Bundles the client-side resources in production mode.publish
: Runs therelease
script to bundle the client-side resources in production mode. It calls the .NET CLI's publish command to publish the app.
Create a file named
*webpack.config.js
in the project root, with the following code:const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const CleanWebpackPlugin = require("clean-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { entry: "./src/index.ts", output: { path: path.resolve(__dirname, "wwwroot"), filename: "[name].[chunkhash].js", publicPath: "/" }, resolve: { extensions: [".js", ".ts"] }, module: { rules: [ { test: /\.ts$/, use: "ts-loader" }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] } ] }, plugins: [ new CleanWebpackPlugin(["wwwroot/*"]), new HtmlWebpackPlugin({ template: "./src/index.html" }), new MiniCssExtractPlugin({ filename: "css/[name].[chunkhash].css" }) ] };
The preceding file configures the Webpack compilation. Some configuration details to note:
- The
output
property overrides the default value ofdist
. The bundle is instead emitted in thewwwroot
directory. - The
resolve.extensions
array includes.js
to import the SignalR client JavaScript.
- The
Create a new src directory in the project root to store the project's client-side assets.
Create
src/index.html
with the following markup.<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>ASP.NET Core SignalR</title> </head> <body> <div id="divMessages" class="messages"> </div> <div class="input-zone"> <label id="lblMessage" for="tbMessage">Message:</label> <input id="tbMessage" class="input-zone-input" type="text" /> <button id="btnSend">Send</button> </div> </body> </html>
The preceding HTML defines the homepage's boilerplate markup.
Create a new src/css directory. Its purpose is to store the project's
.css
files.Create
src/css/main.css
with the following markup:*, *::before, *::after { box-sizing: border-box; } html, body { margin: 0; padding: 0; } .input-zone { align-items: center; display: flex; flex-direction: row; margin: 10px; } .input-zone-input { flex: 1; margin-right: 10px; } .message-author { font-weight: bold; } .messages { border: 1px solid #000; margin: 10px; max-height: 300px; min-height: 300px; overflow-y: auto; padding: 5px; }
The preceding
main.css
file styles the app.Create
src/tsconfig.json
with the following JSON:{ "compilerOptions": { "target": "es5" } }
The preceding code configures the TypeScript compiler to produce ECMAScript 5-compatible JavaScript.
Create
src/index.ts
with the following code:import "./css/main.css"; const divMessages: HTMLDivElement = document.querySelector("#divMessages"); const tbMessage: HTMLInputElement = document.querySelector("#tbMessage"); const btnSend: HTMLButtonElement = document.querySelector("#btnSend"); const username = new Date().getTime(); tbMessage.addEventListener("keyup", (e: KeyboardEvent) => { if (e.keyCode === 13) { send(); } }); btnSend.addEventListener("click", send); function send() { }
The preceding TypeScript retrieves references to DOM elements and attaches two event handlers:
keyup
: This event fires when the user types in thetbMessage
textbox. Thesend
function is called when the user presses the Enter key.click
: This event fires when the user selects the Send button. Thesend
function is called.
Configure the ASP.NET Core app
The code provided in the
Startup.Configure
method displays Hello World!. Replace theapp.Run
method call with calls to UseDefaultFiles(IApplicationBuilder) and UseStaticFiles(IApplicationBuilder).app.UseDefaultFiles(); app.UseStaticFiles();
The preceding code allows the server to locate and serve the
index.html
file, whether the user enters its full URL or the root URL of the web app.Call AddSignalR in
Startup.ConfigureServices
. It adds the SignalR services to the project.services.AddSignalR();
Map a /hub route to the
ChatHub
hub. Add the following lines at the end ofStartup.Configure
:app.UseSignalR(options => { options.MapHub<ChatHub>("/hub"); });
Create a new directory, called Hubs, in the project root. Its purpose is to store the SignalR hub, which is created in the next step.
Create hub
Hubs/ChatHub.cs
with the following code:using Microsoft.AspNetCore.SignalR; using System.Threading.Tasks; namespace SignalRWebPack.Hubs { public class ChatHub : Hub { } }
Add the following code at the top of the
Startup.cs
file to resolve theChatHub
reference:using SignalRWebPack.Hubs;
Enable client and server communication
The app currently displays a simple form to send messages. Nothing happens when you try to do so. The server is listening to a specific route but does nothing with sent messages.
Run the following command at the project root:
npm install @aspnet/signalr
The preceding command installs the SignalR TypeScript client, which allows the client to send messages to the server.
Add the highlighted code to the
src/index.ts
file:import "./css/main.css"; import * as signalR from "@aspnet/signalr"; const divMessages: HTMLDivElement = document.querySelector("#divMessages"); const tbMessage: HTMLInputElement = document.querySelector("#tbMessage"); const btnSend: HTMLButtonElement = document.querySelector("#btnSend"); const username = new Date().getTime(); const connection = new signalR.HubConnectionBuilder() .withUrl("/hub") .build(); connection.on("messageReceived", (username: string, message: string) => { let m = document.createElement("div"); m.innerHTML = `<div class="message-author">${username}</div><div>${message}</div>`; divMessages.appendChild(m); divMessages.scrollTop = divMessages.scrollHeight; }); connection.start().catch(err => document.write(err)); tbMessage.addEventListener("keyup", (e: KeyboardEvent) => { if (e.keyCode === 13) { send(); } }); btnSend.addEventListener("click", send); function send() { }
The preceding code supports receiving messages from the server. The
HubConnectionBuilder
class creates a new builder for configuring the server connection. ThewithUrl
function configures the hub URL.SignalR enables the exchange of messages between a client and a server. Each message has a specific name. For example, messages with the name
messageReceived
can run the logic responsible for displaying the new message in the messages zone. Listening to a specific message can be done via theon
function. You can listen to any number of message names. It's also possible to pass parameters to the message, such as the author's name and the content of the message received. Once the client receives a message, a newdiv
element is created with the author's name and the message content in itsinnerHTML
attribute. The new message is added to the maindiv
element displaying the messages.Now that the client can receive a message, configure it to send messages. Add the highlighted code to the
src/index.ts
file:import "./css/main.css"; import * as signalR from "@aspnet/signalr"; const divMessages: HTMLDivElement = document.querySelector("#divMessages"); const tbMessage: HTMLInputElement = document.querySelector("#tbMessage"); const btnSend: HTMLButtonElement = document.querySelector("#btnSend"); const username = new Date().getTime(); const connection = new signalR.HubConnectionBuilder() .withUrl("/hub") .build(); connection.on("messageReceived", (username: string, message: string) => { let messageContainer = document.createElement("div"); messageContainer.innerHTML = `<div class="message-author">${username}</div><div>${message}</div>`; divMessages.appendChild(messageContainer); divMessages.scrollTop = divMessages.scrollHeight; }); connection.start().catch(err => document.write(err)); tbMessage.addEventListener("keyup", (e: KeyboardEvent) => { if (e.keyCode === 13) { send(); } }); btnSend.addEventListener("click", send); function send() { connection.send("newMessage", username, tbMessage.value) .then(() => tbMessage.value = ""); }
Sending a message through the WebSockets connection requires calling the
send
method. The method's first parameter is the message name. The message data inhabits the other parameters. In this example, a message identified asnewMessage
is sent to the server. The message consists of the username and the user input from a text box. If the send works, the text box value is cleared.Add the
NewMessage
method to theChatHub
class:using Microsoft.AspNetCore.SignalR; using System.Threading.Tasks; namespace SignalRWebPack.Hubs { public class ChatHub : Hub { public async Task NewMessage(long username, string message) { await Clients.All.SendAsync("messageReceived", username, message); } } }
The preceding code broadcasts received messages to all connected users once the server receives them. It's unnecessary to have a generic
on
method to receive all the messages. A method named after the message name suffices.In this example, the TypeScript client sends a message identified as
newMessage
. The C#NewMessage
method expects the data sent by the client. A call is made to SendAsync on Clients.All. The received messages are sent to all clients connected to the hub.
Test the app
Confirm that the app works with the following steps.
Run Webpack in release mode. Using the Package Manager Console window, run the following command in the project root. If you aren't in the project root, enter
cd SignalRWebPack
before entering the command.npm run release
This command generates the client-side assets to be served when running the app. The assets are placed in the
wwwroot
folder.Webpack completed the following tasks:
- Purged the contents of the
wwwroot
directory. - Converted the TypeScript to JavaScript in a process known as transpilation.
- Mangled the generated JavaScript to reduce file size in a process known as minification.
- Copied the processed JavaScript, CSS, and HTML files from
src
to thewwwroot
directory. - Injected the following elements into the
wwwroot/index.html
file:- A
<link>
tag, referencing thewwwroot/main.<hash>.css
file. This tag is placed immediately before the closing</head>
tag. - A
<script>
tag, referencing the minifiedwwwroot/main.<hash>.js
file. This tag is placed immediately after the closing</title>
tag.
- A
- Purged the contents of the
Select Debug > Start without debugging to launch the app in a browser without attaching the debugger. The
wwwroot/index.html
file is served athttp://localhost:<port_number>
.Open another browser instance (any browser). Paste the URL in the address bar.
Choose either browser, type something in the Message text box, and select the Send button. The unique user name and message are displayed on both pages instantly.
Additional resources
ASP.NET Core