How YOU can learn to build real-time Web Apps that scales, using .NET Core, C#, Azure SignalR Service and JavaScript
Tags: dotnet, tutorial, javascript, serverless
Published: 2019-11-01 16:10:01

---
title: How YOU can learn to build real-time Web Apps that scales, using .NET Core, C#, Azure SignalR Service and JavaScript
published: true
description: Building realtime apps using SignalR, Serverless .NET Core and JavaScript
tags: dotnet, tutorial, javascript, serverless
cover_image: https://thepracticaldev.s3.amazonaws.com/i/praxk0bmzsbnls7uw9mi.jpg
canonical_url: https://softchris.github.io/pages/dotnet-signalr.html
---

Follow me on [Twitter](https://twitter.com/chris_noring), happy to take your suggestions on topics or improvements /Chris

> Ok, so you want to build real-time applications? There are some things to consider like what do I do if the browser doesn't support Web Sockets, what's my fallback technology? Also, how do I scale? What about CORS? As you can see there's more to it than just creating a Web Socket. That's why SignalR exists to tackle the above scenarios.

TLDR; There are two things this article will tackle, one is SignalR itself, what it is and why use it. We will also go into Azure SignalR service and talk about the difference. Lastly, we will show a demo using SignalR service and Serverless.

This article is part of [#25DaysOfServerless](https://25daysofserverless.com). New challenges will be published every day from Microsoft Cloud Advocates throughout the month of December. Find out more about how Microsoft Azure enables your [Serverless functions](https://docs.microsoft.com/azure/azure-functions/?WT.mc_id=25days_devto-blog-cxa).

## References

- [Sign up for a free Azure account](https://azure.microsoft.com/free/?wt.mc_id=devto-blog-chnoring)
To be able to use the Azure SignalR Service part you will need a free Azure account

- [SignalR overview](https://docs.microsoft.com/en-us/aspnet/signalr/overview/getting-started/introduction-to-signalr?wt.mc_id=devto-blog-chnoring)
A great page that explains what SignalR is, how it works etc.
- [ASP.NET Core SignalR](https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction?view=aspnetcore-3.0&wt.mc_id=devto-blog-chnoring)
Great overview. Not as detail-heavy as the first page but still covers the basic concepts well a TLDR; version if you will.

- [SignalR GitHub repo](https://github.com/signalr?wt.mc_id=devto-blog-chnoring)
It's open-source and contains examples using different languages for the Serverless part and also clients with and without Auth.

- [SginalR + .NET Core Tutorial](https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr?view=aspnetcore-3.0&tabs=visual-studio-code&wt.mc_id=devto-blog-chnoring)
This tutorial covers how to build a SignalR backend in .NET Core Web project and how we call it from a Client.

## SignalR 

ASP.NET SignalR is a library for ASP.NET developers that simplifies the process of adding real-time web functionality to applications. Real-time web functionality is the ability to have server code *push* content to connected clients instantly as it becomes available, rather than having the server wait for a client to request new data.

> What can I use it for?

 While chat is often used as an example, you can do a whole lot more like Dashboards and monitoring applications, collaborative applications (such as simultaneous editing of documents), job progress updates, and real-time forms.

 > How do I recognize when I should be using SignalR?

 Any time a user refreshes a web page to see new data, or the page implements long polling to retrieve new data, it is a candidate for using SignalR.

> How does it work?

SignalR provides a simple API for creating server-to-client remote procedure calls (RPC) that call JavaScript functions in client browsers (and other client platforms) from server-side .NET code.

![](https://docs.microsoft.com/en-us/aspnet/signalr/overview/getting-started/introduction-to-signalr/_static/image1.png)

> Ok, so SignalR calls my JavaScript code when there's new data?

Correct.

SignalR handles connection management automatically and lets you broadcast messages to all connected clients simultaneously, like a chat room. You can also send messages to specific clients. 

> *Broadcast* and I can also target specific clients, got it.

SignalR uses the new WebSocket transport where available and falls back to older transports where necessary. While you could certainly write your app using WebSocket directly, using SignalR means that a lot of the extra functionality you would need to implement is already done for you.

> Oh, so I could be using WebSockets instead of SignalR but if that doesn't work I would need to code a fallback myself. But if I use SignalR, I don't need to care? I get WebSocket primarily but a fallback behavior?

Correct.

### Hosting

There are two ways to host SignalR:

- **Self-hosted**, we host SignalR ourselves as part of a Web App
- **Azure SignalR Service**, this is SignalR living in the Cloud as a service, it comes with a lot of benefits

Here's an overview:

![](https://docs.microsoft.com/en-us/azure/azure-signalr/media/signalr-overview/managed-signalr-service.png)

### Azure SignalR Service

> Why should I go with the Service over self-hosted?

Switching to SignalR Service will remove the need to manage *backplanes* that handle the scales and client connections. 

> Ok so you handle client connections for me and scaling. Nice, I like the sound of that.

The fully managed service also simplifies web applications and saves hosting costs. 

> Simplifications and saves me money. No objections from me :)

SignalR Service offers global reach and world-class data center and network, scales to millions of connections, guarantees SLA, while providing all the compliance and security at Azure standard.

> Millions of connections! That's a large chat room ;) SLA compliance, that will make my CEO and legal department happy :) Good security is a must of course.

## HOW

I know you want to learn to use this so shall we? We will:

- **Provision** an Azure SignalR service
- **Create an Azure Function app**, that will allow us to connect to the Azure SignalR Service. We will learn how to manage connections and also how to receive and send messages.
- **Create a UI** that is able to connect to our Azure Function App and send/receive messages.

## Provision an Azure SignalR Service

1. Go to `portal.azure.com`

2. Click `+ Create a resource`

3. Enter `SignalR Service` in the search field

![](https://thepracticaldev.s3.amazonaws.com/i/1prqkfx1eey55a6adn60.png)

4. Press `Review + Create` and then `Create` on the next screen.

NOTE, One last step. We need to set up our Azure SignalR service so that it can communicate with Serverless apps, otherwise the handshake, when connecting, will fail. I learned that *the hard way* :)

![](https://thepracticaldev.s3.amazonaws.com/i/b336gn981pg0gcjr86di.png)


## Create Azure Function App

This involves us creating an Azure Function app. It will have two different functions in it:

- **negotiate**, this will talk to our Azure SignalR service and give back an API key that we can use when we want to do things like sending messages
- **messages**, this endpoint will be used to send messages

### Pre requisites

First off, as with any Azure Function we need to ensure we have installed the prerequisites which look different on different OSs:

For Mac:

```
brew tap azure/functions
brew install azure-functions-core-tools
```

For Windows:

```
npm install -g azure-functions-core-tools
```

Read more here if you have Linux as OS:

> https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local

One more thing, to make authoring a Serverless Function I recommend installing the Azure Function extension. This will enable you to scaffold functions as well as debugging and deploying them. Go to your extension tab in VS Code and install the below:

![](https://docs.microsoft.com/en-us/azure/includes/media/functions-install-vs-code-extension/vscode-install-extension.png)

If you are on Visual Studio, have a look here:

> https://docs.microsoft.com/en-us/azure/azure-functions/functions-develop-vs

### Create our Serverless functions

Ok then, for the sake of this article we will be using VS Code as our IDE of choice. We will do the following:

- **Create an Azure Function App**, an Azure Function needs to belong to an app
- **Scaffold two Azure Functions**, `negotiate` and `messages`
- **Configure** our two functions to work with our Azure SignalR service

Bring up the command palette `View/Command Palette`, or `CMD+SHIFT+P` on a Mac. 

![](https://thepracticaldev.s3.amazonaws.com/i/a97mmz4yb21q8iqm69wq.png)

Next, select a directory for your app (I usually pick the one I'm standing in)

![](https://thepracticaldev.s3.amazonaws.com/i/17va0s9nvxjqo49rreu3.png)

After that, we are asked to select a language. As you can see below we have quite a few options. Let's go with `C#` for this one.

![](https://thepracticaldev.s3.amazonaws.com/i/j4rut669vt2r0c5hnjbr.png)

The next step is to select a `Trigger` for your first function (first time when you create a Serverless project it will create project + one function). A `Trigger` determines how our function will be started. In this case, we want it to be started/triggered by an HTTP call so we select `HttpTrigger` below:

![](https://thepracticaldev.s3.amazonaws.com/i/gl99cdac8z2ie074ral6.png)

We have two more steps here, those are: 
- **Name** of our function, let's call it `negotiate` 
- **Namespace**, call it `Company`
- **Authorization** let's go with `Anonymous`

Ok, so now we have gotten a Serverless .NET Core project. Let's bring up the command palette once more `View/Command Palette` and enter `Azure Functions: Create Function` like the below. 

![](https://thepracticaldev.s3.amazonaws.com/i/ico320t1ihuu3p7evizg.png)

Select:

- **Trigger** select `HttpTrigger`
- **Function name**, call it `messages`
- **Namespace** call it `Company`
- **Authorization level**, let's select `anonymous`

Ok, then, we should at this point have a create a Function app/Function Project with two functions in it. It should look like this, after you renamed `negotiate.cs` to `Negotiate.cs` and `messages.cs` have been renamed to `Messages.cs`:

![](https://thepracticaldev.s3.amazonaws.com/i/xsjm3poe4dmv0vr5nc2e.png)

### Configure SignalR

At this point we need to do two things:

- **Add SignalR decorators in code**, this ensures we are connecting to the correct Azure SignalR instance in the Cloud
- **Add Connection String information**, we need to add this information to our config file so it knows what SignalR instance to talk to 

**Add SignalR decorators**

Let's open up `Negotiate.cs` and give it the following code:

```csharp
// Negotiate.cs
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Extensions.SignalRService;

namespace Company
{
  public static class Negotiate
  {
      [FunctionName("negotiate")]
      public static SignalRConnectionInfo GetSignalRInfo(
          [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequest req,
          [SignalRConnectionInfo(HubName = "chat")] SignalRConnectionInfo connectionInfo)
      {
          return connectionInfo;
      }
  }
}
```

From the above code, we can see that we have the decorator `SignalRConnectionInfo` and we point out a so-called hub called `chat`. Additionally, we see that the function ends up returning a `connectionInfo` object. What goes on here is that when this endpoint is being hit by an HTTP request we handshake with our Azure SignalR Service in the Cloud and it ends up giving us the needed connection info back so we can keep talking it when doing things like sending messages.

Now let's open `Messages.cs` and give it the following code:

```csharp
// Messages.cs
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Extensions.SignalRService;

namespace Company
{
  public static class Messages
  {

    [FunctionName("messages")]
    public static Task SendMessage(
          [HttpTrigger(AuthorizationLevel.Anonymous, "post")] object message,
          [SignalR(HubName = "chat")] IAsyncCollector<SignalRMessage> signalRMessages)
    {
      return signalRMessages.AddAsync(
          new SignalRMessage
          {
            Target = "newMessage",
            Arguments = new[] { message }
          });
    }
  }
}
```

This time around we also use a decorator, but it's called `SignalR` but we still give it the Hub value `chat`. Our SignalR decorator decorates a list of messages that has the parameter name `signalRMessages`. 

Let's have a look at the function body next. We see that we call `signalRMessages.AddAsync()`. What does that do? Well, it passes in `SignalRMessage` that consists of two things:

- **Target**, this is the name of an event, in this case, `newMessage`. A client can listen to this event and render its payload for example
- **Arguments**, this is simply the payload, in this case, we just want to broadcast all messages that come from one client, to ensure other listening clients would be updated on the fact that there is new data. 

**Add Connection String**

Ok, so we learned that our code needs SignalR decorators in the code to work properly. Nothing will work however unless we add the Connection String information to our project configuration file called `local.setting.json`. 

Let's have a look at the current state of the file:

```json
{
    "IsEncrypted": false,
    "Values": {
        "FUNCTIONS_WORKER_RUNTIME": "dotnet",
        "AzureSignalRConnectionString": "<add connection string info here>"
    },
    "Host": {
        "LocalHttpPort": 7071,
        "CORS": "<add allowed client domains here>",
        "CORSCredentials": true
    }
}
```

Let's look at `AzureSignalRConnectionString` , this needs to have the correct Connection String info. We can find that if we go our Azure SignalR Service in the Cloud. 

1. Go to `portal.azure.com` 
2. Select your Azure SignalR Service
3. Click `keys` in the left menu
4. Copy the value under `CONNECTION STRING`

Next, let's update the `CORS` property. Because we are running this locally we need to allow, for now, that `http://localhost:8080` is allowed to talk our Azure Function App and Azure SignalR Service. 

NOTE, we will ensure that the client we are about to create will be run on port `8080`.

### Create a UI

Ok, we've taken all the necessary steps to create a backend, and an Azure SignalR service that is able to scale our real-time connections. We've also added a serverless function that is able to proxy any calls made to our Azure SignalR service. What remains is the application code, the part our users will see. 

We will build a chat application. So our app will be able to do the following:

- **Establish** a connection to our Azure SignalR Service
- **Show incoming messages** from other clients
- **Send messages** to other clients

### Establish a connection

Let's select a different directory than that of our serverless app. Now create a file `index.html` and give it the following content:

```html
<html>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@aspnet/signalr@1.1.2/dist/browser/signalr.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios@0.18.0/dist/axios.min.js"></script>
    <script>
    
    </script>
  </body>
</html>
```

Above we have added some script tags:

- **Vue.js**, this is a link to a CDN version on Vue.js, you can go with whatever SPA framework you want here or Vanilla JS
- **SignalR**, this is a link to a CDN version of SignalR, this is a must, we need this to establish a connection to our SignalR Hub and also for sending messages that other clients can listen to
- **Axios**, this is a link to a CDN version of Axios, Axios is a library for handling HTTP requests. You are fine using the native fetch in this case, up to you

How do we establish a connection in code? The code below will do just that. We point `apiBaseUrl` to the location of our Serverless Function App, once it's up and running.

```js
const apiBaseUrl = 'http://localhost:7071';

const connection = new signalR.HubConnectionBuilder()
    .withUrl(`${apiBaseUrl}/api`)
    .configureLogging(signalR.LogLevel.Information)
    .build();
```

The above will set up a connection object. To actually connect we need to call `start()` on our connection object. 

```js
console.log('connecting...');
connection.start()
  .then((response) => {
    console.log('connection established', response);
  })
  .catch(logError);
```

Before we move on let's try to verify that we can connect to our Serverless function and the Azure SignalR service.

**Take it for a spin**

We need to take the following steps to test things out:

1. **Startup** our Serverless function in Debug mode
2. **Startup** our client on `http://localhost:8080`
2. **Ensure** the `connection established` message is shown in the client

Go to our Serverless app and select `Debug/Start Debugging` from the menu. It should look like the below. 

![](https://thepracticaldev.s3.amazonaws.com/i/691hfwgue4ioll0mdft0.png)

Also, place a breakpoint in `Negotiate.cs` and the first line of the function, so we can capture when the client is trying to connect. 

Next, let's start up the client at `http://localhost:8080`. Use for example `http-server` for that at the root of your client code:

> http-server -p 8080

As soon as you go open up a browser on `http://localhost:8080` it should hit your Serverless function `negotiate`, like so:

![](https://thepracticaldev.s3.amazonaws.com/i/2lgfgw55qinnnf5fc4wp.png)

As you can see above, the Azure SignalR service is sending back an `AccessToken` and the `URL` you were connecting against. 

Looking at the browser, we should see something like this:

![](https://thepracticaldev.s3.amazonaws.com/i/rukk4st7j2h73re7514r.png)

Good, everything works so far. This was the hard part. So what remains is building this out to an app that the user wants to use, so that's next. :)

### Build our Vue.js app

Our app should support:

- **Connecting to Azure SignalR Service**, we got that down already
- **Show messages**, be able to show messages from other clients
- **Send message**, the user should be able to send a message

Let's get to work :)

**Create a Vue.js app**

We need to create a Vue app and ensure it renders on a specific DOM element, like so:

```html
<html>
  <body>
    <div id="app">
      App goes here
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@aspnet/signalr@1.1.2/dist/browser/signalr.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios@0.18.0/dist/axios.min.js"></script>
    <script>
      const app = new Vue({
        el: '#app',    
      });

      const apiBaseUrl = 'http://localhost:7071';

      const connection = new signalR.HubConnectionBuilder()
        .withUrl(`${apiBaseUrl}/api`)
        .configureLogging(signalR.LogLevel.Information)
        .build();

      console.log('connecting...');
      connection.start()
        .then((response) => {
          console.log('connection established', response);
      })
        .catch(logError);

      
    </script>
  </body>
</html>
```

Above we have the entire code so far. Let's specifically highlight:

```html
<div id ="app">
</div>
```

and

```js
const app = new Vue({
  el: '#app',    
});
```

Now we have an app, but it does nothing.

**Show messages**

To be able to show messages we need to listen to events being raised from our Serverless function. If you remember, in our Serverless function we called the following code in our `Messages.cs`:

```csharp
return signalRMessages.AddAsync(
  new SignalRMessage
  {
    Target = "newMessage",
    Arguments = new[] { message }
  });
```

We are interested in listening to the event `newMessage` being raised by the above function. The code for that looks like so:

```js
connection.on('newMessage', newMessage);

function newMessage(message) {
  // do something with an incoming message
}
```

> Ok, how do I get that message to show in my Vue.js app?

Let's make sure to update our markup to this:

```html
<div id="app">
  <h2>Messages</h2>
  <div v-for="message in messages">
    <strong>{{message.sender}}</strong> {{message.text}}
  </div>
</div>
```

and our app code to:

```js
const data = {
  messages: []
}

const app = new Vue({
  el: '#app',    
  data: data
});
```

and this:

```js
function newMessage(message) {
  data.messages = [...data.messages, {...message}]
}
```

Now we can render all the messages.

> But there are no messages to show and I'm the only person in the chat room and I can't send a message?

Fair point, let's give you that ability:

**Send message**

We need a way for the user to type in a message in HTML and also a way to send that message to the SignalR Hub in code. Let's start with the HTML

```html
<div>
  <input type="text" v-model="newMessage" id="message-box" class="form-control"
    placeholder="Type message here..." autocomplete="off" />
  <button @click="sendMessage">Send message</button>
</div>
```

and the code for the *send* function:

```js
function createMessage(sender, messageText) {
  return axios.post(`${apiBaseUrl}/api/messages`, {
    sender: sender,
    text: messageText
  }).then(resp => console.log('success sending message',resp.data);
}
```

Our full code so far looks like this:

```html
<html>
  <body>
    <div id="app">
      <h2>
        User
      </h2>
      <div>
        <input type="text" v-model="user" placeholder="user name" />
      </div>
      <div>
          <input type="text" v-model="newMessage" id="message-box" class="form-control"
            placeholder="Type message here..." autocomplete="off" />
        <button @click="sendMessage">Send message</button>
      </div>
      <h2>Messages</h2>
      <div v-for="message in messages">
        <strong>{{message.sender}}</strong> {{message.text}}
      </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@aspnet/signalr@1.1.2/dist/browser/signalr.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios@0.18.0/dist/axios.min.js"></script>
    <script>
      const data = {
        user: 'change me',
        messages: [],
        newMessage: ''
      }

      const app = new Vue({
        el: '#app',    
        data: data,
        methods: {
          sendMessage() {
            createMessage(this.user, this.newMessage);
          }
        }
      });

      const apiBaseUrl = 'http://localhost:7071';

      const connection = new signalR.HubConnectionBuilder()
        .withUrl(`${apiBaseUrl}/api`)
        .configureLogging(signalR.LogLevel.Information)
        .build();

      console.log('connecting...');
      connection.start()
        .then((response) => {
          console.log('connection established', response);
      })
        .catch(logError);

      connection.on('newMessage', newMessage);

      function newMessage(message) {
        data.messages = [...data.messages, {...message}]
      }

      function logError(err) {
        console.error('Error establishing connection', err);
      }

      function createMessage(sender, messageText) {
        return axios.post(`${apiBaseUrl}/api/messages`, {
          sender: sender,
          text: messageText
        }).then(resp => {
          console.log('message sent', resp);
        });
      }

    </script>
  </body>
</html>
```

and running this with two different windows side by side should look something like this:

![](https://thepracticaldev.s3.amazonaws.com/i/7h60p0u3lopio04y2vzp.png)

As you can see this works pretty well but it ain't pretty so feel free to add Bootstrap, Bulma, Animations or whatever else you feel is needed to make it a great app. 

## Summary

We've learned the following:

- **SignalR**, what it is and how it can be hosted either as part of your Web App in App Service or via an Azure SignalR Service + Serverless
- **Serverless**, we've taken our first steps in serverless and learned how to scaffold an app with functions
- **Chat**, we've learned how to build a Chat by Creating a Serverless app as an endpoint and we've also built a Client in Vue.js 

### Want to submit your solution to this challenge? 

<hr/>
Want to submit your solution to this challenge? Build a solution locally and then [submit an issue](https://github.com/microsoft/25-days-of-serverless/issues/new?assignees=&labels=challenge-submission&template=challenge-solution-submission.md&title=%5BCHALLENGE+SUBMISSION%5D+). If your solution doesn't involve code you can record a short video and submit it as a link in the issue description. Make sure to tell us which challenge the solution is for. We're excited to see what you build! Do you have comments or questions? Add them to the comments area below.
<hr/>


Watch for surprises all during December as we celebrate 25 Days of Serverless. Stay tuned here on dev.to as we feature challenges and solutions! Sign up for a [free account on Azure](https://azure.microsoft.com/free/?WT.mc_id=25days_devto-blog-cxa) to get ready for the challenges!