How to Send Tile Toast and Raw Notifications to the Microsoft Push Notification Service to Windows Phone 7

Note: Code here applies to CTP – I have not yet updated for the RTM.
This post provides the code required to send notifications through the Microsoft Push Notifications Service to a Windows Phone 7 device. I have put this together for the purpose of re-use across projects. Receiving and processing the notification on Windows Phone 7 will be the subject of a later post but I have some screenshots of the result here.

I will keep this breif as you are likely already aware, but for those of you who are not there are three types of notifications you can send to a Windows Phone 7 device:

  • Tile – A tile is a visual, dynamic representation of application specific state within the quick launch area of the phone’s start experience
  • Toast – Displays as an overlay onto the user’s current screen, a bit like an outlook email notification popup that you can press that launches you into the application.
  • Raw – provides the ability for your application service to push data to the application while it is in the foreground without the need for your application to poll the appplication service.

So as a teaser – what do these look like in action:

  • Tile – ability to change the count, background image(using either a local image and remote image) and text, yes E=mc^2 influnced by the first non programming book i am reading in about 10yrs – “A Brief History of Time” by Steven Hawking, ok so still technical. but not programming.
  • Tile Push Notification Before

    Tile Push Notification Before

    Tile Push Notification After

    Tile Push Notification After

  • Toast – See the Orange pop up at the top with the lame joke – well that is a toast, it looks like the above before and after as below.
  • Toast Push Notification After

    Toast Push Notification After

  • Raw – provides the ability for your application service to push data to the application while it is in the foreground without the need for your application to poll the appplication service. Well really with raw there is nothing to show until you implement a handler in your application to do something useful with the data (HttpNotificationChannel.HttpNotificationReceived). Therefore no image until next post.
  • So what exactly are we building a RawNotification, TileNotification and ToastNotification class with a Send method that returns a parsed NotificationResponse – here it is:

    An API for sending Push Notifications Tile, Toast and Raw
    Once you finish implementing this with the code provided in the post you will be able to send a notification as simply as follows:
  • Tile – TileNotification.cs
  • ToastNotification toast = new ToastNotification("Wanted Toast", "But forgot the bread ", new Uri(txtUri.Text), Guid.NewGuid(), 2, null);
    ThreadPool.QueueUserWorkItem((o) => toast.Send(ProcessResponse));
  • Toast – ToastNotification.cs
  • TileNotification tile = new TileNotification(@"/Images/Background1.png", 2, "E = mc^2", new Uri(txtUri.Text), Guid.NewGuid(), 1, null);
    ThreadPool.QueueUserWorkItem((o) => tile.Send(ProcessResponse));
  • Raw – RawNotification.cs
  •             XmlDocument rawData = new XmlDocument();
                string rawContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n\r\n" +
    
                                     "<root>\r\n" +
                                        "<foo>c</foo>\r\n" +
                                        "<bar>3000000</bar>\r\n" +
                                     "</root>";
                rawData.LoadXml(rawContent);           
    
                RawNotification raw = new RawNotification(rawData, new Uri(txtUri.Text), Guid.NewGuid(), 3, null);
                ThreadPool.QueueUserWorkItem((o) => raw.Send(ProcessResponse));

    So now you can see how easy it is to use, how is the code for sending the notifications implemented:

    NotificationBase.cs

    using System;
    using System.IO;
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    
    namespace NickHarris.Net.Notification
    {
        public enum NotificationType { Token, Toast, Raw };   
    
        public abstract class NotificationBase
        {
            private Uri _URI;
            private Guid? _UUID;
            private int? _priority;
            private X509Certificate _certificate;
            private NotificationType _target;
            private byte[] FormattedNotificationMessage { get; set; }
    
            public NotificationBase(Uri uri, Guid? uuid, int? priority, X509Certificate certificate, NotificationType target)
            {
                _URI = uri;
                _UUID = uuid;
                _priority = priority;
                _certificate = certificate;
                _target = target;
            }
    
            protected abstract Byte[] GetTemplateFormatted();
            protected abstract bool IsPriorityValid(int priority);
    
            protected HttpWebRequest CreateRequest(Uri uri, Guid? uuid, int? priority, X509Certificate certificate, NotificationType target)
            {
                 if (priority.HasValue && !IsPriorityValid(priority.Value))
                    throw new ArgumentOutOfRangeException("Priority {0} is not valid for this notification type", priority.ToString());
    
                // The URI that the Push Notification service returns to the Push Client when creating a notification channel.
                HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest;
                // HTTP POST is the only allowed method to send the notification.
                request.Method = WebRequestMethods.Http.Post;
                request.ContentType = "text/xml; charset=utf-8";
                FormattedNotificationMessage = GetTemplateFormatted();
                request.ContentLength = FormattedNotificationMessage.Length;
                request.Headers = GetHeaders(uuid, priority, target);
    
                // If the cloud service sending this request is authenticated, it needs to send its certificate.
                // Otherwise, this step is not needed.
                if (certificate != null)
                    request.ClientCertificates.Add(certificate);
                return request;
            }
    
            protected WebHeaderCollection GetHeaders(Guid? uuid, int? priority, NotificationType target)
            {
                WebHeaderCollection headers = new WebHeaderCollection();
    
                // The optional custom header X-MessageID uniquely identifies a notification message. If it is present, the same value is returned.
                // in the notification response. It must be a string that contains a UUID.
                if (uuid.HasValue)
                    headers.Add("X-MessageID", uuid.Value.ToString());
    
                if (target != NotificationType.Raw)
                    headers.Add("X-WindowsPhone-Target", target.ToString().ToLower());
    
                // The optional custom header X-NotificationClass sets the notification class for this notification request. The valid value range is
                // from 1-31. If this value is not present, the server defaults to 2
                if (priority.HasValue)
                    headers.Add("X-NotificationClass", priority.Value.ToString());
    
                return headers;
            }
    
            public void Send(Action<NotificationResponse> callback)
            {
                HttpWebRequest request = CreateRequest(_URI, _UUID, _priority, _certificate, _target);
                request.BeginGetRequestStream((x) =>
                {
                    using (Stream requestStream = request.GetRequestStream())
                    {
                        requestStream.Write(FormattedNotificationMessage, 0, FormattedNotificationMessage.Length);
                        requestStream.Close();
                    }
    
                    //Get Async response from push notification service
                    request.BeginGetResponse((asyncResult) =>
                    {
                        using (WebResponse notifyResponse = request.EndGetResponse(asyncResult))
                        {
                            //callback to notify caller of result
                            callback(new NotificationResponse((HttpWebResponse)notifyResponse));
                        }
                    }, null);
                }, null);
            }
        }
    }

    RawNotification.cs

    using System;
    using System.Security.Cryptography.X509Certificates;
    using System.Text;
    using System.Xml;
    
    namespace NickHarris.Net.Notification
    {
        public class RawNotification:NotificationBase
        {
            public XmlDocument Payload { get; set; }
            public RawNotification(XmlDocument payload, Uri subscriptionUri, Guid? uuid, int? priority, X509Certificate certificate)
            : base(subscriptionUri, uuid, priority, certificate, NotificationType.Raw)
            {
                Payload = payload;
            }
    
            protected override byte[] GetTemplateFormatted()
            {
                return Encoding.UTF8.GetBytes(Payload.OuterXml);
            }
    
            protected override bool IsPriorityValid(int priority)
            {
                return    (priority > 2 && priority < 11)  //3-10 – Real Time. The raw notification is delivered as soon as possible.
                       || (priority > 12 && priority < 21) //13-20 – Priority. The raw notification is delivered at a predefined timeout.
                       || (priority > 22 && priority < 32); //23-31 – Regular. The raw notification is delivered at a predefined timeout which is greater than the Priority batching interval.
            }
        }
    }

    TileNotification.cs

    using System;
    using System.Security.Cryptography.X509Certificates;
    using System.Text;
    
    namespace NickHarris.Net.Notification
    {
        public class TileNotification : NotificationBase
        {
            public string BackgroundImage { get; set; }
            public int Count { get; set; }
            public string Title { get; set; }
    
             public TileNotification(string backgroundImage, int count, string title, Uri subscriptionUri, Guid? uuid, int? priority, X509Certificate certificate)
                : base(subscriptionUri, uuid, priority, certificate, NotificationType.Token)
            {
                BackgroundImage = backgroundImage;
                Count = count;
                Title = title;
            }
    
             protected override Byte[] GetTemplateFormatted()
             {
                 string tileMessage = "X-WindowsPhone-Target: token\r\n\r\n" +  //Note: I have left this in line so you can see the format.  Would be better if you add it to a Resource file
                    "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
                    "<wp:Notification xmlns:wp=\"WPNotification\">" +
                       "<wp:Token>" +
                          "<wp:Img>{0}</wp:Img>" +
                          "<wp:Count>{1}</wp:Count>" +
                          "<wp:Title>{2}</wp:Title>" +
                       "</wp:Token> " +
                    "</wp:Notification>";
    
                 //return new UTF8Encoding().GetBytes(string.Format(Properties.Resources.ToastNotificationTemplate, Environment.NewLine, Environment.NewLine, BackgroundImage, Count, Title));
                 return new UTF8Encoding().GetBytes(string.Format(tileMessage,BackgroundImage, Count, Title));
             }
    
             protected override bool IsPriorityValid(int priority)
             {
                 return    priority == 1   // 1 – Real Time. The tile notification is delivered as soon as possible.
                        || priority == 11  //11 – Priority. The tile notification is delivered at a predefined timeout.
                        || priority == 21; //21 – Regular. The tile notification is delivered at a predefined timeout which is greater than the Priority batching interval.
             }
        }
    }

    ToastNotification.cs

    using System;
    using System.Security.Cryptography.X509Certificates;
    using System.Text;
    
    namespace NickHarris.Net.Notification
    {
        public class ToastNotification: NotificationBase
        {
            public string TitleOne { get; set; }
            public string TitleTwo { get; set; }
    
            public ToastNotification(string titleOne, string titleTwo, Uri subscriptionUri, Guid? uuid, int? priority, X509Certificate certificate)
                : base(subscriptionUri, uuid, priority, certificate, NotificationType.Toast)
            {
                TitleOne = titleOne;
                TitleTwo = titleTwo;
            }
    
            protected override Byte[] GetTemplateFormatted()
            {
                string toastTemplate = "X-WindowsPhone-Target: TOAST\r\n\r\n" +                 //Note: I have left this in line so you can see the format.  Would be better if you add it to a Resource file
                                        "<?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>";
    
                return new UTF8Encoding().GetBytes(string.Format(toastTemplate, TitleOne, TitleTwo));
                //return new UTF8Encoding().GetBytes(string.Format(Properties.Resources.ToastNotificationTemplate, Environment.NewLine, Environment.NewLine, TitleOne, TitleTwo));
            }
    
            protected override bool IsPriorityValid(int priority)
            {
                return priority == 2        //2 – Real Time. The toast notification is delivered as soon as possible.
                       || priority == 12    //12 – Priority. The toast notification is delivered at a predefined timeout.
                       || priority == 22;   //22 – Regular. The toast notification is delivered at a predefined timeout which is greater than the Priority batching interval.
    
            }
        }
    }

    NotificationResponse.cs

    using System;
    using System.Net;
    
    namespace NickHarris.Net.Notification
    {
        public enum NotificationStatus { None = 0, Received, QueueFull, Dropped, Unknown }; //N/A mapped to None, Unknown -> unknown will likely occur with updates to the CTP
        public enum DeviceConnectionStatus { None = 0, Connected, TemporarilyDisconnected, Inactive, Unknown };
        public enum SubscriptionStatus { None = 0, Active, Expired, Unknown };
    
        public struct NotificationResponse
        {
            private string _messageID; //keep as string not everyone will be using guids
            private NotificationStatus _notificationStatus;
            private SubscriptionStatus _subscriptionStatus;
            private DeviceConnectionStatus _deviceConnectionStatus;
            private HttpStatusCode _httpStatusCode;
    
            public NotificationResponse(HttpWebResponse response)
            {
                _messageID = response.Headers["X-MessageID"];
                _notificationStatus = GetNotificationStatus(response.Headers["X-NotificationStatus"]);
                _subscriptionStatus = GetSubscriptionStatus(response.Headers["X-SubscriptionStatus"]);
                _deviceConnectionStatus = GetDeviceConnectionStatus(response.Headers["X-DeviceConnectionStatus"]);
                _httpStatusCode = response.StatusCode;
            }
    
            public HttpStatusCode HttpStatusCode
            {
                get { return _httpStatusCode; }
                private set { _httpStatusCode = value; }
            }
    
            public DeviceConnectionStatus DeviceConnectionStatus
            {
                get { return _deviceConnectionStatus; }
                private set { _deviceConnectionStatus = value; }
            }
    
            public SubscriptionStatus SubscriptionStatus
            {
                get { return _subscriptionStatus; }
                private set { _subscriptionStatus = value; }
            }
    
            public NotificationStatus NotificationStatus
            {
                get { return _notificationStatus; }
                private set { _notificationStatus = value; }
            }
    
            public string MessageID
            {
                get { return _messageID; }
                private set { _messageID = value; }
            }
    
            private static NotificationStatus GetNotificationStatus(string notificationStatus)
            {
                NotificationStatus status = NotificationStatus.Unknown;
    
                if (notificationStatus.ToUpper() == "N/A")
                    status = NotificationStatus.None;
                else if (Enum.IsDefined(typeof(NotificationStatus), notificationStatus))
                    status = (NotificationStatus)Enum.Parse(typeof(NotificationStatus), notificationStatus, true);
                else
                {//leave as unknown
                }
    
                return status;
            }
            private static DeviceConnectionStatus GetDeviceConnectionStatus(string deviceConnectionStatus)
            {
                DeviceConnectionStatus status = DeviceConnectionStatus.Unknown;
                deviceConnectionStatus = deviceConnectionStatus.Replace(" ", string.Empty);
    
                if (deviceConnectionStatus.ToUpper() == "N/A")
                    status = DeviceConnectionStatus.None;
                else if (Enum.IsDefined(typeof(DeviceConnectionStatus), deviceConnectionStatus))
                    status = (DeviceConnectionStatus)Enum.Parse(typeof(DeviceConnectionStatus), deviceConnectionStatus, true);
                else
                {//leave as unknown
                }
    
                return status;
            }
            private static SubscriptionStatus GetSubscriptionStatus(string subscriptionStatus)
            {
                SubscriptionStatus status = SubscriptionStatus.Unknown;
    
                if (subscriptionStatus.ToUpper() == "N/A")
                    status = SubscriptionStatus.None;
                else if (Enum.IsDefined(typeof(SubscriptionStatus), subscriptionStatus))
                    status = (SubscriptionStatus)Enum.Parse(typeof(SubscriptionStatus), subscriptionStatus, true);
                else
                {//leave as unknown
                }
    
                return status;
            }       
    
        }
    }

    So now for the disclaimer – Main thing is – it works (apart from remote tile notifications – currently an issue with the CTP as far as I can tell).  This version of the code was created against the requirements of the April CTP so I expect the message format to change although if you put it into your project resource file as the //comments above dictate you should be able to change this as the developer toolkit evolves.

    I will to keep this blog up to date with fixes for any changes as future CTPs are released or bugs I find as I dig deeper so make sure you subscribe to my RSS feed.

    Enjoy the code and the nights you will save not having to write it :)

    Nick

    FREE Event – The Sydney Architecture User Group – Domain Driven Design

    Re-posting the announcement for the SAUG here:

    Next Meeting: Thursday 27/05/2010 06:00 PM
    Location: Grace Hotel, Kiralee or Pinaroo Function Room,77 York st, Sydney, NSW. 2000

    Title: Practical Domain Driven Design, Message Based Architectures and CQRS
    Presenter: Jak Charlton
    Abstract:
    The original book on Domain Driven Design was subtitled “Tackling Complexity in the Heart of Software”, and even where a system does not merit “full on” DDD, many of the practices and principles can massively reduce the complexity of software, especially when combined with messaging and events.

    Presenter Bio:
    Jak Charlton, is now based in Sydney, Australia, and working as a Senior Consultant for Readify (http://readify.net/). Jak is a well known community figure in the Microsoft and .NET worlds with a reputation for a passionate view of development. With primary interests around Domain Driven Design, software architecture, and putting the world to rights one debate at a time, he is a strong believer in principles and practices, allowing developers to concentrate on delivering business value.

    We hope to see you there. Dont forget to visit http://thesaug.org to keep up to date with the user group details and news.

    - Thanks
    Paul Glavich (paul.glavich@datacom.com.au) and Omar Besiso (Omar.Besiso@datacom.com.au)

    How to Pin your application in Windows Phone 7 emulator April CTP refresh

    The Windows Phone 7 emulator provides Pin capability within the April CTP refresh. To pin your application perform the following:

    1. Right click on you WP7 app project and select properties.
    2. Set the Title under deployment options to your title name “PinMyApp”
    3. Set the Title under Tile options to “Pin My App” – this is the actual text shown over the tile
    4. Set the background image on the Tile using the dropdown.
    5. Deploy your application
    6. When on the Start screen Press to view your applications
    7. Hold down on your application “PinMyApp” until a context menu appears then select “pin to start”
    8.  

    9. Observe your application is now pinned

    Its important to note that you can use Push notifications to change the text, count and image on your applications tile. This can be achieved by setting up a notification channel using HttpNotificationChannel and binding to the shell entry point BindToShellEntryPoint(); Once you have registered a uri will be returned and you can then send tile notifications using a WebRequest. I am currently working on a blog post for this one, so stay tuned

    Nick

    Upgrading your Windows Phone projects for Windows Phone 7 Developer Tools CTP April refresh

    Upgrading your Windows Phone projects for Windows Phone 7 Developer Tools CTP April refresh 

    Here are a bunch of breaking issues I encountered on installing Upgrading your Windows Phone projects for Windows Phone 7 Developer Tools CTP April refresh 

      For each silverlight for mobile project in your solution you get the following warning (Note: XNA framework apps will not provide a warning but will still require the changes)

    • Issue: WarningYou are using a project created by previous version of Windows Phone Developer Tools CTP. Your application may not run properly. Please edit the WMAppManifest.xml file under Properties node and insert the following <Capability> elements between
      <Capabilities></Capabilities> element as shown below. <Capabilities>
      <Capability Name="ID_CAP_NETWORKING" />
      <Capability Name="ID_CAP_LOCATION" />
      <Capability Name="ID_CAP_SENSORS" />
      <Capability Name="ID_CAP_MICROPHONE" />
      <Capability Name="ID_CAP_MEDIALIB" />
      <Capability Name="ID_CAP_GAMERSERVICES" />
      <Capability Name="ID_CAP_PHONEDIALER" />
      <Capability Name="ID_CAP_PUSH_NOTIFICATION" />
      <Capability Name="ID_CAP_WEBBROWSERCOMPONENT" />
      </Capabilities> 
    • Solution: You can find WMAppManifest.xml in the Properties folderof your project once found add the sections listed above
    • Issue: Push Notifications now require the publisher to be defined
    • Solution: This can be done within the WMAppManifest.xml in the Properties folder.  Update the Publisher on <App> E.g 
    <App xmlns="" ProductID="{1ac9139d-aa7e-6c70-acf9-c6ceb69632a1}" Title="PinMyApp"
    RuntimeType="SilverLight" Version="1.0.0.0" Genre="NormalApp" 
    Author="Nick.Harris" Description="Demo" 
    Publisher="www.NickHarris.net">
    • Issue: Push Notification payload has been updated
    • Solution: Update notifications as per the specification here  
    • Issue: The property ‘Visible’ does not exist on the type ‘ApplicationBar’ in the XML namespace ‘clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell’
    • Solution: this has bee renamed to IsVisible
    • Issue: ‘System.Windows.Navigation.NavigationService’ does not contain a definition for ‘TopLevelNavigationService’
    • Solution: change NavigationService.TopLevelNavigationService.Navigate(…) to NavigationService.Navigate(…)  Note: did not find this one in the breaking changes….

    Release notes for other  breaking changes are here

    A first look at SQL 2008 spatial data types to and from SQL Server 2008

    A first look at SQL 2008 spatial data types round trip with sql 2008. I was surprised to find that the Entity Framework, LINQ to SQL and Dataset Designer do not currently support Spatial data types.  Given this was the case and wanting to have a play with the spatial data type the following is how I used the sqlgeometry spatial data type to store and retrieve a list both lines and points consisting of X,Y co-ordinates.

    Lets use a bottom up approach create a table using the GEOMETRY data type:

    CREATE TABLE [dbo].[MyData](
    	[ID] [uniqueidentifier] NOT NULL PRIMARY KEY DEFAULT (newsequentialid()) ,
    	[Data] [GEOMETRY] NULL);
    GO
    

    Create a stored procedure to insert data into the MyData table:

    CREATE PROCEDURE [dbo].[InsertMyData]
                @Lines GEOMETRY 
    AS	
    	INSERT INTO [dbo].[MyData] (Data)
                 VALUES (@Lines);
    RETURN 0
    GO
    

    And create a stored procedure to retrieve a the data:

    CREATE PROCEDURE [dbo].[GetMyData]
    	@ID uniqueIdentifier	
    AS
    	SELECT *
    	FROM [dbo].[MyData]
    	WHERE ID = @ID	
    	
    RETURN 0
    GO
    

    Code to insert and retrieve data:

    1. Add a reference to C:\Program Files (x86)\Microsoft SQL Server\100\SDK\Assemblies\Microsoft.SqlServer.Types.dll which contains SqlGeometryBuilder
    2.                      using System;
                           using System.Collections.Generic;
                           using System.Data;
                           using System.Data.SqlClient;
                           using System.Threading;
                           using System.Threading.Tasks;
                           using Microsoft.SqlServer.Types;
      

    This is where it gets back to the !good old days without generation of your DAL. Add a method to call the InsertMyData stored procedure:

            public bool InsertMyData(Guid id, List> lines)
            {
                int result = 0;
                if (lines.Count > 0)
                {
                    using (SqlConnection conn = new SqlConnection(connString))
                    using (SqlCommand cmd = conn.CreateCommand())
                    {
                        cmd.CommandText = "InsertMyData";
                        cmd.CommandType = CommandType.StoredProcedure;
    
                        cmd.Parameters.Add(new SqlParameter("@ID", id));
                        cmd.Parameters.Add(SpatialDataHelper.GetGeometrySqlParameter("@Data", lines, true));
                        conn.Open();
                        result = cmd.ExecuteNonQuery();
                    }
                }
    
                return (result > 0);
            }
    

    Now you may be wondering what is SpatialDataHelper.GetGeometrySqlParameter. Well that too is something that needs to be implemented. Even though the SQL geometry type is a system type the type must be treated as user defined type. Take note of the sqlParam.UdtTypeName = “geometry”;

     public static class SpatialDataHelper
        {
            public static SqlParameter GetGeometrySqlParameter(string paramName, List> lines, bool makeValid)
            {
                SqlParameter sqlParam = new SqlParameter();
                sqlParam.ParameterName = paramName;
                sqlParam.UdtTypeName = "geometry";
    
                SqlGeometryBuilder geomBuilder = new SqlGeometryBuilder();
    
                // SqlGeometryBuilder.SetSrid Must be called at the beginning of each geometry sequence
                geomBuilder.SetSrid(0); 
                geomBuilder.BeginGeometry(OpenGisGeometryType.GeometryCollection);
                
                //break out each line
                foreach (List lp in lines)
                {
                    if (lp.Count > 0)
                    {
                        if (lp.Count == 1) //check if its a point or a line and start a geometry for the point or line
                            geomBuilder.BeginGeometry(OpenGisGeometryType.Point);
                        else
                            geomBuilder.BeginGeometry(OpenGisGeometryType.LineString);
    
                        int count = 0;
                        foreach (Point p in lp) //add all points
                        {
                            if (count == 0)
                                geomBuilder.BeginFigure(p.X, p.Y);
                            else
                                geomBuilder.AddLine(p.X, p.Y);
    
                            count++;
                        }
    
                        geomBuilder.EndFigure();
                        geomBuilder.EndGeometry();
                    }
                }
                geomBuilder.EndGeometry();
    
                SqlGeometry constructed = geomBuilder.ConstructedGeometry;
                if (makeValid)
                {
                    //Note required to convert into a geometry instance with a valid Open Geospatial Consortium (OGC) type. 
                    // this can cause the points to shift - use with caution...
                    constructed = constructed.MakeValid();
                }
                sqlParam.Value = constructed;
    
                return sqlParam;
            }        
        }
    

    Finally to get your data back out its necessary to call the previously defined GetMyData stored procedure and then crunch out the points into their original form.

            public List> GetMyData(Guid id)
            {
                ConcurrentBag> bag = new ConcurrentBag>();
    
                using (SqlConnection conn = new SqlConnection(connString))
                using (SqlCommand cmd = conn.CreateCommand())
                {
                    cmd.CommandText = "GetMyData";
                    cmd.CommandType = CommandType.StoredProcedure;
    
                    cmd.Parameters.Add(new SqlParameter("@ID", id));
                    conn.Open();
    
                    using (SqlDataReader reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            startLinesID = reader.GetGuid(reader.GetOrdinal("ID"));
                            SqlGeometry gLines = (SqlGeometry)reader["Data"];
                            // using Parallel.For to speed up crunch.  Note: STGeometryN starts with 1 based index therefore start with i of 1
                            Parallel.For(1, gLines.STNumGeometries().Value + 1, delegate(int i)
                            {
                                SqlGeometry geom = gLines.STGeometryN(i);
                                List line = new List();
                                switch (geom.STGeometryType().ToString())
                                {
                                    case "LineString":    //a LineString is a line within the SQLGeometry                               
                                        for (int j = 1; j <= geom.STNumPoints(); j++) //get all points forming the line
                                        {
                                            line.Add(new Point(geom.STPointN(j).STX.ToSqlInt32().Value, geom.STPointN(j).STY.ToSqlInt32().Value));
                                        }
                                        break;
                                    case "Point": //an individual point                                   
                                        line.Add(new Point(geom.STPointN(1).STX.ToSqlInt32().Value, geom.STPointN(1).STY.ToSqlInt32().Value)); //1 is the first index not 0 based
                                        break;
                                }
                                bag.Add(line);
                            });
                        }
                    }
                }
    
                return bag.ToList();
            }  
    

    This seems to do the job although I would be interested to hear of better ways to do this.

    New Windows Phone 7 Developer Tools CTP April refresh 2010

    Download and install Windows Phone 7 Developer Tools April 2010 Refresh.

    Note as part of this install I had to remove the following components in order:

    • Microsoft Visual Studio 2010  RC       
    • Microsoft Windows Phone Developer Resources         
    • Microsoft Windows Phone Developer Tools
    • Microsoft Silverlight 4.0 SDK
    • Microsoft Visual Studio 2010 Express Prerequisites x64.
    • NET Framework 4 Multi-Targeting Pack
    • Microsoft .NET Framework 4 Extended
    • Microsoft .NET Framework 4 Client Profile
    • VC 10.0 Runtime
    • Microsoft Windows Phone Emulator x64
    • Windows Phone 7 Add-in for Visual Studio 2010 – ENU
    • Microsoft XNA Game Studio 4.0

    Ever had one of those days when your mouse batteries go flat in the middle of a blog post.  Then you swap out one of the batteries from the mouse and switch it with the keyboard to suck the last remaining juice from it.  Ten minutes later both are flat and you got no spares.  Lets just say its one of those days :)

    Anyhow the refresh has new features (-take note of the additions to the emulator ) and breaking changes(download release notes at this link) - take note of the new publisher setting and notification templates.