SignalR and Serverless? It comes true

SignalR and Serverless? It comes true

Serverless Real-time Application!

In one of my posts on Azure SignalR Services, It was discussed that with Azure signalr service we can decouple the connections from the hub logic into Asp.net core! What if you don't want to host the app into app services? What if we want to use other languages or what if our business model is more compatible with serverless technology?

What do we need to start?

1. Azure Function App is what you need!

Using Azure Functions along with Azure SignalR Service, you have the ability to notify thousands of clients whenever some events occur. Azure functions help you easily run small pieces of code in the cloud without worrying about a whole application or the infrastructure to run it. Any event can trigger the SignalR and Common events could be HTTP and webhook requests, Timer, Cosmos DB change feed, Queue or ...
A function app is a collection of one or more functions that are managed together. All the functions in a Function App share the same pricing plan.

Once you create a Function App in Azure portal, you will basically have 4 different resources connected to each other in one resource group:

  • Genaral storage account: to keep the configuration files for functions or any other states.
  • App service plan
  • App service (The azure function code)
  • App Insight
    You can create your function as consumption model or App service which has a dedicated VM for it to run.

2. Create Serverless Azure SignalR Service

Azure SignalR Service can be configured in different modes. In order to be able to use serverless, it needs to be configured in Serverless mode.

Build Azure Functions

The best way to build azure functions is by using Azure Function Core Tool It helps with running the function locally, deployment, function creation and etc. You also need DotNet SDK.
Having installed the core tool, you can create your Function App by using:

func init  

Then you can start creating the actual function. Before doing that, you need to understand the concept of 2 things: Triggers and Bindings

function triggers

Triggers are what cause a function to run. A trigger defines how a function is invoked and a function must have exactly one trigger. Each function needs to have a trigger which can be Queue, Timer, Event Grid, Http,...

function bindings

Binding to a function is a way of declaratively connecting another resource to the function. Through bindings you can pass outputs and recieve inputs from other resources. Data from bindings is provided to the function as parameters. Each function can have input bindings, output bindings, or both.
Triggers and bindings let you avoid writing lots of code accessing to other services. I will show you by example.

Input/Output binding

Azure Functions comes with a range of input bindings, and output bindings. For example you want to trigger your Azure function to write a row in Azure Table Storage. Therefor your output binding will be Azure Table Storage.
All triggers and bindings have a direction property in the function.json file. For triggers, the direction is always in. This example is the function whose trigger is WebHook:

{
  "bindings": [
    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "get",
        "post"
      ]
    
    }
  ]
}

If this function is supposed to pass the output to any resource includinh http response then you need to have an output binding:

    {
      "type": "http",
      "direction": "out",
      "name": "res"

Build Azure function App

When you create the actual function, some bindings needs extensions to be installed. Http and timer bindings don't need extension however others dp and tehy should be fetched from Nuget and here is .net SDK coming to help us.

func extension add -n signalr 

Look at the link for Binding Extension document
Some commands you need to know to build the functions through func core tool are:

func new (creates a new azure function and it will ask what is the trigger)
func start (func host start)  Alternatevely you can start debugging  your code by using Visual Studio Code.

func new creates a new Azure function inside a folder ith a json file to define bindings and an actual file (cs,js,...) which is the functionality.
By runningfunct host start this error possibly will appear:

[11/07/2019 8:14:10 PM] [error] Incompatible Node.js version. The version you are using is v11.6.0, but the runtime requires an LTS-covered major version (ex: 8.11.1 or 10.14.1).
LTS-covered versions have an even major version number (8.x, 10.x, etc.) as per https://github.com/nodejs/Release#release-plan. For deployed code, change WEBSITE_NODE_DEFAULT_VERSION in App Settings. Locally, install or switch to a supported node version (make sure to quit and restart your code editor to pick up the changes).

This [error] on incompatible NodeJs means you need another version of
Node. Install NVM and switch node version to LTS version like 8.11.1 or 10.14.1:
choco install nvm (restart your computer)
nvm install 10.14.1
nvm ls available
nvm use 10.14.1

This fixes the issue and run it again and here is the result after the fix:

P.S:
An RPC is analogous to a function call. Like a function call, when an RPC is made, the calling arguments are passed to the remote procedure and the caller waits for a response to be returned from the remote procedure. ... The client makes a procedure call that sends a request to the server and waits.

[11/07/2019 9:08:58 PM] Starting Rpc Initialization Service.
[11/07/2019 9:08:58 PM] Initializing RpcServer
[11/07/2019 9:08:58 PM]

...

Http Functions:

        pushToDB: [GET,POST] http://localhost:7071/api/pushToDB

By running the function a local server instance will be running and host the function returning the url :

local-server

Now you can hit the function by using Postman or Powershell script:

iwr -Method Get -Uri  http://localhost:7071/api/pushToDB?name=Nelly -Headers @{"Content-Type"="application/json"} 

Creating a new Azure SignalR service

We can either use wizard in the portal to create resource group and service or using Azure cli or ARM templates to create the services.

az login
az account list-locations
az group create --name signalrserverless --location australiaeast 

This is the ARM template for serverless signalR service or alternatively you can manually build it:

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "MySignalR_Serverless": {
            "defaultValue": "MySignalR",
            "type": "String"
        }
    },
    "variables": {},
    "resources": [
        {
            "type": "Microsoft.SignalRService/SignalR",
            "apiVersion": "2018-10-01",
            "name": "[parameters('MySignalR_Serverless')]",
            "location": "australiaeast",
            "sku": {
                "name": "Free_F1",
                "tier": "Free",
                "size": "F1",
                "capacity": 1
            },
            "properties": {
                "hostNamePrefix": "MySignalR",
                "features": [
                    {
                        "flag": "ServiceMode",
                        "value": "Serverless",
                        "properties": {}
                    }
                ]
            }
        }
    ]
}

SignalR bindings

A serverless real-time application typically requires two Azure Functions:
1- negotiate: a function that client make a call to in order to obtain a valid signalR service URL and access token. SignalR Service bindings are provided in the Microsoft.Azure.WebJobs.Extensions.SignalRService NuGet package
The Azure function is able to use this connection information to return the connection information of SignalR service as well as signalr service url. Then clients can stick to the service.
Sample of connection informtaion is :

 {
  "disabled": false,
  "bindings": [
      {
          "authLevel": "anonymous",
          "type": "httpTrigger",
          "direction": "in",
          "name": "req"
      },
      {
          "type": "http",
          "direction": "out",
          "name": "res"
      },
      {
          "type": "signalRConnectionInfo",
          "name": "connectionInfo",
          "hubName": "flights",
          "direction": "in"
      }
  ]
}

once you build connection with the API it is implicitly told to connect. doc

client-and-negotiate

  this.hubConnection = new signalR.HubConnectionBuilder()
        .withUrl('http://localhost:7071/api',{withCredentials:true})
        .build();

http://localhost:7071/api is the local endpoint of the Azure func which should be replaced later on with the public host name once it is deployed.
2- Functions to broadcast messages to clients through output binding
At this stage you need to have a look at SignalR output binding.
Azure function is using a json file like below to send a message with Azure SignalR Service.

{
  "type": "signalR",
  "name": "signalRMessages",
  "hubName": "flights",
  "direction": "out"
}

In the code you can assign the payload parameter to the signalr by using the 'name' field which is 'signalRMessages' in the example above.
Let's say when the data is pushed to Queue then Queue will be the trigger of this broadcast function. The payload which is recieved from Queue will be passed to SignalR output. The payload will go to 'name' field which is 'landedFlight' in the example below. Here is the binding:

{
  "bindings": [
    {
      "name": "landedFlight",
      "type": "queueTrigger",
      "direction": "in",
      "queueName": "js-queue-items-flight",
      "connection": "" >> empty connection means take it by default from   AzureWebJobsStorage from local.settings.json file
    },
    {
      "type": "signalR",
      "name": "signalRMessages",
      "hubName": "flights",
      "direction": "out"
 
    }
  ]
}

This is the code for Queue broadcast function:

module.exports =  function (context, landedFlight) {
    context.bindings.signalRMessages =landedFlight;
    context.done();
};

if you don't identify the connection string then it will pick it from app settings which is fetched from AzureSignalRConnectionString parameter from local.settings.json when you are runing locally.
Copy the connection string of the signalr service from portal and put it in tvariable above.

local-setting-file

PS: Can client invoke functions in Hub? It does not make sense for clients to invoke broadcasting function (like send message) in SignalR hub since each time they have to make a http request as per document. Although the SignalR SDK allows client applications to invoke backend logic in a SignalR hub, this functionality is not really recommended.

Note-serverless

client-send-message

However it totally make sense for other resources to invoke broadcast functions using output binding and send data through the websocket connections which SignalR service establish with clients.

Real story as a usecase :

We need to have a real-time application which show the most recent landed flights all over the worlds.
Solution: Timer will trigger a Http function to fetch data from the aviation api and push the values to the queue. Once Queue is triggered, the data will be fetched from Queue and will be sending to SignalR through output binding.

Build Webhook Aviation API & Queue Storage binding

1- build new azure function with Timer trigger which wraps the aviation api (HTTP and timer triggers bindings are supported by default and don't require any extension)
2- output the response to the Queue storage (The queue is already comes with Function app as a storage)
The HttpTrigger has one default output which return the response. However, we need to add another output binding whose purpose is pushing return value to the queue.

queue-output-binding

See how output binding for queue to work. And see how to write function Function

Install the Queue Storage Binding Extension

Once you craete a new Queue Trigger functionn the binding extensions will be automatically added too. However you can easily add it manually by following the instrution below: Look at Queue extension
In Azure Functions version 2.x, bindings are available as separate packages from the functions runtime. While .NET functions access bindings through NuGet packages, extension bundles allow other functions access to all bindings through a configuration setting.
Based on what language and platform you are using, there are different ways to install a binding extensions

func extensions install --package Microsoft.Azure.WebJobs.Extensions.Storage --version 3.0.3

Other than the OBJ folder in local, it would add a extensions.csproj with this content:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
	<WarningsAsErrors></WarningsAsErrors>
	<DefaultItemExcludes>**</DefaultItemExcludes>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="3.0.3" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Script.ExtensionsMetadataGenerator" Version="1.1.0" />
  </ItemGroup>
</Project>

configure the bindings

We want Http Trigger send the result to the Queue so we need to set the binding in Http function to output to Queue.

This is awesome documents for all dev works we need to do.

queue-output-binding-1

When we leave connection string empty, then runtime worker will take it from AzureWebJobsStorage in local.settings.json for development env.
In the Http finction when you get the response you need to pass to the context object by passing databag into Context.done(null,returnValue);

  context.done(null, response);

Queue storage whose connection string is defined in function.json will recieve the response automatically.
push-payload-to-queue

create new Queue Trigger func with SignalR output binding

Create-azure-queue-trigger

Once it is created you can see the required Storage binding extension will also be created for you.
There is a json file inside the newy created folder called function.js which manages the binding:

queue-biding

If you already have a storage that you want to use it's connection string then use the command to fetch it:

func azure functionapp fetch-app-settings <FunctionAppName>
Or
func azure storage fetch-connection-string <StorageAccountName>

You need to add the SignalR extension to support the SignalR output binding by adding the following extension to extensions.csproj

 <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.SignalRService" Version="1.0.0" />

Also you need to create a SignalR service and add it's connection string to the app.settings in local.settings.json file.

Troubleshooting

Once you install the Microsoft.Azure.WebJobs.Extensions.SignalRService SignalR extension, it might install the preview version like Version="1.0.0-preview1-10002" which can cause issues. What you will probably face is an error in your client side once it attempts to start the connection.

connection-error-to-serverless

To resolve the issue, you need to change the version to 1.0.0 in the extensions.csproj file and run:

func extensions install

Deploy the clientside Vue.js app using CLI

The static client side appp is a vue.js app which needs to be deployed to Azure Blob Storage.
First make sure your subscription is added and listed in your local.

az account list

You need to create a storage accound. Having created a resourceGroup before, you can use it to include your static site inside your existing resource group.
In your vue.js app tou need to run npm run build to get everything ready in dist folder.

az storage account create -n <account-name> -g <resourcegroupname> -l australiaeast  --sku Standard_LRS
az storage account update -g <resourcegroupname>  -n <account-name>  --set kind=StorageV2

az storage blob service-properties update --account-name <account-name>  --static-website   --index-document index.html  


az storage blob upload-batch -s <distfolderpath> -d $web --account-name  <account-name>

if you get error on the last command, change directory to dist folder and use

az storage blob upload-batch -s . -d $web --account-name <account-name>

Now you can get the URL for your static site by running:

az storage account show -n <storage-account-name> -g <resource-group-name> --query "primaryEndpoints.web" --output tsv

You can use these real values:

 
az storage account create -n vueserverlessflight -g signalrserverless -l australiaeast  --sku Standard_LRS
az storage account update -g signalrserverless -n vueserverlessflight --set kind=StorageV2
az storage blob service-properties update --account-name vueserverlessflight  --static-website   --index-document index.html  
az storage blob upload-batch -s <distfolderpath> -d $web --account-name vueserverlessflight
az storage account show -n vueserverlessflight -g  signalrserverless --query "primaryEndpoints.web" --output tsv

Deploy Azure functions with CLI

You are required to build your func app locally by running func init as we discussed in the function creation at the begining of the post.
You also need a general resource group to maintain state and other information about your functions.

az storage account create --name <generalstoragename> --location australiaeast --resource-group  <resource-group-name>  --sku Standard_LRS

Create a function app in Azure:

az functionapp create --resource-group myResourceGroup --consumption-plan-location westeurope --name <APP_NAME> --storage-account  <STORAGE_NAME> --runtime <language>

After confirming the Function App is created, publish your source code.
Before you deploy you can also add the recently deployed vue client side app to your CORS list.
IMPORTANT: Obviously you need to get back to your vue client app and update the base URL in the code. Using this option will transfer all local settings to Azure otherwise you need to do it manually.

func azure functionapp publish  <APP_NAME> --publish-local-settings -i

Once it is done, you will get back the function enedpoints. Also yu can see the app settings in portal here:

app-setting-in-azure

This is how you can enable the CORS:

CORS-in-portal

I also found these commands handy to see more detail on Azure Functions through CLI.

az functionapp list --resource-group <resourcegroupname> 
az functionapp cors show --name <functionappname> --resource-group <resourcegroupname>  

Refrences

Binding Extensions
deploy your static client site to Azure
Azure function Github code
Clientside Github code