1
votes

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.

2
Theres a lot of information here but I still don't know what exactly you are trying to do? Do you want to have SignalR post to ServiceBus?Ashley Medway
Nope. All I want to do is sign on to a web page (client) and see the data that's coming through the Azure Service Bus Message Queue.Dominic Whitham
I see so the other way round, when a message comes into the ServiceBus broadcast to SignalR?Ashley Medway
Let me be more specific. Say there are 50 drilling rigs world-wide. While they're drilling, there are lots of sensors, like temperature, pressures, etc... This sensor data is being sent to an Azure Service Bus message queue. I want to be able to view a web page and see some graphical gauges showing this incoming data. I will filter to only show data from the job site that the user has clicked on (I will do this in JavaScript).Dominic Whitham
@DavidMakogon chat would have been better, but I disagree, after getting to the detail I think there is a good answer that can be given. If the question can be edited to include the detail then we can cleanup the comments.Ashley Medway

2 Answers

1
votes

Your are using code to scale out SignalR via ServiceBus (so called ServiceBus back plane - the confusing web role(s) in the sample is actually your MVC application which can run in App Service or as a WebRole. Your SignalR Hub do live together with your web app in the general case). Which is correct for the purpose of scaling out SignalR itself when running on Azure Web Apps. But is not what you ask for.

What you ask for is to read messages from the ServiceBus and broadcast them to your SignalR connected clients. Which is totally different use case. There couple of options you can use to achieve your goal:

In both cases, the least known fact that SignalR has a "native" .NET client will help you (you can connect to SignalR from any .NET Application, including one running on your Windows Phone or your Desktop).

I am not providing any code samples here, but I guess that I give clearance in your doubts.


1
votes

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!