This package simplifies sending notifications via the Apple Push Notification Service (APNS). Designed for ease of use, flexibility, and compatibility with modern C# development practices, this package supports token-based and certificate-based authentication, advanced notification customization, and error handling.
- Token-Based and Certificate-Based Authentication: Configure APNS authentication using either JSON Web Token (JWT) or X509 certificates.
- Flexible Notification Building: Customize notifications with alerts, badges, sounds, live activity, relevance scores, interruption levels, and more.
- Error Handling: Comprehensive error definitions to handle and respond to APNS errors effectively.
- Environment Configuration: Set up easily for development or production environments.
Please, refer to Apple official documentation to create the certificate or JWT Key used to establish and send a notification request
- Establishing a connection to Apple Push Notification service (APNs)
- Establishing a token-based connection to APNs
- Establishing a certificate-based connection to APNs
To install the package, use:
dotnet add package Apns
To create an ApnsClient
instance, the entry point to the whole Fitomad.Apns framework, developers must use DI.
I provide a helper method registered as an IServiceCollection
extension named AddApns
which receives an ApnsSettings
object as parameter. You can create an ApnsSettings object using the ApnsSettingsBuilder
object.
This is an example of DI in an Unit Testing (xunit) environment.
var testSettings = new ApnsSettingsBuilder()
.InEnvironment(ApnsEnvironment.Development)
.SetTopic("com.desappstre.Smarty")
.WithPathToX509Certificate2(certPath, certPassword)
.Build();
var services = new ServiceCollection();
services.AddApns(settings: testSettings);
var provider = services.BuildServiceProvider();
And now, thanks to the built-on DI container available in .NET we can use the ApnsClient
registered type.
Use the AddApns
method to configure the APNS client. First you have to create the APNS connection settings using the ApnsSettingsBuilder
class to set values for the following properties:
- APNS environment. Apple defines two different environments, Production and Development. Use the
InEnvironment()
method to set the desired environment passing aApnsEnvironment
value - APNS topic. It's the
apns-topic
HTTP header and must be the application bundle identifier for the application that will receive our notifications. Use theSetTopic()
method to pass astring
value with the bundle id. - X509 certificate. Use the three different methods to set the X509 certificate used to sign all the APNS notification requests.
WithCertificate(ApnsCertificate certificate)
: Use a customApnsCertificate
structure with the certificate information.WithX509Certificate2(X509Certificate2 certificate)
: A .NETX509Certificate2
classWithPathToX509Certificate2(string pathToCertificate, string password)
: Pass the path to the certificate file and the associated password
- JWT signed. Apple also brings the chance to use a JWT to sign the notification request. If this is your case please use the following methods:
WithJsonToken(ApnsJsonToken jsonToken)
. Use a customApnsJsonToken
structure to set all the information required to apply the JWT signWithPathToJsonToken(string pathToJwt, string keyId, string teamId)
Pass the path to the JWT key file and also the key id provided by Apple. The package also needs the Team Identifier available in your Apple Developer Program account.
string certPath = Environment.GetEnvironmentVariable("Apns:CertPath");
string certPassword = Environment.GetEnvironmentVariable("Apns:CertPassword");
// Set APNS connection settings
var developmentSettings = new ApnsSettingsBuilder()
.InEnvironment(ApnsEnvironment.Development)
.SetTopic("com.desappstre.Smarty")
.WithPathToX509Certificate2(certPath, certPassword)
.Build();
var services = new ServiceCollection();
services.AddApns(settings: developmentSettings);
var provider = services.BuildServiceProvider();
And now take a look to an example using a JWT key to sign the notification requests.
var jwtInformation = new ApnsJsonToken
{
Content = Environment.GetEnvironmentVariable("Apns:JwtContent"),
KeyId = Environment.GetEnvironmentVariable("Apns:JwtKey"),
TeamId = Environment.GetEnvironmentVariable("Apns:TeamId")
};
// Set APNS connection settings
var developmentSettings = new ApnsSettingsBuilder()
.InEnvironment(ApnsEnvironment.Production)
.SetTopic("com.desappstre.Smarty")
.WithJsonToken(jwtInformation)
.Build();
var services = new ServiceCollection();
services.AddApns(settings: developmentSettings);
var provider = services.BuildServiceProvider();
Create notifications using the INotificationBuilder interface:
var alertContent = new Alert()
{
Title = "Test Alert",
Subtitle = "Test Subtitle",
Body = "Test Body"
};
Notification notification = new NotificationBuilder()
.WithAlert(alertContent)
.Build();
ApnsResponse apnsResponse = await _client.SendAsync(notification, deviceToken: _deviceToken);
var alertContent = new LocalizableAlert()
{
TitleLocalizationKey = "push_title_arg",
TitleLocalizationArguments = [ "España" ],
SubtitleLocalizationKey = "push_subtitle_arg",
SubtitleLocalizationArguments = [ "Madrid" ],
BodyLocalizationKey = "push_body_arg",
BodyLocalizationArguments = [ "❤️" ]
};
Notification notification = new NotificationBuilder()
.WithAlert(alertContent)
.Build();
ApnsResponse apnsResponse = await _client.SendAsync(notification, deviceToken: _deviceToken);
In case you need to send a Live Activity notification you can use the same NotificationBuilder
object used to create regular notification, but once you set the common notification properties, you must invoke the UseLiveActivity
method to switch to the Live Activity properties set methods.
You have to set the PushType
property to NotificationType.LiveActivity
in the NotificationSettings
object passed to the SendAsync
method available in the ApnsClient
object.
Setting the PushType property also modify the notification topic adding the proper suffix, in this case .push-type.liveactivity
var notificationSettings = new NotificationSettings
{
PushType = NotificationType.LiveActivity
};
var alert = new Alert
{
Title = "Live Activity Title",
Body = "Live Activity Body"
};
var dataContent = new Dictionary<string, object>
{
{ "name", "@fitomad" }
};
var liveActivityNotification = new NotificationBuilder()
.WithAlert(alert)
.UseLiveActivity()
.SetTimestamp(DateTimeOffset.UtcNow.AddMinutes(2).ToUnixTimeSeconds())
.SetLiveActivityEvent(LiveActivityEvent.Start)
.SetLiveActivityAttributeType("MockAttributeType")
.SetLiveActivityStartAttributes(dataContent)
.WithLiveActivityContent(dataContent)
.Build();
ApnsResponse response = await _client.SendAsync(liveActivityNotification,
notificationSettings: notificationSettings,
deviceToken: _deviceToken);
The response from the APNS service is mapped using the ApnsResponse
class, that implements a Result pattern, that means that instead of throws and exception in case somethign goes wrong, the ApnsResponse
class indicates the operation status using the IsSuccess
property.
There are two additional properties that are fulfill depends on the response.
- Success: You can check the
Guid
property that returns anApnsGuid
record that contains the notification identifier and the unique identifier provided by Apple. - Failure: Check the
Error
property that implements anApnsError
record with the error code and description provided by Apple.
ApnsResponse response = await _client.SendAsync(liveActivityNotification,
notificationSettings: notificationSettings,
deviceToken: _deviceToken)
if(apnsResponse is { IsSuccess: true })
{
// Notification send
}
else
{
// Failure
}
You must set some user-secrets to run the unit-test project
Apns:TeamId = TEAM-ID
Apns:KeyId = KEY-ID
Apns:JwtKeyPath = /path/to/jwt-key
Apns:DeviceToken = DEVICE-TOKEN
Apns:CertPath = /path/to/certificate.p12
Apns:CertPassword = PASSWORD
I adopt the enumeration class implementation approach instead of the common enumeration type as proposed in the ".NET Microservices: Architecture for Containerized .NET Applications" book available at dotnet Microsoft website.
The base class for this enumeration classes in the ApnsEnumeration
abstract class that defines a Key-Value based enumeration cases definition.
It also implements the IApnsRepresentable
protocol, that defines a way to share enumetation values as APNS service expects and the IEquatable
and IComparable
interfaces.
Fluent API Design is a programming style that prioritizes easy-to-read, chainable method calls to build complex queries, configurations, or behaviors in a more intuitive and elegant manner. The design mimics natural language, allowing developers to interact with APIs in a flow that feels logical and sequential. This is widely used for configurations, querying data, or rule-building frameworks, like the one you've designed.
-
Ease of Chaining:
-
Methods like
Where
,VerifyThat
,OnFailure
, andOnSuccess
allow logical operations to be chained fluently. For example, a developer can construct a sequence like:rule.Where(someCondition) .VerifyThat(anotherCondition) .OnFailure(() => HandleError()) .OnSuccess(() => HandleSuccess());
This makes the code expressive and clean.
-
-
Flexibility with Conditions:
- By accepting
Func<bool>
as input parameters in methods likeWhere
andVerifyThat
, the API enables developers to specify custom conditions dynamically.
- By accepting
-
Failure and Success Actions:
- The
OnFailure
andOnSuccess
methods let users attach callbacks or actions, making error handling or success behaviors customizable.
- The
-
Handling Properties:
-
The
Property<TValue>
method introduces a generic interface for managing values, enforcing constraints likeIEquatable<TValue>
andIComparable<TValue>
. This ensures that comparisons and validations can be performed effectively. -
Extended Rules for Properties:
- The
IProperty<TValue>
interface further provides methods to validate properties, such asIsEqualsTo
,InRange
,IsNull
,IsNotNull
, and pattern matching withMatchRegularExpression
. This adds flexibility for handling diverse validation needs with succinct chaining:
property.IsNotNull() .InRange(1, 10) .MatchRegularExpression(@"\d+");
- The
-
This Fluent API approach is well-suited for APNS rule validation, as it simplifies defining complex rule sets. For example:
var rule = new Rule()
.VerifyThat(() => isDeviceTokenValid)
.OnFailure(() => LogError("Invalid Token"))
.OnSuccess(() => SendNotification());