Apple push Notifications Provider in c#

Here is the infrastructure and process I am using:

Brief Overview: I use PushSharp for communicating with the APNS servers. I have a SQL Server backend DB setup to handle all the subscriptions and notifications that get sent. I also have a virtual server (several actually) that all have the .p12 certs copied to them. These servers have process that checks the table for any push notifications that need to go out and then pass the dataset along to the PushSharp process.

Detailed Specs: Table 1 - APNS_Subscriptions

CREATE TABLE [dbo].[APNS_Subscriptions](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [DeviceToken] [varchar](250) NULL,
    [DeviceID] [varchar](250) NULL,
    [NetworkID] [varchar](250) NULL,
    [Application] [varchar](250) NULL,
    [AddedOn] [datetime] NULL,
    [Active] [bit] NULL,
    [Dev] [bit] NULL,
    [BadgeCount] [int] NOT NULL,
 CONSTRAINT [PK_APNSSubscriptions] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

Table 2 - APNS_PushNotifications

CREATE TABLE [dbo].[APNS_PushNotifications](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [DeviceToken] [varchar](250) NULL,
    [AlertMessage] [varchar](250) NULL,
    [BadgeNumber] [int] NULL,
    [SoundFile] [varchar](250) NULL,
    [ApplicationName] [varchar](250) NULL,
    [AddedOn] [datetime] NULL,
    [AddedBy] [varchar](250) NULL,
    [ProcessedOn] [datetime] NULL,
    [ViewedOnDeviceDateTime] [datetime] NULL,
 CONSTRAINT [PK_APNS_PushNotifications] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

I add subscriptions via this SP (this is called through a webservice via each iPhone app that implements APNS:

[ins_APNS_Sub]
    @MyDeviceID VARCHAR(250) ,
    @MyDeviceToken VARCHAR(250) ,
    @MyApplicationName VARCHAR(250)
AS 
    DECLARE @Count AS INT

    SET @Count = ( SELECT   COUNT(id)
                   FROM     dbo.APNS_Subscriptions
                   WHERE    DeviceID = @MyDeviceID
                            AND DeviceToken = @MyDeviceToken
                            AND [Application] = @MyApplicationName
                 )

    IF @Count = 0 
        BEGIN
            DECLARE @NetworkID AS VARCHAR(250)
            SET @NetworkID = ( SELECT TOP 1
                                        networkid
                               FROM     dbo.AuthenticatedDevices
                               WHERE    deviceid = @MyDeviceID
                                        AND COALESCE(banned, 0) = 0
                               ORDER BY lastupdatedon DESC
                             )

            IF @NetworkID IS NOT NULL 
                BEGIN

                    INSERT  INTO dbo.APNS_Subscriptions
                            ( DeviceToken ,
                              DeviceID ,
                              NetworkID ,
                              [Application] ,
                              AddedOn ,
                              Active
                            )
                    VALUES  ( @MyDeviceToken , -- DeviceToken - varchar(250)
                              @MyDeviceID , -- DeviceID - varchar(250)
                              @NetworkID , -- NetworkID - varchar(250)
                              @MyApplicationName , -- Application - varchar(250)
                              CURRENT_TIMESTAMP , -- AddedOn - datetime
                              1  -- Active - bit
                            )
                END
        END

Push Notifications are added via this SP:

[ins_APNS_PushNote]
    @MyNetworkID VARCHAR(250) ,  -- NetworkID of recipient or ALL to go to all recipients
    @MyApplicationName VARCHAR(250) ,  -- Application Name for the iOS app
    @APNSAlertMessage VARCHAR(225) , -- Alert Message (Required)
    @APNSSoundFile VARCHAR(250) = NULL ,
    @WhoRequested VARCHAR(250) -- Process Name that called this SP
AS 


   -- Get the current badge count, make a temp table and increment the appropriate rows in the Sub table
    DECLARE @UpdateTable AS TABLE
        (
          DeviceToken VARCHAR(250) ,
          NetworkID VARCHAR(250) ,
          ApplicationName VARCHAR(250) ,
          BadgeCount INT
        )

    IF @MyNetworkID = 'ALL' 
        BEGIN

            INSERT  INTO @UpdateTable
                    ( DeviceToken ,
                      NetworkID ,
                      ApplicationName ,
                      BadgeCount
                    )
                    SELECT  DeviceToken ,
                            NetworkID ,
                            [Application] ,
                            BadgeCount
                    FROM    dbo.APNS_Subscriptions
                    WHERE   [Application] = @MyApplicationName
                            AND COALESCE(Dev, 0) = 0

            UPDATE  @UpdateTable
            SET     BadgeCount = BadgeCount + 1

            UPDATE  sub
            SET     sub.BadgeCount = temp.BadgeCount
            FROM    dbo.APNS_Subscriptions sub
                    INNER JOIN @UpdateTable temp ON temp.DeviceToken = sub.DeviceToken
                                                    AND temp.NetworkID = sub.NetworkID
                                                    AND temp.ApplicationName = sub.[Application]

            INSERT  INTO dbo.APNS_PushNotifications
                    ( DeviceToken ,
                      AlertMessage ,
                      BadgeNumber ,
                      SoundFile ,
                      ApplicationName ,
                      AddedOn ,
                      AddedBy

                    )
                    SELECT  sub.DeviceToken ,
                            @APNSAlertMessage ,
                            temp.BadgeCount ,
                            @APNSSoundFile ,
                            @MyApplicationName ,
                            CURRENT_TIMESTAMP ,
                            @WhoRequested
                    FROM    dbo.APNS_Subscriptions sub
                            INNER JOIN dbo.AuthenticatedDevices ad ON ad.deviceid = sub.DeviceID
                            INNER JOIN @UpdateTable temp ON temp.DeviceToken = sub.DeviceToken
                                                            AND temp.ApplicationName = sub.[Application]
                    WHERE   COALESCE(ad.banned, 0) = 0
                            AND sub.[Application] = @MyApplicationName
                              --  AND ad.networkid = @MyNetworkID
                            AND COALESCE(sub.Dev, 0) = 0
        END    
    ELSE 
        BEGIN

            DECLARE @Count AS INT = ( SELECT    COUNT(id)
                                      FROM      dbo.APNS_Subscriptions
                                      WHERE     NetworkID = @MyNetworkID
                                                AND Active = 1
                                                AND [Application] = @MyApplicationName
                                    )


            IF @Count = 0 
                BEGIN
                    RETURN
                END     

            INSERT  INTO @UpdateTable
                    ( DeviceToken ,
                      NetworkID ,
                      ApplicationName ,
                      BadgeCount
                    )
                    SELECT  DeviceToken ,
                            NetworkID ,
                            [Application] ,
                            BadgeCount
                    FROM    dbo.APNS_Subscriptions
                    WHERE   [Application] = @MyApplicationName
                            AND COALESCE(Dev, 0) = 0
                            AND NetworkID = @MyNetworkID

            UPDATE  @UpdateTable
            SET     BadgeCount = BadgeCount + 1

            UPDATE  sub
            SET     sub.BadgeCount = temp.BadgeCount
            FROM    dbo.APNS_Subscriptions sub
                    INNER JOIN @UpdateTable temp ON temp.DeviceToken = sub.DeviceToken
                                                    AND temp.NetworkID = sub.NetworkID
                                                    AND temp.ApplicationName = sub.[Application]

            INSERT  INTO dbo.APNS_PushNotifications
                    ( DeviceToken ,
                      AlertMessage ,
                      BadgeNumber ,
                      SoundFile ,
                      ApplicationName ,
                      AddedOn ,
                      AddedBy

                    )
                    SELECT  sub.DeviceToken ,
                            @APNSAlertMessage ,
                            temp.BadgeCount ,
                            @APNSSoundFile ,
                            @MyApplicationName ,
                            CURRENT_TIMESTAMP ,
                            @WhoRequested
                    FROM    dbo.APNS_Subscriptions sub
                            INNER JOIN dbo.AuthenticatedDevices ad ON ad.deviceid = sub.DeviceID
                            INNER JOIN @UpdateTable temp ON temp.DeviceToken = sub.DeviceToken
                                                            AND temp.ApplicationName = sub.[Application]
                    WHERE   COALESCE(ad.banned, 0) = 0
                            AND sub.[Application] = @MyApplicationName
                            AND sub.networkid = @MyNetworkID
                            AND COALESCE(sub.Dev, 0) = 0
                            AND COALESCE(sub.Active, 0) = 1

        END   

This is called from several different places in several different DB's this way: EXECUTE [ins_APNS_PushNote] @NetworkID ,@iOSApplicationName ,@AlertMessage ,@SoundFile ,@RequestedBy

The SP that retrieves these APNS requests for the virtual server (PushSharp):

[get_APNSToSend]
AS 
    BEGIN

        DECLARE @CurrentTimestamp AS DATETIME = CURRENT_TIMESTAMP

        UPDATE dbo.APNS_PushNotifications
        SET ProcessedOn = CURRENT_TIMESTAMP
        WHERE ProcessedOn IS NULL

        SELECT  id ,
                DeviceToken ,
                AlertMessage ,
                BadgeNumber ,
                SoundFile ,
                ai.APNSDistCertFile AS APNSCertFile
        FROM    dbo.APNS_PushNotifications apns
                INNER JOIN dbo.ApplicationInfo ai ON ai.ApplicationName = apns.ApplicationName
        WHERE   ProcessedOn = @CurrentTimestamp
                AND ai.APNSDistCertFile IS NOT NULL


    END 

Now for the changes I made to the PushSharp app. Really just boils down to two methods: static void Main(string[] args) { checkForPushRequest(); }

    static void checkForPushRequest()
    {
        string YourConnString = "YourConnectionStringToTheDBGoesHere";

            Stored_Procedure SP = new Stored_Procedure {
            Name = "get_APNSToSend",
            Parameters = new List<SqlParameter>()
        };

        try {
            System.Data.DataTable dt = DatabaseOperations.Execute_Database_Command(YourConnString, SP, true);

            if ((dt != null) && !(dt.Rows.Count < 1)) {
                foreach (System.Data.DataRow dRow in dt.Rows) {
                    string deviceToken = Convert.ToString(dRow[1]);
                    string alertMessage = Convert.ToString(dRow[2]);
                    int badgeNumber =  Convert.ToInt16(dRow[3]);
                    string soundFile = Convert.ToString(dRow[4]);
                    string apnsCertFileToUse = Convert.ToString(dRow[5]);
                    sendPush(deviceToken, alertMessage, soundFile, badgeNumber, apnsCertFileToUse);
                }
            }
        } catch (Exception ex) {
            // Handle your exception
        }
    }

    static void sendPush(string DeviceToken, string AlertMessage, string SoundFile, int BadgeNumber, string apnsCertFileToUse)
    {
        //Create our service    
        PushService push = new PushService();

        //Wire up the events
        push.Events.OnDeviceSubscriptionExpired += new PushSharp.Common.ChannelEvents.DeviceSubscriptionExpired(Events_OnDeviceSubscriptionExpired);
        //push.Events.OnDeviceSubscriptionIdChanged += new PushSharp.Common.ChannelEvents.DeviceSubscriptionIdChanged(Events_OnDeviceSubscriptionIdChanged);
        push.Events.OnChannelException += new PushSharp.Common.ChannelEvents.ChannelExceptionDelegate(Events_OnChannelException);
        push.Events.OnNotificationSendFailure += new PushSharp.Common.ChannelEvents.NotificationSendFailureDelegate(Events_OnNotificationSendFailure);
        push.Events.OnNotificationSent += new PushSharp.Common.ChannelEvents.NotificationSentDelegate(Events_OnNotificationSent);

        //Configure and start Apple APNS
        // IMPORTANT: Make sure you use the right Push certificate.  Apple allows you to generate one for connecting to Sandbox,
        //   and one for connecting to Production.  You must use the right one, to match the provisioning profile you build your
        //   app with!  
        //  This comes from the ApplicationInfo table.  Each app that supports APNS has it's own certfile name in the column
        string certFileToUse = "C:\\APNS_Certs\\" + apnsCertFileToUse;

        var appleCert = File.ReadAllBytes(certFileToUse);

        //IMPORTANT: If you are using a Development provisioning Profile, you must use the Sandbox push notification server 
        //  (so you would leave the first arg in the ctor of ApplePushChannelSettings as 'false')
        //  If you are using an AdHoc or AppStore provisioning profile, you must use the Production push notification server
        //  (so you would change the first arg in the ctor of ApplePushChannelSettings to 'true')
        push.StartApplePushService(new ApplePushChannelSettings(false, appleCert, "P12PasswordHere"));

        //Fluent construction of an iOS notification
        //IMPORTANT: For iOS you MUST MUST MUST use your own DeviceToken here that gets generated within your iOS app itself when the Application Delegate
        //  for registered for remote notifications is called, and the device token is passed back to you
        push.QueueNotification(NotificationFactory.Apple()
            .ForDeviceToken(DeviceToken)
            .WithAlert(AlertMessage)
            .WithSound(SoundFile)
            .WithBadge(BadgeNumber));

        //Console.WriteLine("Waiting for Queue to Finish...");

        //Stop and wait for the queues to drains
        push.StopAllServices(true);

       // Console.WriteLine("Queue Finished, press return to exit...");         
    }

I added a Console project to the PushSharp solution and deployed the Console to the APNS server. This console app is fired based on scheduled task to run every minute.

If you have more questions, let me know. I have been using this process for the last year in an enterprise environment and have had no issues. Works flawlessly.


As your using MoonAPNS I would suggest reading the 4-part guide on Sending Apple Push Notifications in ASP.NET and C# by the libraries author.