Pushing to Windows Phone 7 devices using MetroPimp

Recently I released a little open source project with the aim of making it easier for developers to write services that push information to Windows Phone 7 devices called MetroPimp.

This library is available via nuget and on bitbucket.

Based on a blog post I wrote when WP7 was still in beta, MetroPimp provides a simple and consistent API that allows you to push Toast, Tile (single and double sided) and Raw push notifications to WP7 devices using the Microsoft Push Notification Service (MPNS).

As developers who have tried this already may know, there is quite a bit of work that goes into setting up push notifications. First you have to register for notifications on the device by opening a channel. Then you have to get the URI provided by MPNS and send it to a service of your own. Lastly, using this URI you need to form XML packets and craft HTTP requests with the right headers to push the data to the device.

MetroPimp simplifies the last step of this process by providing a model that represents the messages you want to send to devices, a single method call to handle serializing the message and sending it up the wire and a simple result which, in the case of failure, identifies the reason for the failure and provides informative message.

Enough talking… show us the code!!!

The main entry point of MetroPimp is the Pusher class (which can be mocked via the IPusher interface). It has a single method named Send that accepts a Message and returns a SendMessageResponse. To create a Pusher, simply new it up (or inject it using your preferred IOC container).

var pusher = new Pusher();

The following sections will define the classes you can use to push various notification formats to a device. For more information on message formats, please refer to Sending Push Notifications for Windows Phone on MSDN.

Raw

To send raw data to the phone (i.e. a notification that is not a toast or tile update), create an instance of Raw and send it using the pusher.

var rawMessage = new Raw {
    Uri = /* insert MPNS uri here */,
    Content = "hello world!"
    };
pusher.Send(rawMessage);

MPNS does not limit you to sending strings in the raw message. However, seeing as just about anything can be represented as a string, I decided this may be more universal. Under the covers, MetroPimp simply converts the string to its UTF8 representation and drops it into the request body.

For more information on sending and receiving raw notifications, refer to How to: Send and Receive Raw Notifications for Windows Phone on MSDN.

Toast

Sending a toast message is similar to sending a raw message in that you simply create an instance of a Toast object and pass it into Send. Toast has 3 fields that differ it from a raw:

  • Text1 – The
    first bit of bold text that appears in the toast (required)
  • Text2 – The second bit of text that appears in the toast (optional)
  • Param – A parameter string to be passed to the application if the user taps the notification (optional)
var toastMessage = new Toast {
    Uri = /* insert MPNS uri here */,
    Text1 = "Hello World!",
    Text2 = "This is a toast.",
    Param = "helloWorldToasted"
    };
pusher.Send(toastMessage);

For more information on sending and receiving toast notifications, refer to How to: Send and Receive Toast Notifications for Windows Phone on MSDN.

Tile

Tile notifications can be specified to be either single or double sided tiles. As double sided tiles are only supported by WP7 Mango, the functionality has been split into two classes. Sending a single sided tile is done by specifying properties available on the Tile class.

  • BackgroundImageUri – The URI to the background image for the tile. Can be either embedded or remote. (required)
  • Title – The text to use as the title of the tile. (required)
  • Count – The number to show in the blue bubble. Not defining or using 0 will clear the bubble. (optional)
var tileMessage= new Tile {
    Uri = /* insert MPNS uri here */,
    BackgroundImageUri = "background.png",
    Title = "Hello World",
    Count = 9
    };
pushed.Send(tileMessage);

Double sided tiles are sent by specifying properties on the DoubleSidedTile class. This class has the same set of properties as Tile for the front side of the tile, along with the following properties for the back side:

  • BackBackgroundImageUri – The URI to the background image for the back of the tile.
  • BackTitle – The text to use as the title for the back of the tile.
  • BackContent – The text to display over the top of the background image on the back of the tile.
var doubleSidedTileMessage = new DoubleSidedTile {
    Uri = /* insert MPNS uri here */,
    BackgroundImageUri = "background.png", 
    Title = "Hello World", 
    Count = 9, 
    BackBackgroundImageUri = "back_background.png",
    BackTitle = "Hello Back",
    BackContent = "This is the back"
    };
pushed.Send(doubleSidedTileMessage);

For more information on sending and receiving toast notifications, refer to How to: Send and Receive Tile Notifications for Windows Phone on MSDN.

Auxiliary Properties

Raw, Toast, Tile and DoubleSidedTile all inherit the base Message class. This class provides the following optional properties to give you greater control over your notification messages:

  • Id – a GUID value representing the message identifier.
  • DeliveryInterval – Defines when the message should be delivered to the device. This can either be immediate (default), within 450 seconds or within 900 seconds.

SendMessageResponse

As soon as you’ve started trying to send notifications to devices, you’ll want to know if it gets through and/or why it failed. To handle this, Pusher.Send returns a SendMessageResponse object with the following properties:

  • ErrorMessage – The message returned from MPNS on the HTTP response.
  • MessageId – The
    identifier of the message. Only included if specified when sending.
  • HttpStatusCode – The HTTP status code (e.g. 200, 400, 401, etc.) of the HTTP response returned from MPNS.
  • HttpStatusDescription – The description of the HTTP status code returned from MPNS.
  • StatusCode – The actual status of the notification’s delivery returned from MPNS (i.e. Received, Queue Full, Suppressed or Dropped).
  • DeviceConnectionStatusCode – The status of the device’s connection to MPNS (i.e. Connected, Temporarily Disconnected, Inactive or Disconnected).
  • SubscriptionStatusCode – The status of the device’s subscription to MPNS (i.e. Active or Expired).
  • DetailsStatusDescription – The detailed description of the notification message’s status given the HTTP status code, MPNS status code, connection and subscription status according to the MSDN documentation.

For more information on response codes, refer to Push Notification Service Response Codes for Windows Phone on MSDN.

Advertisements

Back button broken for URL hashes in IE on WP7 Mango

I’ve been on a little twitter rant recently about this, and thought I’d use my blog as another distribution medium.

Feel free to leave comments here, here (connect forum) or here (jQuery Mobile forum).

Here’s the content of my connect forum post on the matter:

Mango seems to have brought along with it a broken browser…

When you link to an anchor on your page using a hash, the back button doesn’t seem to work too well.

For example, go to http://jquerymobile.com/demos/1.0b1/. Those in the know will realise why… To put it simply, jQuery Mobile uses hash changes to navigate between “pages” in a single or multiple page web application. It uses the hash changed events to determine when to use javascript to request the next page, load it into the DOM and perform an animation to display the next page.

Navigating forward on a jQuery Mobile site works perfectly as expected. It’s when you hit the back button that things go wrong. On a desktop browser, going from the landing page to “Into to jQuery Mobile” swipes in the intro page and hitting back swipes it out again to show the landing page. However, on Mango, hitting the back button when on the intro page does nothing. If you were on another site before you went the the demo landing page, then hitting the back button again will take you back to the previous site.

I have been working on an embedded web application recently and this has been driving me crazy! I’m seeing a Navigated event being raised by the WebBrowser control when it goes to the second page in the application, but back buttons (and manually invoking history.back() and history.go(-1) in javascript) just don’t do anything.

This seems to be a breaking change to me and would potentially mean I push to have Windows Phone 7 dropped as a targetted platform for the (relatively high traffic) website I am working on. An embarrassing idea seeing as I pushed for it to be targetted… 😦

UPDATE 14 July 2011 – It’s been noted that the jQuery Mobile docs site mentioned does work in previous builds of WP7, running IE 7 Mobile. This verifies that this issue has been introduced with IE9 Mobile on WP7 Mango.

UPDATE II 14 July 2011 – The issue is a little deeper than I initially realized. If you define a simple page with an anchor at the top of the page referencing a div at the bottom of the page then we see some more exotic behaviour. Clicking the link and hitting back will work the first time. However, you’ll notice that in the address bar the hash is not removed from the URL. This means that clicking the link and hitting back again will get the browser into a tangle. Here is the HTML snippet I used:

<a href="#bottom">bottom</a>
<div style="height: 1000px"></div>
<a id="bottom" href="#" onclick="javascript:history.back();return false;">back</a>

UPDATE III October 2011 – The issue appears to have been fixed in Mango RTM.

Making MVC 3 a little more… dynamic

The other day I ran into a little problem. I wanted to be able to post some JSON to a web service and have it accept a whole bunch of data (not exactly the technical term) that was not predefined.

Given this very vague problem, I decided I wanted to use ASP .NET MVC 3. So starting with File\New Project, I added a method to my HomeController as follows:

[HttpPost]
public ActionResult DoSomething(string a, string b, dynamic c)
{
    return new EmptyResult();
}

The thought behind this was that I wanted to supply two required properties (a and b) and basically have a dynamic bag for everything else. Also, I didn’t want to be restricted to a single layer of properties. Instead, I wanted to be able to pass a deep tree and have it totally accessible.

Then I pulled up fiddler and set up a request with the following details:

Method POST
URL http://localhost:2643/Home/DoSomething
Request Headers User-Agent: Fiddler

Content-Type: application/json

Request Body { a: “Hello”, b: “World”, c: { d: “this”, e: “is”, f: “dynamic” } }

image

When I hit execute and set a breakpoint on the action, I received the following values in my locals:

clip_image002

As expected, the JsonValueProviderFactory has kicked in and populated the values of a and b using the JSON provided. However, it cannot match c properly so simply throws an object at it that is not dynamic and does not have values on it, hence useless.

After some searching, I found a few valid solutions. The most useful one was this blog post by Anoop Madhusundanan. In this post, he describe the process of using a model binder and a custom model binder attribute to identify parameters of an action method that are to use the model binder. This seemed like a great solution for me, but had a couple of little problems… Most importantly, Anoop’s solution does not allow for multiple parameters on an action method or mapping the parameter to the property in the JSON object.

I came up with a very similar way of solving this problem. I created a class named DynamicJsonBinder which implements IModelBinder, just like Anoop. The main difference with my solution is that I provided a way to ensure that the parameter being populated with the dynamic object is mapped based on the name of the parameter. This is done by using a switch on the attribute called MatchName, which will cause the binder to look at the parameter name and find the matching property in the JSON object to return.

Here’s the attribute:

using System.Web.Mvc;

public class DynamicJsonAttribute : CustomModelBinderAttribute
{
    public override IModelBinder GetBinder()
    {
        return new DynamicJsonBinder(MatchName);
    }

    public bool MatchName { get; set; }
}

And here’s the model binder:

using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Web.Helpers;
using System.Web.Mvc;

public class DynamicJsonBinder : IModelBinder
{
    private readonly bool matchName;

    public DynamicJsonBinder(bool matchName)
    {
        this.matchName = matchName;
    }

    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var contentType = controllerContext.HttpContext.Request.ContentType;
        if (!contentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
            return null;

        string bodyText;
        using (var stream = controllerContext.HttpContext.Request.InputStream)
        {
            stream.Seek(0, SeekOrigin.Begin);
            using (var reader = new StreamReader(stream))
                bodyText = reader.ReadToEnd();
        }

        if (string.IsNullOrEmpty(bodyText)) return null;

        var desiralized = Json.Decode(bodyText);

        if (!matchName) return desiralized;

        var members = desiralized.GetDynamicMemberNames() as IEnumerable&lt;string&gt;;
        return members == null
            || members.Contains(bindingContext.ModelName)
            ? desiralized[bindingContext.ModelName] : null;
    }
}

So if by adding a DynamicJson attribute to the c parameter on the action method, we’ll get the following:

public ActionResult DoSomething(string a, string b, [DynamicJson] dynamic c)
{
    return new EmptyResult();
}

Debugging into this with the same post request as earlier:

clip_image004

We see that c has been populated with a DynamicJsonObject (the result of the call to Json.Decode) and that it has recursively mapped the properties under the JSON object to dynamic object. However, the value is the entire JSON object, that is it’s not mapped to the c property.

To enable name matching we get the following code:

[HttpPost]
public ActionResult DoSomething(string a, string b, [DynamicJson(MatchName = true)] dynamic c)
{
    return new EmptyResult();
}

Debugging we get:

clip_image006

And we see that the property has been matched correctly and the correct value is passed through to the parameter.

Using these two simple classes and a bit of dynamic know-how, I’ve managed to post anything to my web service and have it dynamically accessible. This basically means we have the equivalent of a property bag on steroids that can be queried and manipulated in the same way as any dynamic object. We can even re-serialize the dynamic object to JSON and store it as is if required.

A Day in the Life of a Metro-veloper

This is a follow-up post to my Windows Phone 7 presentation last week at the SDDN.

I have uploaded my powerpoint deck to SlideShare.

Some of the sample apps I used are available on MSDN.

And there was a recent video on youtube of an awesome golfing app that really shows the power of the WP7 UX.

push notifications in windows phone 7

Basics

There’s lots of information hiding within about 10 links on MSDN about how push notifications for WP7. The short of it is the phone opens a HttpNotificationChannel which gives it a unique URI for your applications to send notifications to it.

There are three types of notifications – tile, toast and raw. Tile notifications are used for changing the background, count and title of the application when it is pinned to the Start screen. Toast notifications provide unobtrusive notifications to users when they are outside the application, allowing them to step in easily to perform an action. Raw notifications are messages of any format that can be sent to the phone application and received while it is active.

The problem with the information out there is that it is slightly conflicting because of the recent changes to the APIs with different versions of the toolkit. As a result, I spent many hours trying to get this relatively simple piece of functionality working. At one point I decided that whenever I got something working I’d try shrink wrap it and publish it, so here we go…

DISCLAIMER: WOMM…

WARNING: There’s a lot of code here… Please read the Client Side and Server Side sections before believing you are qualified to download the sample. The sample has a thin WPF client implementation over the server side code to make it a little easier to get started. Remember to check the Debug output window for the phone application when you need a Uri to send a notification.

Client Side

My aim was to get the code required for subscribing to notifications to be as simple as possible. I got it down to the following few lines of code:

// in App.ctor
NotificationService notificationService = new NotificationService("some funky channel name");
notificationService.RawNotificationReceived += RawNotificationReceived;
notificationService.ToastNotificationReceived += ToastNotificationReceived;
notificationService.ChannelUriUpdated += ChannelUriUpdated;

// in Application_Launching & Application_Activated
notificationService.Subscribe();

// event handlers
void ToastNotificationReceived(object sender, ToastNotificationReceivedEventArgs e)
{    // example of handling a toast notification within the application
    MessageBox.Show(e.Message, e.Title, MessageBoxButton.OK);
}

void RawNotificationReceived(object sender, RawNotificationRecievedEventArgs e)
{
    MessageBox.Show(e.Message);
}

void ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e)
{
    // call a webservice to report e.ChannelUri
}

There are a couple of things to note. Firstly, the Subscribe() method is called in the Application_Launching and Application_Activated events. This is due to the tombstoning nature of WP7.

Next, there are separate event handlers for Toast and Raw notifications. They are handled differently because toast notifications have a definite payload format that specifies the ability to send to pieces of text (a title and a message). A raw notification can be anything, which is why I have decided to simply surface the string representation of the payload. A toast must also be handled if the application is executing – i.e. it will not be displayed by the OS.

Also, there is no tile notification handler. This is because the OS handles this directly.

Lastly, the ChannelUriUpdated event handler. This is necessary for your phone application to let the server application know that there is a phone waiting for notifications. When this occurs, the phone application should call a web service to register the URI. The NotificationService class will write this information to the Debug console whenever the application is started, so it is not required for debugging purposes.

Server Side

While I was busy working on the client side, I realised that obviously I’d eventually need to have some server side code to send the notifications. Again, I wanted to simplify it as much as possible:

NotificationService service = new NotificationService();

// send a raw notification
service.SendRaw(clientUri, messageText);

// send a toast notification
service.SendToast(clientUri, titleText, messageText);

// send a tile notification
service.SendTile(clientUri, backgroundImageUri, countValue, titleText);

See… simple. Smile

The Real Code

As I mentioned earlier, you can download all this here. Otherwise, feel free to read through this (or just copy/paste it) to get a better understanding of how the HttpNotificationChannel works.

Client side NotificationService implementation:

public class NotificationService
{
    string channelName;
    HttpNotificationChannel channel;

    public NotificationService(string channelName)
    {
        this.channelName = channelName;
    }

    /// <summary>
    /// Subscribes to the notification events on the channel.
    /// If the channel doesn't already exist, it will be created
    /// and bound to shell tile and toasts.
    /// </summary>
    public void Subscribe()
    {
        if (channel == null) BindChannel();
    }

    /// <summary>
    /// Unubscribes from the notification events on the channel.
    /// </summary>
    public void Unsubscribe()
    {
        if (channel != null) UnsubscribeFromChannelEvents();
    }

    /// <summary>
    /// Finds or creates the notification channel and binds the shell tile
    /// and toast notifications as well as events.
    /// </summary>
    private void BindChannel()
    {
        channel = HttpNotificationChannel.Find(channelName);

        if (channel == null || channel.ChannelUri == null)
        {
            if (channel != null) DisposeChannel();

            channel = new HttpNotificationChannel(channelName);
            channel.ChannelUriUpdated += channel_ChannelUriUpdated;
            channel.Open();
        }
        else System.Diagnostics.Debug.WriteLine(channel.ChannelUri.AbsoluteUri);

        SubscribeToChannelEvents();

        if (!channel.IsShellTileBound) channel.BindToShellTile();
        if (!channel.IsShellToastBound) channel.BindToShellToast();
    }

    /// <summary>
    /// Subscribes to the channel's events.
    /// </summary>
    private void SubscribeToChannelEvents()
    {
        channel.ShellToastNotificationReceived += channel_ShellToastNotificationReceived;
        channel.HttpNotificationReceived += channel_HttpNotificationReceived;
        channel.ErrorOccurred += channel_ErrorOccurred;
    }

    /// <summary>
    /// Unsubscribes from the channel's events
    /// </summary>
    private void UnsubscribeFromChannelEvents()
    {
        channel.ShellToastNotificationReceived -= channel_ShellToastNotificationReceived;
        channel.HttpNotificationReceived -= channel_HttpNotificationReceived;
        channel.ErrorOccurred -= channel_ErrorOccurred;
    }

    /// <summary>
    /// Closes the channel and disposes it.
    /// </summary>
    private void DisposeChannel()
    {
        channel.Close();
        channel.Dispose();
        channel = null;
    }

    /// <summary>
    /// Event handler for the ChannelUriUpdate event.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void channel_ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e)
    {
        channel.ChannelUriUpdated -= channel_ChannelUriUpdated;
        System.Diagnostics.Debug.WriteLine(e.ChannelUri.AbsoluteUri);
        OnChannelUriUpdated(e);
    }

    /// <summary>
    /// Raised when the notification channel is given a URI.
    /// </summary>
    /// <remarks>
    /// This is when you would call a web service to tell it that a client is
    /// registered and what the notification URI is.
    /// </remarks>
    public event EventHandler<NotificationChannelUriEventArgs> ChannelUriUpdated;

    /// <summary>
    /// Raises the ChannelUriUpdated event.
    /// </summary>
    /// <param name="e"></param>
    protected virtual void OnChannelUriUpdated(NotificationChannelUriEventArgs e)
    {
        if (ChannelUriUpdated != null) ChannelUriUpdated(this, e);
    }

    /// <summary>
    /// Event handler for the HtppNotificationReceived event.
    /// This is called when a raw notification is received.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void channel_HttpNotificationReceived(object sender, HttpNotificationEventArgs e)
    {
        byte[] bytes;
        using (var stream = e.Notification.Body)
        {
            bytes = new byte[stream.Length];
            stream.Read(bytes, 0, (int)stream.Length);
        }
        var message = Encoding.UTF8.GetString(bytes, 0, bytes.Length);

        OnRawNotificationReceived(message);
    }

    /// <summary>
    /// Occurs when a raw notification is received.
    /// </summary>
    public event EventHandler<RawNotificationRecievedEventArgs> RawNotificationReceived;

    /// <summary>
    /// Raises the RawNotificationReceived event on the UI thread.
    /// </summary>
    /// <param name="message"></param>
    protected virtual void OnRawNotificationReceived(string message)
    {
        Deployment.Current.Dispatcher.BeginInvoke(() =>
        {
            if (RawNotificationReceived != null) 
                RawNotificationReceived(
                    this, 
                    new RawNotificationRecievedEventArgs(message)
                    );
        });
    }

    /// <summary>
    /// Event handler for the ShellToastNotificationReceived event.
    /// This occurs when a toast notification is received on the channel.
    /// </summary>
    /// <remarks>
    /// This must be handled by the application if it is running when a toast is received.
    /// </remarks>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void channel_ShellToastNotificationReceived(object sender, NotificationEventArgs e)
    {
        var title = e.Collection.Values.First();
        var message = e.Collection.Values.Skip(1).FirstOrDefault() ?? string.Empty;
        OnToastNotificationReceived(title, message);
    }

    /// <summary>
    /// Occurs when a toast notification is received.
    /// </summary>
    /// <remarks>
    /// This must be handled by the application if it is running when a toast is received.
    /// </remarks>
    public event EventHandler<ToastNotificationReceivedEventArgs> ToastNotificationReceived;

    /// <summary>
    /// Raises the ToastNotificationReceived event on the UI thread.
    /// </summary>
    /// <param name="title"></param>
    /// <param name="message"></param>
    protected virtual void OnToastNotificationReceived(string title, string message)
    {
        Deployment.Current.Dispatcher.BeginInvoke(() =>
        {
            if (ToastNotificationReceived != null) 
                ToastNotificationReceived(
                    this, 
                    new ToastNotificationReceivedEventArgs(title, message)
                    );
        });
    }

    /// <summary>
    /// Event handler for the ErrorOccurred event.
    /// Handles different events according to ErrorType.
    /// </summary>
    /// <remarks>
    /// Needs more work... ;-(
    /// </remarks>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void channel_ErrorOccurred(object sender, NotificationChannelErrorEventArgs e)
    {
        switch (e.ErrorType)
        {
            // something went severely wrong. lets wait a while before trying again.
            case ChannelErrorType.ChannelOpenFailed:
                DisposeChannel();
                System.Threading.Thread.Sleep(60000);
                BindChannel();
                break;
            // an image uri has been referenced in a notification that was
            // not bound to the shell tile.
            case ChannelErrorType.MessageBadContent:
                break;
            // too many notifications have been received in too short a time span.
            case ChannelErrorType.NotificationRateTooHigh:
                break;
            // a bad payload was received. re-establish the connection to overcome this.
            case ChannelErrorType.PayloadFormatError:
                DisposeChannel();
                BindChannel();
                break;
            // the type notifications we're receiving is going to change.
            case ChannelErrorType.PowerLevelChanged:
                break;
            default:
                break;
        }
    }
}

public class RawNotificationRecievedEventArgs : EventArgs
{
    public string Message { get; private set; }
    public RawNotificationRecievedEventArgs(string message)
    {
        Message = message;
    }
}

public class ToastNotificationReceivedEventArgs : EventArgs
{
    public string Title { get; private set; }
    public string Message { get; private set; }
    public ToastNotificationReceivedEventArgs(string title, string message)
    {
        Title = title;
        Message = message;
    }
}

Server side code:

public interface INotificationService
{
    NotificationResponse SendTile(string uri, string backgroundImageUri, int count, 
                                  string title, [Optional] Guid messageId);
    NotificationResponse SendToast(string uri, string text1, [Optional] string text2, 
                                   [Optional] Guid messageId);
    NotificationResponse SendRaw(string uri, string message, [Optional] Guid messageId);
}

public class NotificationService : INotificationService
{
    const int maxPayloadLength = 1024;

    const string targetHeader = "X-WindowsPhone-Target";
    const string notificationClassHeader = "X-NotificationClass";
    const string messageIdHeader = "X-MessageID";
    const string notificationStatusHeader = "X-NotificationStatus";
    const string subscriptionStatusHeader = "X-SubscriptionStatus";
    const string deviceConnectionStatusHeader = "X-DeviceConnectionStatus";

    const string tileMessageFormat =
        "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
        "<wp:Notification xmlns:wp=\"WPNotification\">" +
            "<wp:Tile>" +
                "<wp:BackgroundImage>{0}</wp:BackgroundImage>" +
                "<wp:Count>{1}</wp:Count>" +
                "<wp:Title>{2}</wp:Title>" +
            "</wp:Tile>" +
        "</wp:Notification>";
    /// <summary>
    /// X-WindowsPhone-Target: token
    /// </summary>
    const string tileTarget = "token";
    /// <summary>
    /// X-NotificationClass: 1
    /// </summary>
    const string tileNotificationClass = "1";

    const string toastMessageFormat =
        "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
        "<wp:Notification xmlns:wp=\"WPNotification\">" +
            "<wp:Toast>" +
                "<wp:Text1>{0}</wp:Text1>" +
                "<wp:Text2>{1}</wp:Text2>" +
            "</wp:Toast>" +
        "</wp:Notification>";
    /// <summary>
    /// X-WindowsPhone-Target: toast
    /// </summary>
    const string toastTarget = "toast";
    /// <summary>
    /// X-NotificationClass: 2
    /// </summary>
    const string toastNotificationClass = "2";

    /// <summary>
    /// X-NotificationClass: 3
    /// </summary>
    const string rawNotificationClass = "3";

    public NotificationResponse SendTile(string uri, string backgroundImageUri, int count, 
                                         string title, [Optional] Guid messageId)
    {
        var str = string.Format(tileMessageFormat, backgroundImageUri, count, title);
        return SendNotification(uri, str, tileNotificationClass, tileTarget, messageId);
    }

    public NotificationResponse SendToast(string uri, string text1, [Optional] string text2, 
                                          [Optional] Guid messageId)
    {
        var str = string.Format(toastMessageFormat, text1, text2);
        return SendNotification(uri, str, toastNotificationClass, toastTarget, messageId);
    }

    public NotificationResponse SendRaw(string uri, string message, 
                                        [Optional] Guid messageId)
    {
        return SendNotification(uri, message, rawNotificationClass, messageId: messageId);
    }

    private NotificationResponse SendNotification(string uri, string message, 
                                                  string notificationClass, 
                                                  [Optional] string target, 
                                                  [Optional] Guid messageId)
    {
        var payload = Encoding.UTF8.GetBytes(message);
        if (payload.Length > maxPayloadLength) 
            throw new ArgumentException(
                "The message provided is longer than the maximum payload length (1024B).",
                message
                );

        var sendNotificationRequest = WebRequest.Create(uri) as HttpWebRequest;
            
        sendNotificationRequest.Method = WebRequestMethods.Http.Post;
        sendNotificationRequest.ContentLength = payload.Length;
        sendNotificationRequest.ContentType = "text/xml";

        // X-NotificationClass: 1, 2, 3
        sendNotificationRequest.Headers.Add(notificationClassHeader, notificationClass);
        // X-WindowsPhone-Target: token, toast
        if (!string.IsNullOrWhiteSpace(target)) 
            sendNotificationRequest.Headers.Add(targetHeader, target);
        // X-MessageId: 00000000-0000-0000-0000-000000000000
        if (messageId != null && messageId != Guid.Empty) 
            sendNotificationRequest.Headers.Add(messageIdHeader, messageId.ToString());

        using (var requestStream = sendNotificationRequest.GetRequestStream())
            requestStream.Write(payload, 0, payload.Length);

        HttpWebResponse response;
        string errorMessage = null;
        try
        {
            response = sendNotificationRequest.GetResponse() as HttpWebResponse;
        }
        catch (WebException ex)
        {
            response = ex.Response as HttpWebResponse;
            errorMessage = ex.Message;
        }
        return new NotificationResponse
        {
            MessageId = response.Headers[messageIdHeader],
            ErrorMessage = errorMessage,
            NotificationStatus = response.Headers[notificationStatusHeader],
            SubscriptionStatus = response.Headers[subscriptionStatusHeader],
            DeviceConnectionStatus = response.Headers[deviceConnectionStatusHeader],
            StatusCode = response.StatusCode
        };
    }
}

public class NotificationResponse
{
    public string ErrorMessage { get; set; }
    public DateTimeOffset Timestamp { get; set; }
    public string MessageId { get; set; }
    public HttpStatusCode StatusCode { get; set; }
    public string NotificationStatus { get; set; }
    public string DeviceConnectionStatus { get; set; }
    public string SubscriptionStatus { get; set; }
}

DOWNLOAD IT NOW!

Remember, to get the Uri just watch the Debug output window when your WP7 application starts up.

Good luck Smile

[Update 2010-08-27] Added extra check to Client.NotificationService.BindChannel() so that a channel that already exists but does not have a URI is disposed and a new one is created.

XPS 16 Mini-Review

At the request of a colleague, I’m writing a little review on my precious baby… 🙂

Before I start boring most, I’m going to go out there and say I agree with *almost* everything this guy says, so I’ll just rehash a few things and give my opinion.

Firstly, some quick specs:

  • Processor: Intel Core i7 820QM (8MB 1.73GHz – 3.066GHz)
  • Memory: 8GB – 2DIMM 1333MHz DDR3
  • HDD: 128 GB G.Skill Falcon SSD (aftermarket – orignially with 500GB 7200RPM)
  • Graphics: 1GB ATI Mobility Radeon HD 4670
  • Display: 16.0" 1080p Full HD RGBLED LCD with 2.0 MP Webcam
  • Optical Drive: 4X Blu-ray Disc Combo Drive (DVD/CD +/- RW +BD Read)
  • OS: Windows 7 Ultimate 64-bit
  • Wireless: Intel WiFi Link 5300 (802.11a/g/n)
  • Battery: 9-cell
  • Dimensions: 2.5-5cm x 38cm x 26cm with 9-cell battery (H x W x D) 

Build and Design

Pros

Just like the rest of the (Studio) XPS range, this laptop just looks sexy. It’s sleek, shiny, dressed in a little leather, and has all the shiny touch-sensitive media buttons to match. The backlit keyboard is an absolute pleasure to use with its big spaced keys. The unit has very little flex in it at all. The 16” frameless glossy RGBLED screen is so gorgeous it makes staring at code even more fun. All the ports are in the right place (security lock, VGA, Ethernet, DisplayPort, HDMI, 2x USB, 2x headphone, microphone, USB/eSATA combo, FireWire, ExpressCard and card reader). And the sound is awesome…

Cons

The shininess of the plastic means that fingerprints really stand out. The Synaptics multi-touch track pad is hard to use and can get very annoying when trying to do simple things like crop using in a picture editor. The gloss on the screen makes it pretty hard to use outside on a bright day. The slot-loader drive is not my favourite as it tends to be a bit noisy (compared to tray-loaders) and quite slow. Also, the battery doesn’t quite line up with the rest of the unit so there is a bit of a gap constantly staring me in the face as I’m typing.

Most importantly… THERE’S NO SEPARATE RAM AND HDD COVER ON THE MAIN PANEL!!! This really got on my nerves when I switched in my SSD, because it meant what is usually a 2 minute job on most laptops became substantially more due to the funny screw placements and fear of “screwing” something up.

Performance

I ran a few benchmarks in an earlier post, and found my PCMarks at 9018 and WEI at 6.7. The review on notebookreview had the PCMarks at 6303. It also had the WPrime 32M result at 31.827s, which I bested at 15.257s. I think it’s safe to say that this baby ain’t no slug… 🙂

However, there is usually one downfall to all this power that not even the XPS 16 could surpass… HEAT! This thing gets sooo hot that putting it to good use means it can’t be used as a “laptop”. Noise was not too bad, but probably because the fan isn’t actually doing much.

Also, the battery life is a bit too short for my liking. My 9-cell only gets me about 2.5hrs, which some might think is quite high given what it’s running, but I think could be improved.

Conclusion

I love  it. It’s sexy and fast. It falls down on the build quality a bit like most Dell laptops and can be used to help you get through the winters, but the bang for buck this machine offers more than makes up for it in my eyes.

XPS 16 Performance Benchmarks

Before installing a heap of junk, I thought I’d run a couple of benchmarking tools over my new machine.

PCMark Vantage (x64)– 9018 PCMarks

 pcmark - 9018

One thing you may note is the HDD Test Suite score thanks to this slight mod… 🙂

Windows Experience Index (WEI) – 6.7

image

I guess this means I need to upgrade my video card… =p

Replacing the Hard Drive in an XPS 16 with an SSD

Within an hour of accepting my laptop this morning I had already taken it apart. Why…? to install this little beasty:

hdtune g.skill take 2

As soon as I turned the machine over though, I already knew this would be a little harder than usual. Unlike most laptops, the XPS 16 doesn’t have a simple slot on the side to remove the hard drive.

After turning the machine over and removing the battery, you need to undo all the screws in the base plate. The screws do not come out of the holes though, so don’t keep turning hoping they will. After you’ve undone them, push the plate slightly left and lift it off the unit.

IMG_0264

Now there are three screws holding the hard drive bracket in. These need to be removed, keeping in mind that there is no screw in the bottom right hole, as the screw in the base plate goes here.

Pull the blue tab to unplug the hard drive and lift the drive out. Then take the SATA plug off the drive and take the four screws out that are holding it into the bracket.

Put the drive somewhere safe and reverse the process replacing the rust with your lovely SSD.

  1. Plug the SATA plug into the drive.
  2. Hold the drive in the bracket and make sure you have the bracket facing the right way so that it will slot in to the laptop and the plug will be facing the right direction.
  3. Put the four screws in to support it.
  4. Place the drive in the laptop (again ensuring the SATA plug is facing the right direction).
  5. Put the three screws in the bracket to hold it to the unit (leaving the bottom right hole empty).
  6. Replace the base plate and fasten all the screws.
  7. Plug in the battery and away you go!

Unboxing My New Laptop – Dell XPS 16

I finally received my new laptop… A Dell XPS 16. Specs:

Processor Intel Core i7 802QM (1.73GHz, 3.06GHz turbo)
RAM 8GB 1333MHz DDR3
Video 1GB ATI Mobility Radeon HD 4670
Optical Drive Slot Load  Blu-Ray BDROM, DVD +/- RW combo
Monitor 16″ Full HD RGBLED
etc…  

So, first thing’s first… unbox it!

IMG_0260

IMG_0261

 IMG_0262

 IMG_0263

What next…? pull it apart of course!

Commerce Server 2009 R2 and Visual Studio 2010

So you’re a Commerce Server developer that’s sitting on the bleeding edge…? Well now you’ve got the same chance of starting your site as easily as you did with CS2007 and VS2008 – i.e. not much. Why, you ask…? Because the team have not updated the template that they use for the Project Creation Wizard addin, so it’s just as useful as it always has been… =p

Let’s see how it works…

Using the Project Creation Wizard

Just like in the old days of CS2007 and pre-R2, hit File > New Website and you’ll get the “New Web Site” wizard. Select your language of choice (C# is the better one) and “Commerce C# ASP.NET Web Application”.

image

Although for some odd reason, they’ve decided you can’t use the file system or any non-localhost url (like above), so at least when you first create the site you need to do so under localhost.

If you’re a real developer, you’ll say yes to this too… 🙂

image

You’ll then have the Commerce Server Site Packager application pop up to unpack a default web site. This will ask for the site url, but not ask any of the good old questions like what you want to name the application directories, meaning you’re stuck with a crappy prefix on every directory it unpacks.

Anyway, once that’s done you should have an unpacked beginning of a web site. If you go to this point without a couple of COM errors, then congratulations. But now you’ll also notice that the project created was a “Web Site” project instead of a “Web Application” project.

So what’s next… oh yeah! Find another way to do it that actually makes sense.

Manual Site Creation

If you’ve been through the process of using the Commerce Server Site Package application to extract a site in previous versions of commerce server, then you’re not going to learn much here… nothing has changed! If you haven’t, please read on.

Well if you’ve been through the Project Creation Wizard then you have two things up your sleeve, you have a good web.config to start from and a csapp.ini file that points to the original PUP file used to unpack the empty website. If you haven’t, then don’t worry – I’ve uploaded the Commerce Server 2009 Starter Files to my SkyDrive and I can tell you that the original PUP file lives at “C:\Program Files (x86)\Microsoft Commerce Server 9.0\Extensibility Kits\Samples\Pup Packages\empty.pup”.

Now you can open the Commerce Server Site Package application manually at “C:\Program Files (x86)\Microsoft Commerce Server 9.0\PuP.exe”. It will ask you if you want to package or unpackage, but if you don’t have a site on your machine package will be disabled and unpackage will be selected. After hitting next, select the PUP file mentioned above and select Custom Unpack then hit next again. Then select create a new site and hit next.

Now you need to type a name into Site Name text box that does not conflict with any existing sites and click next – e.g. CommerceSample. This name is used by your web application to identify which site resources are used by the application because Commerce Server allows you to have multiple “Sites” on a machine. Then you’ll want to unpack all the resources available and click next. Click next again to create the authentication and profiling resources.

Now you will be setup all the database connections for each resource in the site.

image

Selecting a resource and clicking Modify allows you to set all the basic connection details. If you are using a remote database server, this is where you need to change the settings.

image

After modifying the connection strings as necessary and clicking next, you will be able to select the applications you want to unpack.

image

Each Site is “usually” made up of 5 applications – a Marketing, Orders, Profile and Catalog web service and a Web app. After selecting them all hit next.

You can now rename any or all of the applications and change the web site in IIS that they will be hosted on and virtual directory they will be under.

image

After a few seconds, you will be asked to provide some scripts. Click next twice to skip this.

After about a minute, you will be notified of whether the database connections were successfully set up. Click next to continue. You will then be notified of whether all the resources were extracted successfully. Click done.

Now, to get the web config into the right place, find the location of the web application that was extracted and drop in the web.config. This folder will have 3 files in it before you drop in the config file– csapp.ini, OrderObjectMappings.xml and OrderPipelineMappings.xml.

And that’s it! Well, not really… you now have extracted a Commerce Server web site, but it will not run. Now you’re in another world of pain called “Configuring a Commerce Server Site”.