Tag Archives: Signalr

Calling an Azure Signalr Instance from an Azure function

I’ve been playing around with the Azure Signalr Service. I’m particularly interested in how you can bind to this from an Azure function. Imagine the following scenario:

You’re sat there on your web application, and I press a button on my console application and you suddenly get notified. It’s actually remarkably easy to set-up (although there are definitely a few little things that can trip you up – many thanks to Anthony Chu for his help with some of those!)

If you want to see the code for this, it’s here.

Create an Azure Signalr Service

Let’s start by setting up an Azure Signalr service:

You’ll need to configure a few things:

The pricing tier is your call, but obviously, free is less money than … well, not free! The region should be wherever you plan to deploy your function / app service to, although I won’t actually deploy either of those in this post, and the ServiceMode should be Serverless.

Once you’ve created that, make a note of the connection string (accessed from Keys).

Create a Web App

Follow this post to create a basic web application. You’ll need to change the startup.cs as follows:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSignalR().AddAzureSignalR();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            //app.UseDefaultFiles();
            //app.UseStaticFiles();

            app.UseFileServer();
            app.UseRouting();
            app.UseAuthorization();

            app.UseEndpoints(routes =>
            {
                routes.MapHub<InfoRelay>("/InfoRelay");
            });
        }

Next, we’ll need to change index.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    
    <script src="lib/@microsoft/signalr/dist/browser/signalr.js"></script>
    <script src="getmessages.js" type="text/javascript"></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">

</head>
<body>
    <div class="container">
        <div class="row">
            <div class="col-2">
                <h1><span class="label label-default">Message</span></h1>
            </div>
            <div class="col-4">
                <h1><span id="messageInput" class="label label-default"></span></h1>
            </div>
        </div>
        <div class="row">&nbsp;</div>
    </div>
    <div class="row">
        <div class="col-12">
            <hr />
        </div>
    </div>
</body>

</html>

The signalr package that’s referenced is an npm package:

npm install @microsoft/signalr

Next, we need the getmessages.js:

function bindConnectionMessage(connection) {
    var messageCallback = function (name, message) {
        if (!message) return;

        console.log("message received:" + message.Value);

        const msg = document.getElementById("messageInput");
        msg.textContent = message.Value;
    };
    // Create a function that the hub can call to broadcast messages.
    connection.on('broadcastMessage', messageCallback);
    connection.on('echo', messageCallback);
    connection.on('receive', messageCallback);
}

function onConnected(connection) {
    console.log("onConnected called");
}

var connection = new signalR.HubConnectionBuilder()
    .withUrl('/InfoRelay')
    .withAutomaticReconnect()
    .configureLogging(signalR.LogLevel.Debug)
    .build();

bindConnectionMessage(connection);
connection.start()
    .then(function () {
        onConnected(connection);
    })
    .catch(function (error) {
        console.error(error.message);
    });

The automatic reconnect and logging are optional (although at least while you’re writing this, I would strongly recommend the logging).

Functions App

Oddly, this is the simplest of all:

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

The big thing here is the binding – SignalRMessage binding allows it to return the message to the hub (specified in HubName). Also, pay attention to the Target – this needs to match up the the event that the JS code is listening for (in this case: “broadcastMessage”).

Console App

Finally, we can send the initial message to set the whole chain off – the console app code looks like this:

        static async Task Main(string[] args)
        {
            Console.WriteLine($"Press any key to send a message");
            Console.ReadLine();

            HttpClient client = new HttpClient();
            string url = "http://localhost:7071/api/messages";
            
            HttpContent content = new StringContent("{'Value': 'Hello'}", Encoding.UTF8, "application/json");

            HttpResponseMessage response = await client.PostAsync(url, content);
            string results = await response.Content.ReadAsStringAsync();

            Console.WriteLine($"results: {results}");
            Console.ReadLine();
        }

So, all we’re doing here is invoking the function.

Now when you run this (remember that you’ll need to run all three projects), press enter in the console app, and you should see the “Hello” message pop up on the web app.

References

https://docs.microsoft.com/en-us/aspnet/core/signalr/javascript-client?view=aspnetcore-3.1

https://docs.microsoft.com/en-us/aspnet/core/signalr/dotnet-client?view=aspnetcore-3.1&tabs=visual-studio

https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-signalr-service?tabs=csharp

Getting Started With SignalR

SignalR is an open source framework allowing bi-directional communication between client and server. Basically, it uses a stack of technologies; the idea being that the Signalr framework will establish the “best” way to maintain a bi-directional data stream, starting with web sockets, and falling all the way back to simply polling the server.

The following gives the basics of establishing a web site that can accept Signalr, and a console app that can send messages to it.

Create project

Let’s go MVC:

Hubs

Hubs are the way in which the Signalr service communicates with its clients. Obviously, the term service here may not actually represent a service.

To add a hub class, select the project, right-click and “New Item..”:

This adds the file, along with new references:

The code above that gets added is:

public void Hello()
{
    Clients.All.hello();
}

Clients.All returns a dynamic type, so we lose intellisense at this point. It’s important that the signature of this method is exactly correct, and that it is decorated with the name of the hub, and that it is decorated with the name of the hub; so let’s replace with:

[HubName("MyHub1")]
public class MyHub1 : Hub
{
    public void Hello(string message)
    {
        Clients.All.Hello(message);
    }
}

Change Startup.cs:

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        ConfigureAuth(app);
 
        app.MapSignalR();
    }
}

For all this to actually do anything, the next thing to do is hook up the JavaScript:

$(function () {
    // Declare a proxy to reference the hub. 
    var hub = $.connection.MyHub1;
    // Create a function that the hub can call to broadcast messages.
    hub.client.hello = function (message) {
 
        alert("Hello");
    };
 
    
    $.connection.hub.start()
        .done(function () { console.log("MyHub1 Successfully Started"); })
        .fail(function () { console.log("Error: MyHub1 Not Successfully Started"); })
});

Effectively, once we receive a message, we’re just going to display an alert. Once the event handler is wired up, we try to start the hub.

Next, reference the required files in BundleConfig.cs:

bundles.Add(new ScriptBundle("~/bundles/signalr").Include(
    "~/Scripts/jquery-3.1.1.min.js").Include(
    "~/Scripts/jquery.signalR-2.2.1.js"));

These are referenced in _Layout.cshtml; remember also that, because SignalR references Jquery, you’ll need to remove other references to Jquery:

<title>@ViewBag.Title - My ASP.NET Application</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")    
@Scripts.Render("~/bundles/signalr")    
<script type="text/javascript" src="~/signalr/hubs"></script>
<script type="text/javascript" src="~/Scripts/Notification.js"></script>

. . .

    </div>
    
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
</body>

Notes on Bundles

The purpose of bundling is to shrink the size of the bundled files. The idea being that small files make for a speedy web-site.

Console App

The next step is to create an application that can fire a notification to the page. In this case, I’m using a console app, just because I like to see everything working with console apps.

Start with a NuGet Reference:

The code:

class Program
{
    static void Main(string[] args)
    {
        Console.Write("Message: ");
        string message = Console.ReadLine();
 
        HubConnection connection = new HubConnection("http://localhost:4053/");
        IHubProxy hub = connection.CreateHubProxy("myHub1");
                    
        connection.Start().Wait();
        hub.Invoke<string>("Hello", message).Wait();            
 
        Console.WriteLine("Sent");
        Console.ReadLine();
    }
}

And that’s it – you should be able to send a message to the web site from the console app. The examples that are typically given elsewhere on the net are chat rooms, but this clearly has many more uses.

Some abstract notes that I made while researching this.

Adding:

Version 1

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
 
    >RouteTable.Routes.MapHubs(new HubConfiguration());
 
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
…

Gives:

Severity Code Description Project File Line Source Suppression State
Error CS0619 ‘SignalRRouteExtensions.MapHubs(RouteCollection, HubConfiguration)’ is obsolete: ‘Use IAppBuilder.MapSignalR in an Owin Startup class. See http://go.microsoft.com/fwlink/?LinkId=320578 for more details.’ SignalRTest3 C:\Users\Paul\documents\visual studio 14\Projects\SignalRTest3\SignalRTest3\Global.asax.cs 18 Build Active

This was for v1 Signal R – superseded in 2.

CORS

During trying to get this working, the prospect of using CORS came up. This enables cross domain requests, which are typically prohibited.

Proxies

The generated Proxy can be viewed (navigate to http://localhost:4053/signalr/hubs):

 $.hubConnection.prototype.createHubProxies = function () {
        var proxies = {};
        this.starting(function () {
            // Register the hub proxies as subscribed
            // (instance, shouldSubscribe)
            registerHubProxies(proxies, true);
this._registerSubscribedHubs();
        }).disconnected(function () {
            // Unsubscribe all hub proxies when we "disconnect".  This is to ensure that we do not re-add functional call backs.
            // (instance, shouldSubscribe)
            registerHubProxies(proxies, false);
        });
proxies['MyHub1'] = this.createHubProxy('MyHub1'); 
        proxies['MyHub1'].client = { };
        proxies['MyHub1'].server = {
            hello: function (message) {
                return proxies['MyHub1'].invoke.apply(proxies['MyHub1'], $.merge(["Hello"], $.makeArray(arguments)));
             }
        };
return proxies;
    };

References:

https://www.asp.net/signalr/overview/guide-to-the-api/hubs-api-guide-javascript-client

https://docs.microsoft.com/en-us/aspnet/signalr/overview/getting-started/tutorial-getting-started-with-signalr

https://docs.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/hubs-api-guide-javascript-client

https://github.com/SignalR/SignalR/wiki/Faq

http://stackoverflow.com/questions/42108193/signalr-test-project-not-working-as-expected

http://www.jeffreyfritz.com/2015/05/where-did-my-asp-net-bundles-go-in-asp-net-5/