Monday, Oct 8, 2018
ASP.NET WebAPI, APNS and Android working together
Introduction
This post described the application I recently created to send the location of an Android device to Apple Push Notification Service (APNS). I don’t want Android to talk directly to APNS, as there are some backend tasks that needs to be performed whenever the location is updated, so I will use Microsoft MVC 4 WebAPI service between them. I am using Xamarin Mono for Android and MonoDevelop 3.0.35 for developing the Android app.
You may begin to wonder why such an exotic mix of technologies is used. Well, this project is a part of a solution that is described in my earlier series of articles on iPhone Augmented Reality sample project. We wanted to add the Android devices to this project, so they could also be located and displayed in our AR viewer. Here is what I’ve came up with along the way.
The basic usage scenario will work like this:
- Android device starts monitoring for location changes using its sensors
- When a location is changed, a message is sent to the WebAPI service, containing the current coordinates of a device
- WebAPI service receives this message and adds it to the queue
- WebAPI service sends messages from its queue to APNS
- APNS receives a message and forwards it to the client iPhone device
MVC WebAPI service
We start by creating a new MVC WebAPI solution and project in Visual Studio. If you are not familiar with this project type, please visit some of the tutorials on Microsoft’s web site for more details. After creating the solution and project named ServerApp, add a new C# Class Library project which will contain our model data and name it Model. We will keep it in the separate assembly, so it could be used in the Android project.
Our model contains only two classes, one for the device token info and other for the notification data. Token.cs serves as a container for token data that is received from the client device. Notification.cs, holds notification data and contains a method to help us format the notification content.
Here is how these classes look:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Model
{
public class DeviceToken
{
#region Properties
public string Token { get ; set ; }
#endregion
#region Methods
public DeviceToken( string token)
{
Token = token;
}
#endregion
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Model
{
public class Notification
{
#region Properties
public int Id { get ; set ; }
public string Description { get ; set ; }
public string SenderId { get ; set ; }
#endregion
#region Constructor
public Notification()
{
}
public Notification( double latitude, double longitude, double altitude, string senderId)
{
this .SenderId = senderId;
this .Description = String.Format( "{0};{1};{2};{3}" , senderId, latitude.ToString( "#.############" ), longitude.ToString( "#.############" ), altitude.ToString( "#.###" ));
}
#endregion
#region Methods
#region Overrides
public override string ToString()
{
return this .Description;
}
#endregion
#endregion
}
}
We will use an open source C# library called Moon-APNS to send messages to APNS. You can find more info on it on arashnorouzi blog. Just download the library from GitHub, add its project to the solution and add references to the Model project and Moon-APNS project from the ServerApp.
Add a new controller to the ServerApp and name it NotificationsController.cs. This controller is used by AndoidApp and needs only POST method which adds notification to the queue.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using Model;
namespace ServerApp.Controllers
{
public class NotificationsController : ApiController
{
// POST /api/notifications
public void Post(Notification value)
{
WebApiApplication.NotificationsQueue.Add(value);
}
}
}
Next add a class called SubscribeController.cs and also add a POST method to it. This controller is used by iPhone devices to subscribe to our notification service.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using Model;
namespace ServerApp.Controllers
{
public class SubscribeController : ApiController
{
// POST /api/subscribe
public void Post(DeviceToken value)
{
WebApiApplication.NotificationsQueue.SetToken(value.Token);
}
}
}
To keep the above controllers simple, all logic is placed in an utility class named ¸NotificationQueue.cs. It keeps track of client subscriptions, receives messages and places them in the queue, sends bulk messages, and combines messages to utilize the maximum notification size.
Edit this class to set your application parameters, such as the p12 certificate file path and the certificate password.
To keep this article short, I am showing only the method headers, but the complete code can be found in the zip file attached to this post. Only the content of the SendNotifications() is shown because it contains all the code needed to send notifications using MoonAPNS.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using Model;
namespace ServerApp.Controllers
{
public class NotificationsController : ApiController
{
// POST /api/notifications
public void Post(Notification value)
{
WebApiApplication.NotificationsQueue.Add(value);
}
}
}
Next add a class called SubscribeController.cs and also add a POST method to it. This controller is used by iPhone devices to subscribe to our notification service.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using Model;
namespace ServerApp.Controllers
{
public class SubscribeController : ApiController
{
// POST /api/subscribe
public void Post(DeviceToken value)
{
WebApiApplication.NotificationsQueue.SetToken(value.Token);
}
}
}
To keep the above controllers simple, all logic is placed in an utility class named NotificationQueue.cs. It keeps track of client subscriptions, receives messages and places them in the queue, sends bulk messages, and combines messages to utilize the maximum notification size.
Edit this class to set your application parameters, such as the p12 certificate file path and the certificate password.
To keep this article short, I am showing only the method headers, but the complete code can be found in the zip file attached to this post. Only the content of the SendNotifications() is shown because it contains all the code needed to send notifications using MoonAPNS.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Model;
namespace ServerApp
{
/// <summary>
/// Responsible for sending messages to APNS server.
/// </summary>
public class NotificationQueue
{
/// <summary>
/// Device token where APNS messages will be sent.
/// </summary>
/// <param name="token"></param>
public void SetToken( string token)
/// <summary>
/// Adds notification to queue or replaces existing one and executes queue.
/// </summary>
/// <param name="notification">Notification to add.</param>
public void Add(Notification notification)
/// <summary>
/// Sends all notifications from queue to APNS.
/// </summary>
/// <returns>True if successful.</returns>
private bool SendNotifications()
{
//Check if anyone subscribed for notifications
if (String.IsNullOrEmpty(deviceToken))
return false ;
//Check if messages are already sending
if (notificationsCurrentlySending.Count > 0)
return true ;
lock (_lock)
{
try
{
var notificationList = new List<MoonAPNS.NotificationPayload>();
do
{
//Create list of notifications created from all messages in queue.
var content = GetNotificationContent();
var payload = new MoonAPNS.NotificationPayload(deviceToken, content, 1);
notificationList.Add(payload);
} while (notificationsToSend.Count > 0);
//Send notifications to APNS
if (push == null )
push = new MoonAPNS.PushNotification( true , p12File, certPassword);
var rejected = push.SendToApple(notificationList);
if (rejected.Count > 0)
{
notificationsCurrentlySending.Clear();
return true ;
}
else
{
return false ;
}
}
catch
{
return false ;
}
}
}
/// <summary>
/// Formats content for notification in maximum length.
/// </summary>
/// <returns></returns>
private string GetNotificationContent()
/// <summary>
/// Formats content for notification in maximum length.
/// </summary>
/// <param name="appendTo">Existing message.</param>
/// <returns>Notification payload.</returns>
private string GetNotificationContent( string appendTo)
}
Last thing that needs to be done is to add a reference to NotificationQueue in Global.asax and create a new instance on application start. Here is how complete solution looks like in Solution Explorer.
The next task is to create an Android application for sending the location information to our WebAPI service. This is where MonoDevelop steps in. If this is the first time you are using it, please visit this post for explanation on how to create simple Mono for Android project.
After creating a new project, we need to change the AndroidManifest.xml to support the features we will need.
<? xml version = "1.0" encoding = "utf-8" ?>
< manifest xmlns:android = "http://schemas.android.com/apk/res/android" android:versionCode = "1" >
< uses-sdk android:targetSdkVersion = "14" />
< application android:label = "AndroidApp" ></ application >
< uses-permission android:name = "android.permission.ACCESS_COARSE_LOCATION" />
< uses-permission android:name = "android.permission.ACCESS_FINE_LOCATION" />
< uses-permission android:name = "android.permission.ACCESS_NETWORK_STATE" />
< uses-permission android:name = "android.permission.ACCESS_WIFI_STATE" />
< uses-permission android:name = "android.permission.READ_PHONE_STATE" />
</ manifest >
Next, add a new activity named MainActivity.cs which will be used as a controller for the main view - Main.xaml. This class will also implement ILocationListener interface and OnLocationChanged(location) event by sending messages to our WebAPI service. Communication with the WebAPI is done using WebRequest, and JSON data is sent using POST method. Location updates are stopped before reading data from the LocationManager and restarted after sending the request. Remember to place the URL of your WebAPI service here.
private void PublishLocation ( double latitude, double longitude, double altitude)
{
_locationManager.RemoveUpdates ( this );
//Publish message
var notif = new Model.Notification (latitude, longitude, altitude, deviceId);
string url = "http://192.168.0.193/WebApi/api/Notifications" ; //TODO Add here URL of your WebAPI
var request = WebRequest.Create (url) as WebRequest;
System.Json.JsonObject jsonNotif = new System.Json.JsonObject ()
{{ "Id" , 1}, { "Description" , notif.Description}};
string body = jsonNotif.ToString ();
request.ContentLength = body.Length;
request.Method = "POST" ;
request.ContentType = "application/json" ;
StreamWriter stOut = new StreamWriter (request.GetRequestStream (), System.Text.Encoding.ASCII);
stOut.Write (body);
stOut.Close ();
request.GetResponse ();
StartLocationMonitor ();
}
LocationManager is configured to use the most accurate provider, location is updated every 5 seconds and will monitor changes greater than 2 meters.
var criteria = new Criteria () { Accuracy = Accuracy.Fine };
string bestProvider = _locationManager.GetBestProvider (criteria, true );
_locationManager.RequestLocationUpdates (bestProvider, 5000, 2, this );
Here is image how Main.xaml looks like:
This view contains buttons for activation/deactivation of the Location Monitor and activation/deactivation of the location publishing service. Geographical location and altitude are also shown. The bottom part of this view contains debugging information, including the contents of the last update, the total number of updates, our device id and data accuracy information.
Please use the attached file if you would like to learn more about tools and techniques used in this project.