PushSharp 关注点分离

Posted

技术标签:

【中文标题】PushSharp 关注点分离【英文标题】:PushSharp Separation of Concerns 【发布时间】:2016-03-17 00:52:08 【问题描述】:

我目前正在开发一个 C# Web 应用程序,并且我正在尝试使用 PushSharp 包来获取推送通知。我在项目的 Global.asax 文件中有用于推送通知的所有代码,但我不断收到错误消息:

The collection has been marked as complete with regards to additions.

这是我的 Global.asax 文件:

using BYC.Models;
using BYC.Models.Enums;
using Newtonsoft.Json.Linq;
using PushSharp.Apple;
using PushSharp.Google;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace BYC

    public class WebApiApplication : System.Web.HttpApplication
    

        protected void Application_Start()
        
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        
        protected void Application_End()
        
            PushBrokerSingleton pbs = new PushBrokerSingleton();
            pbs.SendQueuedNotifications();
        
    

    public sealed class PushBrokerSingleton
    
        private static ApnsServiceBroker Apns  get; set; 
        private static GcmServiceBroker Gcm  get; set; 
        private static bool ApnsStarted = false;
        private static bool GcmStarted = false;
        private static object AppleSyncVar = new object();
        private static object GcmSyncVar = new object();

        private static readonly log4net.ILog log = log4net.LogManager.GetLogger
(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        public PushBrokerSingleton()
        
            if (Apns == null)
        
            string thumbprint = (AppSettings.Instance["APNS:Thumbprint"]);
            X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
            store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);

            ApnsConfiguration.ApnsServerEnvironment production = Convert.ToBoolean(AppSettings.Instance["APNS:Production"]) ?
                ApnsConfiguration.ApnsServerEnvironment.Production : ApnsConfiguration.ApnsServerEnvironment.Sandbox;

            X509Certificate2 appleCert = store.Certificates
              .Cast<X509Certificate2>()
              .SingleOrDefault(c => string.Equals(c.Thumbprint, thumbprint, StringComparison.OrdinalIgnoreCase));


            ApnsConfiguration apnsConfig = new ApnsConfiguration(production, appleCert);
            Apns = new ApnsServiceBroker(apnsConfig);
            Apns.OnNotificationFailed += (notification, aggregateEx) => 

                aggregateEx.Handle(ex => 

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

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

                        log.Error($"Notification Failed: ID=apnsNotification.Identifier, Code=statusCode");

                    
                    else 
                        // Inner exception might hold more useful information like an ApnsConnectionException           
                        log.Error($"Notification Failed for some (Unknown Reason) : ex.InnerException");
                    

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

            Apns.OnNotificationSucceeded += (notification) => 
                log.Info("Notification Successfully Sent to: " + notification.DeviceToken);
            ;
        
        if(Gcm == null)
        
            GcmConfiguration gcmConfig = new GcmConfiguration(AppSettings.Instance["GCM:Token"]);
            Gcm = new GcmServiceBroker(gcmConfig);
        
    

    public bool QueueNotification(Notification notification, Device device)
    
        if (!ApnsStarted)
        
            ApnsStarted = true;
            lock (AppleSyncVar)
            
                Apns.Start();
            
        
        if(!GcmStarted)
        
            GcmStarted = true;
            lock (GcmSyncVar)
            
                Gcm.Start();
            
        
        switch (device.PlatformType)
        
            case PlatformType.ios:
                return QueueApplePushNotification(notification, device.PushRegistrationToken);
            case PlatformType.android:
                return QueueAndroidPushNotification(notification, device.PushRegistrationToken);
            default: return false;
        
    

    private bool QueueApplePushNotification(Notification notification, string pushNotificationToken)
    
        string appleJsonFormat = "\"aps\": \"alert\":" + '"' + notification.Subject + '"' + ",\"sound\": \"default\", \"badge\": " + notification.BadgeNumber + "";
        lock (AppleSyncVar)
        
            Apns.QueueNotification(new ApnsNotification()
            
                DeviceToken = pushNotificationToken,
                Payload = JObject.Parse(appleJsonFormat)
            );
        
        return true;
    

    private bool QueueAndroidPushNotification(Notification notification, string pushNotificationToken)
    
        string message = "\"alert\":\"" + notification.Subject + "\",\"badge\":" + notification.BadgeNumber + "\"";
        lock (GcmSyncVar)
        
            Gcm.QueueNotification(new GcmNotification()
            
                RegistrationIds = new List<string>
                         
                             pushNotificationToken
                         ,
                Data = JObject.Parse(message),
                Notification = JObject.Parse(message)
            );
        
        return true;
    

    public void SendQueuedNotifications()
    
        if(Apns != null)
        
            if (ApnsStarted)
            
                lock(AppleSyncVar)
                    Apns.Stop();
                    log.Info("Sent Apns Notifications");
                    ApnsStarted = false;
                
            
        
        if(Gcm != null)
        
            if (GcmStarted)
            
                lock (GcmSyncVar)
                
                    Gcm.Stop();
                    log.Info("Sent Gcm Notifications");
                    GcmStarted = false;
                
            
        
    

【问题讨论】:

【参考方案1】:

当您尝试重用已调用 Stop() 的服务代理实例(例如:ApnsServiceBroker)时,就会发生这种情况。

我猜你的 Application_End 会在某个时候被调用,Application_Start 会再次被调用,但由于PushBrokerSingleton.Apns 不为空(它是一个静态字段,因此即使应用程序已停止,它也必须继续存在/开始),它永远不会被重新创建。

PushSharp 很难与 ASP.NET 模式一起很好地工作,某种服务守护程序会更好。

主要问题是您的应用可能会在您不期望的情况下被回收或终止。同一个应用程序中的不相关请求可能会导致您的进程中断,或者您的 AppDomain 可能会被拆除。如果发生这种情况并且代理的Stop() 调用无法成功结束,一些排队的消息可能会丢失。这是一篇关于一些注意事项的精彩文章:http://haacked.com/archive/2011/10/16/the-dangers-of-implementing-recurring-background-tasks-in-asp-net.aspx/ 在实践中,这可能没什么大不了的,您当然可以减轻其中的一部分,但请记住这一点。

说了这么多,我认为一个简单的解决方法是在您的Application_Start 中创建PushBrokerSingleton.ApnsPushBrokerSingleton.Gcm 的新实例。这可能会给您带来其他问题,所以我不确定它是否是 正确 修复,但它可以解决在调用 Stop() 后不打算重用代理的问题。

我还将考虑添加一些方法来“重置”集合。我不确定在.Stop() 结束后自动执行此操作是否是一个好主意,但我可能会考虑添加.Reset() 或类似的方法来实现此目的。无论如何,现在创建一个新的代理实例是完全可以接受的。

【讨论】:

非常感谢您快速且内容丰富的回复!是时候尝试一下了。 另外,有没有办法判断之前是否使用过推送代理? 是的,您可以检查myServiceBroker.IsCompleted 属性。如果为真,则内部 BlockingCollection 已完成(因此已调用 .Stop() 另一个问题:在我们的日志中,它指出通知已成功发送到多个设备令牌(尽管其中 2 个令牌已过时),但我们没有收到任何推送通知。为什么会发生这种情况有什么奇怪的原因吗?成功是相对的成功吗? 误报。测试成功!在尝试达到这一点 2 个月后取得了胜利。

以上是关于PushSharp 关注点分离的主要内容,如果未能解决你的问题,请参考以下文章

用于 android 的 vb.net 中的 pushSharp

关注点分离

关注点分离与单一职责

关注点分离; MVC;为啥?

自定义安全 HTTP 标头是不是违反关注点分离

关注点分离 - DAO、DTO 和 BO