Click here to Skip to main content
15,867,330 members
Articles / Desktop Programming / WPF

GeoPlaces: Hybrid smart client involving RESTful WCF/WPF and more

Rate me:
Please Sign up or sign in to vote.
4.95/5 (122 votes)
8 Jun 2009CPOL33 min read 285.4K   2.5K   264   108
A nice explar of how to use RESTful WCF and WPF.

Contents

Introduction

The last article I wrote was Sonic, which at the time of publishing, I stated was my best article yet. Now Sonic was pretty neat and used lots of nice stuff, but I have to say that now I have finished this article, I think I am actually more happy with this one. Anyway, you guys/girls can decide. I guess we will start by stating what this article actually does/doesn't do....so let's do that.

In one sense, this article is probably the most useless (yes useless, you read right) article, in that it does not solve any specific problem domain, nor will it get you up to speed in 1 hour, or get you out of a hole when you really need a component that does some missing code magic. However, on another level, it should serve as a pretty decent template of how to work with technologies X/Y and Z should you need to use them.

Technology X/Y and Z in this case will be WCF (or Dub CF as I like to call it), and the new .NET 3.5 RESTful WCF possibilities, and also my personal favourite WPF (Dub PF), and it also makes use of the ADO.NET Entity Framework for persistence.

So essentially, this article is about WCF/RESTful WCF/WPF. Now, in order to show you how to do all the things that I considered to be important when working with these technologies, I needed some sort of problem that was able to be solved using these technologies. I know, I know I have already said there is no problem solved by this article; well, there is a hypothetical problem domain which is what this article solves. To this end, I developed the attached demo code. So what does it all do? Get on with it Sacha, stop blabbering, and tell us what it does. OK, well, it is pretty simple, I have created the following:

  • A RESTful WCF service that allows:
    • Existing users to login, matched against persisted users
    • New users to register, and be persisted with a name and password
    • New geographical places of interest to be stored against a user
    • All geographical places of interest to be retrieved against a user
  • An ADO.NET Framework Entity Model to allow the persistence of data to SQL Server 2005 (or 2008).
  • A WPF client to allow all the service calls to function and to display the results in a rich client like way. Actually, I have to say, this WPF client is cool I think, as it uses a hosted Microsoft Virtual Earth instance to show the users geographical places of interest, on the VE globe, and allows full VE functionality (though not layering). The WPF client also has a plethora of styled/custom controls, and uses some 3D to add extra jazz, so hopefully, there will be something in there for most UI minded folk.

Although the demo app problem domain is dead simple, it has enough meat to allow me to demonstrate most of things you will need to know in order to work with WPF/WCF/RESTful WCF, and it also uses the ADO.NET Entity Framework, which some readers may not have come up against before.

This article contains a lot of code sections, probably too many, but I just felt that they were all quite necessary to demonstrate the text a little better. So I am sorry if you are a bit overwhelmed by it all.

Show it

Right, so before we proceed, let me just show you what it looks like. Essentially, there are three steps through the app. The user may not advance past step 1 unless they complete the login or registration process.

Step 1: Login or Register

If logging in, the user must type in their pre-saved username and password (the details they registered with), or they can register as a new user. You can see that there are also two arrow like buttons or the left and right, which allow you to advance/go back to previous steps.

Note: The articles code includes a SQL script to set up a user and some places of interest to allow you to start with some dummy data. I will discuss this in more detail within the Things to Do to Get it Running For You section.

Image 1

Step 2: Create a New Place / Look at Your Saved Places

Once a user has been logged in, the STEP navigation buttons will be enabled, which will allow the user to skip to step 2. The UI itself will perform a nice smooth animation to get to step 2. It looks nice, try it out for yourself.

When the user is at step 2, they can enter new details about geographical places of interest to them. To do this, the user will need to enter the following information:

  • Place name
  • Place description
  • Place latitude (so it can be shown on the hosted Virtual Earth instance)
  • Place longitude (so it can be shown on the hosted Virtual Earth instance)

The entering of these details is via a control that is hosted in a 3D flippable surface, so that the user can enter a new place and flip the 3D surface and see all the places that they have managed to save (persist to SQL via ADO.NET Entity Framework model).

Here is what the user sees at step 2, by default:

Image 2

And here it is half way through a 3D flip:

Image 3

And here are all the places after a flip (shown on the back of the 3D mesh):

Image 4

Step 3: Allows Users to Look at Saved Places Using Virtual Earth

The last step allows users to view the saved places on the hosted Virtual Earth instance. Obviously, if there are no places saved for the current user, there is not going to be any items to show.

Assuming that there are places to show, the user can select one, and the hosted VE instance will show it and do a fly to zoom to the place that the user selected. The hosted VE instance can also be controlled in the following ways from the WPF client:

  • Zoom in/out using the butttons provided
  • Panned using the buttons provided, or just using the mouse
  • Mode changed to Hybrid/Roads only or Aerial only, using the buttons provided

Here is an example showing one of my favourite places in the entire world, "The Chrysler Building" in New York City; firstly, the user clicks on the saved place:

Image 5

Then we get automatically zoomed to it:

Image 6

So that is what it all looks like. Some of you may not like the visual style, but doing an article is a personal thing, and this is the style I like, so I do it the way I like, because it is my article, simple as that.

Prerequisites

As I mentioned early on within the text of this article, this article makes use of the following technologies:

  • WCF
  • WPF
  • RESTful WCF
  • ADO.NET Entity Framework / SQL Server
  • Virtual Earth

As such, there is obviously a list of prerequisites that need to be covered if you wish to run this article's code at home/work. This list is as follows:

  1. SQL Server 2005/2008
  2. .NET 3.5 SP1 (as I am using some of the latest RESTful WCF stuff that came with .NET 3.5 SP1, and a D3DImage for the DirectX interop used for the hosted VE instance)
  3. Visual Studio 2008
  4. Microsoft Visual Studio 2008 Service Pack 1
  5. Virtual Earth 3D (Beta)

Things to Do to Get it Running For You

In order to get the code up and running, you will need to download and have installed all the prerequisites above. Having done that, you should do the following in the order shown below:

1. Create Empty SQL Server Database

Within your own SQL Server installation, create a new database called "GeoPlaces".

2. Setup SQL Server Database

The article's code includes a SQL script that can be used to setup the actual SQL Server database schema. This SQL script is called "GenerateDBScript.sql". This script must be run against an existing SQL Server database called "GeoPlaces". This script simply adds two tables to the "GeoPlaces" database; these two tables will be "Users" and "Places".

3. Setup SQL Dummy Data

The article's code includes a SQL script that can be used to setup some initial dummy SQL data. This SQL script is called "CreateInitialDummyData.sql". This SQL script simply adds a single user called "sacha" with a password of "sacha", with a couple of my favourite places, so the hosted VE instance will have some places to show, should you log in as "sacha" with a password of "sacha".

4. Change the App.Config within the GeoPlacesServiceHost project

The article's demo code includes a solution with a project called GeoPlacesServiceHost; you should now amend the App.Config to point to your own SQL Server installation. That would be these lines:

XML
<!-- HOME-->
<connectionStrings>
    <add name="GeoPlacesEntities" 
     connectionString="metadata=res://GeoPlacesData/GeoPlacesModel.csdl|
                       res://GeoPlacesData/GeoPlacesModel.ssdl|
                       res://GeoPlacesData/GeoPlacesModel.msl;
                       provider=System.Data.SqlClient;
                       provider connection string="Data Source=YOUR SQL NAME HERE;
                       Initial Catalog=GeoPlaces;User ID=sa;Password=sa;
                       MultipleActiveResultSets=True"" 
     providerName="System.Data.EntityClient" />
</connectionStrings>

The part that you will need to change is the ADO.NET Entity Framework connection string. This should be simply a matter of changing the DataSource to point to your SQL Server instance.

5. Start/Install the Service Host

In order to run a WCF Service of any kind (RESTful or SOAP based), the WCF Service must be hosted somewhere. There are various options for this:

  • IIS
  • Self hosting console app
  • Inside a Windows Service

The article's demo code is written to support either a self hosting console app that will host the RESTful WCF Service when run, or is able to install an actual Windows Service that hosts the RESTful WCF Service.

I can not tell you what option to go for, that is up to you. Rather, I will talk you through both hosting options that I allow for, and you can decide what you want to use. Of course, you must use, and have running, one of these hosting options if you expect to be able to call the RESTful WCF Service at all from the WPF client.

I have developed a simple class that caters for hosting the WCF Service as either a Self Hosting Console App or within a Windows Service. The basic idea is that the current build mode (Debug|Release) is examined, and if it is Debug, I will start the RESTful WCF hosting inside a console application with an infinite timeout. If the current build mode is Release, I will attempt to use an actual Windows Service host for the hosting.

The skeleton code that does this is shown below:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.ServiceProcess;

namespace GeoPlacesServiceHost
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// Runs as console app if in debug.
        /// </summary>
        static void Main()
        {
#if (!DEBUG)
            try
            {
                Console.WriteLine(String.Format(
                      "Initialising GeoPlacesDataService.GeoService in assembly " +
                      "{0} RELEASE windows service mode.",
                      Assembly.GetExecutingAssembly().FullName));

                ServiceBase[] ServicesToRun;
                ServicesToRun = new ServiceBase[] { new Service() };
                ServiceBase.Run(ServicesToRun);
            }
            catch (Exception ex)
            {
                Console.WriteLine(String.Format("Exception Occurred :", ex.Message));
            }
#else
            try
            {
                Console.WriteLine(String.Format(
                      "Initialising GeoPlacesDataService.GeoService " + 
                      "in assembly {0} DEBUG console mode.",
                      Assembly.GetExecutingAssembly().FullName));

                Console.WriteLine("Starting GeoPlacesDataService.GeoService");
                Service.StartService();
                Console.WriteLine("GeoPlacesDataService.GeoService Started");

                System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
            }
            catch (Exception ex)
            {
                Console.WriteLine(String.Format("Exception Occurred :", ex.Message));
            }
#endif
        }
    }
}

Where the actual Windows Service class looks like this (should you be interested):

C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Web;
using System.ServiceProcess;
using System.Text;
using GeoPlacesDataService;

namespace GeoPlacesServiceHost
{
    /// <summary>
    /// Windows service to host the actual Restful 
    /// WCF KMLService
    /// </summary>
    public partial class Service : ServiceBase
    {
        #region Data
        private static ServiceHost GeoPlacesServiceHost;
        #endregion

        #region Ctor
        public Service()
        {
            InitializeComponent();
        }
        #endregion

        #region Overrides
        protected override void OnStart(string[] args)
        {
            Service.StartService();
        }

        protected override void OnStop()
        {
            try
            {
                Service.StopServiceHost(GeoPlacesServiceHost);
            }
            catch (Exception ex)
            {

                Console.WriteLine(String.Format(
                    "Exception while attempting to stop GeoService " +
                    "service type {0} the following exception was thrown {1}.",
                    this.GetType().FullName, ex.ToString()));
            }
        }
        #endregion

        #region Public Methods
        public static void StartService()
        {
            try
            {
                GeoPlacesServiceHost = new WebServiceHost(typeof(GeoService),
                    new Uri(ConfigurationManager.AppSettings[
                        "GeoServiceEndpointAddress"]));

                StartServiceHost(GeoPlacesServiceHost);
            }
            catch (TargetInvocationException tiEx)
            {
                Console.WriteLine(String.Format("Exception occurred", tiEx.Message));
            }
            catch (Exception ex)
            {
                Console.WriteLine(String.Format("Exception occurred", ex.Message));
            }
        }
        #endregion

        #region Private Methods
        private static void StartServiceHost(ServiceHost serviceHost)
        {

            Boolean openSucceeded = false;

            try
            {

                serviceHost = new WebServiceHost(typeof(GeoService),
                       new Uri(ConfigurationManager.AppSettings[
                           "GeoServiceEndpointAddress"]));

                serviceHost.Open();
                openSucceeded = true;
            }
            catch (Exception ex)
            {
                Console.WriteLine(String.Format(
                    "A failure occurred trying to open the " +
                    "GeoService ServiceHost, Error message : {0}",
                        ex.Message));
            }
            finally
            {
                if (!openSucceeded)
                {
                    serviceHost.Abort();
                    Console.WriteLine(String.Format("{0} Aborted.",
                        serviceHost.Description.Name));
                }
            }

            if (serviceHost.State == CommunicationState.Opened)
            {
                serviceHost.Faulted += ServiceHost_Faulted;
                Console.WriteLine("GeoService is running...");
            }
            else
            {
                Console.WriteLine("GeoService failed to open");
                Boolean closeSucceeded = false;
                try
                {
                    serviceHost.Close();
                    closeSucceeded = true;
                    Console.WriteLine(String.Format("{0} Closed.",
                        serviceHost.Description.Name));
                }
                catch (Exception ex)
                {
                    Console.WriteLine(String.Format(
                        "A failure occurred trying to close the " +
                        "GeoServicee ServiceHost, Error message : {0}",
                            ex.Message));
                }
                finally
                {
                    if (!closeSucceeded)
                    {
                        serviceHost.Abort();
                        Console.WriteLine(String.Format("{0} Aborted.",
                            serviceHost.Description.Name));
                    }
                }
            }
        }

        private static void StopServiceHost(ServiceHostBase serviceHost)
        {
            if (serviceHost.State != CommunicationState.Closed)
            {
                Boolean closeSucceeded = false;
                try
                {
                    serviceHost.Close();
                    closeSucceeded = true;
                    Console.WriteLine(String.Format("{0} Closed.",
                        serviceHost.Description.Name));
                }
                catch (Exception ex)
                {
                    Console.WriteLine(String.Format(
                        "A failure occurred trying to close the " +
                        "GeoService ServiceHost, Error message : {0}",
                            ex.Message));
                }
                finally
                {
                    if (!closeSucceeded)
                    {
                        serviceHost.Abort();
                        Console.WriteLine(String.Format("{0} Aborted.",
                            serviceHost.Description.Name));
                    }
                }
            }
        }

        private static void RestartServiceHost(ServiceHost serviceHost)
        {
            StopServiceHost(serviceHost);
            StartServiceHost(serviceHost);
        }

        private static void LogServiceHostInfo(ServiceHostBase serviceHost)
        {
            var strBuilder = new StringBuilder();
            strBuilder.AppendFormat("'{0}' Starting", serviceHost.Description.Name);
            strBuilder.Append(Environment.NewLine);

            // Behaviors
            var annotation = 
                serviceHost.Description.Behaviors.Find<ServiceBehaviorAttribute>();

            strBuilder.AppendFormat("Concurrency Mode = {0}", 
                annotation.ConcurrencyMode);
            strBuilder.Append(Environment.NewLine);
            strBuilder.AppendFormat("InstanceContext Mode = {0}", 
                annotation.InstanceContextMode);
            strBuilder.Append(Environment.NewLine);

            // Endpoints
            strBuilder.Append("The following endpoints are exposed:");
            strBuilder.Append(Environment.NewLine);
            foreach (ServiceEndpoint endPoint in serviceHost.Description.Endpoints)
            {
                strBuilder.AppendFormat("{0} at {1} with {2} binding; "
                       , endPoint.Contract.ContractType.Name
                       , endPoint.Address
                       , endPoint.Binding.Name);
                strBuilder.Append(Environment.NewLine);
            }

            // Metadata
            var metabehaviour = 
                serviceHost.Description.Behaviors.Find<ServiceMetadataBehavior>();

            if (metabehaviour != null)
            {
                if (metabehaviour.HttpGetEnabled)
                {
                    if (metabehaviour.HttpsGetUrl != null)
                    {
                        strBuilder.AppendFormat("Metadata enabled at {0}", 
                            serviceHost.BaseAddresses[0]);
                    }
                    else
                    {
                        strBuilder.AppendFormat("Metadata enabled at {0}", 
                            metabehaviour.HttpGetUrl);
                    }
                }
                if (metabehaviour.HttpsGetEnabled)
                    strBuilder.AppendFormat(" and {0}.", metabehaviour.HttpsGetUrl);
                if (metabehaviour.ExternalMetadataLocation != null)
                    strBuilder.AppendFormat(" Metadata can be found externally at {0}", 
                        metabehaviour.ExternalMetadataLocation);
            }

            Console.WriteLine(strBuilder.ToString());
        }

        private static void ServiceHost_Faulted(Object sender, EventArgs e)
        {
            var serviceHost = sender as ServiceHost;
            Console.Write(String.Format("{0} Faulted. Attempting Restart.",
                serviceHost.Description.Name));
            RestartServiceHost(serviceHost);
        }
        #endregion
    }
}

There is also an installer for the Windows Service, but that is pretty bulk standard stuff, so I will not muddy the waters with that.

Anyway, I just wanted to explain the methods of hosting. Now, let's discuss how you can use these different hosting methods.

Self Hosting Console App

This is probably the easiest way to host WCF Services, as it is a simple class with a main method and a bit of code to host the service, and really that is it. All you have to do to use this method is navigate to the GeoPlacesServiceHost\bin\Debug\ folder and run the GeoPlacesServiceHost.exe console app.

You should then see the host started as follows:

Image 7

Windows Service Hosting

Like I say, I have created a class that allows console hosting in Debug mode and Windows Service hosting in Release mode. In order to host the WCF Service within a Windows Service, we need to carry out the following steps:

Install the Service

From the command line, run the following: installutil.exe "XXXXXXXXX\GeoPlacesServiceHost\bin\Release\GeoPlacesServiceHost.exe", where XXXXXXXXX is your path to the GeoPlaces code you have downloaded.

This will present a login screen that you need to fill in for the service to run.

Image 8

This will place a new Windows Service within the list of available Windows Services. After this installation, you will need to start the Windows Service. Go to the list of available Windows Services and start the GeoPlaces service.

Image 9

I tend to prefer the Console app method when I am working in Visual Studio developing code, and the Windows Service method for actually finished deployable code. I think Windows Service hosting is cool, as the actual Windows Service can be made to manage starting / stopping of the hosted RESTful WCF Service. Which is cool, as it is something I no longer have to worry about; basically, the Windows Service manages the hosted RESTful WCF Service lifecycle.

Restful WCF Service

WCF has been around a while now, and there are lots of articles on using it, so I will not cover the basics (if you like, you can read one of my older WCF articles to learn the basics about SOAP based WCF); rather, I am going to talk about how to work with the new RESTful WCF capabilities.

Fo those that do not even know what REST is, REST stands for Representational State Transfer (REST). The term is often used more loosely to describe any simple interface which transmits domain-specific data over HTTP without an additional messaging layer such as SOAP or session tracking via HTTP cookies.

When working with RESTful WCF, what we are really talking about is exposing Type(s) as either JSON or XML at a specific URI.

As some of you may know, standard SOAP based WCF is exposed via endpoints, and RESTful WCF is no different; the only difference is that as well as proxy classes to talk to the RESTful WCF Service, the user may use a standard browser.

This is done by the use of the standard HTTP Verbs:

  • GET: Retrieves a resource
  • PUT: Updates an existing resource
  • POST: Creates a new resource
  • DELETE: Removes a resource

So how does all this work? Well, it is actually quite simple. Part of .NET 3.5 allows us to adorn our WCF Service OperationContract(s) with additional RESTful attributes. Let us examine a few of these.

GET

Within the GeoPlaces database (if you have populated it with the sample data), there will be a number of places stored against a specific user (for me, this user is 10, it may be different for you), so we have a table that looks like this in SQL Server:

Image 10

We also have a RESTful WCF Service method that looks like this:

C#
[OperationContract]
[WebGet(UriTemplate = "/placesList/{userId}",
ResponseFormat = WebMessageFormat.Xml)]
List<Places> GetAllPlacesForUser(String userId);

Note the use of the WebGetAttribute, which has specified a UriTemplate, this allows the user to type a URI into a browser that will pattern match against all the methods within the RESTful WCF Service to see if there is a match. If there is a match, that method will be called, and the return value will be serialized to the format the user specifies, XML in this case.

This is what the actual method implementation looks like:

C#
/// <summary>
/// Gets all places for a particular user
/// </summary>
public List<Places> GetAllPlacesForUser(String userId)
{
    Int32 id = -1;
    if (Int32.TryParse(userId, out id))
    {
        try
        {
            GeoPlacesEntities model = new GeoPlacesEntities();
            return model.Places.Where((pl) => pl.Users.ID == id).ToList();
        }
        catch (Exception ex)
        {
            //WebProtocolException is part of WCF REST Starter Kit Preview 2
            throw new WebProtocolException(HttpStatusCode.BadRequest,
                String.Format("Couldn't find places for user id {0}", userId), null);

        }
    }
    else
        return null;
}

So how should a user be able to call a RESTful service method using a GET? Well, it is still down to the WCF Service being hosted somewhere. In the attached demo code, the WCF Service can either be hosted within a Console app or within a Windows Service; in both cases, there is an App.Config setting that dictates that the RESTful service should be available at the following address: "http://localhost:8085/GeoPlacesDataService". So if the RESTful WCF Service is hosted and running, the user is then able to type the following, which matches the WebGetAttribute UriTemplate match we saw above:

http://localhost:8085/GeoPlacesDataService + /placesList/{userId}, with actual substitutions into a browser to obtain resource. An example may be:

http://localhost:8085/GeoPlacesDataService/placesList/10, which we would expect to return a list of place resources for the user who has an ID of 10. Let's try that in the browser.

Note: The method parameters must match the substitution sections within UriTemplate, and all parameters must be of type String, as this is all that is possible through a browser.

Image 11

What do you know, it works. This is all thanks to the pattern matching and the WebGetAttribute, which has specified a UriTemplate that allows the pattern matching to work and find the correct method to call.

You can see that this is XML, and you may be wondering where this XAML comes from. Well, the demo code is using the ADO.NET Entity Framework to persist data to/from SQL Server, so this is the default serialization that occurs for the Places object within the demo code's ADO.NET Entity Framework model. To fully understand what is happening here, I will show you what the Places class looks like:

C#
/// <summary>
/// There are no comments for GeoPlacesModel.Places in the schema.
/// </summary>
/// <KeyProperties>
/// ID
/// </KeyProperties>
[global::System.Data.Objects.DataClasses.EdmEntityTypeAttribute(
    NamespaceName="GeoPlacesModel", Name="Places")]
[global::System.Runtime.Serialization.DataContractAttribute(IsReference=true)]
[global::System.Serializable()]
public partial class Places : global::System.Data.Objects.DataClasses.EntityObject
{
    /// <summary>
    /// Create a new Places object.
    /// </summary>
    /// <param name="ID">Initial value of ID.</param>
    /// <param name="name">Initial value of Name.</param>
    /// <param name="description">Initial value of Description.</param>
    /// <param name="longitude">Initial value of Longitude.</param>
    /// <param name="latitude">Initial value of Latitude.</param>
    public static Places CreatePlaces(int ID, string name, 
        string description, double longitude, double latitude)
    {
        Places places = new Places();
        places.ID = ID;
        places.Name = name;
        places.Description = description;
        places.Longitude = longitude;
        places.Latitude = latitude;
        return places;
    }
    /// <summary>
    /// There are no comments for Property ID in the schema.
    /// </summary>
    [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(
        EntityKeyProperty=true, IsNullable=false)]
    [global::System.Runtime.Serialization.DataMemberAttribute()]
    public int ID
    {
        get
        {
            return this._ID;
        }
        set
        {
            this.OnIDChanging(value);
            this.ReportPropertyChanging("ID");
            this._ID = global::System.Data.Objects.DataClasses.
                StructuralObject.SetValidValue(value);
            this.ReportPropertyChanged("ID");
            this.OnIDChanged();
        }
    }
    private int _ID;
    partial void OnIDChanging(int value);
    partial void OnIDChanged();
    /// <summary>
    /// There are no comments for Property Name in the schema.
    /// </summary>
    [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)]
    [global::System.Runtime.Serialization.DataMemberAttribute()]
    public string Name
    {
        get
        {
            return this._Name;
        }
        set
        {
            this.OnNameChanging(value);
            this.ReportPropertyChanging("Name");
            this._Name = global::System.Data.Objects.DataClasses.
                StructuralObject.SetValidValue(value, false);
            this.ReportPropertyChanged("Name");
            this.OnNameChanged();
        }
    }
    private string _Name;
    partial void OnNameChanging(string value);
    partial void OnNameChanged();
    /// <summary>
    /// There are no comments for Property Description in the schema.
    /// </summary>
    [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)]
    [global::System.Runtime.Serialization.DataMemberAttribute()]
    public string Description
    {
        get
        {
            return this._Description;
        }
        set
        {
            this.OnDescriptionChanging(value);
            this.ReportPropertyChanging("Description");
            this._Description = global::System.Data.Objects.DataClasses.
                StructuralObject.SetValidValue(value, false);
            this.ReportPropertyChanged("Description");

            this.OnDescriptionChanged();
        }
    }
    private string _Description;
    partial void OnDescriptionChanging(string value);
    partial void OnDescriptionChanged();
    /// <summary>
    /// There are no comments for Property Longitude in the schema.
    /// </summary>
    [global::System.Data.Objects.DataClasses.
        EdmScalarPropertyAttribute(IsNullable=false)]
    [global::System.Runtime.Serialization.DataMemberAttribute()]
    public double Longitude
    {
        get
        {
            return this._Longitude;
        }
        set
        {
            this.OnLongitudeChanging(value);
            this.ReportPropertyChanging("Longitude");
            this._Longitude = global::System.Data.Objects.DataClasses.
                StructuralObject.SetValidValue(value);
            this.ReportPropertyChanged("Longitude");
            this.OnLongitudeChanged();
        }
    }
    private double _Longitude;
    partial void OnLongitudeChanging(double value);
    partial void OnLongitudeChanged();
    /// <summary>
    /// There are no comments for Property Latitude in the schema.
    /// </summary>
    [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)]
    [global::System.Runtime.Serialization.DataMemberAttribute()]
    public double Latitude
    {
        get
        {
            return this._Latitude;
        }
        set
        {
            this.OnLatitudeChanging(value);
            this.ReportPropertyChanging("Latitude");
            this._Latitude = global::System.Data.Objects.DataClasses.
                StructuralObject.SetValidValue(value);
            this.ReportPropertyChanged("Latitude");
            this.OnLatitudeChanged();
        }
    }
    private double _Latitude;
    partial void OnLatitudeChanging(double value);
    partial void OnLatitudeChanged();

You can see that it implements Serializable, and also has a DataContractAttribute, which will use the DataContractSerializer for serialization. You can see, it simply gets serialized and sent as XML (not very pretty XML, but it does work just fine).

If you want more control over the way the resources are serialized, you can write your own serialization using a return type of Message. I talk a lot more about serialization options when working with RESTful WCF, over at my blog; you can read about your options from this post: http://sachabarber.net/?p=475.

OK, so that is the basic idea. We have some new RESTful attributes, and we can expose the service methods via an HTTP URI, and we can pattern match on certain URIs and even use part of the URI as parameters to methods.

Now I am all in favour of using a browser when the entire service consists of GET requests, as there is no need for a DataContract client proxy class at all. The browser is able to get the resource(s) it wants by simply using a specific URI. However, as we all know, things are never as clear cut as all that, and most, if not all, applications will be required to update/delete and alter data; to this end, you will almost always need to use some of the other HTTP verbs apart from GET requests.

POST / PUT / DELETE

These HTTP verbs are more than likely going to need entire objects to be sent, where these objects are either XML / JSON serlialized, and which are going to be needed to be deserialized and translated into CLR types. Now, I like coding, but I am no masochist, and I would always choose to pick the right tool for the right job; to this end, I would recommend using a proxy, which I discuss in detail below in the WPF Client section.

For now, all you need to know is that there is WebInvokeAttribute, which can be used for the POST/PUT and DELETE HTTP verbs.

Here is an example:

C#
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "User/Add/",
    ResponseFormat = WebMessageFormat.Xml)]
Users AddUser(Users newUser);

The WebInvokeAttribute must be specified with a Method type as well. Possible values are POST/PUT and DELETE; this is required as all three HTTP verbs may end up with the same URI template, and there needs to be some way to distinguish between them.

WebProtocolExceptions

Those of you that have worked with WCF before may have come across something called SOAP faults, which are realized using FaultException<T> in normal SOAP based WCF Services. FaultException<T> allows you to flow SOAP faults back to the client app of the WCF Service. Now, as we are working with HTTP with a RESTful service, we can no longer use FaultException<T> as we need an HTTP representative fault message. As luck would have it, Microsoft has thought about this and released a RESTful WCF starter kit out of bands, which contains a number of useful classes. One of which is the WebProtocolException class which allows WebProtocolExceptions to be treated just like normal WebProtocolExceptions. These can then be used by the client of the RESTful WCF Service. Here is an example of the actual RESTful WCF Service raising a WebProtocolException:

C#
/// <summary>
/// Gets all places for a particular user
/// </summary>
public List<Places> GetAllPlacesForUser(String userId)
{
    Int32 id = -1;
    if (Int32.TryParse(userId, out id))
    {
        try
        {
            GeoPlacesEntities model = new GeoPlacesEntities();
            return model.Places.Where((pl) => pl.Users.ID == id).ToList();
        }
        catch (Exception ex)
        {
            //WebProtocolException is part of WCF REST Starter Kit Preview 2
            throw new WebProtocolException(HttpStatusCode.BadRequest,
                String.Format("Couldn't find places for user id {0}", userId), null);

        }
    }
    else
        return null;
}

Note that with SOAP based WCF, using FaultException<T> meant that we had to mark up the ServiceContract with FaultContractAttribute(s); this is not required using the WebProtocolException class.

ETag(s)

Wikipedia has this to say about ETags:

"An ETag (entity tag) is an HTTP response header returned by an HTTP/1.1 compliant web server used to determine change in content at a given URL. When a new HTTP response contains the same ETag as an older HTTP response, the contents are considered to be the same without further downloading. The header is useful for intermediary devices that perform caching, as well as for client web browsers that cache results. "

However, Jon Flanders, who is the author of an excellent book on REST, says it better, in my opinion. What he says is this:

"An ETag is a per resource, opaque, unique value. An ETag is generally a hashed value generated by a server in response to a GET request for a resource that is based on some information from the resource itself. When the user agent makes another request for the same resource, the value of the ETag is presented in the If-None-Match header.

When the server receives the request, it has to generate the ETag for the resource again, and if the current ETag matches the value of the If-No-Match header, the resource hasn't changed."

- Jon Flanders, RESTful .NET, O'Reilly.

Now, what all this means to developers of RESTful WCF Services is that when we get a request for a resource, we should be good developers and create an ETag that can be checked the next time a request is made.

Consider the GET method that the demo code has:

C#
[OperationContract]
[WebGet(UriTemplate = "/users/{userId}",
    ResponseFormat = WebMessageFormat.Xml)]
Users GetUser(String userId);

Which has an actual implementation as follows:

C#
/// <summary>
/// Gets a user based on its Id
/// </summary>
public Users GetUser(String userId)
{

    Int32 id = -1;
    if (Int32.TryParse(userId, out id))
    {
        try
        {
            Users u = FindUser(id);
#if HTTP
            string etag = GenerateETag(u.ID + u.Name + u.Password);

            if (CheckETag(etag))
                return null;

            if (u == null)
            {
                OutgoingWebResponseContext ctx =
                    WebOperationContext.Current.OutgoingResponse;
                ctx.SetStatusAsNotFound();
                ctx.SuppressEntityBody = true;
            }

            SetETag(etag);
#endif
            return u;

        }
        catch (Exception ex)
        {
            //WebProtocolException is part of WCF REST Starter Kit Preview 2
            throw new WebProtocolException(HttpStatusCode.BadRequest,
                String.Format("Couldn't find user with id {0}", userId), null);
        }
    }
    else
        return null;
}

You will notice that this code uses three ETag helper methods (if #HTTP is defined), which are listed below, which are used to manage the checking of ETags and creation of new ETags. This allows caching of resources:

C#
/// <summary>
/// Sets a ETag (caching for the object) on the current
/// OutgoingResponse context
/// </summary>
private void SetETag(string etag)
{
    OutgoingWebResponseContext ctx =
        WebOperationContext.Current.OutgoingResponse;
    ctx.ETag = etag;
}

/// <summary>
/// Creates a ETag (caching for the object) 
/// </summary>
private  string GenerateETag(String valueToHash)
{
    byte[] bytes = Encoding.UTF8.GetBytes(valueToHash);
    byte[] hash = MD5.Create().ComputeHash(bytes);
    string etag = Convert.ToBase64String(hash);
    return etag;
}

/// <summary>
/// Examines the incoming request context to see if there
/// is a cached ETag for the object requested
/// </summary>
private bool CheckETag(string currentETag)
{
    IncomingWebRequestContext ctx =
        WebOperationContext.Current.IncomingRequest;
    string incomingEtag =
        ctx.Headers[HttpRequestHeader.IfNoneMatch];
    if (incomingEtag != null)
    {
        if (currentETag == incomingEtag)
        {
            return true;
        }
    }
    return false;
}

Play Nice: Provide New URIs for New Resources

As users of a RESTful WCF Service may add new resources, they should be able to get to these added resources using a URI, so it is plain polite that whenever a new resource is added that you (the developer) create a new URI for the new resource. This can be seen when we allow new users to be added.

C#
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "User/Add/",
    ResponseFormat = WebMessageFormat.Xml)]
Users AddUser(Users newUser);

Where a user's resource is available using the following:

C#
[OperationContract]
[WebGet(UriTemplate = "/users/{userId}",
    ResponseFormat = WebMessageFormat.Xml)]
Users GetUser(String userId);

So if we examine the implementation of the AddUser() method, we can see that when a new user's resource is created, we also play nice and create a new URI for the new user's resource. This will be returned via the current web context, and the user will then be able to use the new URI to GET the newly added resource.

C#
/// <summary>
/// Adds a user to the System
/// </summary>
public Users AddUser(Users newUser)
{
    try
    {
        GeoPlacesEntities model = new GeoPlacesEntities();
        model.AddToUsers(newUser);
        model.SaveChanges();

        Users userFromDb = model.Users.Where((u) =>
           u.Name.ToLower().Equals(newUser.Name) &&
           u.Password.ToLower().Equals(newUser.Password)
           ).First();

#if HTTP
        OutgoingWebResponseContext ctx = 
            WebOperationContext.Current.OutgoingResponse;
        ctx.SetStatusAsCreated(CreateNewUserUri(userFromDb));
#endif

        return userFromDb;
    }
    catch(Exception ex)
    {
        //WebProtocolException is part of WCF REST Starter Kit Preview 2
        throw new WebProtocolException(HttpStatusCode.BadRequest,
            "Couldn't add new user", null);
    }
}
        
/// <summary>
/// Create a new User Uri for the Resource
/// </summary>
private Uri CreateNewUserUri(Users u)
{
    UriTemplate ut = new UriTemplate("/users/{user_id}");
    Uri baseUri = WebOperationContext.Current.
        IncomingRequest.UriTemplateMatch.BaseUri;
    Uri ret = ut.BindByPosition(baseUri, u.ID.ToString());
    return ret;
}

WCF Extensibility

The last thing I wanted to talk about is WCF extensibility. Basically, if you can think of anything you want to extend in WCF, there will be an extension point. For example, I wanted all my HTTP Content-Type header values to be set automatically without me having to write this for every request/response.

As it turns out, you can do this by extending IEndpointBehavior, as shown below:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;

namespace GeoPlacesDataService
{
    /// <summary>
    /// Taken directly from the excellent RESTful .NET by Jon Flanders
    /// 
    /// It simply alters the endpoint behaviour by automatically setting the
    /// Content Type, by the use of the ContentTypeMessageInspector class
    /// </summary>
    public class ContentTypeBehaviour : IEndpointBehavior
    {

        public string ContentType { get; set; }

        #region IEndpointBehavior Members

        public void AddBindingParameters(ServiceEndpoint endpoint, 
            BindingParameterCollection bindingParameters)
        {
            //do nothing
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, 
            ClientRuntime clientRuntime)
        {
            //work out what the correct ContentType should be
            ContentTypeMessageInspector mi = new 
                ContentTypeMessageInspector { ContentType = this.ContentType };
            clientRuntime.MessageInspectors.Add(mi);
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, 
            EndpointDispatcher endpointDispatcher)
        {
            //do nothing
        }

        public void Validate(ServiceEndpoint endpoint)
        {
            //do nothing
        }

        #endregion
    }
}

Where this class makes use of a helper class called ContentTypeMessageInspector, which is as shown below:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.ServiceModel;

namespace GeoPlacesDataService
{
    /// <summary>
    /// Taken directly from the excellent RESTful .NET by Jon Flanders
    /// 
    /// Automatically sets the Content Type, by extending WCF namely 
    /// setting the HttpRequestMessageProperty headers
    /// </summary>
    public class ContentTypeMessageInspector : IClientMessageInspector
    {
        public string ContentType { get; set; }

        #region IClientMessageInspector Members

        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            //do nothing
        }

        /// <summary>
        /// Apply the Content-Type header to the HttpRequestMessageProperty
        /// </summary>
        public object BeforeSendRequest(ref Message request, 
            IClientChannel channel)
        {
            HttpRequestMessageProperty prop = 
                request.Properties[HttpRequestMessageProperty.Name]
                    as HttpRequestMessageProperty;
            
            if (prop != null && (prop.Method == "POST" || 
                prop.Method == "PUT"))
            {
                prop.Headers["Content-Type"] = this.ContentType;
            }
            return null;

        }

        #endregion
    }
}

I make no claims to have authored these two classes myself; these are lifted directly from Jon Flanders' excellent RESTful .NET book. Jon knows his stuff, it's a great book, highly recommended.

WPF Client

There is a WPF client app that makes use of the running RESTful WCF Service that is hosted within a Windows Service (or self hosted in a console app, if you simply use the standalone EXE). This section will outline the most important parts of the WPF client.

MVVM: The Model View View Model Pattern

If you are working with WPF, you really should be using the MVVM pattern, which allows the view to be abstracted to a ViewModel. The idea being that the ViewModel can be tested in isolation and that the View code simply contains the actual presentation, this being the XAML. Typically, the View will bind to an associated ViewModel's properties, where the ViewModel will be set as the View's DataContext.

The ViewModel will either expose bindable properties as DependencyPropertys, or CLR Properties where INotifyPropertyChanged will be used to ensure change notifications (allows bindings to update to latest values).

The attached demo code has several View and ViewModels all performing various parts of the overall functionality of the WPF client, but if you were to examine the code-behind for the View(s), you will see very little code. The ViewModel is doing all the work. Here is a list of the Views/ViewModels:

View NameView ModelDescription
LoginControlLoginViewModelAllows users to login or register
MainWindowMainWindowViewModelThe main window
PlaceControlPlacesViewModelRepresents the places for a user

Let's consider a single example, say the Login View/ViewModel, which I will be using for most of the discussions in subsequent sections:

Here is the ViewModel code (Note: ViewModelBase implements the INotifyPropertyChanged interface):

C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows.Input;
using GeoPlacesModel;

namespace GeoPlaces
{
    /// <summary>
    /// Simple ViewModel for the LoginControl
    /// </summary>
    public class LoginViewModel : ViewModelBase
    {
        #region Data
        private String userName = String.Empty;
        private String password = String.Empty;
        private Boolean isAuthenticatedUser = false;
        private Boolean isBusy = false;
        private ICommand loginCommand = null;
        private ICommand registerCommand = null;
        private IView view = null;
        #endregion

        #region Ctor
        public LoginViewModel(IView view)
        {
            this.view = view;

            //wire up loginCommand
            loginCommand = new SimpleCommand
            {
                CanExecuteDelegate = x => !IsBusy,
                ExecuteDelegate = x => Login()
            };

            //wire up registerCommand
            registerCommand = new SimpleCommand
            {
                CanExecuteDelegate = x => !IsBusy,
                ExecuteDelegate = x => Register()
            };
        }
        #endregion

        #region Public Properties
        public ICommand RegisterCommand
        {
            get { return registerCommand; }
        }

        public ICommand LoginCommand
        {
            get { return loginCommand; }
        }

        public Boolean IsBusy
        {
            get { return isBusy; }
            set
            {
                isBusy = value;
                NotifyChanged("IsBusy");
            }
        }

        public String UserName
        {
            get { return userName; }
            set
            {
                userName = value;
                isAuthenticatedUser = false;
                NotifyChanged("IsAuthenticatedUser");
                NotifyChanged("UserName");
            }
        }

        public String Password
        {
            get { return password; }
            set
            {
                password = value;
                isAuthenticatedUser = false;
                NotifyChanged("IsAuthenticatedUser");
                NotifyChanged("Password");
            }
        }

        public Boolean IsAuthenticatedUser
        {
            get { return isAuthenticatedUser; }
            set
            {
                isAuthenticatedUser = value;
                NotifyChanged("IsAuthenticatedUser");
            }
        }
        #endregion

        #region Private Methods
        private void Login()
        {
            isBusy = true;

            App.Current.Properties.Remove("CurrentUser");


            Users dbReadUser = ServiceCalls.LoginUser(userName, password);
            if (dbReadUser != null)
            {
                App.Current.Properties.Add("CurrentUser", dbReadUser);
                Mediator.Instance.NotifyColleagues(
                    ViewModelMessages.IsAuthenticatedUser, true);
                view.ShowMessage(String.Format(
                    "Sucessfully logged in user {0}, " + 
                    "please proceed to view/add your places",
                    userName));
            }
            else
            {
                App.Current.Properties.Add("CurrentUser", null);
                Mediator.Instance.NotifyColleagues(
                    ViewModelMessages.IsAuthenticatedUser, false);
                view.ShowMessage(String.Format(
                    "Could not log in user {0}, please try again",
                    userName));
            }

            isBusy = false;
        }


        private void Register()
        {
            isBusy = true;

            App.Current.Properties.Remove("CurrentUser");

            Users dbReadUser = ServiceCalls.RegisterUser(userName, password);
            if (dbReadUser != null)
            {
                App.Current.Properties.Add("CurrentUser", dbReadUser);
                Mediator.Instance.NotifyColleagues(
                    ViewModelMessages.IsAuthenticatedUser, true);
                view.ShowMessage(String.Format(
                    "Sucessfully added user {0}, please " + 
                    "proceed to view/add your places",
                    userName));
            }
            else
            {
                App.Current.Properties.Add("CurrentUser", null);
                Mediator.Instance.NotifyColleagues(
                    ViewModelMessages.IsAuthenticatedUser, false);
                view.ShowMessage(String.Format(
                    "Could not add user {0}, please try again",
                        userName));
            }

            isBusy = false;
        }
        #endregion
    }
}

And here is LoginControl.xaml:

XML
<StackPanel Orientation="Vertical">
    <Label FontSize="18" Content="Login or Register"/>
    
    <StackPanel Orientation="Horizontal">

        <Label FontSize="14" 
           Content="UserName" Width="100"/>
        <TextBox Text="{Binding Path=UserName, Mode=TwoWay, 
            UpdateSourceTrigger=LostFocus}"/>
    
    </StackPanel>


    <StackPanel Orientation="Horizontal">

        <Label FontSize="14" 
            Content="Password" Width="100"/>
        <TextBox Text="{Binding Path=Password, Mode=TwoWay, 
            UpdateSourceTrigger=LostFocus}"/>

    </StackPanel>


    <StackPanel Orientation="Horizontal">
        <Button x:Name="btnLogin" Content="Login"
                Height="30" Margin="10" 
                Style="{StaticResource GelButton}"
                ToolTip="Login Using Your Previous Details"                    
                Command="{Binding Path=LoginCommand}"/>
        <Button x:Name="btnRegister" Content="Register"
                Height="30" Margin="10" 
                ToolTip="Register As A New User"                    
                Style="{StaticResource GelButton}"
                Command="{Binding Path=RegisterCommand}"/>
    </StackPanel>
</StackPanel>

The ViewModel is set as the DataContext in the code-behind as follows. Notice how the code-behind is pretty dumb. The ViewModel does the lion's share of the work. The View is pretty passive, and just responds to changes via binding updates from the ViewModel.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace GeoPlaces
{
    /// <summary>
    /// A simple Login control that allows existing
    /// users to login, or new users to register
    /// </summary>
    public partial class LoginControl : UserControl, IView
    {
        #region Data
        private LoginViewModel loginViewModel = null;
        #endregion

        #region Ctor
        public LoginControl()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(LoginControl_Loaded);
            loginViewModel = new LoginViewModel(this);
        }
        #endregion

        #region Private Methods
        private void LoginControl_Loaded(object sender, RoutedEventArgs e)
        {
            this.DataContext = loginViewModel;
        }
        #endregion

        #region IView Members

        public void ShowMessage(string message)
        {
            MessageBox.Show(message,"information",
                MessageBoxButton.OK,MessageBoxImage.Information);
        }

        #endregion

    }
}

Commanding: Commands

In order to fully support the MVVM pattern, you will want to consider some sort of commanding infrastructure such that the View can bind to an instance of ICommand typically exposed as properties on the ViewModel, and ideally would allow the command's code to be executed within the ViewModel. While this is possible using the standard WPF RoutedCommands, it is a bit tedious as it requires the developer to create command binding in the XAML or the code-behind for the View and wire up delegates etc., believe me, it's tedious.

Lately, a number of people, including those working on the Microsoft Composite WPF and Silverlight (A.K.A. PRISM) code, have abandoned the standard WPF commanding in favour of lighter weight, easier to use delegate based commands. The ViewModel simply exposes an ICommand property that the View binds to. The ViewModel contains the delegates that are run when the ICommand that the View is bound executes. The delegate based commands also enable the command execution state to be determined and shown within the View. Here is an example where we make use of Predicate<object> for the CanExecute handler from ICommand, and Action<object> for the Execute delegate of ICommand.

ViewModel code
C#
/// <summary>
/// Simple ViewModel for the LoginControl
/// </summary>
public class LoginViewModel : ViewModelBase
{
    #region Data
    ....
    ....
    private ICommand loginCommand = null;
    private ICommand registerCommand = null;
    #endregion

    #region Ctor
    public LoginViewModel(IView view)
    {

        this.view = view;

        //wire up loginCommand
        loginCommand = new SimpleCommand
        {
            CanExecuteDelegate = x => !IsBusy,
            ExecuteDelegate = x => Login()
        };

        //wire up registerCommand
        registerCommand = new SimpleCommand
        {
            CanExecuteDelegate = x => !IsBusy,
            ExecuteDelegate = x => Register()
        };
    }
    #endregion

    #region Public Properties
    public ICommand RegisterCommand
    {
        get { return registerCommand; }
    }


    public ICommand LoginCommand
    {
        get { return loginCommand; }
    }

    ....
    ....
    #endregion
}
View code
XML
<StackPanel Orientation="Horizontal">
    <Button x:Name="btnLogin" Content="Login"
            Height="30" Margin="10" 
            Style="{StaticResource GelButton}"
            ToolTip="Login Using Your Previous Details"                    
            Command="{Binding Path=LoginCommand}"/>
    <Button x:Name="btnRegister" Content="Register"
            Height="30" Margin="10" 
            ToolTip="Register As A New User"                    
            Style="{StaticResource GelButton}"
            Command="{Binding Path=RegisterCommand}"/>
</StackPanel>

This uses my good buddy Marlon Grech's SimpleCommand code, which is as follows:

C#
using System;
using System.Windows.Input;

namespace GeoPlaces
{
    /// <summary>
    /// Implements the ICommand and wraps up all the verbose 
    /// stuff so that you can just pass 2 delegates 1 for the 
    /// CanExecute and one for the Execute
    /// </summary>
    public class SimpleCommand : ICommand
    {
        /// <summary>
        /// Gets or sets the Predicate to execute when the 
        /// CanExecute of the command gets called
        /// </summary>
        public Predicate<object> CanExecuteDelegate { get; set; }

        /// <summary>
        /// Gets or sets the action to be called when the 
        /// Execute method of the command gets called
        /// </summary>
        public Action<object> ExecuteDelegate { get; set; }

        #region ICommand Members

        /// <summary>
        /// Checks if the command Execute method can run
        /// </summary>
        /// <param name="parameter">THe command parameter to 
        /// be passed</param>
        /// <returns>Returns true if the command can execute. 
        /// By default true is returned so that if the user of 
        /// SimpleCommand does not specify a CanExecuteCommand 
        /// delegate the command still executes.</returns>
        public bool CanExecute(object parameter)
        {
            if (CanExecuteDelegate != null)
                return CanExecuteDelegate(parameter);
            return true;// if there is no can execute default to true
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        /// <summary>
        /// Executes the actual command
        /// </summary>
        /// <param name="parameter">THe command parameter 
          /// to be passed</param>
        public void Execute(object parameter)
        {
            if (ExecuteDelegate != null)
                ExecuteDelegate(parameter);
        }

        #endregion
    }
}

Interface usage: IView

In the spirit of the MVVM pattern, we do not want to pollute our code-behind with extra code that is obviously now done by the ViewModel code. The only problem is that occasionally, the ViewModel needs to do something UI like, such as show a MessageBox. Believe me, you do want to try and stick with the ViewModel approach, as it creates very clean separated / easily testable code. However, this MessageBox situation just outlined is an issue; what can we do about it?

Well, one approach is for the View to expose services such as a MessageBox service. One of my fellow WPF Disciples, Bill Kempf, is working on this very idea in a great project called WPF Onyx, which I will be working on after this article. Onyx uses this service based approach to really help you create good WPF apps that use the MVVM pattern.

Anyway, what I do for this demo app is simply pass the View into the ViewModel, and from there, the ViewModel is able to call a service off the View, which is exposed by known interfaces. Let's see an example, using the MessageBox problem outlined above. Notice that the View implements an IView interface, which exposes a MessageBox service method that the ViewModel could call to tell the View to do something. Again, this avoids spaghetti code in the code-behind of the View as the ViewModel will only be able to communicate with the View using well-known, exposed service interfaces.

View code
C#
/// <summary>
/// A simple Login control that allows existing
/// users to login, or new users to register
/// </summary>
public partial class LoginControl : UserControl, IView
{
    #region Data
    private LoginViewModel loginViewModel = null;
    #endregion

    #region Ctor
    public LoginControl()
    {
        InitializeComponent();
        this.Loaded += new RoutedEventHandler(LoginControl_Loaded);
        loginViewModel = new LoginViewModel(this);
    }
    #endregion

    #region Private Methods
    private void LoginControl_Loaded(object sender, RoutedEventArgs e)
    {
        this.DataContext = loginViewModel;
    }
    #endregion

    #region IView Members

    public void ShowMessage(string message)
    {
        MessageBox.Show(message,"information",
            MessageBoxButton.OK,MessageBoxImage.Information);
    }

    #endregion

}

And here is the ViewModel code. Notice how the constructor takes an IView as a parameter, and when the ViewModel wants to show a MessageBox, it asks its internal IView instance to do it, and it will use the MessageBox service method we saw above. This way, the ViewModel is able to perform UI type things, but does not contain any UI code as such; the view does all that. The ViewModel simply asks the view to do something via some well know interface, and the View does it. This concept is very powerful, and enables extremely clean code separation between the Presentation Layer and the actual Model that drives the View. I like this idea a lot, and can't wait to start trying a big article with WPF Disciples Bill Kempf's pet project WPF Onyx. You will definitely see more from me on WPF Onyx; it looks very, very promising.

C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows.Input;
using GeoPlacesModel;

namespace GeoPlaces
{
    /// <summary>
    /// Simple ViewModel for the LoginControl
    /// </summary>
    public class LoginViewModel : ViewModelBase
    {
        ....
        ....
        ....
        private IView view = null;

        #region Ctor
        public LoginViewModel(IView view)
        {
            this.view = view;
        }
        #endregion

        #region Private Methods
        private void Login()
        {
          .......
          .......
          view.ShowMessage(String.Format(
             "Could not log in user {0}, please try again",
             userName));
          .......
          .......

        }
        #endregion
    }
}

Mediator: The Mediator Pattern

As one can imagine, if a single View is abstracted to a single ViewModel, there will be occasions when View/ViewModels need to talk to each other. Hmmm, that is a bit of an issue since the Views do not know about each other, or the ViewModel(s) about each other, so how can we perform messaging between ViewModels?

Luckily, help is at hand, via the use of the Mediator pattern. The Mediator pattern can be thought of as an event aggregator that knows about messages and who the message results should be routed to. The basic idea is that for each message, those interested in getting a callback based on a particular message's data state change will register interest for particular messages. When a message data state changes, those that registered interest for the changed message data will be notified via some form of callback.

So how does all this translate to code? Well, it's fairly simple; thanks again to Action<T> delegates, we have a Mediator class that looks like the following:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GeoPlaces
{
    /// <summary>
    /// Available cross ViewModel messages
    /// </summary>
    public enum ViewModelMessages { IsAuthenticatedUser = 1, NewPlaceAdded=2 };

    /// <summary>
    /// A message Mediator singleton to allow unconnected ViewModel to 
    /// send and receive messages
    /// </summary>
    public sealed class Mediator
    {
        #region Data
        static readonly Mediator instance = new Mediator();
        private volatile object locker = new object();

          //specialized Dictionary (see the code for this)
        MultiDictionary<ViewModelMessages, Action<Object>> internalList
            = new MultiDictionary<ViewModelMessages, Action<Object>>();
        #endregion

        #region Ctor
        static Mediator()
        {
        }

        private Mediator()
        {

        }
        #endregion

        #region Public Properties


        public static Mediator Instance
        {
            get
            {
                return instance;
            }
        }

        #endregion

        #region Public Methods
        /// <summary>
        /// Registers a Colleague to a specific message
        /// </summary>
        /// <param name="callback">The callback to use when the message it seen</param>
        /// <param name="message">The message to register to</param>
        public void Register(Action<Object> callback, ViewModelMessages message)
        {
            internalList.AddValue(message, callback);
        }

        /// <summary>
        /// Notify all colleagues that are registed to the specific message
        /// </summary>
        /// <param name="message">The message for the notify by</param>
        /// <param name="args">The arguments for the message</param>
        public void NotifyColleagues(ViewModelMessages message, object args)
        {
            if (internalList.ContainsKey(message))
            {
                //forward the message to all listeners
                foreach (Action<object> callback in internalList[message])
                    callback(args);
            }
        }
        #endregion
    }
}

It can be seen that the Mediator simply maintains a Dictionary of messages and callbacks, and that the callback Action<Object> delegates are invoked when a message data state changes, where the new state is passed as an Object to the callback Action<Object> delegates. This is done by the NotifyCollegues method of the Mediator, which looks through all registered callback Action<Object> delegates, and calls them passing the new state object. So, all ViewModels that wish to communicate with each other will use the Register() method of the Mediator, and will receive a callback via the Action<Object> delegates that were used when registering for the message data state changes.

Here is what the code for a ViewModel looks like when registering with the Mediator:

C#
//register to the mediator for the IsAuthenticatedUser message
Mediator.Instance.Register(
    (Object o) =>
    {
        isAuthenticatedUser = (Boolean)o;
        if (isAuthenticatedUser)
            GetAllPlaces();
    }, ViewModelMessages.IsAuthenticatedUser);

And here is an example that updates a message data state, and notifies those interested that new data state is available:

C#
Mediator.Instance.NotifyColleagues(
    ViewModelMessages.IsAuthenticatedUser, true);

In this example, you can see that the callback delegate is actually defined as follows, and we must cast the Object parameter passed to the callback Action<Object> delegate's delegate to the expected type (Boolean in this case).

C#
(Object o) =>
{
    isAuthenticatedUser = (Boolean)o;
    if (isAuthenticatedUser)
        GetAllPlaces();
}

I think this method works well.

Service Proxy: The WCF Service Proxy

As mentioned in the Restful WCF Service section, RESTful WCF makes use of HTTP. Which is cool, but 9 times out of 10, you do not simply want to GET data; you will want to POST and DELETE data. In which case, you are going to have to use some sort of API that allows you to work either with HTTP, or you can use the WebChannelFactory class which allows you to use a RESTful service in much the same way that you would have used a SOAP based WCF Service.

The WebChannelFactory class is a special ChannelFactory that automatically adds the WebHttpBehavior to the endpoint if it is not already present. Furthermore, it adds a default WebHttpBinding to the endpoint if the binding is not explicitly configured and the address is an HTTP or HTTPS address.

So you can see that the WebChannelFactory class does most of the heavy lifting for our client code. All that we need to do is really create a new WebChannelFactory, and that is pretty much job done. Of course, we are good developers and we want to create robust re-usable code that abstracts the notion of a client ChannelFactory into something more natural.

To this end, I have developed the following RESTful WCF service proxy class, which makes the whole process of using the WebChannelFactory a lot easier, and provides error handling.

C#
using System;
using System.Configuration;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Web;
using GeoPlacesDataService;
using Microsoft.ServiceModel.Web;

namespace GeoPlaces
{
    /// <summary>
    /// The client proxy delegate, which is typically an anonomous delegate
    /// in the actual client code
    /// </summary>
    public delegate void UseServiceDelegate(IGeoService proxy);

    /// <summary>
    /// This section of code was originally obtained from the following source
    /// http://www.iserviceoriented.com/blog/post/Indisposable+-+WCF+Gotcha+1.aspx
    /// 
    /// It was subsequently changed in order to make it work with the web based
    /// RestFul WCf service. This class should handle restarting the proxy in case
    /// of a faulted channel
    /// </summary>
    public static class Service
    {
        #region Data

        private static IClientChannel proxy = null;
        public static ChannelFactory<IGeoService> _channelFactory = null;

        #endregion

        static Service()
        {
            try
            {
                Uri serviceUri = new Uri(
                    ConfigurationManager.AppSettings["GeoServiceEndpointAddress"]);
                _channelFactory = new WebChannelFactory<IGeoService>(serviceUri);
                _channelFactory.Endpoint.Behaviors.Add(
                    new ContentTypeBehaviour { ContentType = "text/xml" });
            }
            catch (Exception e)
            {
                ApplicationException ae = new ApplicationException(
                    "Error initiating WCF channel for The GeoService", e);
                Console.WriteLine(String.Format("An exception occurred : {0}", ae));
                throw ae;
            }
        }


        #region Public Methods
        public static void Use(UseServiceDelegate codeBlock)
        {
            bool success = false;

            if (proxy != null && (proxy.State == CommunicationState.Opened ||
                                  proxy.State == CommunicationState.Opening))
            {
                //do nothing, all ok
            }
            else
                proxy = (IClientChannel)_channelFactory.CreateChannel();

            //try to create the Proxy
            try
            {
                codeBlock((IGeoService)proxy);
                success = true;
            }


            //WebException is only avaiable WCF REST Starter Kit Preview 2
            //http://aspnet.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=24644
            catch (WebProtocolException webExp)
            {
                Console.WriteLine(String.Format("An exception occurred : {0}",
                    webExp.Message));

                throw new ApplicationException(
                    "A GeoService WebProtocolException occured", webExp);
            }
            catch (WebException ex)
            {
                using (System.IO.Stream respStream = ex.Response.GetResponseStream())
                    using(System.IO.StreamReader reader = 
                        new System.IO.StreamReader(respStream))
                            Console.WriteLine(String.Format("An exception occurred : {0}",
                            reader.ReadToEnd()));
            }
            catch (FaultException fex)
            {
                Console.WriteLine(String.Format("An exception occurred : {0}",
                    fex.Message));
                throw new ApplicationException(
                    "A GeoService FaultException occured", fex);
            }
            catch (Exception ex)
            {
                Console.WriteLine(String.Format("An exception occurred : {0}",
                    ex.Message));
                throw new ApplicationException(
                    "A GeoService Exception occured", ex);
            }
            finally
            {
                if (!success && proxy != null)
                    proxy.Abort();
            }
        }
        #endregion
    }
}

One thing to note is the WebProtocolException, which you will not find as part of the standard .NET codebase. This is an extra class that is part of the WCF REST Starter Kit Preview 2. The starter kit contains a few good classes that you can use to get you started with RESTful WCF. This starter kit is not a prerequisite as I have included the necessary DLLs in this app's codebase.

You will see how this proxy helper class is used just below:

Service Calls: Making Service Calls

As I just stated, I have developed a nice, easy to use RESTful WCF proxy class that greatly aids in the usage of a WebChannelFactory. So how do we go about using this helper class? Well, it is actually quite simple. Let's see some POST/GET examples that are used against the demo code's RESTful WCF Service, shall we?

AuthenticateUser (POST)

Which is defined as follows within the RESTful WCF Service:

C#
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "User/Add/",
    ResponseFormat = WebMessageFormat.Xml)]
Users AddUser(Users newUser);

And has an implementation that looks like the code shown below, where we persist the new user to the SQL Server database using the ADO.NET Entity Framework. I am also using some defines to determine if we need to create HTTP response codes and create a new REST URI for the new resource (it is polite; the user may actually be using a browser, so will appreciate the new URI being created for the user's new resource they just added).

C#
/// <summary>
/// Adds a user to the System
/// </summary>
public Users AddUser(Users newUser)
{
    try
    {
        GeoPlacesEntities model = new GeoPlacesEntities();
        model.AddToUsers(newUser);
        model.SaveChanges();

        Users userFromDb = model.Users.Where((u) =>
           u.Name.ToLower().Equals(newUser.Name) &&
           u.Password.ToLower().Equals(newUser.Password)
           ).First();

#if HTTP
        OutgoingWebResponseContext ctx = 
            WebOperationContext.Current.OutgoingResponse;
        ctx.SetStatusAsCreated(CreateNewUserUri(userFromDb));
#endif

        return userFromDb;
    }
    catch(Exception ex)
    {
        //WebProtocolException is part of WCF REST Starter Kit Preview 2
        throw new WebProtocolException(HttpStatusCode.BadRequest,
            "Couldn't add new user", null);
    }
}

So now, let us look at the WPF client code to call this using our proxy helper:

C#
/// <summary>
/// Logs a user in, and returns the logged in user
/// 
/// Calls WCF Service method
/// [OperationContract]
/// [WebInvoke(Method = "POST", UriTemplate = "User/Add/",
///     ResponseFormat = WebMessageFormat.Xml)]
/// Users AddUser(Users newUser);
/// </summary>
public static Users RegisterUser(String username, String password)
{
    Boolean isAuthenticatedUser = false;

    Users currentUser = new Users
    {
        Name = username,
        Password = password,
        Places = null
    };

    Users dbReadUser = null;

    try
    {
        //Use the GEOPlacesService Proxy
        Service.Use((client) =>
        {
            //need OperationContextScope to use WebOperationContext(s)
            //and HttpStatusCode(s)
            using (new OperationContextScope((IContextChannel)client))
            {

                dbReadUser = client.AddUser(currentUser);

                IncomingWebResponseContext rctx =
                    WebOperationContext.Current.IncomingResponse;
                if (rctx.StatusCode == System.Net.HttpStatusCode.Created)
                {
                    if (dbReadUser != null)
                        isAuthenticatedUser = dbReadUser.ID >= 0;
                }
            }

        });

        if (isAuthenticatedUser)
            return dbReadUser;
        else
        {
            Console.WriteLine("Error registering user");
            return null;
        }
    }
    catch (ApplicationException Ex)
    {
        return null;
    }
}

The line that looks like the code just below is the proxy helper actually being used, where the client is the actual IClientChannel which is created within the service proxy helper class. Can you see it? Again, it uses delegates, so the code to run is done against the IClientChannel created within the service proxy helper class. Also note that we must use the OperationContextScope to allow us to examine the HTTP status codes to work.

C#
Service.User((client) => { }

For completeness, let us also consider a GET.

GetAllPlacesForUser (GET)

Which is defined as follows within the RESTful WCF Service:

C#
[OperationContract]
[WebGet(UriTemplate = "/placesList/{userId}",
    ResponseFormat = WebMessageFormat.Xml)]
List<Places> GetAllPlacesForUser(String userId);

And has an implementation that looks like this, where we obtain all the places for a user from the SQL Server database using the ADO.NET Entity Framework.

C#
/// <summary>
/// Gets all places for a particular user
/// </summary>
public List<Places> GetAllPlacesForUser(String userId)
{
    Int32 id = -1;
    if (Int32.TryParse(userId, out id))
    {
        try
        {
            GeoPlacesEntities model = new GeoPlacesEntities();
            return model.Places.Where((pl) => pl.Users.ID == id).ToList();
        }
        catch (Exception ex)
        {
            //WebProtocolException is part of WCF REST Starter Kit Preview 2
            throw new WebProtocolException(HttpStatusCode.BadRequest,
                String.Format("Couldn't find places for user id {0}", userId), null);

        }
    }
    else
        return null;
}

Again, we use the service proxy helper class, which was the line, as before, we must use the OperationContextScope to allow us to examine the HTTP status codes.

C#
Service.User((client) => { }
C#
/// <summary>
/// Logs a user in, and returns the logged in user
/// 
/// Calls WCF Service method
/// [OperationContract]
/// [WebGet(UriTemplate = "/placesList/{userId}",
///    ResponseFormat = WebMessageFormat.Xml)]
/// List<Places> GetAllPlacesForUser(String userId);
/// </summary>
public static ObservableCollection<Places> GetAllPlacesForUser(String userId)
{
    Boolean completedOk = false;
    ObservableCollection<Places> places = null;
    List<Places> placesReturned = null;

    try
    {
        //Use the GEOPlacesService Proxy
        Service.Use((client) =>
        {
            //need OperationContextScope to use WebOperationContext(s)
            //and HttpStatusCode(s)
            using (new OperationContextScope((IContextChannel)client))
            {
                placesReturned = client.GetAllPlacesForUser(userId.ToString());

                IncomingWebResponseContext rctx =
                    WebOperationContext.Current.IncomingResponse;
                if (rctx.StatusCode == System.Net.HttpStatusCode.OK)
                {
                    completedOk = placesReturned != null;
                }
            }

        });

        if (completedOk)
        {
            places = new ObservableCollection<Places>(placesReturned);
            return places;
        }
        else
        {
            Console.WriteLine("Error getting all places for user");
            return null;
        }
    }
    catch (ApplicationException Ex)
    {
        return null;
    }
}

All the ViewModels communicate with the RESTful WCF Service via similar methods to those two shown above, where all the actual WCF Service calls are made via another helper class called "ServiceCalls", which does the RESTful WCF Service calls shown above, and allows the ViewModel code to remain clean and free of any WCF Service layer code/using statements.

VE Hosted Instance: The VE Map

As stated at various places within this article, I am using a hosted Microsoft Virtual Earth instance, which obviously means you have to have Microsoft Virtual Earth installed. But what about the control which looks like this? How does that work?

Image 12

Well, it is actually a bit of a cheat; it is a WPF control. I did not write this control, but I do know enough about it to talk about it. It is by InfoStrat, and is available at Virtual Earth 3D for WPF and Microsoft Surface (again, my attached code includes all you need, you do not have to download it unless you want to).

The InfoStrat.VE control uses an internal D3DImage, which is a WPF control that can be used to host DirectX 3D content in a WPF application.

So what Infrostrat does is effectively use a IntPtr handle to point the DirectX rendering at a D3DImage (I was using a very similar idea when I started out using Google Earth's COM API). Here is the most important part of the InfoStrat.VE.Map code, where we get the VE DirectX pointer to the 3D surface that will be used to host on the WPF based D3DImage.

C#
/// <summary>
/// Gets pointer to the Virtual Earth D3D backbuffer
/// </summary>
/// <returns></returns>
private IntPtr GetSourceSurfacePtr()
{
    GraphicsEngine3D graphicsEngine = GetGraphicsEngine();

    if (graphicsEngine == null)
        return IntPtr.Zero;

    Microsoft.MapPoint.Graphics3D.Types.Surface surfSrc = null;

    IntPtr ret = IntPtr.Zero;

    try
    {

        surfSrc = graphicsEngine.Device.GetRenderTarget(0);

        if (surfCpy == null)
        {
            CreateVESurface();
        }

        if (surfSrc != null && surfCpy != null)
        {
            graphicsEngine.Device.StretchRectangle(surfSrc, 
                new System.Drawing.Rectangle(0, 0, surfSrc.Description.Width, 
                    surfSrc.Description.Height), surfCpy, 
                new System.Drawing.Rectangle(0, 0, surfSrc.Description.Width, 
                    surfSrc.Description.Height));

            Microsoft.MapPoint.GraphicsAPI.Graphics.Surface internalSurf = null;

            internalSurf = 
               ReadPrivateVariable<Microsoft.MapPoint.Graphics3D.Types.Surface,
               Microsoft.MapPoint.GraphicsAPI.Graphics.Surface>(surfCpy, 
               "internalSurface");

            if (internalSurf != null)
            {

                //NativePointer is hidden from intellisense
                ret = internalSurf.NativePointer;
            }
        }
    }
    finally
    {
        if (surfSrc != null)
            surfSrc.Dispose();
    }

    return ret;
}

Probably, the best tutorial on using this D3DImage control is by Dr. WPF (so you know it is 1,000,000% correct), and can be found at D3DImage.aspx.

So by using the InfoStrat.VE.Map control, all that is left to do is create an instance of the control and have some buttons that can carry out various map functionality, such as:

  • Zoom in/out using the buttons provided
  • Pan using the buttons provided, or just using the mouse (the mouse is fun, and strongly satisfying; spinning the world never felt better; I know what Atlas felt like now)
  • Mode changed to: Hybrid/Roads only or lanscape only, using the buttons provided

This is all done in the code-behind. I know, I know, that is not very MVVM, now is it? Well, in this case, it just didn't seem to fit the MVVM pattern. Here is the code for all these map functions. I should point out that I had to resort to creating the actual InfoStrat.VE.Map in the code-behind as occasionally Intellisense was lost even though the app compiled and ran correctly.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using InfoStrat.VE;
using System.Linq;
using GeoPlacesModel;

namespace GeoPlaces
{
    /// <summary>
    /// Virtual Earth control and a List of Places
    /// </summary>
    public partial class VEMapControl : UserControl, IView
    {
        #region Data
        private PlacesViewModel placesViewModel = null;
        private VEMap map = null;
        private VEPushPin newPin = null;
        #endregion

        #region Ctor
        public VEMapControl()
        {
            InitializeComponent();
            SetUpMap();
            this.Loaded += new RoutedEventHandler(VEMapControl_Loaded);
            placesViewModel = new PlacesViewModel(this);
            map.Show3DCursor = true;

            //wire up PlaceClicked
            this.AddHandler(PlaceControlDetailed.PlaceClickedEvent, 
                new PlaceClickedRoutedEventHandler(PlaceClicked));
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Had to resort to setting up Map in code, as intellisense
        /// was being lost (though all was ok at runtime) if map was
        /// set up in XAML
        /// </summary>
        private void SetUpMap()
        {
            //Create the VE Map
            map = new VEMap
              {
                  Width = 590,
                  Height = 590,
                  Margin = new Thickness(5),
                  VerticalAlignment = VerticalAlignment.Top,
                  HorizontalAlignment = System.Windows.HorizontalAlignment.Center,
                  MapStyle = VEMapStyle.Hybrid,
                  LatLong = new Point(38.9444195081574, -77.0630161230201),
                  Clip = new EllipseGeometry
                     {
                         RadiusX = 230,
                         RadiusY = 230,
                         Center = new Point(295, 295)
                     }
              };

            //Ceeate a default pin location (my house)
            newPin = new VEPushPin(new VELatLong(50.826958333333337, 
                -0.16388055555555556));
                
            newPin.SetResourceReference(VEPushPin.StyleProperty, "PushPinStyle");
            newPin.Content = new Label
            {
                Content = "Waiting",
                HorizontalAlignment = HorizontalAlignment.Center,
                FontSize = 20
            };
            newPin.Click += VEPushPin_Click;
            map.Items.Add(newPin);


            //I do not like doing this with indexes that may change, but
            //I had no choice as I wanted map to be exactly 4th child, and 
            //when setting up map in XAML it would sometimes loose intellisense
            mainGrid.Children.Insert(4,map);
        }


        private void VEMapControl_Loaded(object sender, RoutedEventArgs e)
        {
            this.DataContext = placesViewModel;
        }

        private void btnZoomIn_Click(object sender, RoutedEventArgs e)
        {
            map.DoMapZoom(1000, false);
        }

        private void btnZoomOut_Click(object sender, RoutedEventArgs e)
        {
            map.DoMapZoom(-1000, false);
        }

        private void BtnRoad_Click(object sender, RoutedEventArgs e)
        {
            map.MapStyle = InfoStrat.VE.VEMapStyle.Road;
        }
        private void BtnAerial_Click(object sender, RoutedEventArgs e)
        {
            map.MapStyle = InfoStrat.VE.VEMapStyle.Aerial;
        }
        private void BtnHybrid_Click(object sender, RoutedEventArgs e)
        {
            map.MapStyle = InfoStrat.VE.VEMapStyle.Hybrid;
        }
  
        private void VEPushPin_Click(object sender, VEPushPinClickedEventArgs e)
        {
            VEPushPin pin = sender as VEPushPin;
            map.FlyTo(pin.LatLong, -90, 0, 300, null);
        }

        private void btnPanUp_Click(object sender, RoutedEventArgs e)
        {
            map.DoMapMove(0, 1000, false);
        }

        private void btnPanDown_Click(object sender, RoutedEventArgs e)
        {
            map.DoMapMove(0, -1000, false);
        }

        private void btnPanLeft_Click(object sender, RoutedEventArgs e)
        {
            map.DoMapMove(1000, 0, false);
        }

        private void btnPanRight_Click(object sender, RoutedEventArgs e)
        {
            map.DoMapMove(-1000, 0, false);
        }

        /// <summary>
        /// Remove old pin and add new pin when user selects a place to view
        /// </summary>
        private void PlaceClicked(Object sender, PlaceClickedEventArgs args)
        {
            Places selectedPlace = args.PlaceSelected;
            newPin.Latitude = selectedPlace.Latitude;
            newPin.Longitude = selectedPlace.Longitude;
            newPin.Content = new Label
            {
                Content = selectedPlace.Name,
                HorizontalAlignment = HorizontalAlignment.Center,
                FontSize = 20
            };
            map.FlyTo(new VELatLong(selectedPlace.Latitude, 
                selectedPlace.Longitude), -80, 0, 300, null);
        }
        #endregion

        #region IView Members

        public void ShowMessage(string message)
        {
            MessageBox.Show(message, "information",
                MessageBoxButton.OK, MessageBoxImage.Information);
        }

        #endregion

    }
}

The only really important part here is that there is an event handler set up to handle a previously SQL saved user's place being clicked. When a place is clicked from the list of places, the map pin is moved to the new latitude/longitude positions, and the map is flown to the pin new position. It's cool I think.

Oh, one thing worth a mention is that, by default, the InfoStrat.VE.Map control is square, but by using a Ellipse for the clip, we effectively clip the control to be an Ellipse.

Google Earth API Slight Diversion

When I first started this article, I was using Google Earth's COM API and hosting that in a WinForms control, which was then hosted via Interop in a WPF app, which I did using the following code, if any one is interested. As soon as I saw the Infostrat.VE.Map stuff, Google Earth's COM API just seemed a bit naff, and hacky. I think Google Earth's Web control is better though.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace GoogleEarthControl
{
    public class Win32
    {
        [DllImport("user32", CharSet = CharSet.Auto)]
        public extern static IntPtr GetParent(IntPtr hWnd);

        [DllImport("user32", CharSet = CharSet.Auto)]
        public extern static bool MoveWindow(IntPtr hWnd, 
            int X, int Y, int nWidth, int nHeight, bool bRepaint);

        [DllImport("user32", CharSet = CharSet.Auto)]
        public extern static IntPtr SetParent(IntPtr hWndChild, 
            IntPtr hWndNewParent);

        [DllImport("user32", CharSet = CharSet.Auto)]
        public extern static IntPtr PostMessage(int hWnd, 
            int msg, int wParam, int IParam);

        [DllImport("user32", CharSet = CharSet.Auto)]
        public extern static bool SetWindowPos(int hWnd, 
            IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

        [DllImport("coredll.dll", 
              CharSet = CharSet.Auto, SetLastError = false)]
        public static extern IntPtr SendMessage(IntPtr hWnd, 
                      uint Msg, IntPtr wParam, IntPtr lParam);


        public static readonly Int32  WM_QUIT           = 0x0012;
        public static readonly IntPtr HWND_TOP          = new IntPtr(0);
        public static readonly IntPtr HWND_BOTTOM       = new IntPtr(1);
        public static readonly UInt32 SWP_HIDEWINDOW    = 128;
        public static readonly UInt32 SWP_SHOWWINDOW    = 64;
        public static readonly uint   WM_SYSCOMMAND     = 0x0112;
        public static readonly int    SC_CLOSE          = 0xF060;

        public static IntPtr GEHrender = (IntPtr)5;
    }
}

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using EARTHLib;
using System.Runtime.InteropServices;

namespace GoogleEarthControl
{
    public partial class WinFormGEContainerControl : UserControl
    {

        private ApplicationGEClass googleEarth;
        private IntPtr mainWindowPtr = (IntPtr)(-1);

        public WinFormGEContainerControl()
        {
            InitializeComponent();
        }

        private void WinFormGEContainerControl_Load(object sender, EventArgs e)
        {
            mainWindowPtr = this.Handle;
            googleEarth = new ApplicationGEClass();
            Win32.GEHrender = (IntPtr)googleEarth.GetRenderHwnd();
            Win32.MoveWindow(Win32.GEHrender, 0, 0, (int)this.Width, 
               (int)this.Height, true);
            Win32.SetParent(Win32.GEHrender, mainWindowPtr);
            Win32.SetWindowPos(googleEarth.GetMainHwnd(), Win32.HWND_BOTTOM,
                10, 10, 10, 10, Win32.SWP_HIDEWINDOW);
        }

        public void StopGE()
        {
            try
            {
                Win32.SendMessage((IntPtr)googleEarth.GetMainHwnd(), 
                     Win32.WM_SYSCOMMAND,
                    (IntPtr)Win32.SC_CLOSE, (IntPtr)0);
            }
            catch (Exception)
            {
                //Ok P/Invoke close didn't work, so have no choice but to kill process
                Process[] p = Process.GetProcessesByName("googleearth");
                if (p.Length > 0)
                {
                    try
                    {
                        p[0].Kill();
                    }
                    catch (Exception)
                    {
                        Console.WriteLine(
                          "There was a problem shutting down googleearth");
                    }
                }
            }
            finally
            {
                try
                {
                    if (googleEarth != null)
                            Marshal.ReleaseComObject(googleEarth);
                }
                catch (ArgumentException argEx)
                {
                    Console.WriteLine("There was a problem shutting down googleearth");
                }
            }
        }
    }
}

3D

As I stated earlier, the WPF client makes use of a 3D flipping control that allows the user to have two interactive controls hosted on the front and back of a 3D mesh, and allows the user to flip this in 3D space. I actually used this technique a while back in my MyFriends app; it was cool, but a little messy, and it required the user to know a bit about 3D, in general.

Now, luckily, one of my WPF heroes/mates and general WPF aficionado Mr. Josh Smith, did a great job of abstracting this into a single, easy to use 3D WPF control. Josh is calling his 3D library Thriple, and you can read all about it over at the Thriple site.

Styles/Templates

Of course, the WPF client makes heavy usage of Styles/Templates, but that is all bulk standard WPF stuff; if you want to know about that, just jump into the code.

There is a kinda nice pulsing Ring control, cunningly named "PulsingRingControl".

That's it Folks

I would just like to mention that this article has taken me about a month to write in my spare time, so any votes/comments would be gratefully received. Anyway, I hope you got something out of the article, and that it taught you a little something.

Like I say, stay tuned for more articles on WPF Onyx; it is a very promising framework, well done Bill!

Useful Links

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
QuestionAutomagic updating Pin
stixoffire27-May-15 4:40
stixoffire27-May-15 4:40 
GeneralThanks Pin
zhujinlong1984091327-Aug-09 1:50
zhujinlong1984091327-Aug-09 1:50 
GeneralXamlParseException Pin
Clingfree9-Jul-09 9:10
Clingfree9-Jul-09 9:10 
GeneralRe: XamlParseException Pin
Sacha Barber9-Jul-09 21:43
Sacha Barber9-Jul-09 21:43 
GeneralRe: XamlParseException Pin
Clingfree21-Sep-09 9:27
Clingfree21-Sep-09 9:27 
QuestionAmazing article! Question - How do you expose your RESTful service to the internet if you are self-hosting? Pin
User 467791630-Jun-09 11:59
User 467791630-Jun-09 11:59 
AnswerRe: Amazing article! Question - How do you expose your RESTful service to the internet if you are self-hosting? Pin
Sacha Barber30-Jun-09 21:36
Sacha Barber30-Jun-09 21:36 
Yeah you would have to host in IIS

Sacha Barber
  • Microsoft Visual C# MVP 2008/2009
  • Codeproject MVP 2008/2009
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralAmazing article! Pin
breezback26-Jun-09 22:02
breezback26-Jun-09 22:02 
GeneralRe: Amazing article! Pin
Sacha Barber28-Jun-09 21:41
Sacha Barber28-Jun-09 21:41 
GeneralDr. Sacha Pin
User 27100919-Jun-09 2:54
User 27100919-Jun-09 2:54 
GeneralRe: Dr. Sacha Pin
Sacha Barber19-Jun-09 2:58
Sacha Barber19-Jun-09 2:58 
Generalgood Pin
nørdic8-Jun-09 10:59
nørdic8-Jun-09 10:59 
GeneralRe: good Pin
Sacha Barber8-Jun-09 21:28
Sacha Barber8-Jun-09 21:28 
GeneralITS ALL FIXED NOW Pin
Sacha Barber8-Jun-09 9:39
Sacha Barber8-Jun-09 9:39 
GeneralGreat work Pin
inTagger8-Jun-09 2:10
inTagger8-Jun-09 2:10 
QuestionBing Maps 3D Pin
inTagger6-Jun-09 0:13
inTagger6-Jun-09 0:13 
AnswerRe: Bing Maps 3D Pin
mtonsager7-Jun-09 8:39
mtonsager7-Jun-09 8:39 
GeneralRe: Bing Maps 3D Pin
Sacha Barber7-Jun-09 21:31
Sacha Barber7-Jun-09 21:31 
GeneralRe: Bing Maps 3D Pin
inTagger8-Jun-09 1:58
inTagger8-Jun-09 1:58 
GeneralRe: Bing Maps 3D Pin
Sacha Barber8-Jun-09 2:46
Sacha Barber8-Jun-09 2:46 
GeneralRe: Bing Maps 3D Pin
mtonsager8-Jun-09 3:23
mtonsager8-Jun-09 3:23 
GeneralRe: Bing Maps 3D Pin
Sacha Barber8-Jun-09 3:40
Sacha Barber8-Jun-09 3:40 
QuestionSilent crash. What am I missing? Pin
rghubert28-May-09 22:30
professionalrghubert28-May-09 22:30 
AnswerRe: Silent crash. What am I missing? Pin
Sacha Barber29-May-09 0:11
Sacha Barber29-May-09 0:11 
GeneralCool Artical! Pin
Mubi | www.mrmubi.com15-May-09 3:07
professionalMubi | www.mrmubi.com15-May-09 3:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.