How to Write an Apple Push Notification Provider in C#

How to write an Apple Push Notification Provider in C#?

Working code example:

int port = 2195;
String hostname = "gateway.sandbox.push.apple.com";

//load certificate
string certificatePath = @"cert.p12";
string certificatePassword = "";
X509Certificate2 clientCertificate = new X509Certificate2(certificatePath, certificatePassword);
X509Certificate2Collection certificatesCollection = new X509Certificate2Collection(clientCertificate);

TcpClient client = new TcpClient(hostname, port);
SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
null
);

try
{
sslStream.AuthenticateAsClient(hostname, certificatesCollection, SslProtocols.Tls, true);
}
catch (AuthenticationException ex)
{
client.Close();
return;
}

// Encode a test message into a byte array.
MemoryStream memoryStream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(memoryStream);

writer.Write((byte)0); //The command
writer.Write((byte)0); //The first byte of the deviceId length (big-endian first byte)
writer.Write((byte)32); //The deviceId length (big-endian second byte)

String deviceId = "DEVICEIDGOESHERE";
writer.Write(ToByteArray(deviceId.ToUpper()));

String payload = "{\"aps\":{\"alert\":\"I like spoons also\",\"badge\":14}}";

writer.Write((byte)0); //First byte of payload length; (big-endian first byte)
writer.Write((byte)payload.Length); //payload length (big-endian second byte)

byte[] b1 = System.Text.Encoding.UTF8.GetBytes(payload);
writer.Write(b1);
writer.Flush();

byte[] array = memoryStream.ToArray();
sslStream.Write(array);
sslStream.Flush();

// Close the client connection.
client.Close();

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.

Send Push Notification in C#

You are using the legacy API. There is 5 year complete C# walk though on it here if you wish to continue to use that.

Apple now supports APNs over http/2. Instead of writing your own code take a look at some existing libraries such as PushSharp which will take care of the low level API and error handling for you.

// Configuration (NOTE: .pfx can also be used here)
var config = new ApnsConfiguration (ApnsConfiguration.ApnsServerEnvironment.Sandbox,
"push-cert.p12", "push-cert-pwd");

// Create a new broker
var apnsBroker = new ApnsServiceBroker (config);

// Wire up events
apnsBroker.OnNotificationFailed += (notification, aggregateEx) => {

aggregateEx.Handle (ex => {

// See what kind of exception it was to further diagnose
if (ex is ApnsNotificationException) {
var notificationException = (ApnsNotificationException)ex;

// Deal with the failed notification
var apnsNotification = notificationException.Notification;
var statusCode = notificationException.ErrorStatusCode;

Console.WriteLine ($"Apple Notification Failed: ID={apnsNotification.Identifier}, Code={statusCode}");

} else {
// Inner exception might hold more useful information like an ApnsConnectionException
Console.WriteLine ($"Notification Failed for some unknown reason : {ex.InnerException}");
}

// Mark it as handled
return true;
});
};

apnsBroker.OnNotificationSucceeded += (notification) => {
Console.WriteLine ("Apple Notification Sent!");
};

// Start the broker
apnsBroker.Start ();

foreach (var deviceToken in MY_DEVICE_TOKENS) {
// Queue a notification to send
apnsBroker.QueueNotification (new ApnsNotification {
DeviceToken = deviceToken,
Payload = JObject.Parse ("{\"aps\":{\"alert\":\"" + "Hi,, This Is a Sample Push Notification For IPhone.." + "\",\"badge\":1,\"sound\":\"default\"}}")
});
}

// Stop the broker, wait for it to finish
// This isn't done after every message, but after you're
// done with the broker
apnsBroker.Stop ();

Sending HTTP/2 request to Apple Push Notification Service using HttpClient, Client Certificate and WinHttpHandler raises HttpRequestException

Turned out that this is not supported.
Mainly because of missing functionality in the Windows version. According to a comment in github in the corefx team this should work from Windows version 2004, build 19573+ github discussion. The problem with this version is that it is not released yet and there is no plan on when it will be.

Add single push notification to single device

after much investigation and trials, I found out that I don't have to rely on azure, its push notification or anything like that. I can do the entire thing by executing C# code like #2.
All I had to do is setup the .p12 certificate and give it a password. Bring it over from Mac to my pc and executed the C# code and was able to receive notifications on my phone.



Related Topics



Leave a reply



Submit