Saturday, December 1, 2012

SignalR in Web, WPF, Console and Windows Services App in VB


Update: I have updated this post to reflect the latest SignalR (1.0.1) release.  This is some testing I did with SignalR in VB for different kinds of .Net projects like Windows Service, console and WPF application.  This post has different sections and each section has its own .Net solution so you can run them individually.  All the sourcecode for this blog post is available on my github page.  There are two SignalR Owin Host applications, a Console application and a Windows Services Application written in Vb.NET and it will be consumed by various .NET client applications such as Console Application, Web Application using Javascript, WPF Application using VB.Net.

SignalR Owin Hosts
    Console Application
    Windows Services Application
SignalR Owin Clients
    Console Application
       Web Application
       WPF Application

So let’s get started by first creating our SignalR Owin Host Console Application. 

SignalR Server Console Application in VB
Create a console application in VB and Install following nuget packages using Package Manager Console to create SignalR Hub.

>Install-Package Microsoft.Aspnet. Owin.Hosting -pre
>Install-Package Microsoft.Aspnet. Owin.Host.HttpListener -pre
>Install-Package Microsoft.Aspnet.Signalr.Owin

Without further due here is the code to start the server and create a hub which will start sending messages to clients upon arrival.
---------------------------------------
Imports Microsoft.AspNet.SignalR
Imports Microsoft.AspNet.SignalR.Hubs
Imports Microsoft.Owin.Hosting
Imports Owin
Module Module1
    Sub Main()
        Dim url As String = "http://localhost:8080/"
        Using WebApplication.Start(Of Startup)(url)
            Console.ForegroundColor = ConsoleColor.Green
            Console.WriteLine("Server running on {0}", url)
            Console.WriteLine("Press any key to start sending events to connected clients")
            Console.ReadLine()
            Dim context As IHubContext = GlobalHost.ConnectionManager.GetHubContext(Of MyHub)()
            For x As Integer = 0 To 100
                System.Threading.Thread.Sleep(3000)
                Console.WriteLine("Server Sending Value to Client X: " + x.ToString())
                context.Clients.All.addMessage(x.ToString())
            Next
            Console.ReadLine()
        End Using
    End Sub
    Public Class Startup
        Public Sub Configuration(ByVal app As IAppBuilder)
            Dim config = New HubConfiguration With {.EnableCrossDomain = True}
            app.MapHubs(config)
        End Sub
    End Class
    <HubName("myHub")> _
    Public Class MyHub
        Inherits Hub
        Public Sub Chatter(param As String)
            Console.WriteLine(param)
            Clients.All.addMessage(param)
        End Sub
    End Class
End Module
-----------------------------------------------
 IHubContext will give you access to all the clients and then you can communicate to all the clients. When you run this application you will see the following screenshot.  Once you have your client code written then you can press any key and it will start sending values to connected clients. 


Now lets consume this Self Host in a Client Console Application
SignalR Client Console Application in VB.  
Create a Console Application in VB and install nuget package using Package Manager Console. 

>Install-Package Microsoft.Aspnet.Signalr.Client

Then paste the following code into your Module1.vb page.  And run the application assuming you have already started the host application.

--------------------------------------
Imports Microsoft.AspNet.SignalR.Client.Hubs
Imports Microsoft.AspNet.SignalR
Module Module1

    Sub Main()
        Dim connection = New HubConnection("http://localhost:8080")

        Dim myHub = connection.CreateHubProxy("myHub")

        connection.Start().Wait()
        Console.ForegroundColor = ConsoleColor.Yellow
        myHub.Invoke(Of String)("chatter""Hi!! Server") _
        .ContinueWith(
            Sub(task)
                If task.IsFaulted Then
                    Console.WriteLine("Could not Invoke the server method Chatter: {0}", _
                                      task.Exception.GetBaseException())
                Else
                    Console.WriteLine("Success calling chatter method")
                End If
            End Sub)

        myHub.On(Of String)("addMessage", _
            Sub(param)
                Console.WriteLine("Client receiving value from server: {0}", param.ToString())
            End Sub)
        Console.ReadLine()
    End Sub
End Module
-----------------------------------

Now it is time to test our application.  Run our application side by side.  I have server or host running on the left hand side with green color text and client running on the right with yellow color text.


When the client application is started, it invokes the chatter method on the server. 
  
      myHub.Invoke(Of String)("chatter""Hi!! Server") _
        .ContinueWith(
            Sub(task)
                If task.IsFaulted Then
                    Console.WriteLine("Could not Invoke the server method Chatter: {0}", _
                                      task.Exception.GetBaseException())
                Else
                    Console.WriteLine("Success calling chatter method")
                End If
            End Sub)

You will see “Hi!! Server” value received by the server on the left.  That value is again round tripped to the client and you can see it displayed by the client on the right hand side. 

  myHub.On(Of String)("addMessage", _
            Sub(param)
                Console.WriteLine("Client receiving value from server: {0}", param.ToString())
            End Sub)

Now click on the server console window and press any key.  It will start sending value to the client in real time.  We do this by first getting IHubContext object which knows about all the clients.

            Dim context As IHubContext = GlobalHost.ConnectionManager.GetHubContext(Of MyHub)()
            For x As Integer = 0 To 100
                System.Threading.Thread.Sleep(3000)

                Console.WriteLine("Server Sending Value to Client X: " + x.ToString())
                context.Clients.All.addMessage(x.ToString())
            Next



Now let’s create some more clients that will work with our SignalR Owin Host Console Application. 

SignalR Client WPF Application in VB 
Create a WPF Application in VB and install the following package using Package Manager Console.

>Install-Package Microsoft.AspNet.SignalR.Client.

XAML Side
When you create a WPF application by default MainWindow.xaml file is opened and there paste the following code.  There is a button which will invoke Chatter method on the server. 

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525" Loaded="MainWindow_Loaded" >
    <ScrollViewer>
        <StackPanel>
            <Button x:Name="btnShowSignal" Content="Invoke Chatter Method"  Click="btnShowSignal_Click"></Button>
            <TextBlock Name="txtblock_message" Text="{Binding UpdateText}"></TextBlock>
        </StackPanel>
    </ScrollViewer>
</Window>

Codebehind side
On the codebehind side in the MainWindow.xaml.vb we will create a property named UpdateText which will be set everytime our client receives a new text.  In the XAML side, we bind our TextBlock to this UpdateText property so whenever this property is changed it will be shown in the UI.  For all this to happen we have to implement INotifyPropertyChanged Interface. 

--------------------------------------
Imports Microsoft.AspNet.SignalR.Client
Imports System.ComponentModel
Imports Microsoft.AspNet.SignalR.Client.Hubs

Partial Public Class MainWindow
    Inherits Window
    Implements INotifyPropertyChanged
    Public connection As HubConnection = New HubConnection("http://localhost:8080")
    Public myHub As IHubProxy = connection.CreateHubProxy("myHub")

    Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgsHandles Me.Loaded
        DataContext = Me
        connection = New HubConnection("http://localhost:8080")

        myHub = connection.CreateHubProxy("myHub")
        myHub.On(Of String)("addMessage"AddressOf addMessage)
    End Sub

    Async Sub btnShowSignal_Click(sender As Object, e As RoutedEventArgs)
        Await connection.Start()
        Await myHub.Invoke("Chatter""Hello Server")
    End Sub

    Private m_updatetext As String
    Public Property UpdateText() As String
        Get
            Return m_updatetext
        End Get
        Set(ByVal value As String)
            m_updatetext = value
            RaisePropertyChanged("UpdateText")
        End Set
    End Property

    Private Sub addMessage(ByVal sValue As String)
        UpdateText += Environment.NewLine
        UpdateText += sValue
    End Sub
    Private Sub RaisePropertyChanged(prop As String)
        RaiseEvent PropertyChanged(MeNew PropertyChangedEventArgs(prop))
    End Sub
    Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgsImplements INotifyPropertyChanged.PropertyChanged
End Class
-----------------------------------------------
Let’s run both of our applications side by side. First Run the Host application and then run the WPF application and put them side by side.  In the screenshot below I have SignalR Owin Host Console Application on the left and WPF application on the right hand side.  Click on the button Invoke Chatter Method which will invoke the server’s Chatter method. 


 As soon as you click on the button it passes the value “Hello Server” to the server and server sends that value back to all the connected clients.  If you want to give it a try then fire up all the SignalR Client Console application too and press any key on the server console window.


After you press any key on the Host application it will send values to two clients as shown below.


And you might be tempted to ask me what about a web application which is the most used application by all of us right. 

SignalR Client Hub Web App consuming Owin Host
Let’s create an empty web application and install Nuget packages as shown below.

>Install-Package Jquery
>Install-Package Microsoft.AspNet.SignalR.JS

Create a simple html page, I have named it as SignalRClientNoProxy.html. You can run the application now to see the output.
-------------------------------------------------
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script src="Scripts/jquery-2.0.0.min.js"></script>
    <script src="Scripts/jquery.signalR-1.0.1.min.js"></script>    
    <script src="/signalr/hubs" type="text/javascript"></script>
    <script type="text/javascript">
        $(function () {
            var connection = $.hubConnection('http://localhost:8080');

            connection.start();
            var myHub = connection.createHubProxy("myHub");

            $("#broadcast").click(function () {
                myHub.invoke('chatter', $("#msg").val());
            });

            myHub.on('addMessage',function (message) {
                $("#message").append('<li>' + message + '</li>');
            });
        });
    </script>
</head>
<body>
    <div>
        <input id="msg" type="text"/>
        <input type="button" id="broadcast" value="broadcast" />
        <ul id="message">

        </ul>
    </div>
</body>
</html>
------------------------------------------
As a pre-requisite to running our client application on the right we run our server application on the left.


Type something in the textbox on the right window and post something to the server. 


Lets’ run all the applications so far and see how this works.  So one Host and three clients right.

Let now move on to some real world application like Windows Service Application running SignalR Owin Host sending messages to all clients.

SignalR Server Windows Services Application
Now porting this SignalR Owin Host code to window service is easy. Here is the code to do a simple service with signalR. You can check out the code to do a simple windows service from the All-In-One code framework from micrsoft.
------------------------------------------------------
Imports System.Threading
Imports Microsoft.AspNet.SignalR
Imports Microsoft.AspNet.SignalR.Hubs
Imports Microsoft.Owin.Hosting
Imports Owin

Public Class Service1
    Private stopping As Boolean
    Private stoppedEvent As ManualResetEvent
    'global variable context to update clients from everywhere in the service.
    Dim context As IHubContext = GlobalHost.ConnectionManager.GetHubContext(Of MyHub)()
    Dim url As String = "http://localhost:8080"
    Public Sub New()
        InitializeComponent()

        Me.stopping = False
        Me.stoppedEvent = New ManualResetEvent(False)
    End Sub

    Protected Overrides Sub OnStart(ByVal args() As String)
        ' Log a service start message to the Application log.
        Me.EventLog1.WriteEntry("Service is in OnStart.")
        Dim url As String = "http://localhost:8080/"
        Using WebApplication.Start(Of Startup)(url)
            
        End Using
        ' Queue the main service function for execution in a worker thread.
        ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf ServiceWorkerThread))
    End Sub


    ''' <summary>
    ''' The method performs the main function of the service. It runs on a 
    ''' thread pool worker thread.
    ''' </summary>
    ''' <param name="state"></param>
    Private Sub ServiceWorkerThread(ByVal state As Object)
        ' Periodically check if the service is stopping.
        Do While Not Me.stopping
            ' Perform main service function here...
            Dim context As IHubContext = GlobalHost.ConnectionManager.GetHubContext(Of MyHub)()
            For x As Integer = 0 To 100
                System.Threading.Thread.Sleep(3000)
                context.Clients.All.addMessage(x.ToString())
            Next
            Thread.Sleep(2000)  ' Simulate some lengthy operations.
        Loop

        ' Signal the stopped event.
        Me.stoppedEvent.Set()
    End Sub

    Protected Overrides Sub OnStop()
        ' Log a service stop message to the Application log.
        Me.EventLog1.WriteEntry("Service is in OnStop.")

        ' Indicate that the service is stopping and wait for the finish of 
        ' the main service function (ServiceWorkerThread).
        Me.stopping = True
        Me.stoppedEvent.WaitOne()
    End Sub
End Class
Public Class Startup
    Public Sub Configuration(ByVal app As IAppBuilder)
        Dim config = New HubConfiguration With {.EnableCrossDomain = True}
        app.MapHubs(config)
    End Sub
End Class
<HubName("myHub")> _
Public Class MyHub
    Inherits Hub
    Public Sub Chatter(param As String)
        Console.WriteLine(param)
        Clients.All.addMessage(param)
    End Sub
End Class
----------------------------------------
You won’t be able to just hit F5 with a windows service, first you will have to install the windows service then you can run your WPF application and then you could see real time output from the windows service.  As I said before the entire source code for all these apps is on my GitHub page.

1 comment:

  1. Any full source sample using SignalR and Winforms application for notify long running process?

    ReplyDelete