Written by Irmak Tevfik on 11 - Oct - 2014

Creating a simple application using Universal Apps with SignalR and Mobile Services(Push Notifications)

This article will cover step by step approach for creating a Universal App using SignalR and Mobile Services. We will be following a simple item auction scenario for demonstrating these technologies. Created project can be found from GitHub link.

Scenario:
1-Items on the database will be loaded to the client and each time user places a bid, we will  increment the item bid amount by 10. SignalR will help us to update all of the clients as soon as we invoke the bidding method.

2-We will also be using push notifications to notify the windows phone user


so if we summarize, overall layout of the project will look like the diagram below:

Step 1: Creating Mobile Services
1- Login to your https://manage.windowsazure.com windows azure account and Create a new mobile service by selecting the shown items from NEW button.

2- You will be asked for a URL and database selection. I will be using bidmobileservices as the url for this example and will pick a free 20MB sql Database (with new server).


3- Now it will take a few seconds to create our mobile service. Once it is ready, we can move on to the dash board and selecting "CREATE A NEW WINDOWS OR WINDOWS PHONE APP". On the expanded tab, we will pick c# and click download. Which will generate us mobile service and Universal app projects.



Step 2: Configure Solution - Mobile Services
1- Ok, now it is time to configure our solution starting from service layer to the client. We will be needing NuGet Packages for SignalR to perform on server side. Lets go ahead and install "WindowsAzure.MobileServices.Backend.SignalR" package to our MobileService project.

Please note that we need to register SignalR proxies to the project. This will be done in WebApiConfig under App_Start folder. Lets modify it to look as 
public static void Register()
{
    // Use this class to set configuration options for your mobile service
    ConfigOptions options = new ConfigOptions();
    // Use this class to set WebAPI configuration options
    HttpConfiguration config = ServiceConfig.Initialize(new ConfigBuilder(options));
    //Register SignalR
    SignalRExtensionConfig.Initialize();
    // To display errors in the browser during development, uncomment the following
    // line. Comment it out again when you deploy your service for production use.
    // config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; 
    Database.SetInitializer(new bidmobileservicesInitializer());
}

now our project knows how to deal with SignalR.
2- Now lets add our model object. I think only ItemName and BidAmount properties are enough for this example.
public class BidItems : EntityData
{
    public string Id { get; set; }
 
    public string ItemName { get; set; }
 
    public int BidAmount { get; set; }
}

Now, lets move to our WebApiConfig class. where we can see that TodoItems are created and are added to table on start. Lets delete and add our items. it will be shown as:

last step is to add the object to our DbContext. lets go ahead and add 
public DbSet<BidItems> BidItems { get; set; }
to our bidmobileservicesContext.cs

3- Lets add our Hubs for SignalR. Create a folder named "Hubs" under the solution and add a new hub named as BidHub.cs shown below

4- inside our hubs class, we heed two functionalities only. Either we are going to get the ItemList or we are going to perform a bidding for an item. So lets add these two methods to our hub
public class BidHub : Hub
  {
      public void PushBid(string bidItemId)
      {
          using (bidmobileservicesContext context = new bidmobileservicesContext())
          {
              //Get Selected Bid Item
              var data = context.BidItems.Where(p => p.Id == bidItemId).First();
              //Increment the bid Amount by 10
              data.BidAmount += 10;
              //save changes
              context.SaveChanges();
              //get all bids
              GetBids(string.Empty);
          }
      }
 
      public string GetBids(string test)
      {
          using (bidmobileservicesContext context = new bidmobileservicesContext())
          {
              string jsonData = string.Empty;
              //get all items list
              var data = context.BidItems.ToList();
              //convert out list to json
              jsonData = JsonConvert.SerializeObject(data);
              //send data to all clients
              Clients.All.Message(jsonData);
              return jsonData;
          }
      }
  }
Steps are explained and should be really simple. PushBid method processes the item bidding and invokes GetBids to return updated list as JSON.

Step 3: Configure Solution - Universal App
For our Universal App, we will be using MVVM pattern and will be moving whatever we have to be shared. But first lets add our PCL library for our models. Separating our models from Universal App and services will be useful if we would like to use it on both ends. Later, all we need to do is take our PCL target to .NET Framework 4.5.1 along with our mobile services (I believe Mobile Services default to .NET framework 4.5)

1- lets add a Portable Class Library to our solution named as "mobileservicesModels" and add our class "BidItems" to be mapped. Our class should look like 

namespace bidmobileservicesModels
{
    public class BidItems
    {
        public string Id { get; set; }
        public string ItemName { get; set; }
        public int BidAmount { get; set; }
    }
}

2- Lets move on and reference our model library project to our client application. Please remember that we need to reference our PCL both to windows and Windows Phone projects to be able to use it under shared location. Lets carry on and clear our project by:

a- Deleting Common folder under windows project
b- Move all assets under shared location and delete the other ones.
c- Delete every single line on MainPage.cs until you have page constructor like
public MainPage()
{
    this.InitializeComponent();
    this.NavigationCacheMode = NavigationCacheMode.Required;
}
d- Remove every single element on pages until we have our <Grid></Grid> at the end our project will look like 


3- Ok, as we are going to implement MVVM, we will be needing our RelayCommand class to fire Commands (which gets added on newly added project templates). Lets go ahead and create a folder named "Common" under our shared folder and add a class named RelayCommand.

public class RelayCommand : ICommand
 {
     private readonly Action _execute;
     private readonly Func<bool> _canExecute;
 
     public event EventHandler CanExecuteChanged;
 
     public RelayCommand(Action execute)
         : this(execute, null)
     {
 
     }
 
     public RelayCommand(Action execute, Func<bool> canExecute)
     {
         if (execute == null)
             throw new ArgumentNullException("execute");
         _execute = execute;
         _canExecute = canExecute;
     }
 
     public bool CanExecute(object parameter)
     {
         return _canExecute == null ? true : _canExecute();
     }
 
     public void Execute(object parameter)
     {
         _execute();
     }
 
     public void RaiseCanExecuteChanged()
     {
         var handler = CanExecuteChanged;
         if (handler != null)
         {
             handler(this, EventArgs.Empty);
         }
     }
 }

4- Now lets create a folder named Helpers (as we are going to need some) and add two classes as:
JsonHelper class: Will be used to serialize/deserialize JSON objects
public class JsonHelper
  {
     public static T Deserialize<T>(string request)
     {
         return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(request);
     }
 
     public static string Serialize<T>(T request)
     {
         return JsonConvert.SerializeObject(request);
     }
  }
Extensions class: will be used to convert List to ObservableCollection and also will have an extension for initial async Signalr connection where we will be keeping live
public static class Extensions
{
    /// <summary>
    /// list to Observable Collection
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="col"></param>
    /// <returns></returns>
    public static ObservableCollection<T> ToObservableCollection<T>(this List<T> col)
    {
        return new ObservableCollection<T>(col);
    }
 
    /// <summary>
    /// Continous live connection
    /// </summary>
    /// <param name="task"></param>
    public static void Forget(this Task task)
    {
        task.ContinueWith(p => { var e = task.Exception; });
    }
}

5- We have everything ready for our ViewModel except Nuget for SignalR Client. Lets add Nuget "Microsoft ASP.NET SignalR .NET Client" to both Windows and Windows Phone project references. At the end our references should look like

6-Our MobileServiceClient i included under our App.xaml.cs file. You will notice that it has been set to use localhost by default. Be brave and uncomment live settings having https://bidmobileservices.azure-mobile.net/ as host and application key from azure.


7- Now it is time to add our ViewModel. Go ahead and create a folder named "ViewModels" under Shared project and add a class named "MainPageViewModel.cs" and add the codes below. I have explained the code line by line. Please resolve all of the conflicts.
public class MainPageViewModel : INotifyPropertyChanged
{
    const string _mobileServiceUrl = "https://bidmobileservices.azure-mobile.net/";
    private MobileServiceUser _user;
    private HubConnection _hubConnection;
    private IHubProxy _proxy;
    private CoreDispatcher _dispatcher;
 
    #region change handling
    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(string propertyName)
    {
        //Fire the PropertyChanged event in case somebody subscribed to it
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion
 
    /// <summary>
    /// Observable collection for the Page collection data
    /// </summary>
    private ObservableCollection<BidItems> _bidData;
    public ObservableCollection<BidItems> BidData
    {
        get
        {
            return this._bidData;
        }
    }
 
    /// <summary>
    /// will hold the selected item from listview
    /// </summary>
    private BidItems _selectedBidItem;
    public BidItems SelectedBidItem
    {
        get
        {
            return this._selectedBidItem;
        }
        set
        {
            this._selectedBidItem = value;
            this.OnPropertyChanged("SelectedBidItem");
        }
 
    }
 
    #region Commands
    private RelayCommand _processBid;
    public RelayCommand ProcessBid
    {
        get
        {
            if (_processBid == null)
            {
                _processBid = new RelayCommand(() => this.InvokeBid(), () => this.CanBid());
            }
            return _processBid;
        }
        set
        {
            _processBid = value;
        }
    }
 
    private bool CanBid()
    {
        //ok, it is not a good logic to set it as true. Being lazy
        //best way is to have have status depending on the connection state.
        //if connection is lost, user should not be able to process bid.
        return true;
    }
 
    private async void InvokeBid()
    {
        //invokes the PushBid method on our hub with the selected item Id
        if (SelectedBidItem != null)
        await _proxy.Invoke("PushBid", SelectedBidItem.Id.ToString());
    }
    #endregion
 
 
    #region ctor
    public MainPageViewModel(CoreDispatcher dispatcher)
    {
        _bidData = new ObservableCollection<BidItems>();
        _dispatcher = dispatcher;
        // will keep the connection live.
        ConnectToHub().Forget();
    }
 
    #endregion
 
    private async Task ConnectToHub()
    {
        _hubConnection = new HubConnection(_mobileServiceUrl);
 
        if (_user != null)
        {
            _hubConnection.Headers["x-zumo-auth"] = _user.MobileServiceAuthenticationToken;
        }
        else
        {
            //either way, we need to add x-zumo-application header to authenticate on the service
            _hubConnection.Headers["x-zumo-application"] = App.MobileService.ApplicationKey;
        }
 
        //create Hub proxy as named and start connection
        _proxy = _hubConnection.CreateHubProxy("BidHub");
        await _hubConnection.Start();
 
        //will be triggered once " Clients.All.Message(jsonData);" has been triggered from Hub
        _proxy.On<string>("message", async msg =>
        {
            var fullBidItems = Helpers.JsonHelper.Deserialize<List<BidItems>>(msg);
            await this._dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
            {
                _bidData = fullBidItems.ToObservableCollection();
                this.OnPropertyChanged("BidData");
            });
        });
 
 
        while (_hubConnection.State != ConnectionState.Connected)
        {
            //put a delay and retry
            await Task.Delay(3000);
            ConnectToHub();
        }
 
        // get items
        string result = await _proxy.Invoke<string>("GetBids");
        var fullData = Helpers.JsonHelper.Deserialize<List<BidItems>>(result);
        await this._dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
        {
            _bidData = fullData.ToObservableCollection();
            this.OnPropertyChanged("BidData");
        });
 
        //need to add these lines. I have included the source at the end of the article.
        //explained by a team member in Channel9
        while (true)
        {
            try
            {
                // HTTP streaming transports don't work well on the Windows Phone stack so we force long polling.
                // support for WebSockets to Windows Store/Phone 8.1 apps in SignalR 2.2.0
                await _hubConnection.Start(new LongPollingTransport());
                if (_hubConnection.State == ConnectionState.Connected)
                    break;
            }
            catch (Exception)
            {
 
            }
            await Task.Delay(3000);
        }
 
    }
 
 
}

Code is pretty simple actually. Please be aware that, we have CoreDispatcher as our parameter on the constructor. As we are trying to bind some of the properties to the page, without using the dispatcher will cause exception: 
The application called an interface that was marshalled for a different thread.
Which is expected. Code simply gets the items for the page and invokes the method for any update when ProcessBid Command triggered. Lets go ahead and add these lines to our HomePage.cs file to tell we are going to use MainPageViewModel.cs as our DataContext
public MainPage()
{
    this.InitializeComponent();
    this.NavigationCacheMode = NavigationCacheMode.Required;
    this.DataContext = new ZUMOAPPNAME.ViewModels.MainPageViewModel(Dispatcher);
}

8- So lets move on and add our xaml. Insert the following xaml to both pages (btw we can also move MainPage.xaml to shared)
<Page
    x:Class="bidmobileservices.MainPage"
    IsTabStop="false"
    xmlns:local="using:bidmobileservices"
    mc:Ignorable="d"
    RequestedTheme="Light">
 
    <Grid Background="WhiteSmoke">
        <ListView SelectedItem="{Binding SelectedBidItem, Mode=TwoWay}"
                  ItemsSource="{Binding BidData}"
                  Name="listview"      
                  SelectionMode="Single"
                  Foreground="#FF191919">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Border CornerRadius="2"
                            BorderThickness="1"
                            Padding="0,5">
                        <StackPanel VerticalAlignment="Top">
                            <TextBlock Text="{Binding ItemName}"
                                       FontFamily="Segoe UI"
                                       FontSize="20"
                                       FontWeight="Bold"
                                       Foreground="#FF7E7E7E"
                                       Margin="10,0" />
                            <TextBlock Text="{Binding BidAmount}"
                                       FontFamily="Segoe UI"
                                       FontSize="20"
                                       FontWeight="Bold"
                                       Foreground="#FFA6A2A2"
                                       TextWrapping="Wrap"
                                       Margin="10,0" />
                        </StackPanel>
                    </Border>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <Button Name="btnProcessBid" Command="{Binding ProcessBid}">
            <TextBlock
                Text="Process Selected Bid"
                                       FontFamily="Segoe UI"
                                       FontSize="20"
                                       FontWeight="Bold"
                                       Foreground="#FFA6A2A2"
                                       TextWrapping="Wrap"
                                       Margin="10,0" />
        </Button>
    </Grid>
</Page>

9- We have made some changes to the services so lets publish our Mobile Service



10- Lets run our projects and see the magic :)

Also our mobile project will look like

please note that as soon as we select an item and process a bid, next client will also get the results.

Step 4: Add push notifications to Mobile Service
Now we can go ahead and add a simple push notification from Azure to our mobile, so whenever the items updated, we can notify user about the status.

1- Go ahead and add Push Notifications to the mobile project shown below:


Once you logged into your account, you will be asked to reserve a app name. I picked "BidAppTest"

After selecting your service, wizard will be adding some files to your mobile project

IMPORTANT: Please note that during these changes, a new instance of "MobileServiceClient" will be created inside our App.xaml.cs

Delete the created one and change the references to point existing "MobileService"


2-Now, lets add a few lines to our Hub Method where we updated the records
public class BidHub : Hub
{
    public ApiServices Services { get; set; }
    public async void PushBid(string bidItemId)
    {
        using (bidmobileservicesContext context = new bidmobileservicesContext())
        {
            //Get Selected Bid Item
            var data = context.BidItems.Where(p => p.Id == bidItemId).First();
            //Increment the bid Amount by 10
            data.BidAmount += 10;
            //save changes
            context.SaveChanges();
 
            //lets Push our notification
            string wnsToast = string.Format("<?xml version=\"1.0\" encoding=\"utf-8\"?><toast><visual><binding template=\"" + data.ItemName.ToString()
                                            + "\"><text id=\"1\">{0}</text></binding></visual></toast>", data.BidAmount.ToString());
            WindowsPushMessage message = new WindowsPushMessage();
            message.XmlPayload = wnsToast;
            await Services.Push.SendAsync(message);
 
 
            //get all bids
            GetBids(string.Empty);
        }
    }
 
    public string GetBids(string test)
    {
        using (bidmobileservicesContext context = new bidmobileservicesContext())
        {
            string jsonData = string.Empty;
            //get all items list
            var data = context.BidItems.ToList();
            //convert out list to json
            jsonData = JsonConvert.SerializeObject(data);
            //send data to all clients on event "message"
            Clients.All.Message(jsonData);
            return jsonData;
        }
    }
}

and also make a few changes to our Web.config file as (shown on the default page shown after notification implementation):
Add the following connection string in the <connectionStrings> section in web.config:
<add name="MS_NotificationHubconnectionString" connectionString="........." />
Replace the "MS_NotificationHubName" key in the <appSettings> section in web.config with the following:
<add key="MS_NotificationHubName" value="bidmobileservicesHub" />

We will be changing the method to be async and also add a few simple lines to push notification. Lets Publish changes to Azure then test.

3- Now, if any of the items are published, we will be notified as shown:

So, we have covered simple functionalities about SignalR, Mobile Services Push Notifications on Universal Apps. You can cover more on SignalR by following Damian Edwards on http://channel9.msdn.com/Events/Speakers/Damian-Edwards
You can go ahead and download the project from but please note that Keys and URLs are deleted from solution.
https://github.com/irmaktevfik/SignalR/

Enjoy coding:)

comments powered by Disqus