Dela via


Real-time with ASP.NET SignalR and Azure Mobile .NET Backend

We just released an update for Azure Mobile Services .NET backend which enables you to use ASP.NET SignalR for real-time, bi-directional communications with your mobile applications. SignalR will use WebSockets under the covers when it's available, and fallback to other “techniques” (i.e. HTTP hacks ;) when it isn't. Regardless of the mode, your application code stays the same.

The integration with Azure Mobile Services includes:

  • Unified Authentication: Protect your SignalR Hubs the same way you protect any of your Mobile Service Web API controllers using a simple AuthorizeLevel attribute.
  • Dependency Injection: SignalR Hubs are integrated with Autofac so that they can get instantiated with whatever dependencies they need.
  • Web API Integration: Send messages to your connected SignalR applications from any Web Api controller or scheduled job – we automatically give you access to SignalR Hubs from the ApiServices context.
  • Automatic Scale-out: When scaling out your Azure Mobile Service using multiple front-ends we automatically scale out SignalR using Azure Service Bus as the backplane for sync’ing between the front-ends.

If you are not familiar with ASP.NET SignalR then there’s a bunch of great tutorials available and if you are new to Azure Mobile Services then there’s a good introduction in Scott Guthrie’s blog.

Ok, enough with the intro, let’s get started…

Getting the Latest Bits

You can either start from a Visual Studio Azure Mobile Service project template or you can start using a Quick Start project downloaded from your already created Azure Mobile Service (using the Azure Portal). For simplicity, we here use the Quick Start project with an associated Windows Store client App. First you need to update the following NuGet references in your Azure Mobile .NET backend Visual Studio project to point to the latest release (1.0.295):

  1. WindowsAzure.MobileServices.Backend
  2. WindowsAzure.MobileServices.Backend.Tables
  3. WindowsAzure.MobileServices.Backend.Entity 

It should look like this:

UpdateNugets

Second, you need to add a reference to the WindowsAzure.MobileServices.Backend.SignalR NuGet package:

SignalRNuget

Creating a Simple SignalR Hub

ASP.NET SignalR is an optional extension to Azure Mobile Services that only is loaded if you ask it to, so the first thing to do is to add this line to your WebApiConfig.Register method (see line 6 -- the rest of that methods stays the same):

    1: public static class WebApiConfig
    2: {
    3:     public static void Register()
    4:     {
    5:         // Initialize SignalR
    6:         SignalRExtensionConfig.Initialize();
    7:  
    8:         ...
    9:  
   10:     }
   11: }

Next, add a Hub class to your project that looks like this:

    1: public class ChatHub : Hub
    2: {
    3:     public ApiServices Services { get; set; }
    4:  
    5:     public string Send(string message)
    6:     {
    7:         return "Hello from SignalR Chat Hub!";
    8:     }
    9: }

Note that we automatically inject the ApiServices class giving you access to a bunch of other Mobile Service features. You can of course also add your own dependencies (see Autofac and Azure Mobile Services .NET Backend).

This is all your need for the first round of the server code so compile and deploy your service to Azure directly from Visual Studio by right-clicking on the service project and select Publish. Then pick a mobile service and publish it. It should look like this:

Publish

Adding Client Support

In this scenario we use the Windows Store App from the Quick Start project but you can use any of the platforms supported by Azure Mobile Services including iOS, Android, Windows Phone, etc.

Start by installing the Microsoft ASP.NET SignalR .NET Client NuGet package into your client project. Then in the MainPage.xaml.cs file, add a method to the MainPage class looking like this (line 15 onwards is just to support the sample):

    1: private async Task ConnectToSignalR()
    2: {
    3:     hubConnection = new HubConnection(App.MobileService.ApplicationUri.AbsoluteUri);
    4:     if (user != null)
    5:     {
    6:         hubConnection.Headers["x-zumo-auth"] = user.MobileServiceAuthenticationToken;
    7:     }
    8:     else
    9:     {
   10:         hubConnection.Headers["x-zumo-application"] = App.MobileService.ApplicationKey;
   11:     }
   12:     IHubProxy proxy = hubConnection.CreateHubProxy("ChatHub");
   13:     await hubConnection.Start();
   14:  
   15:     string result = await proxy.Invoke<string>("Send", "Hello World!");
   16:     var invokeDialog = new MessageDialog(result);
   17:     await invokeDialog.ShowAsync();
   18:  
   19:     proxy.On<string>("hello", async msg =>
   20:     {
   21:         await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
   22:         {
   23:             var callbackDialog = new MessageDialog(msg);
   24:             callbackDialog.Commands.Add(new UICommand("OK"));
   25:             await callbackDialog.ShowAsync();
   26:         });
   27:     });
   28: }

Note that you can either be logged in with the Application Key (the default) or as a specific User using one of the authentication providers supported by Azure Mobile Services (Facebook, Microsoft Account, Twitter, Google).

Now add hubConnection as a private variable on the MainPage class (see line 6):

    1: public sealed partial class MainPage : Page
    2: {
    3:     private MobileServiceCollection<TodoItem, TodoItem> items;
    4:     private IMobileServiceTable<TodoItem> todoTable = App.MobileService.GetTable<TodoItem>();
    5:     private MobileServiceUser user;
    6:     private HubConnection hubConnection;

Finally, wire the ConnectToSignalR() method up to OnNavigatedTo(…) in the MainPage class so that it gets called upon app startup after any authentication step (if present):

    1: protected override async void OnNavigatedTo(NavigationEventArgs e)
    2: {
    3:     await AuthenticateAsync();
    4:  
    5:     await ConnectToSignalR();
    6:  
    7:     RefreshTodoItems();
    8: }

If you haven’t wired up authentication then don’t despair – by default the authentication model requires Application Key level access which ConnectToSignalR() takes into account already.

With this you should be able to run your client after which you should see a dialog looking like this:

HelloFromChatHub

Controlling Access Control

You can customize the authentication requirements by setting the required level for all SignalR clients or you can control it per Hub or even per Hub action using the Microsoft.AspNet.SignalR.AuthorizeLevelAttribute, for example, this would require User level authentication for talking to the Send action on the ChatHub:

    1: public class ChatHub : Hub
    2: {
    3:     public ApiServices Services { get; set; }
    4:  
    5:     [AuthorizeLevel(AuthorizationLevel.User)]
    6:     public string Send(string message)
    7:     {
    8:         return "Hello from SignalR Chat Hub!";
    9:     }
   10: }

To set it for all Hubs, set it using the ConfigOptions class in the WebApiConfig.Register method:

    1: public static void Register()
    2: {
    3:     // Initialize SignalR
    4:     SignalRExtensionConfig.Initialize();
    5:  
    6:     // Use this class to set configuration options for your mobile service
    7:     ConfigOptions options = new ConfigOptions();
    8:     options.SetRealtimeAuthorization(AuthorizationLevel.User);
    9:  
   10:     ...
   11: }

Sending Messages from Outside a SignalR Hub

In addition to talking to connection SignalR clients from inside a Hub you can also talk to them from Web API controllers and scheduled jobs. To illustrate this we will send a “hello” message from an ApiController but doing it from a scheduled job works just the same. First add a custom controller to your server project:

    1: public class SampleController : ApiController
    2: {
    3:     public ApiServices Services { get; set; }
    4:  
    5:     public string Get()
    6:     {
    7:         IHubContext hubContext = Services.GetRealtime<ChatHub>();
    8:         hubContext.Clients.All.hello("Hello Chat Hub clients from custom controller!");
    9:         return "Hello from custom controller!";
   10:     }
   11: }

Note that we get the IHubContext from the ApiServices class which is automatically injected into the controller. Now, deploy this and then hit the custom controller in Azure using the help page (you will get prompted for a username and password – for password use the Application Key, the user name doesn’t matter). You can now invoke the custom API:

CustomControllerChatHubIn addition to the response from the controller you should also see a message in your client looking like this:

HelloFromCustomController

Using SignalR Persistent Connections

For more advanced scenarios you can also add a SignalR Persistent Connection which is a lower level abstraction than the Hub. The principle is the same as for Hubs, you just inherit from PersistentConnection instead. A simple example looks like this:

    1: public class TestConnection : PersistentConnection
    2: {
    3:     public ApiServices Services { get; set; }
    4:  
    5:     protected override Task OnConnected(IRequest request, string connectionId)
    6:     {
    7:         base.Connection.Broadcast("Hello!");
    8:         return base.OnConnected(request, connectionId);
    9:     }
   10:  
   11:     protected override Task OnReceived(IRequest request, string connectionId, string data)
   12:     {
   13:         Connection.Broadcast(data);
   14:         return Task.FromResult(true);
   15:     }
   16: }

Scaling up using Service Bus Backplane

If you deploy your service in Basic or Standard tier which supports scale-out to more than one instance then we automatically wire up Azure Service Bus as the backplane for synchronizing the instances:

SignalRScaleOut

You can set the scaling tier for your mobile service using the Scale tab in the Azure Portal:

ScaleTab

That it – your mobile app can now go real-time!

Have fun!

Henrik

Comments

  • Anonymous
    May 30, 2014
    That's pure awesomeness! Thank you Henrik!
  • Anonymous
    May 30, 2014
    Really great! Thanks for the article!
  • Anonymous
    May 30, 2014
    Great stuff! Additional awesomeness points for automatic scale-out :)
  • Anonymous
    May 31, 2014
    Hi,await hubConnection.Start() returns 404 error :( On local machine it works though. Does anyone know where is the problem?
  • Anonymous
    May 31, 2014
    Mosso,Do you see anything in the logs (available form the portal)? Feel free to ping me on twitter at @FrystykHenrik
  • Anonymous
    June 01, 2014
    Hello,I am very uncomfortable when you use "real-time" for such technologies.Real-time in the industry means a lot of things.I understand the marketing messages are very important but sometimes, evangelism should adopt ethic.ASP.NET, SignalR and Azure Mobile stuff do not belong to real-time stuff.
  • Anonymous
    June 04, 2014
    @Christophe PichaudRight, 'cause your definition is the one "true" definition of real-time and everyone else is wrong.There is always latency when sending a packet from one place to another.  My tolerance for this latency can be higher than yours for me to still consider it as "real-time" because it's good enough for my application.
  • Anonymous
    June 13, 2014
    This is poetry, not an article. Simply awesome, thank you !!
  • Anonymous
    June 13, 2014
    Can I use a timer in the server side hub class to act as a scheduler ? Can I be sure this hub will.never recycle/suspend, even if all clients are disconnected ?Thank you.
  • Anonymous
    June 16, 2014
    @Henrik ?
  • Anonymous
    June 19, 2014
    I couldn't get the sample code to work with the latest SignalR NuGet packages (v2.1.0) - I had to revert to SignalR v2.0.3. Problem was caused by Azure Mobile Services backend not yet supporting v2.1.0Mobile Services log info:Found conflicts between different versions of the same dependent assembly 'Microsoft.AspNet.SignalR.Core': 2.1.0.0, 2.0.3.0. Please change your project to use version '2.0.3.0' which is the one currently supported by the hosting environment.
  • Anonymous
    June 23, 2014
    @Henrik I had a SignalR implementation in a WebRole (running locally) which was working fine with my Windows Phone client. I used the walkthrough above to move to Mobile Services (also running locally).My app connects and invokes methods just fine but calls from the server are now failing. The calls are in the form of hubContext.Clients.Client(<connectionId>).Send("message from server");Is there any reason why this would no longer work?Thanks
  • Anonymous
    June 23, 2014
    By "failing" I mean that there is not error, the code gets executed, but the client doesn't raise the event.
  • Anonymous
    July 05, 2014
    I noticed in the sample you use the .NET SignalR client and add the zumo headers to authenticate to the mobile service. How do you add the zumo headers when using the javascript client?
  • Anonymous
    August 21, 2014
    Pure awesomeness. Had I this just 3 months earlier I would have chosen this, it's perfect for my needs.
  • Anonymous
    September 24, 2014
    This is simply amazing! It fits like a glove to what I need! Great post, man!
  • Anonymous
    October 30, 2014
    Could you please add complete (and WORKING) solution sources? I'm trying to reproduce your tutorial but I can't get it working :(I'm trying to build a simple Azure-based game server for WP and store clients. But "Microsoft way" looks overcomplicated for that simple task :(
  • Anonymous
    December 23, 2014
    I don't see support for SignalR in the Azure SDK for iOS, neither for Android.
  • Anonymous
    December 29, 2014
    The comment has been removed
  • Anonymous
    April 18, 2015
    Has this changed for the newly release "Web Apps"?I can seem to add the WindowsAzure.MobileServices.Backend.SignalR nuget package to a new mobile app.
  • Anonymous
    June 27, 2015
    Hi Henrik Thanks for the awesome article!  However, wish to check with you, is this article still valid and works as is without requiring any modifications, given the recent changes of transition of Azure Mobile Services to the Azure App Services model/architecture changes?  Thanks!