PushSharp Разделение проблем

В настоящее время я работаю над веб-приложением С# и пытаюсь заставить push-уведомления работать с помощью пакета 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;
                }
            }
        }
    }
}

}


person iHowell    schedule 17.03.2016    source источник


Ответы (1)


Это происходит, когда вы пытаетесь повторно использовать экземпляр сервисного брокера (например, ApnsServiceBroker), к которому был вызван Stop().

Я предполагаю, что ваш 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/ На практике это может не иметь большого значения, и вы, безусловно, можете частично смягчить его, но имейте это в виду.

Сказав все это, я думаю, что простым решением будет создание нового экземпляра PushBrokerSingleton.Apns и PushBrokerSingleton.Gcm в вашем Application_Start. Это может вызвать у вас другие проблемы, поэтому я не уверен, что это правильное исправление, но оно решит проблему, заключающуюся в том, что брокер не предназначен для повторного использования после вызова Stop().

Я также собираюсь рассмотреть возможность добавления какого-либо способа «сбросить» коллекцию. Я не уверен, что делать это автоматически после окончания .Stop() — хорошая идея, но я могу рассмотреть возможность добавления .Reset() или подобного метода для достижения этой цели. В любом случае, создание нового экземпляра брокера на данный момент вполне приемлемо.

person Redth    schedule 17.03.2016
comment
Большое спасибо за быстрый и очень информативный ответ! Время попробовать. - person iHowell; 17.03.2016
comment
Кроме того, есть ли способ узнать, использовался ли ранее push-брокер? - person iHowell; 17.03.2016
comment
Да, вы можете проверить свойство myServiceBroker.IsCompleted. Если это правда, внутренний BlockingCollection был завершен (поэтому был вызван .Stop()) - person Redth; 17.03.2016
comment
Еще один вопрос: в наших журналах указано, что уведомления были успешно отправлены на несколько токенов устройств (хотя 2 токена устарели), но мы не получили push-уведомлений. Любая странная причина, по которой это могло произойти? Является ли успех относительным успехом? - person iHowell; 17.03.2016
comment
Ложная тревога. Тестирование сработало! Взял круг почета после попыток добраться до этой точки в течение 2 месяцев. - person iHowell; 17.03.2016
comment
Успех для APNS, к сожалению, означает только то, что мы доставили его в Apple, и они не выдали нам ошибку. Что оттуда происходит с устройством, для нас загадка :( - person Redth; 17.03.2016
comment
stackoverflow.com/questions/40237407/ Исключение волшебным образом исчезло. Я загрузил исходный код push Sharp вместо того, чтобы ориентироваться на пакет Nuget. Я ссылаюсь на pushsharp из решения, и мне не удалось воспроизвести ту же проблему. Решение отлично работало с локальной ссылкой на проект в решении. Я подумал, что, возможно, проблема связана с pushsharp Nuget. Я удаляю локальный проект PS из решения, а затем снова ссылаюсь на пакет PushSharp nuget. На этот раз все работало нормально без исключения. - person Emy Stats; 26.10.2016