0 Comments

So it’s 2019 now, almost 2020. More and more companies start migrating solutions to the cloud. In my profession, building cloud and web solutions, I see that companies start migrating their solutions to different architectures, more suitable for cloud environments. Classic web systems are replaced by serverless systems and huge IT systems are thorn apart into microservices. Since Azure Functions became mature, they’re a really good replacement for classic ASP.NET Web solutions running on a Web App or on IIS. So I started using these serverless solutions more and more. Because I also like to create user-friendly software I often use the SignalR real-time framework to notify the user of processes going on on the server. For example, when sending a command to a serverless function, you may want to inform the user whether processing that command was successful (or not). In the past, you required a web host to run SignalR, but running a web host in the cloud is relatively expensive. Today, SignalR is one of the native cloud services Azure can deliver. During this blog, I’m going to implement this SignalR service.

The demo project

Developers always create demo projects to try something new. The idea is great, but there’s never time to finish the project and so it lands in the trash can somewhere between now and 5 years. So for this blog, to show how the SignalR Service works, I… Yes… created a demo project. It’s an Angular front-end project uploading images to Azure Blob Storage. An Azure Function will be triggered by the blob creation and start resizing the image into two versions, a thumbnail and a fairly decent web size (1024 x 768). Image references are stored in Azure Table Storage and once both images are sized correctly, the state of the image in Table Storage will be set to available. Then a message will be broadcasted using SignalR, which enables the front-end system to respond. Pretty awesome, you could also use this exact same scenario for example when importing data. Just upload the data, make a function that imports, and report status through SignalR.

signalr-service-createSo first I navigated to the Azure Portal and started creating a SignalR Service.

Now when the Azure created the resource, navigate to the newly created SignalR Services and open the Keys blade. Here you’ll find two keys and two connection strings. Copy one of the connection strings, you’re going to need that one in the Azure Functions project. Then navigate to the CORS blade and validate if there’s an allowed origin *. If not, add it. You may want to change this to a valid endpoint once your system goes to production, but for this demo, you’ll be fine. Please note, that I selected Serverless as a ServiceMode. This mode should only be selected when you use SignalR from an Azure Functions project.

Next Up, The Functions project

Now open Visual Studio, I used VS 2019 (16.3.18) and Azure Functions v2. Create a new Azure Functions project and see if your project contains a local.settings.json file. If not, create it and add the copied Connection String value as a setting called ‘AzureSignalRConnectionString’. Your local.settings.json looks like this (or something similar):

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "AzureSignalRConnectionString": "Endpoint=https://your-signalr.service.signalr.net;AccessKey=/--secred-access-key-here--/;Version=1.0;"
  },
  "Host": {
    "LocalHttpPort": 7071,
    "CORS": "http://localhost:4200",
    "CORSCredentials": true
  }
}

The Angular client makes HTTP requests to the negotiate function to initiate the connection negotiation. When the client application is hosted on a different domain than the Azure Function app, cross-origin resource sharing (CORS) must be enabled on the Function app or the browser will block the requests. This is why I also added some CORS settings in the settings file. I know my Angular client is going to run on localhost port 4200. Once again, you may want to change these settings once you go to production.

As you all know, an Azure Function is fired by a trigger and may use bindings (input and/or output) to use external data or services, or send data to external services. We’re going to use a SignalR Output Binding which means we send data out to the SignalR Service. This data fires an event on the client which can be handled accordingly. The bindings for the SignalR Service can be installed by adding a NuGet package to your project. Look for the packed called Microsoft.Azure.WebJobs.Extensions.SignalRService, My project used version 1.0.2, just so you know.

Now it’s time to implement the negotiate endpoint. SignalR uses this endpoint to initiate a connection and determine server and client capabilities. In your Azure Functions project, create a new endpoint with an HTTP trigger and which looks like this:

[FunctionName("negotiate")]
public static SignalRConnectionInfo SignalRNegotiate(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post")]  HttpRequestMessage req,
    [SignalRConnectionInfo(HubName = "notifications")] SignalRConnectionInfo connectionInfo)
{
    return connectionInfo;
}

That's pretty much all there is to it. This endpoint allows you to connect to the SignalR service. Connecting to this endpoint redirects to your SignalR Service, which in turn returns its capabilities (like available transport types and so).

I explained I persist a reference to uploaded pictures in table storage. Once a file is uploaded and successfully scaled, I send a command on a queue that sets an availability flag on the picture entity in table storage. When the table entity is successfully updated, I send a message through the SignalR Service.

The function looks like so (I stripped code which doesn’t add value for this demo):

[FunctionName("PictureStatusCommandQueueHandler")]
public static async Task PictureStatusCommandQueueHandler(
    [QueueTrigger(Constants.QueueNamePictureStatusCommands)] string pictureStatusCommandMessage,
    [Table(TableNames.Pictures)] CloudTable picturesTable,
    [SignalR(HubName = SignalRHubNames.NotificationsHub)] IAsyncCollector signalRMessages,
    ILogger log)
{
    log.LogInformation("Picture status command retrieved");
    SetStorageConsumptionCommand consumptionCommand = null;
    ...
    if (...)
    {

	...
        Update the table entity here
        ...

        var pictureDto = new PictureDto
        {
            CreatedOn = entity.Timestamp,
            Id = Guid.Parse(entity.RowKey),
            Name = entity.Name,
            Url = picturesTable.ServiceClient.BaseUri.ToString()
        };
        await signalRMessages.AddAsync(
            new SignalRMessage
            {
                Target = "newPicture",
                Arguments = new object[] { pictureDto }
            });
        }
    }
    return consumptionCommand;
}

So what happens here is basically that I create a Data Transfer Object (DTO), which I want to push to the client, and I happen to use SignalR as a mechanism to do that for me. The DTO will be converted to JSON and passed to the client. The Target here (newPicture) is the event that will be raised client side, and the arguments can be seen as the payload of that message.

The Angular project

Before we run into a discussion that doesn’t make sense… I’m a cloud solution architect and I really like C# and the Microsoft development stack. I also have a strong affinity with Angular. Because I use Angular as a demo project doesn’t mean it’s the best solution. Vue, React and all other frameworks/component libraries work fine! So I created this Angular project and inside that project created a service. This service uses the @aspnet/signalr package so you need to install that. For your information, my demo project used version 1.1.4.

npm i @aspnet/signalr

or yarn if you like

yarn add @aspnet/signalr

Now back to the service, since the service is quite large, I created a Github Gist here. The service contains a connect and a disconnect function. The endpoint to connect to is your Azure Functions project URL http://{az-functions-project}/api

By connecting to that location, the SignalR client will send a post request to the negotiate endpoint of your Azure Functions project, and the SignalR service does the rest for you.

Now if your scroll down to line 22 of the gist, you see this code:

this.connection.on('newPicture', (dto: PictureDto) => {
    console.log('New picture arrived');
});

This fragment subscribes to the ‘newPicture’ event. Remember the Azure Function in which we send a message with a target ‘newPicture’? Well, this is the event handler on the client handling that event. In this case, a message is written to the browser’s console, but you also see the dto of type PictureDto, which contains the actual information about the image as it was passed by the Azure Function.

Now create a component that consumes the realtime service and calls the service’s connectSignalR() function and you’re good to go!!

I have quite some history with SignalR, so I expected a very complicated solution. It took me some time to figure out how the SignalR service is implemented, but mostly because I expected something difficult. The reality is that the SignalR Service integrates extremely well and lowers the complexity bar big time! Have fun experimenting!

Post comment