Search code examples
c#asp.net-mvcazuresignalrservicebus

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.


Solution

  • 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!