SignalR

SignalR On OWIN & JavaScript

The purpose of this post is to show the different SignalR implementations available for an OWIN WebApi & JavaScript.


OWIN WebApi

NuGet Packages used:

  • Microsoft.Owin.SelfHost
  • Microsoft.AspNet.SignalR.SelfHost
  • And all related dependencies

To get started, follow the tutorial here

In the Startup class, where the OWIN configuration is set, we must include the SignalR configuration to run in the same pipeline.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void Configuration(IAppBuilder app)
{
var httpConfiguration = SetupHttpConfiguration();
app.UseWebApi(httpConfiguration);

app.Map("/signalr", map =>
{
// Setup the cors middleware to run before SignalR.
// By default this will allow all origins. You can
// configure the set of origins and/or http verbs by
// providing a cors options with a different policy.
map.UseCors(CorsOptions.AllowAll);

var hubConfiguration = new HubConfiguration
{
// You can enable JSONP by uncommenting line below.
// JSONP requests are insecure but some older browsers (and some
// versions of IE) require JSONP to work cross domain
// EnableJSONP = true
};

// Run the SignalR pipeline. We're not using MapSignalR
// since this branch already runs under the "/signalr" path.
map.RunSignalR(hubConfiguration);
});
}

The following 2 hubs purely to show the difference of how they will be used in the front-end.

  • Generated Proxy Hub

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class GeneratedProxyHub : Hub
    {
    // http://stackoverflow.com/questions/31169509/signalr-how-to-truly-call-a-hubs-method-from-the-server-c-sharp
    private static IHubContext context = GlobalHost.ConnectionManager.GetHubContext<GeneratedProxyHub>();

    public override Task OnConnected()
    {
    var qs = Context.QueryString["user"];
    Send("GeneratedProxyHub", qs);
    return base.OnConnected();
    }

    public void Send(string name, string message)
    {
    Clients.All.addMessage(name, "Generated Proxy Hub: " + message);
    }

    public static void Push(string name, string message)
    {
    context.Clients.All.addMessage(name, "Generated Proxy Hub: " + message);
    }
    }

There is nothing special in the setup between them, except for the IHubContext in the Generated Proxy Hub (above). That is only used when calling Push() from a controller as explained later. So the IHubContext can be removed safely without breaking the normal functionality.

  • Manual Proxy Hub

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class ManualProxyHub : Hub
    {
    public override Task OnConnected()
    {
    var qs = Context.QueryString["manualProxyUser"];
    ManualProxySend("ManualProxyHub", qs);
    return base.OnConnected();
    }

    public void ManualProxySend(string name, string message)
    {
    Clients.All.addManualMessage(name, "Manual Proxy Hub: " + message);
    }

    public void ManualProxyPush(string name, string message)
    {
    Clients.All.addManualMessage(name, "Manual Proxy Hub: " + message);
    }
    }

Front-End

The 2 front-end implementations are: (best explained here)

  • Generated Proxy
  • Manual Proxy (without generated proxy)

The major difference being:

  • Generated Proxy: Gets the hub’s context in a .js script generated by the SignalR WebApi on page load via <script> tags from the index.html

    1
    2
    <script src="bower_components/signalr/jquery.signalR.js"></script>
    <script src="http://localhost:9000/signalr/hubs"></script>

    Usage:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    var generatedProxy = function() {
    // By default, the hub location is the current server
    // if you are connecting to a different server, specify the URL before calling the start method,
    $.connection.hub.url = "http://localhost:9000/signalr";

    // Declare a proxy to reference the hub.
    var generatedProxyHub = $.connection.generatedProxyHub;

    // Create a function that the hub can call to broadcast messages.
    generatedProxyHub.client.addMessage = function (name, message) {
    addMessage(name, message);
    };

    // add querystring to send data to server on connect
    $.connection.hub.qs = { 'user' : username };

    // Start the connection.
    $.connection.hub.start().done(function () {
    $('#sendmessage').click(function () {
    // Call the Send method on the hub.
    generatedProxyHub.server.send($('#displayname').val(), $('#message').val());
    // Clear text box and reset focus for next comment.
    $('#message').val('').focus();
    });
    });
    }
  • Manual Proxy: Only accessing the hubs when needed. Notice the custom createHubProxy("ManualProxyHub")

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    var manualProxy = function() {
    // declare connection and proxy to communicate with the hub
    var hubConnection = $.hubConnection("http://localhost:9000/signalr");
    var hubProxy = hubConnection.createHubProxy("ManualProxyHub");

    hubProxy.on("addManualMessage", function(name, message) {
    addMessage(name, message);
    });

    // add querystring to send data to server on connect
    hubConnection.qs = { 'manualProxyUser' : username };

    hubConnection.start().done(function () {
    $('#sendManualProxyMessage').click(function () {
    // Call the Push method on the manual proxy hub.
    hubProxy.invoke('ManualProxyPush', $('#displayname').val(), $('#message').val());

    // Clear text box and reset focus for next comment.
    $('#message').val('').focus();
    });
    });
    }

Other small differences include dynamic function calls vs event binding and invoking methods.

SignalR access from ApiController

As requests to an ApiController has a HTTP Context, we need to get the context for the Hub’s WebSocket protocol.

For ease of use I have created a static IHubContext in the Generated Proxy Hub (above) which can be used when calling the Hub’s methods from the controllers, as seen here.

1
2
3
4
5
6
7
8
public class TestController : ApiController
{
public string Get()
{
GeneratedProxyHub.Push("WebApi", "Hello World!");
return "Result sent to request and connected SignalR hub clients";
}
}

Resources

Demo Source Code: https://github.com/johan-v-r/SignalR

http://www.asp.net/signalr/overview/deployment/tutorial-signalr-self-host

http://www.asp.net/signalr/overview/guide-to-the-api/hubs-api-guide-javascript-client

http://stackoverflow.com/questions/31169509/signalr-how-to-truly-call-a-hubs-method-from-the-server-c-sharp

https://stackoverflow.com/a/31063193/5954805