windows-phone-7azurewindows-phone-8mpnsazure-notificationhub

Azure Notification Hub and WP8 Intermitant notifications


This is a fairly long piece of code but I am getting nowhere with this and cannot see any issues, although I am new to using notification hubs. I am trying to register for targeted notifications (the logged on user) using the notification hub in Azure. After the registration, a test notification is sent.

The issue I am having is that sometimes the notification is sent to the device, and sometimes it is not. It mostly isn't but occasionally when I step through the code on the server, i will get the notification on the emulator come through. Once when I deployed the app to my phone the notification came though on the emulator! I cannot discover a pattern.

My Controller class looks like this;

    private NotificationHelper hub;

    public RegisterController()
    {
        hub = NotificationHelper.Instance;
    }
    public async Task<RegistrationDescription> Post([FromBody]JObject registrationCall)
    {        
        var obj = await hub.Post(registrationCall);
        return obj;
    }

And the helper class (which is used elsewhere so is not directly in the controller) looks like this;

    public static NotificationHelper Instance = new NotificationHelper();

    public NotificationHubClient Hub { get; set; }

    // Create the client in the constructor.
    public NotificationHelper()
    {
        var cn = "<my-cn>";
        Hub = NotificationHubClient.CreateClientFromConnectionString(cn, "<my-hub>");
    }

    public async Task<RegistrationDescription> Post([FromBody] JObject registrationCall)
    {            
        // Get the registration info that we need from the request. 
        var platform = registrationCall["platform"].ToString();
        var installationId = registrationCall["instId"].ToString();
        var channelUri = registrationCall["channelUri"] != null
            ? registrationCall["channelUri"].ToString()
            : null;
        var deviceToken = registrationCall["deviceToken"] != null
            ? registrationCall["deviceToken"].ToString()
            : null;
        var userName = HttpContext.Current.User.Identity.Name;

        // Get registrations for the current installation ID.
        var regsForInstId = await Hub.GetRegistrationsByTagAsync(installationId, 100);

        var updated = false;
        var firstRegistration = true;
        RegistrationDescription registration = null;

        // Check for existing registrations.
        foreach (var registrationDescription in regsForInstId)
        {
            if (firstRegistration)
            {
                // Update the tags.
                registrationDescription.Tags = new HashSet<string>() {installationId, userName};

                // We need to handle each platform separately.
                switch (platform)
                {
                    case "windows":
                        var winReg = registrationDescription as MpnsRegistrationDescription;
                        winReg.ChannelUri = new Uri(channelUri);
                        registration = await Hub.UpdateRegistrationAsync(winReg);
                        break;
                    case "ios":
                        var iosReg = registrationDescription as AppleRegistrationDescription;
                        iosReg.DeviceToken = deviceToken;
                        registration = await Hub.UpdateRegistrationAsync(iosReg);
                        break;
                }
                updated = true;
                firstRegistration = false;
            }
            else
            {
                // We shouldn't have any extra registrations; delete if we do.
                await Hub.DeleteRegistrationAsync(registrationDescription);
            }
        }

        // Create a new registration.
        if (!updated)
        {
            switch (platform)
            {
                case "windows":
                    registration = await Hub.CreateMpnsNativeRegistrationAsync(channelUri,
                        new string[] {installationId, userName});
                    break;
                case "ios":
                    registration = await Hub.CreateAppleNativeRegistrationAsync(deviceToken,
                        new string[] {installationId, userName});
                    break;
            }
        }

        // Send out a test notification.
        await SendNotification(string.Format("Test notification for {0}", userName), userName);

        return registration;

And finally, my SendNotification method is here;

    internal async Task SendNotification(string notificationText, string tag)
    {
        try
        {
            var toast = PrepareToastPayload("<my-hub>", notificationText);
            // Send a notification to the logged-in user on both platforms.
            await NotificationHelper.Instance.Hub.SendMpnsNativeNotificationAsync(toast, tag);      
            //await hubClient.SendAppleNativeNotificationAsync(alert, tag);
        }
        catch (ArgumentException ex)
        {
            // This is expected when an APNS registration doesn't exist.
            Console.WriteLine(ex.Message);
        }
    }

I suspect the issue is in my phone client code, which is here and SubscribeToService is called immediately after WebAPI login;

    public void SubscribeToService()
    {
        _channel = HttpNotificationChannel.Find("mychannel");
        if (_channel == null)
        {
            _channel = new HttpNotificationChannel("mychannel");
            _channel.Open();
            _channel.BindToShellToast();
        }

        _channel.ChannelUriUpdated += async (o, args) =>
                                            {              
                                                var hub = new NotificationHub("<my-hub>", "<my-cn>");
                                                await hub.RegisterNativeAsync(args.ChannelUri.ToString());
                                                await RegisterForMessageNotificationsAsync();

                                            };

    }

    public async Task RegisterForMessageNotificationsAsync()
    {
        using (var client = GetNewHttpClient(true))
        {
            // Get the info that we need to request registration.
            var installationId = LocalStorageManager.GetInstallationId(); // a new Guid

            var registration = new Dictionary<string, string>()
                               {
                                   {"platform", "windows"},
                                   {"instId", installationId},
                                   {"channelUri", _channel.ChannelUri.ToString()}
                               };

            var request = new HttpRequestMessage(HttpMethod.Post, new Uri(ApiUrl + "api/Register/RegisterForNotifications"));

            request.Content = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json");

            string message;

            try
            {
                HttpResponseMessage response = await client.SendAsync(request);
                message = await response.Content.ReadAsStringAsync();
            }
            catch (Exception ex)
            {
                message = ex.Message;
            }
            _registrationId = message;
        }
}

Any help would be greatly appriciated as I have been stuck on this now for days! I know this is a lot of code to paste up here but it is all relevant. Thanks,

EDIT: The SubscribeToService() method is called when the user logs in and authenticates with the WebAPI. The method is here;

    public async Task<User> SendSubmitLogonAsync(LogonObject lo)
    {
        _logonObject = lo;
        using (var client = GetNewHttpClient(false))
        {
        var logonString = String.Format("grant_type=password&username={0}&password={1}", lo.username, lo.password);
            var sc = new StringContent(logonString, Encoding.UTF8);

            var response = await client.PostAsync("Token", sc);
            if (response.IsSuccessStatusCode)
            {
                _logonResponse = await response.Content.ReadAsAsync<TokenResponseModel>();
                var userInfo = await GetUserInfoAsync();                

                if (_channel == null)
                    SubscribeToService();
                else
                    await RegisterForMessageNotificationsAsync();                 

                return userInfo;
            }
    // ...
        }
    }

Solution

  • I have solved the issue. There are tons of fairly poorly organised howto's for azure notification hubs and only one of them has this note toward the bottom;

    NOTE:

    You will not receive the notification when you are still in the app. To receive a toast notification while the app is active, you must handle the ShellToastNotificationReceived event.

    This is why I was experiencing intermittent results, as i assumed you would still get a notification if you were in the app. And this little note is pretty well hidden.