2

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.

  • 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 Sep 13 '16 at 10:56
  • 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 Sep 13 '16 at 10:58
  • I see so the other way round, when a message comes into the ServiceBus broadcast to SignalR? – Ashley Medway Sep 13 '16 at 10:59
  • 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 Sep 13 '16 at 11:00
  • For now, I'd just settle for seeing text data coming in via SignalR (I can wire up the rest, i.e. gauges, no problem). – Dominic Whitham Sep 13 '16 at 11:02
  • I guess I'm not understanding the implementation of "seeing" the data in the Azure Service Bus Message Queue via a web app. What functions in what classes are doing this? What is a Web Role for? Do I need a web role at all? What functions "peek" at the SBMQ data and then send them along through the Hub and into the web page view? – Dominic Whitham Sep 13 '16 at 11:06
  • Yeah so you need to have created a SignalR hub lets call it "DrillHub", you then need to be listening to "DrillHub" in JavaScript on a page. Somewhere else in your code you need to be listening to your ServiceBus, when a message comes into the ServiceBus relay the message to the "DrillHub". Unfortunately is not really a lot of code but it is really specific. – Ashley Medway Sep 13 '16 at 11:06
  • If you look at this tutorial, that should be pretty helpful for setting up the SignalR hub. http://www.asp.net/signalr/overview/getting-started/tutorial-getting-started-with-signalr – Ashley Medway Sep 13 '16 at 11:08
  • Thanks. That always seems to be where I'm getting stuck. I don't know what methods are used to look at the SBMQ data and then transfer them up to the hub? How do I get them to talk to one another. – Dominic Whitham Sep 13 '16 at 11:08
  • See this post on how to get an instance of your hub, after it has been setup, i.e. to use in your ServiceBus handler. http://stackoverflow.com/a/15128273/1398425 – Ashley Medway Sep 13 '16 at 11:09
  • I've looked at literally HUNDREDS of examples. I've downloaded lots of sample projects. And I bought 5 books on SignalR, including the "SignalR Real-time Application Cookbook". And I've spent hundreds of hours on this, programming-wise. Not to mention I've got 33 years of programming under my belt. So, what is the process of what I need to do? thanks! – Dominic Whitham Sep 13 '16 at 11:09
  • See https://azure.microsoft.com/en-us/documentation/articles/service-bus-dotnet-get-started-with-queues/ on how to listen to a MessageQueue – Ashley Medway Sep 13 '16 at 11:10
  • It is quite literally a mix of the three links I've mentioned, I've favourited the question and I can post a full answer this evening, but that's approx 10 hours from now. Can't do much more until then. – Ashley Medway Sep 13 '16 at 11:12
  • Wow, that's great, thanks! So #1, I create a class that listens for new queue messages, via the client.onMessage, and the #2, I send that message up to the Hub, and then SignalR JavaScript code on the client web page grabs it, right? – Dominic Whitham Sep 13 '16 at 11:14
  • Yeah thats the gist, bare minimum I think you need two C# class: SignalR hub & MessageQueue Listener. Then the 1 JavaScript to listen to the SignalR hub. – Ashley Medway Sep 13 '16 at 11:16
  • 1
    @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 Sep 13 '16 at 15:53
  • As I related above, I spent 3 weeks on this and bought 5 books on SignalR--so just learning the correct procedures/what I need to do to accomplish my goal, is something that 1) I haven't found anywhere else on the web, including at SO, and 2) it is a very specific question that I needed an answer to. The web is chock full of very simplistic usages for SignalR, like Chat, but a real-world solution is what I needed. I would have thought that displaying real-time sensor data is exactly what SignalR was designed to do. I would bet that BP Oil is using it...Now, on to getting it done! – Dominic Whitham Sep 14 '16 at 08:14

2 Answers2

1

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.


astaykov
  • 30,768
  • 3
  • 70
  • 86
  • I am checking into your answer...I believe it's the solution I need, in the same way that Ashley Medway had presented what I needed to do, in the comments above. I will come back and mark it as an Answer, once I've done my due diligence, thanks. – Dominic Whitham Sep 14 '16 at 08:09
1

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!

  • Thanks for posting the solution. The discussion in the comments on the original question were quite helpful as well. – Greg Grater Oct 25 '16 at 16:38