user713723 user713723 - 2 months ago 20
C# Question

Using SignalR to broadcast data to Azure Web App from Azure ServiceBus Message Queue

I have an Azure ServiceBus Message Queue that's receiving messages from a full-blown Windows Application. This part works great (dozens of messages per minute). I know the messages are there. The ServiceBus namespace is something like dtx-ns, and the Queue name is something like dtxrm001.

I also have a fully developed MVC 5 Azure Web App. I want to wire up a new View page that, using SignalR, receives the Messages from the Azure Service Bus Message Queue.

All I want to do is see the ServiceBus Queue messages (that are coming from anyone running the full-blown Windows application) by going to a web page.

This is basically sensor data coming in, say like temperatures from sensors.

After buying 5 SignalR books and spending 3 weeks on this, I guess I need some direction. Do I add a web role to my Azure MVC app? Do I add a worker role? Do I use dependency injection and the SignalRBackplaneMessageBus?

Seems like a simple task, but I'm at a loss, now, at this point, as to what the methodology is? Nothing seems to make sense when you really get into a sample and then try to wire up the actual broadcasting of the messages from the ServiceBus Message Queue.

Here's some code for the Startup of the MVC web app that I've tried:

Dim cn1 As String = "Endpoint=sb://dtx-ns.servicebus.windows.net/;SharedAccessKeyName=myname;SharedAccessKey=mykey"
Dim config As New ServiceBusScaleoutConfiguration(cn1, "dtx1")
config.TopicCount = 3
config.BackoffTime = New TimeSpan(1, 0, 1)
config.IdleSubscriptionTimeout = New TimeSpan(1, 0, 0)
config.MaximumMessageSize = 20000
config.MaxQueueLength = 50

GlobalHost.DependencyResolver.UseServiceBus(config)
GlobalHost.Configuration.TransportConnectTimeout = TimeSpan.FromSeconds(10)

app.UseCors(CorsOptions.AllowAll)
app.MapSignalR()


The endpoint shown above is part of the connection string of the Service Bus Message Queue. If the above looks correct, then what do I do to program the Hub to send messages FROM THIS CONNECTION?

Do I need a Web Role? Do I need to somehow implement a 'Backplane Hub' in a project that I add to my current MVC app? I'm stumped.

Answer

My need to have my Azure MVC-5 Web App read from my Azure Service Bus Message Queue and then send data to all client web pages via SignalR, has been resolved.

Code-wise, it is quite easy and elegant. Here is my solution:

First, my Visual Studio Solution has just one Project--the main web app project. In the root of the project, I have my SignalR hub class, called djwHub;

Imports Microsoft.AspNet.SignalR
Imports Microsoft.AspNet.SignalR.Hubs
Imports Microsoft.AspNet.SignalR.Messaging
Imports System.Threading.Tasks
Imports Microsoft.ServiceBus
Imports Microsoft.ServiceBus.Messaging

<HubName("djwHub")>
Public Class djwHub
    Inherits Hub

    Private connectString As String = "Endpoint=sb://mynamespace.servicebus.windows.net/;SharedAccessKeyName=myKeyName;SharedAccessKey=myKey"
    Private queueName As String = "myQueueName"
    Private m_count As Integer

    Public Sub beginReadingMessageQue()
        Dim rf = MessagingFactory.CreateFromConnectionString(connectString)

        Dim taskTimer = Task.Factory.StartNew(Async Function()
                                                  Dim receiver = Await rf.CreateMessageReceiverAsync(queueName, ReceiveMode.PeekLock)

                                                  While True
                                                      Dim timeNow As String = DateTime.Now.ToString()
                                                      Clients.All.sendServerTime(timeNow)

                                                      Try
                                                          Dim message = Await receiver.ReceiveAsync(TimeSpan.FromSeconds(5))

                                                          If message IsNot Nothing Then
                                                              Dim messageBody = message.GetBody(Of [String])()

                                                              Clients.All.sendNewMessage(messageBody)

                                                              Await message.CompleteAsync
                                                          Else
                                                              'no more messages in the queue
                                                              Exit Try
                                                          End If
                                                      Catch e As MessagingException
                                                          If Not e.IsTransient Then
                                                              'Console.WriteLine(e.Message)
                                                              'Throw
                                                          End If
                                                      End Try

                                                      'Delaying by 1/2 second.
                                                      Await Task.Delay(500)
                                                  End While

                                              End Function, TaskCreationOptions.LongRunning)
    End Sub
End Class

Now, my MVC-5 web app doesn't have a Startup class in the root. Instead, my startup occurs in the IdentityConfig.vb class that's located in my App_Start folder. So this is where I put app.MapSignalR() as shown here;

Imports Microsoft.AspNet.Identity
Imports Microsoft.Owin
Imports Microsoft.Owin.Security.Cookies
Imports Owin
Imports myApp.Users.Infrastructure
Imports Microsoft.Owin.Security.Google
Imports Microsoft.AspNet.SignalR
Imports Microsoft.Owin.Cors
Imports Microsoft.AspNet.SignalR.ServiceBus

Namespace Users
    Public Class IdentityConfig
        Public Sub Configuration(app As IAppBuilder)
            app.CreatePerOwinContext(Of AppIdentityDbContext)(AddressOf AppIdentityDbContext.Create)
            app.CreatePerOwinContext(Of AppUserManager)(AddressOf AppUserManager.Create)
            app.CreatePerOwinContext(Of AppRoleManager)(AddressOf AppRoleManager.Create)

            app.UseCookieAuthentication(New CookieAuthenticationOptions() With { _
                .AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, _
                .LoginPath = New PathString("/Account/Login") _
            })

            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie)
            app.MapSignalR()
        End Sub
    End Class
End Namespace

The only part left, is the web View page. Note that I've got 6 FusionChart gauges on the web page right now. But you should be able to pick out the SignalR function calls that talk to the djwHub;

@Code
    Layout = Nothing
End Code

<head>
    <title>Drillers Readout - Job #@ViewBag.JobNumber</title>
    @Scripts.Render("~/bundles/modernizr")
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @Scripts.Render("~/bundles/jqueryui")

    <script src="~/Scripts/jquery.signalR-2.2.1.js"></script>
    <script src="~/Scripts/jquery.signalR-2.2.1.min.js"></script>
    <script src="~/signalr/hubs"></script>
    <script type="text/javascript">
        (function () {
            var currentIndex = -1;
            var lastTime;
            var GxDisplay;
            var GyDisplay;
            var GzDisplay;
            var HxDisplay;
            var HyDisplay;
            var HzDisplay;

            var myHub = $.connection.djwHub;
            $.connection.hub.logging = true;

            myHub.client.sendNewMessage = function (message) {
                var myArray = JSON.parse(message);

                currentIndex += 1;
                //var dataId = myArray.dataCount;

                if (currentIndex == 0) {
                    lastTime = new Date(myArray.time1);
                } else {
                    var newTime = new Date(myArray.time1);
                    if (newTime >= lastTime) {
                        var dataId = myArray.dataCount;
                        var Gx = myArray.Gx;
                        var Gy = myArray.Gy;
                        var Gz = myArray.Gz;
                        var Hx = myArray.Hx;
                        var Hy = myArray.Hy;
                        var Hz = myArray.Hz;

                        lastTime = newTime;

                        GxDisplay.feedData("value=" + Gx);
                        GyDisplay.feedData("value=" + Gy);
                        GzDisplay.feedData("value=" + Gz);
                        HxDisplay.feedData("value=" + Hx);
                        HyDisplay.feedData("value=" + Hy);
                        HzDisplay.feedData("value=" + Hz);

                        $("#newMessage").text('#' + dataId + ": " + lastTime + " Gx=" + Gx.toFixed(2) + " Gy=" + Gy.toFixed(2) + " Gz=" + Gz.toFixed(2)
                            + " Hx=" + Hx.toFixed(2) + " Hy=" + Hy.toFixed(2) + " Hz=" + Hz.toFixed(2));
                    }
                }
            };

            $.connection.hub.start().done(function () {
                myHub.server.beginReadingMessageQue();
            });

            myHub.client.sendServerTime = function (serverTime) {
                $("#newTime").text(serverTime);
            };

            FusionCharts.ready(function () {
                GxDisplay = new FusionCharts({
                    type: 'angulargauge',
                    renderAt: 'GxChart',
                    width: '250',
                    height: '175',
                    dataFormat: 'json',
                    dataSource: {
                        "chart": {
                            "caption": "Gx",
                            "subcaption": "",
                            "lowerLimit": "-2000",
                            "upperLimit": "2000",
                            "lowerLimitDisplay": "",
                            "upperLimitDisplay": "",
                            "showValue": "1",
                            "valueBelowPivot": "1",
                            "theme": "fint"
                        },
                        "colorRange": {
                            "color": [{
                                "minValue": "-2000",
                                "maxValue": "2000",
                                "code": "#ADD8E6"
                            }]
                        },
                        "dials": {
                            "dial": [{
                                "id": "fcGx",
                                "value": "0"
                            }]
                        }
                    }
                });

                GyDisplay = new FusionCharts({
                    type: 'angulargauge',
                    renderAt: 'GyChart',
                    width: '250',
                    height: '175',
                    dataFormat: 'json',
                    dataSource: {
                        "chart": {
                            "caption": "Gy",
                            "subcaption": "",
                            "lowerLimit": "-2000",
                            "upperLimit": "2000",
                            "lowerLimitDisplay": "",
                            "upperLimitDisplay": "",
                            "showValue": "1",
                            "valueBelowPivot": "1",
                            "theme": "fint"
                        },
                        "colorRange": {
                            "color": [{
                                "minValue": "-2000",
                                "maxValue": "2000",
                                "code": "#ADD8E6"
                            }]
                        },
                        "dials": {
                            "dial": [{
                                "id": "fcGy",
                                "value": "0"
                            }]
                        }
                    }
                });

                GzDisplay = new FusionCharts({
                    type: 'angulargauge',
                    renderAt: 'GzChart',
                    width: '250',
                    height: '175',
                    dataFormat: 'json',
                    dataSource: {
                        "chart": {
                            "caption": "Gz",
                            "subcaption": "",
                            "lowerLimit": "-2000",
                            "upperLimit": "2000",
                            "lowerLimitDisplay": "",
                            "upperLimitDisplay": "",
                            "showValue": "1",
                            "valueBelowPivot": "1",
                            "theme": "fint"
                        },
                        "colorRange": {
                            "color": [{
                                "minValue": "-2000",
                                "maxValue": "2000",
                                "code": "#ADD8E6"
                            }]
                        },
                        "dials": {
                            "dial": [{
                                "id": "fcGz",
                                "value": "0"
                            }]
                        }
                    }
                });

                HxDisplay = new FusionCharts({
                    type: 'angulargauge',
                    renderAt: 'HxChart',
                    width: '250',
                    height: '175',
                    dataFormat: 'json',
                    dataSource: {
                        "chart": {
                            "caption": "Hx",
                            "subcaption": "",
                            "lowerLimit": "-100000",
                            "upperLimit": "100000",
                            "lowerLimitDisplay": "",
                            "upperLimitDisplay": "",
                            "showValue": "1",
                            "valueBelowPivot": "1",
                            "theme": "fint"
                        },
                        "colorRange": {
                            "color": [{
                                "minValue": "-100000",
                                "maxValue": "100000",
                                "code": "#ff1493"
                            }]
                        },
                        "dials": {
                            "dial": [{
                                "id": "fcHx",
                                "value": "0"
                            }]
                        }
                    }
                });

                HyDisplay = new FusionCharts({
                    type: 'angulargauge',
                    renderAt: 'HyChart',
                    width: '250',
                    height: '175',
                    dataFormat: 'json',
                    dataSource: {
                        "chart": {
                            "caption": "Hy",
                            "subcaption": "",
                            "lowerLimit": "-100000",
                            "upperLimit": "100000",
                            "lowerLimitDisplay": "",
                            "upperLimitDisplay": "",
                            "showValue": "1",
                            "valueBelowPivot": "1",
                            "theme": "fint"
                        },
                        "colorRange": {
                            "color": [{
                                "minValue": "-100000",
                                "maxValue": "100000",
                                "code": "#ff1493"
                            }]
                        },
                        "dials": {
                            "dial": [{
                                "id": "fcHy",
                                "value": "0"
                            }]
                        }
                    }
                });

                HzDisplay = new FusionCharts({
                    type: 'angulargauge',
                    renderAt: 'HzChart',
                    width: '250',
                    height: '175',
                    dataFormat: 'json',
                    dataSource: {
                        "chart": {
                            "caption": "Hz",
                            "subcaption": "",
                            "lowerLimit": "-100000",
                            "upperLimit": "100000",
                            "lowerLimitDisplay": "",
                            "upperLimitDisplay": "",
                            "showValue": "1",
                            "valueBelowPivot": "1",
                            "theme": "fint"
                        },
                        "colorRange": {
                            "color": [{
                                "minValue": "-100000",
                                "maxValue": "100000",
                                "code": "#ff1493"
                            }]
                        },
                        "dials": {
                            "dial": [{
                                "id": "fcHz",
                                "value": "0"
                            }]
                        }
                    }
                });

                GxDisplay.render();
                GyDisplay.render();
                GzDisplay.render();
                HxDisplay.render();
                HyDisplay.render();
                HzDisplay.render();
            });
        }());
    </script> 
</head>
<body>
    <div id="newTime"></div><br />
    <ul id="newMessage"></ul>
    <div id="gCharts">
        <div id="GxChart"></div>
        <div id="GyChart"></div>
        <div id="GzChart"></div>       
    </div>
    <div id="hCharts">
        <div id="HxChart"></div>
        <div id="HyChart"></div>
        <div id="HzChart"></div>
    </div>
</body>

Special thanks here to astaykov & Ashley Medway, and to Microsoft, for making all of this possible!