如何使用C#发送iOS APN通知(含P12证书配置及代码示例)
基于.NET Framework 4.5/4.7 C# Windows Service 发送APN通知完整实现方案
前置准备工作
- 持有苹果开发者账号生成的APNs认证密钥(.p8格式),相比旧版.p12证书无需定期更新,优先推荐使用
- 提前记录3个核心参数:密钥ID(Key ID)、开发者团队ID(Team ID)、待推送App的Bundle ID
- 获取目标设备的推送Token,注意开发环境和生产环境的Token不通用
- 确认Windows服务运行环境已安装对应版本的.NET Framework 4.5/4.7运行时
依赖安装
通过NuGet安装2个依赖包即可:
System.Net.Http.WinHttpHandler:用于支持HTTP/2协议(APNs强制要求使用HTTP/2)System.IdentityModel.Tokens.Jwt:用于生成APNs校验所需的JWT令牌Newtonsoft.Json:用于序列化通知 payload
具体代码实现
1. APNs配置实体类
public class ApnConfig { // 苹果开发者后台下载的p8密钥完整内容 public string P8PrivateKey { get; set; } // 密钥ID public string KeyId { get; set; } // 开发者团队ID public string TeamId { get; set; } // App的Bundle ID public string BundleId { get; set; } // APNs地址:开发环境填"api.sandbox.push.apple.com",生产环境填"api.push.apple.com" public string ApnHost { get; set; } // 固定端口443 public int Port { get; set; } = 443; }
2. JWT令牌生成工具方法
using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Security.Cryptography; public static class JwtHelper { public static string GenerateApnJwt(ApnConfig config) { // 预处理p8密钥,去除格式标记和换行符 var privateKeyContent = config.P8PrivateKey .Replace("-----BEGIN PRIVATE KEY-----", string.Empty) .Replace("-----END PRIVATE KEY-----", string.Empty) .Replace("\r", string.Empty) .Replace("\n", string.Empty) .Trim(); var privateKeyBytes = Convert.FromBase64String(privateKeyContent); using var ecDsa = ECDsa.Create(); ecDsa.ImportPkcs8PrivateKey(privateKeyBytes, out _); var securityKey = new ECDsaSecurityKey(ecDsa) { KeyId = config.KeyId }; var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.EcdsaSha256); var claims = new[] { new Claim("iss", config.TeamId), new Claim("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString()) }; // APNs要求JWT有效期最长不超过60分钟 var jwtToken = new JwtSecurityToken( issuer: config.TeamId, claims: claims, expires: DateTime.UtcNow.AddMinutes(20), signingCredentials: signingCredentials ); return new JwtSecurityTokenHandler().WriteToken(jwtToken); } }
3. APNs推送核心逻辑
using System.Net.Http; using System.Net.Http.Headers; using System.Text; using Newtonsoft.Json; public class ApnPushCore { private readonly ApnConfig _config; // 复用HttpClient避免端口耗尽 private static readonly HttpClient _httpClient = new HttpClient(new WinHttpHandler() { ServerCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true }); public ApnPushCore(ApnConfig config) { _config = config; } public async Task<bool> SendPush(string deviceToken, string title, string body, int badge = 0, string sound = "default") { try { var jwtToken = JwtHelper.GenerateApnJwt(_config); var requestUrl = $"https://{_config.ApnHost}:{_config.Port}/3/device/{deviceToken}"; using var request = new HttpRequestMessage(HttpMethod.Post, requestUrl); // 强制指定HTTP/2协议 request.Version = new Version(2, 0); request.Headers.Authorization = new AuthenticationHeaderValue("bearer", jwtToken); request.Headers.TryAddWithoutValidation("apns-topic", _config.BundleId); // 推送类型:普通通知填alert,静默通知填background request.Headers.TryAddWithoutValidation("apns-push-type", "alert"); // 构造通知payload var payload = new { aps = new { alert = new { title = title, body = body }, badge = badge, sound = sound }, // 自定义扩展参数可自行添加 custom_param = "" }; var jsonPayload = JsonConvert.SerializeObject(payload); request.Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); var response = await _httpClient.SendAsync(request); return response.IsSuccessStatusCode; } catch (Exception ex) { // 自行扩展日志记录逻辑 Console.WriteLine($"推送失败:{ex.Message}"); return false; } } }
4. Windows Service集成示例
public partial class ApnPushService : ServiceBase { private ApnPushCore _pushCore; private Timer _taskTimer; public ApnPushService() { InitializeComponent(); // 配置信息建议加密存储在配置文件中,不要硬编码 var apnConfig = new ApnConfig { P8PrivateKey = "替换为你的p8密钥内容", KeyId = "替换为你的密钥ID", TeamId = "替换为你的团队ID", BundleId = "替换为你的App Bundle ID", ApnHost = "api.sandbox.push.apple.com" }; _pushCore = new ApnPushCore(apnConfig); } protected override void OnStart(string[] args) { // 定时执行推送任务,间隔可根据业务需求调整 _taskTimer = new Timer(ExecutePushTask, null, TimeSpan.Zero, TimeSpan.FromSeconds(30)); } private async void ExecutePushTask(object state) { // 此处替换为业务逻辑:例如从数据库拉取待推送的消息列表、设备Token列表 var targetDeviceToken = "替换为目标设备的推送Token"; var pushResult = await _pushCore.SendPush(targetDeviceToken, "测试通知", "这是从Windows服务发出的APN通知", 1); // 自行扩展推送结果记录逻辑 } protected override void OnStop() { _taskTimer?.Dispose(); } }
注意事项
- 严格区分开发环境和生产环境的APNs地址、设备Token,二者不互通,配置错误会直接推送失败
- Windows Server 2012及以上系统才原生支持HTTP/2,低版本系统需要先安装对应系统补丁
- p8密钥请妥善保管,不要明文存储在代码或配置文件中
- 短时间内大量推送重复内容会被苹果服务器拦截,建议合理控制推送频率
内容的提问来源于stack exchange,提问作者Sumesh
相关产品推荐
相关产品推荐

