I have been busy developing backend of an IPhone application which is sending considerable number of push notifications twice a day. Trying to fix some issues and bugs on other free and open source c# push notification libraries I came up with the idea of writing my own c# library and as a big fan of open source I’m going to share the library on GitHub.
The library is called Moon-APNS and it can be downloaded from here.
This is the first version of the library, but It has been tested under pressure and on a production version of an application so feel safe to plug it to you application. Moon-APNS can be used in any .net application web or windows based.
Moon-APNS is benefiting from new Apple Push Notification structure, called Enhanced Push Notification. Which enables the library to receive feedback on each notification sent to apple server asynchronously, you may say we can receive that response with apple feedback service, but unfortunately if you send a payload to apple server with wrong format, broken or missing device tokens apple will terminate the connection straight away and it’s really hard to figure out which one was the faulty payload when you are sending them one after each other because it takes up to 2 seconds for the connection to be closed. Using Enhanced push notification you can sign each payload with a unique identifier and even better you can set TTL on each payload so apple know how long they should try to deliver the push notification before it expires. Moon-APNS will send payloads and receive feedback asynchronously and will re connect and resume sending the queue in case of any errors. All device tokens of rejected payloads will return as a list when the queue is sent and feedback is received from apple with error code sent by apple so you will know why the payload was rejected.
In Moon-APNS I’m using free and open source NLog library to log all events happening behind the scene which makes it really easy to debug the application while you are on production and you can’t attach a debugger.
Sending Apple push notification has never been so easy with Moon-APNS you will need less than 10 lines of code to send push notification and receive the feedback.
Ok enough talking I think it’s time for some coding.
1) You should generate your payloads:
1: var payload1 = new NotificationPayload("DeviceToken","Hello !",1,"default");
Feel free to add custom parameters to your payload as bellow:
1: payload1.AddCustom("CustomKey", "CustomValue");
2) Moon-APNS accepts a list of payloads, so I will add my payloads to a List:
1: var notificationList = new List {payload1, payload2, payload3};
3) create an instance of Moon-APNS push notification class, and pass true or false for using sandbox, location of your p12 file, and password for thep12 file if you have one and blank string if you don’t have one.
1: var push = new PushNotification(false, "P12File location","password");
4) Create a list for your feedback, returned from library which will contain rejected payloads list.
5) Saved best for last call SendToApple method and pass list of your payload.
1: var rejected = push.SendToApple(notificationList);
Is that all? Well yes it is! So if we ignore lines for generating payloads I can say we are sending push notification ad receiving feedback in 2 lines of code. Everything you need to do is handled by Moon-APNS library for you so you can have more time to consider on your application logic.
Feel free to email me or post your questions as a comment and I’ll be more than happy to assist you as much as I can.
This is the first version and I will commit new builds with new features soon.
Hi arashnorouzi
First of all thank you for the code it looks great.
i have a question, in my application i cannot control the amount of chats , the user can send when ever they want.
which means i always create a new instance which has a list of one message and sends it , i understand that apple would prefer that i add them up a bit and then send them, lets say with a 15 second delay between connection attempt to another.
any recommendations on how to implement that using your code ?
Ziv
Hi Ziv,
You are most welcome.
I think it’s easier for you to check Items count in your push notification queue, For example you can:
Crate a list of payloads, when you receive a new massage after adding it to your queue check Items count and if it was 15 call PushToApple method and pass your current queue.
After receiving feedback, clear the list.
I can write a sample code for you if it helps.
Hi arashnorouzi
I’m not sure it will work for me.
in my application users can send each other chat messages and i cant control the stress load, if i take your suggestion for example there is a possibility that in morning time (slow traffic) there wont be 15 message for several hours which will miss the point of push and in the evening time there would be hundreds in a minute.
i need someway to combine the 15 ( a number) with a time interval that will say OK no 15 but its waiting for a minute so I’m sending anyway.
i was thinking maybe writing all messages to DB and every X time run a procedure that will sent them all, but its not very elegant and i would prefer doing all inside the code.
anyway i would love to see your example to get a better feel of how you see implementation of a queue.
thank you
ZIv
Hi Ziv,
What you need is threading. your thread will run every 15 seconds for example. If count of items in queue is larger than 20 it will send the message, if not it will sleep for another 15 seconds.
And you will increase a number every time you count items in the queue, if its larger than 4 which means every minute and your items in list are less than 20, you will send the queue.
I hope this helps.
Please let me know if you need more info.
Thanks.
HI
I am having a problem with certificates..
I am seeing an exception error when running the code in my visual studio development environment on windows 7 machine. I have a feeling that it is just a windows setup issue. Can anyone help? I have spent many hours trying to fix this issue, and am stumped!
Ex error follows:
“The credentials supplied to the package were not recognized
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.ComponentModel.Win32Exception: The credentials supplied to the package were not recognized
Source Error:
Line 215: try
Line 216: {
Line 217: _apnsStream.AuthenticateAsClient(host, certificates, System.Security.Authentication.SslProtocols.Ssl3, false);
Line 218: }
Line 219: catch (System.Security.Authentication.AuthenticationException ex)
Source File: c:\inetpub\wwwroot\MoonAPNS\App_Code\PushNotification.cs Line: 217
Stack Trace:
[Win32Exception (0x80004005): The credentials supplied to the package were not recognized]
System.Net.SSPIWrapper.AcquireCredentialsHandle(SSPIInterface SecModule, String package, CredentialUse intent, SecureCredential scc) +6153980
System.Net.Security.SecureChannel.AcquireCredentialsHandle(CredentialUse credUsage, SecureCredential& secureCredential) +280
System.Net.Security.SecureChannel.AcquireClientCredentials(Byte[]& thumbPrint) +1108
System.Net.Security.SecureChannel.GenerateToken(Byte[] input, Int32 offset, Int32 count, Byte[]& output) +582
System.Net.Security.SecureChannel.NextMessage(Byte[] incoming, Int32 offset, Int32 count) +112
System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest) +34
System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest) +53
System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest) +120
System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest) +85
System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest) +61
System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest) +137
System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest) +53
System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest) +120
System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest) +85
System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest) +61
System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest) +137
System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest) +53
System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest) +120
System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest) +85
System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest) +61
System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest) +137
System.Net.Security.SslState.ForceAuthentication(Boolean receiveFirst, Byte[] buffer, AsyncProtocolRequest asyncRequest) +143
System.Net.Security.SslState.ProcessAuthentication(LazyAsyncResult lazyResult) +99
System.Net.Security.SslStream.AuthenticateAsClient(String targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, Boolean checkCertificateRevocation) +47
MoonAPNS.PushNotification.OpenSslStream(String host, X509CertificateCollection certificates) in c:\inetpub\wwwroot\MoonAPNS\App_Code\PushNotification.cs:217
MoonAPNS.PushNotification.Connect(String host, Int32 port, X509CertificateCollection certificates) in c:\inetpub\wwwroot\MoonAPNS\App_Code\PushNotification.cs:182
MoonAPNS.PushNotification.SendQueueToapple(IEnumerable`1 queue) in c:\inetpub\wwwroot\MoonAPNS\App_Code\PushNotification.cs:111
MoonAPNS.PushNotification.SendToApple(List`1 queue) in c:\inetpub\wwwroot\MoonAPNS\App_Code\PushNotification.cs:84
_Default.Page_Load(Object sender, EventArgs e) in c:\inetpub\wwwroot\MoonAPNS\Default.aspx.cs:29
System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e) +14
System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e) +35
System.Web.UI.Control.OnLoad(EventArgs e) +91
System.Web.UI.Control.LoadRecursive() +74
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +2207″
Hi Clive,
Just received your comment. Have you installed the certificates properly? Do you have passwords on the certificate ?
Have you set that ?
var push = new PushNotification(True for using production certificates or False to use sandbox, “Location of P12 file”,”password”);
Please let me know if you could not find the problem.
Hi
It seemed I needed the certificate without the key, not as advised by Apple.. now the messages are going and being received on the test phone.
However the log shows an error message which may not matter…
“An error occurred while reading Apple response for token xyz Safe handle has been closed”
Is this a problem?
Thanks
Cive
Thanks for this great apns library by the way.
Further to my Post about ‘Safe handle has been closed’:
I am baffled by the the full exception error at that point which is…
System.ObjectDisposedException: Safe handle has been closed
at System.Runtime.InteropServices.SafeHandle.DangerousAddRef(Boolean& success)
at System.StubHelpers.StubHelpers.SafeHandleAddRef(SafeHandle pHandle, Boolean& success)
at System.Net.UnsafeNclNativeMethods.OSSOCK.setsockopt(SafeCloseSocket socketHandle, SocketOptionLevel optionLevel, SocketOptionName optionName, Int32& optionValue, Int32 optionLength)
at System.Net.Sockets.Socket.SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, Int32 optionValue, Boolean silent)
at System.Net.Sockets.NetworkStream.SetSocketTimeoutOption(SocketShutdown mode, Int32 timeout, Boolean silent)
at System.Net.Sockets.NetworkStream.set_ReadTimeout(Int32 value)
at System.Net.Security.SslStream.set_ReadTimeout(Int32 value)
at MoonAPNS.PushNotification.ReadResponse(IAsyncResult ar) in c:\inetpub\wwwroot\xxxx\App_Code\CSCode\PushNotification.cs:line 147
Can you suggest where to look for the problem?
Thanks a million
Clive
For information: the line 147 referenced is at this point
146 var info = ar.AsyncState as MyAsyncInfo;
147 info.MyStream.ReadTimeout = 100;
Hey, What’s the licensing for the MoonAPNS library?
Its published under Apache License 2.0
I have updated code files on Git-hub today.
Thanks for notification.
Hi arashnorouzi.
Thank you so much for this beautiful library. I could easily develop provider server with this library.
By the way, I have a question.
If I use Non-English language for ‘alert’ when creating payload, it seems to be sent to the Apple but nothing happens to my device, while it does work if I use English.
Do you have any idea of why? And can you suggest how to solve it?
Hi Sejong,
I’m really happy that Moon-APNS is useful for you.
As much as I know, you can send custom localized text to be shown on your buttons when the push notification is shown on the device.
But I have never tried to send non English push notifications. I would be thankful if yo share your results with us as well.
I have sold the problem.
On line 296 in ‘PushNotification.cs’ file, I have modified your code as follows..
// String length
byte[] apnMessageLength = BitConverter.GetBytes((Int16)Encoding.UTF8.GetBytes(apnMessage).Length);
and on line 305 was modified as
// Write the message
memoryStream.Write(Encoding.UTF8.GetBytes(apnMessage), 0, Encoding.UTF8.GetBytes(apnMessage).Length);
At first, I just replaced ‘ASCII’ to ‘UTF8′ on line 305, and my push alert text broken; It showed just question marks like ‘?????’.
The problem was that UTF8 takes 3bytes(maybe 2bytes? whatever..) per a character while ASCII takes 1byte. So I worked on line 296 as above and it worked fine!:)
Thanks.
Hi
I am using this excellent code in the asp.net webservice for a new iPhone app.
All is working well in our live testing but would now like to make use of the ability to add custom items to the payload.
I am going through a ‘push list’ from a database like this:
============== code =============
Dim p = New List(Of NotificationPayload)
‘ build push list
while rdr.Read()
strToken = rdr(“Token”)
strMsg = rdr(“Message”)
badgenumber = rdr(“BadgeNumber”)
alertsound = rdr(“AlertSound”)
strUserID = rdr(“userID”)
‘ add new payload item
p.Add(New NotificationPayload(strToken, strMsg, badgeNumber, alertsound))
‘ here I would like to add, for example, AddCustom(“UserID”, strUserID) to this payload item
End While
============= end of code===========
Since I am not adding the payloads as individual objects such as payload1, payload2, etc , I can’t figure out the syntax to add the custom items. Can you advise?
Thanks a lot
Clive