Feed Icon  

Contact

  • Bryant Likes
  • Send mail to the author(s) E-mail
  • twitter
  • View Bryant Likes's profile on LinkedIn
  • del.icio.us
Get Microsoft Silverlight
by clicking "Install Microsoft Silverlight" you accept the
Silverlight license agreement

Hosting By

Hot Topics

Tags

Open Source Projects

Archives

Ads

Enabling WPF Magic Using WCF - Part 2

Posted in WCF | WPF at Wednesday, 20 September 2006 11:13 Pacific Daylight Time

In part 1 we created a very simple application that actually didn't even use WCF. The next step in the process is to migrate our ContactProvider from an in-proc class to be a WCF service. We will then use WCF callbacks to notify the application that the contact list has changed. In order to learn WCF and specifically how to use call backs I used two resources: this MSDN Magazine Article and this Rough Cuts Book, both by Juval Löwy.

The first thing to do is create a new console application and add references to System.ServiceModel and System.Runtime.Serialization. Next we will recreate both our Contact class and the ContactProvider class and interface. First, here is our new Contact class as defined in our ContactService console project:

namespace ContactService
{
  [DataContract(Namespace="ContactService")]
  public class Contact

    [DataMember]
    public string FirstName;

    [DataMember]
    public string LastName;

    [DataMember]
    public string Phone;
  }
}

The first thing you will probably notice is the DataContract/DataMember attributes. If you don't know what these are then I would suggest reading a WCF primer like the book mentioned above. The other thing is that I've changed the properties to fields and no longer implement INotifyPropertyChanged. I've switched to fields because really this is just a structure from the services point of view. The INotifyPropertyChanged will be taken care of later on. Next we create our interface as shown below:

namespace ContactService
{
  [ServiceContract]
  public interface IContactProvider
  {
    [OperationContract]
    List<Contact> GetContacts();
  }
}

This defines the WCF contract that we will be implementing to provide a list of contacts to our application. Next we need to create the ContactProvider implementation:

namespace ContactService
{
  public class ContactProvider : IContactProvider
  {
    private static readonly string Filepath = 
      ConfigurationManager.AppSettings["contacts"];

    public ContactProvider()
    {}

    public List<Contact> GetContacts() 
    { 
      using (FileStream fs = new FileStream(Filepath, FileMode.Open)) 
      { 
        DataContractSerializer dcs = 
          new DataContractSerializer(typeof(List<Contact>)); 
        return (List<Contact>)dcs.ReadObject(fs); 
      } 
    } 
  } 
}

This class looks very similar to our previous ContactProvider class. However, since this is running on the server side we don't allow the client to pass in the path to the file, we just use a value from the config file. We are also using the DataContractSerializer instead of the XmlSerializer class. This is a new serializer that is part of WCF and is more appropriate in this case because we are deserializing an object that is a DataContract. However, because we are using this class we need to add a default namespace to our XML as shown below:

<ArrayOfContact xmlns="ContactService">...</ArrayOfContact>

Now we are ready to create our host. Our host is very simple since we are using a config file to manage all the WCF settings:

namespace ContactService
{
  class Program
  {
    static void Main(string[] args)
    {
      ServiceHost host = new ServiceHost(typeof(ContactProvider));
      
      host.Open();
      Console.WriteLine("Ready to accept connections...");
      Console.ReadLine();
      host.Close();

    }
  }
}

If you run the console application you should get an error since we have not configured anything at this point. We will add an App.config file to the project and fill it up with all the settings shown below:

<?xml version="1.0" encoding="utf-8" ?>
  <configuration>
      <appSettings>
          <add key="contacts" value="c:\dev\ContactSample\contacts.xml"/>
      </appSettings>
      <system.serviceModel>
          <services>
              <service name="ContactService.ContactProvider" 
                       behaviorConfiguration="MEXGET">
                  <host>
                      <baseAddresses>
                          <add baseAddress="net.tcp://localhost:8001/"/>
                      </baseAddresses>
                  </host>
                  <endpoint
                      address="contacts"
                      binding="netTcpBinding"
                      contract="ContactService.IContactProvider" />
                  <endpoint
                      address="MEX"
                      binding="mexTcpBinding"
                      contract="IMetadataExchange" />
              </service>
          </services>
          <behaviors>
              <serviceBehaviors>
                  <behavior name="MEXGET">
                      <serviceMetadata />
                  </behavior>
              </serviceBehaviors>
          </behaviors>
      </system.serviceModel>
  </configuration>

In this file we have defined our service along with two endpoints and a behavior. The behavior enabled the metadata exchange to occur which we will use to build our client proxy. There is one endpoint for the metadata exchange interface and another for our client to connect to via TCP. At this point we should be able to start our console application and it should run. With the service running, open a command prompt in the ContactApp folder and run the following command (note: you will need to have (1) installed the Windows SDK for RC1 and (2) added the SDK bin folder to your path for this to work):

svcutil net.tcp://localhost:8001/MEX /edb /config:app.config /async /out:Contact.g.cs

This will generate two files which we will need to add to our WPF project: app.config and Contact.g.cs. Note that Contact.g.cs contains the new contact definition (I use Contact.g.cs to so that it is clear the file is generated). If you look at the definition you will notice that the generated class implements INotifyPropertyChanged. The file also contains the service contract interface and proxy class. Our next step will be to plug in these new classes to our application. First remove the Contact.cs, IContactProvider.cs, and ContactProvider.cs files from the project. Next open the ContactDataModel and add the following constructor:

public class ContactDataModel : ObservableCollection<Contact>
{
  public ContactDataModel(Contact[] contacts) : base(new List<Contact>(contacts))
  { } 

  ... 
}

We will need this constructor since our new proxy class doesn't return a generic list of contacts but an array of contacts. We could edit the generated file to change this and it would work, but I prefer to not edit generated files. Next we need to update our ContactViewModel class to use the new proxy class. In this case we will also add the IDisposable interface to our class so that we can call the Close method on the proxy. Below is the updated code for our class:

namespace ContactApp
{

    public class ContactViewModel : IDisposable
    {
        private ContactProviderClient _proxy;
        private ContactDataModel _contacts;
        private Dispatcher _dispatcher;

        public ContactViewModel()
        {

            _proxy = new ContactProviderClient();
            _dispatcher = Dispatcher.CurrentDispatcher;
            _contacts = new ContactDataModel(_proxy.GetContacts());
        }

        public ContactDataModel Contacts
        {
            get { return _contacts; }
        }

        public void Dispose()
        {
            _proxy.Close();

        }
    }
}

This is very similar to our previous ContactViewModel, but this time we are getting our list of contacts via WCF instead of loading them in-proc. You can ignore the Dispatcher for now, but we will be using it shortly. The last change we need to make before our application will work is to modify our Window1.xaml.cs file as follows:

public Window1()
{
  InitializeComponent();

  _content.Content = new ContactViewModel();

}

All we did was to remove the ContactProvider object in the constructor since the new constructor is empty. At this point you should be able to fire up the console app and then once you get the "ready" message on the console you can fire up the WPF application. You should get a list of contacts as before, but again we've removed the updating feature. So if you edit the contacts.xml file the change will not be reflected in the WPF application unless you reload it.

The next step will be to build in the notification of a change to the file into the service. When this was hosted in-proc we did this using an event. However, we are no longer in-proc so we must do things the WCF way which is a client callback. I recommend you read the MSDN article referenced above prior to continuing since this might not make sense otherwise.

The first step is to create our callback contract interface. It is important to note that this contract will be implemented by the client, not the service. Since we are reporting a change to the list of changes, our interface will pass the updated list to the client. Below is the interface:

namespace ContactService
{ 
  interface IListChangedCallback 
  { 
    [OperationContract(IsOneWay = true)] 
    void OnCallback(Contact[] contacts); 
  } 
}

Next we need to reference this contract in our original contract as a callback:

[ServiceContract(CallbackContract = typeof(IListChangedCallback))]
public interface IContactProvider
{...}

Now that we have all the proper contracts defined we can implement them in our service. Since we're still using a simple file we can just reuse our code from our previous ContactProvider (with a few changes). The first thing we need is to define the file system watcher variable and initialize it when we create our ContactProvider:

  private FileSystemWatcher _fileWatch;


  public ContactProvider()
  {
    _fileWatch = new FileSystemWatcher(
      Path.GetDirectoryName(Filepath),
      Path.GetFileName(Filepath)
      );

    _fileWatch.Changed += new FileSystemEventHandler(OnListChanged);

    _fileWatch.EnableRaisingEvents = true;
  }

When the file changes our (currently undefined) OnListChanged method will be called. The trick is that when this method is called we will need to call back to the client. Before we can call back to the client we need some kind of reference to the client's callback. In order to do this we need to grab that callback from the first call to our service (GetContacts). In order to do this we create a list to store the callbacks and we grab them during the first call using the OperationContext. I've also refactored the actual call to get the contacts out of this method for reasons that will be apparent soon. Below is our new GetContacts method and GetContactsFromFile method:

  private static List<IListChangedCallback> _callbacks = 
    new List<IListChangedCallback>();

  public List<Contact> GetContacts()
  {
     IListChangedCallback callback = 
         OperationContext.Current.GetCallbackChannel<IListChangedCallback>();

     if (_callbacks.Contains(callback) == false)
     {
         _callbacks.Add(callback);
     }

     return GetContactsFromFile();
  }  

  private List<Contact> GetContactsFromFile()
  {
     using (FileStream fs = new FileStream(Filepath, FileMode.Open))
     {
        DataContractSerializer dcs = 
            new DataContractSerializer(typeof(List<Contact>));

        return (List<Contact>)dcs.ReadObject(fs);
     }
  }

Now when the first call to GetContacts comes in we will grab the client's callback from the OperationContext which we will make use of when the file changed event is fired. Below is our OnListChanged method:

private void OnListChanged(object sender, FileSystemEventArgs e)
  {
    if (_callbacks.Count > 0)
    {
        List<Contact> contacts = GetContactsFromFile();
        Action<IListChangedCallback> invoke =
          delegate(IListChangedCallback callback)
          {
             callback.OnCallback(contacts.ToArray());
          };
        _callbacks.ForEach(invoke);
    }
  }

Of course, passing this entire list back anytime anything changes might not be the smartest move, but for this example it works. A real implementation would probably spend some time figuring out what changed and only send back the changes. Now that we've implemented everything on the server side the next step is to update the client. However, before we can do that we need to regenerate our proxy files. Start up the console application and the run the svcutil command shown above again.

The updated generated file will contain the new callback interface that we defined. The next step is to implement this interface in our ContactViewModel so that the service can call back to us. Below is the updated code with changes in bold:

namespace ContactApp
  {
      public class ContactViewModel : IContactProviderCallback, IDisposable
      {
          private ContactProviderClient _proxy;
          private ContactDataModel _contacts;
          private Dispatcher _dispatcher;
   
          public ContactViewModel()
          {
              _proxy = new ContactProviderClient(new InstanceContext(this));
              _dispatcher = Dispatcher.CurrentDispatcher;
              _contacts = new ContactDataModel(_proxy.GetContacts());
          }
   

          public ContactDataModel Contacts
          {
              get { return _contacts; }
          }
   

          public void OnCallback(Contact[] contacts)
          {
              _dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle,
                      new ThreadStart(delegate
                          {
                              _contacts.Clear();
                              foreach (Contact contact in contacts)
                                  _contacts.Add(contact);
                          }));
          }
   
          public IAsyncResult BeginOnCallback(Contact[] contacts, AsyncCallback callback, object asyncState)
          {
              throw new Exception("The method or operation is not implemented.");
          }
   
          public void EndOnCallback(IAsyncResult result)
          {
              throw new Exception("The method or operation is not implemented.");
          }
   
          public void Dispose()
          {
              _proxy.Close();
          }
      }
  }

When the contact list is updated the service will call the OnCallback method and pass us the new list of contacts. That is all there is to it. Now you get to see the magic. :)

Fire up the console app, wait for the "Ready" message, and then fire up the WPF application. Now make a change to the contacts.xml file and save your changes. Magic! The changes are reflected almost instantly.

So what's next? While storing contacts in a file might work in some situations, you're probably going to use a database in the real world. In part 3 I will demonstrate how to apply the same magic using SQL Server 2005 and query notifications.

Technorati Tags: WPF WCF

Saturday, 23 September 2006 11:15:48 (Pacific Daylight Time, UTC-07:00)
Hi Bryant
A very interestting artcile.
I am new to WCF/WPF and this article did help me a lot.
I did manage to compile and test the code in the part 1 of the article.
But as part of the part 2, I am not able to get the IContactProviderCallback interface.
Is there anything specific that I have to do to get the IContactProviderCallback interface.

I am hosting the WCF on IIS and without the call back it is working just fine. I actually wanted to test the auto updation of UI on file change, but not able to do so as I am not getting the IContactProviderCallback in my proxy.

If you can give me your mail ID I can mail you my source code.

Pls do let me know
Wednesday, 28 February 2007 15:51:38 (Pacific Standard Time, UTC-08:00)
Your source is cut due to ur page margin can u fix that
Tariq Sheikh
Thursday, 17 May 2007 07:22:51 (Pacific Daylight Time, UTC-07:00)
How to convert a generic List to ObservableCollection?
Seeker
Friday, 13 July 2007 00:58:05 (Pacific Daylight Time, UTC-07:00)
Where do I have to add the default namespace so that the ContactDataModel.cs have access to the Contact class defined in Contact.g.cs?
<ArrayOfContact xmlns="ContactService">...</ArrayOfContact>
ChrisF
Wednesday, 19 August 2009 01:27:04 (Pacific Daylight Time, UTC-07:00)
This tutorial serie is great and the only one I've found on the subject.

However, it would be great to see an updated version where you use the WCF service in VisualStudio. It's hard to map your code back to a VS08 silverlight web project with an added WCF service.

cheers manne
manne
Comments are closed.