Www.dotnetspider.com



Example code: Chat application:

Before I start bombarding people with the inner mechanisms of the attached WPF/WCF application, shall we have a quick look at the finished product? There are 3 main areas within the attached demo application:

A login screen

[pic]

A main window, from which the user can choose who to chat with:

[pic]

And a window where chatters may openly chat:

[pic]

The application is based on using Visual Studio 2005 with The Visual Studio Designer for WPF installed, or using Expression BLEND and Visual Studio 2005 combination, or Wordpad if you prefer to write stuff in that. Obviously, as it's a WPF/WCF application, you will also need the .NET 3.0 Framework. This application will cover the following concepts:

• WCF

o The new service orientated attributes

o The use of interfaces

o The use of callbacks

o Asynchronous delegates

o Creating the proxy

• WPF

o Styles

o Templates

o Animations

o Databinding

o Multithreading a WPF application

However, this application is not really that orientated to WPF, as that is covered in numerous other WPF articles at The Code Project. The WPF stuff is really just a wrapper around the WCF article, which is the real guts of this article. Although there is some nice WPF stuff going on, just to make the chat application look nicer than an ordinary console application. I will, however, discuss interesting bits of the WPF implementation.

Prerequisites

1. To run the code supplied with this article, you will need to install the May 2006 LINQ CTP, which is available here. There is a new March 2007 CTP available, but it's about 4GB for the full install (as its not just LINQ but the entire next generation of Visual Studio codenamed "Orcas") and quite fiddly, and probably going to change anyway. So, the May 2006 LINQ CTP will be OK for the purpose of what this article is trying to demonstrate.

2. The .NET 3.0 Framework, which is available for download here.

A Brief Overview of the Demo App and What We are Trying to Achieve

In the attached demo application, we are trying to carry our the following functionality:

1. Allow chatters to pick a name for themselves and pick an image (Avatar) that they would like to be presented by

2. Allow the chatters to join a peer-to-peer chat

3. Allow chatters to see who else is available to chat to

4. Allow chatters to send private message

5. Allow chatters to send global messages

6. Allow chatters to leave the chat environment

7. Make it all look pretty using WPF (not strictly a requirement for a chat app, but I like WPF, so humour me)

In order to achieve all of this I have developed 3 separate assemblies, which by the end I hope you will understand.

• ChatService.Dll: the WCF chat server, that allows chat clients to connect and is the main message router

• Common.Dll: is a simple serializable class which is used by both the ChatService.Dll and the WPFChatter.Dll files

• WPFChatter.Dll: the pretty WPF wrapper around the client WCF functions

Some Key Concepts Explained

There are a number of key concepts that were mentioned earlier that need to be explained in order for the full application (which covers a lot of ground) to be understood. So I'll just explain each of these a little bit at a time, so the the final application should be a little easier to explain (well that's the idea anyway).

WCF: the New Service Orientated Attributes

There are a number of new attributes that may be used with WCF to adorn our NET classes/interfaces, shown below are the ones that are used as part of the attached demo application.

ServiceContractAttribute

Indicates that an interface or a class defines a service contract in a Windows Communication Foundation (WCF) application. It has the following members:

|Name |Description |

|CallbackContract |Gets or sets the type of callback contract when the contract is a duplex |

| |contract. |

|ConfigurationName |Gets or sets the name used to locate the service in an application |

| |configuration file. |

|HasProtectionLevel |Gets a value that indicates whether the member has a protection level |

| |assigned. |

|Name |Gets or sets the name for the element in Web Services Description |

| |Language (WSDL). |

|Namespace |Gets or sets the namespace of the element in Web Services |

| |Description Language (WSDL). |

|ProtectionLevel |Specifies whether the binding for the contract must support the value of the |

| |ProtectionLevel property. |

|SessionMode |Gets or sets whether sessions are allowed, not allowed or required. |

|TypeId |(Inherited from Attribute) |

See the MSDN article for more details.

OperationContractAttribute

Indicates that a method defines an operation that is part of a service contract in a Windows Communication Foundation (WCF) application. It has the following members:

|Name |Description |

|Action |Gets or sets the WS-Addressing action of the request message. |

|AsyncPattern |Indicates that an operation is implemented asynchronously using a |

| |Begin and End method pair in a service contract. |

|HasProtectionLevel |Gets a value that indicates whether the messages for this operation must be |

| |encrypted, signed, or both. |

|IsInitiating |Gets or sets a value that indicates whether the method implements an operation|

| |that can initiate a session on the server (if such a session exists). |

|IsOneWay |Gets or sets a value that indicates whether an operation returns a reply |

| |message. |

|IsTerminating |Gets or sets a value that indicates whether the service operation causes the |

| |server to close the session after the reply message, if any, is sent. |

|Name |Gets or sets the name of the operation. |

|ProtectionLevel |Gets or sets a value that specifies whether the messages of an operation must |

| |be encrypted, signed, or both. |

|ReplyAction |Gets or sets the value of the SOAP action for the reply message of the |

| |operation. |

|TypeId |(Inherited from Attribute) |

See the MSDN article for more details

ServiceBehaviorAttribute

Specifies the internal execution behavior of a service contract implementation. It has the following members:

|Name |Description |

|AddressFilterMode |Gets or sets the AddressFilterMode that is used by the |

| |dispatcher to route incoming messages to the correct endpoint. |

|AutomaticSessionShutdown |Specifies whether to automatically close a session when a |

| |client closes an output session. |

|ConcurrencyMode |Gets or sets whether a service supports one thread, multiple |

| |threads, or reentrant calls. |

|ConfigurationName |Gets or sets the value used to locate the service element in an|

| |application configuration file. |

|IgnoreExtensionDataObject |Gets or sets a value that specifies whether to send unknown |

| |serialization data onto the wire. |

|IncludeExceptionDetailInFaults |Gets or sets a value that specifies that general unhandled |

| |execution exceptions are to be converted into a |

| |System.ServiceModel.FaultException of type |

| |System.ServiceModel.ExceptionDetail and sent as a fault |

| |message. Set this to true only during development to |

| |troubleshoot a service. |

|InstanceContextMode |Gets or sets the value that indicates when new service objects |

| |are created. |

|MaxItemsInObjectGraph |Gets or sets the maximum number of items allowed in a |

| |serialized object. |

|Name |Gets or sets the value of the name attribute in the service |

| |element in Web Services Description Language (WSDL). |

|Namespace |Gets or sets the value of the target namespace for the service |

| |in Web Services Description Language (WSDL). |

|ReleaseServiceInstanceOnTransactionComplete |Gets or sets a value that specifies whether the service object |

| |is released when the current transaction completes. |

|TransactionAutoCompleteOnSessionClose |Gets or sets a value that specifies whether pending |

| |transactions are completed when the current session closes |

| |without error. |

|TransactionIsolationLevel |Specifies the transaction isolation level for new transactions |

| |created inside the service, and incoming transactions flowed |

| |from a client. |

|TransactionTimeout |Gets or sets the period within which a transaction must |

| |complete. |

|TypeId |(Inherited from Attribute) |

|UseSynchronizationContext |Gets or sets a value that specifies whether to use the current |

| |synchronization context to choose the thread of execution. |

|ValidateMustUnderstand |Gets or sets a value that specifies whether the system or the |

| |application enforces SOAP MustUnderstand header processing. |

See the MSDN article or more details. Here is an example of how these new WCF attributes are used within the demo application, Service project -> ChatService.cs.

[pic]Collapse

[ServiceContract(SessionMode = SessionMode.Required,

CallbackContract = typeof(IChatCallback))]

interface IChat

{

[OperationContract(IsOneWay = true, IsInitiating = false,

IsTerminating = false)]

void Say(string msg);

[OperationContract(IsOneWay = true, IsInitiating = false,

IsTerminating = false)]

void Whisper(string to, string msg);

[OperationContract(IsOneWay = false, IsInitiating = true,

IsTerminating = false)]

Person[] Join(Person name);

[OperationContract(IsOneWay = true, IsInitiating = false,

IsTerminating = true)]

void Leave();

}

WCF: the Use of Interfaces

"The notion of a contract is the key to building a WCF service. Those of you that have a background in classic DCOM or COM technologies might be surprised to know that WCF contracts are expressed using interface-based programming techniques (everything old is new again!). While not mandatory, the vast amount your WCF applications will begin by defining a set of .NET interface types that are used to represent the set of members a given WCF type will support. Specifically speaking, interfaces that represent a WCF contract are termed service contracts. The classes (or structures) that implement them are termed service types."

Pro C# with .NET3.0, Apress. Andrew Troelsen

So there you are -- that's what a nice new book says -- but what does this look like to us in code? Well the actual ChatService.cs class implements the IChat interface just mentioned, and so looks like this:

[pic]Collapse

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession,

ConcurrencyMode = ConcurrencyMode.Multiple)]

public class ChatService : IChat

{

}

WCF: the Use of Callbacks

Recall earlier, when I mentioned the ServiceContractAttribute I also mentioned that one of its properties was a CallBackContract that was defined as, which allows the WCF service to call the client back. This new WCF method is very nice. I remember trying to do something like this with remoting and events back to the client, and it was not fun at all. I much prefer this method. Let's have a look. Recall that the original service contract interface was declared like this:

[pic]Collapse

[ServiceContract(SessionMode = SessionMode.Required,

CallbackContract = typeof(IChatCallback))]

interface IChat

{

....

}

Well we still need to define an interface to allow the callback to work, so an example of this may be (as done in the demo app):

[pic]Collapse

interface IChatCallback

{

[OperationContract(IsOneWay = true)]

void Receive(Person sender, string message);

[OperationContract(IsOneWay = true)]

void ReceiveWhisper(Person sender, string message);

[OperationContract(IsOneWay = true)]

void UserEnter(Person person);

[OperationContract(IsOneWay = true)]

void UserLeave(Person person);

}

WCF: Asynchronous Delegates

"The .NET Framework allows you to call any method asynchronously. To do this you define a delegate with the same signature as the method you want to call; the common language runtime automatically defines BeginInvoke and EndInvoke methods for this delegate, with the appropriate signatures.

The BeginInvoke method initiates the asynchronous call. It has the same parameters as the method you want to execute asynchronously, plus two additional optional parameters. The first parameter is an AsyncCallback delegate that references a method to be called when the asynchronous call completes. The second parameter is a user-defined object that passes information into the callback method. BeginInvoke returns immediately and does not wait for the asynchronous call to complete. BeginInvoke returns an IAsyncResult, which can be used to monitor the progress of the asynchronous call.

The EndInvoke method retrieves the results of the asynchronous call. It can be called any time after BeginInvoke; if the asynchronous call has not completed, EndInvoke blocks the calling thread until it completes. "

Calling Synchronous Methods Asynchronously

WCF: Creating the Proxy

In order to for the client to communicate with a WCF service, we need a proxy object. This can be quite a daunting task (and a little complicated to be honest). Luckily like a lot of things in .NET 3.0/3.5 there are tools provided to make our lives easier (you still have to know about them though), and WCF is no different. It has a little tool called "svcutil" which comes to the rescue.

So how do we create a proxy for our little WCF service (ChatService.exe for the demo app) using svcutil. Well I have read one thing that said you should be able to just start the WCF service, point svcutil at the running WCF service, and be able to create the client proxy that way. But I have to say, I could NOT get that to work at all. It seems to a common gripe, if you search the internet. So the way I got it to work was as follows:

1. Open a visual studio command prompt, and change to the directory that contains the WCF service

2. Run the following command line: svcutil

3. This will list a few files, namely *.wsdl, and *.xsd, and a schemas..2003.10.Serialization.xsd file

4. Next you need to run the following command line: svcutil *.wsdl *.xsd /language:C# /out:MyProxy.cs /config:app.config

5. You now have 2 new client files, MyProxy.cs and app.config, so you can copy these to your client application

To give you an idea of what svcutil.exe produces in terms of client files, let's have a look. Here is the MyProxy.cs C# file that was auto-generated by svcutil.exe.

[pic]Collapse

//---------------------------------------------------------------------------

//

// This code was generated by a tool.

// Runtime Version:2.0.50727.312

//

// Changes to this file may cause incorrect behavior and will be lost if

// the code is regenerated.

//

//---------------------------------------------------------------------------

namespace Common

{

using System.Runtime.Serialization;

[System.piler.GeneratedCodeAttribute(

"System.Runtime.Serialization", "3.0.0.0")]

[System.Runtime.Serialization.DataContractAttribute()]

public partial class Person : object,

System.Runtime.Serialization.IExtensibleDataObject

{

private System.Runtime.Serialization.ExtensionDataObject

extensionDataField;

private string ImageURLField;

private string NameField;

public System.Runtime.Serialization.ExtensionDataObject ExtensionData

{

get

{

return this.extensionDataField;

}

set

{

this.extensionDataField = value;

}

}

[System.Runtime.Serialization.DataMemberAttribute()]

public string ImageURL

{

get

{

return this.ImageURLField;

}

set

{

this.ImageURLField = value;

}

}

[System.Runtime.Serialization.DataMemberAttribute()]

public string Name

{

get

{

return this.NameField;

}

set

{

this.NameField = value;

}

}

}

}

[System.piler.GeneratedCodeAttribute("System.ServiceModel",

"3.0.0.0")]

[System.ServiceModel.ServiceContractAttribute(ConfigurationName="IChat",

CallbackContract=typeof(IChatCallback),

SessionMode=System.ServiceModel.SessionMode.Required)]

public interface IChat

{

[System.ServiceModel.OperationContractAttribute(IsOneWay=true,

IsInitiating=false, Action="")]

void Say(string msg);

[System.ServiceModel.OperationContractAttribute(IsOneWay=true,

IsInitiating=false, Action="")]

void Whisper(string to, string msg);

[System.ServiceModel.OperationContractAttribute(

Action=,

ReplyAction="")]

Common.Person[] Join(Common.Person name);

[System.ServiceModel.OperationContractAttribute(IsOneWay=true,

IsTerminating=true, IsInitiating=false,

Action="")]

void Leave();

}

[System.piler.GeneratedCodeAttribute("System.ServiceModel",

"3.0.0.0")]

public interface IChatCallback

{

[System.ServiceModel.OperationContractAttribute(IsOneWay=true,

Action="")]

void Receive(Common.Person sender, string message);

[System.ServiceModel.OperationContractAttribute(IsOneWay=true,

Action="")]

void ReceiveWhisper(Common.Person sender, string message);

[System.ServiceModel.OperationContractAttribute(IsOneWay=true,

Action="")]

void UserEnter(Common.Person person);

[System.ServiceModel.OperationContractAttribute(IsOneWay=true,

Action="")]

void UserLeave(Common.Person person);

}

[System.piler.GeneratedCodeAttribute("System.ServiceModel",

"3.0.0.0")]

public interface IChatChannel : IChat, System.ServiceModel.IClientChannel

{

}

[System.Diagnostics.DebuggerStepThroughAttribute()]

[System.piler.GeneratedCodeAttribute("System.ServiceModel",

"3.0.0.0")]

public partial class ChatClient :

System.ServiceModel.DuplexClientBase,

IChat

{

public ChatClient(System.ServiceModel.InstanceContext callbackInstance) :

base(callbackInstance)

{

}

public ChatClient(System.ServiceModel.InstanceContext callbackInstance,

string endpointConfigurationName) :

base(callbackInstance, endpointConfigurationName)

{

}

public ChatClient(System.ServiceModel.InstanceContext callbackInstance,

string endpointConfigurationName, string remoteAddress) :

base(callbackInstance, endpointConfigurationName, remoteAddress)

{

}

public ChatClient(System.ServiceModel.InstanceContext callbackInstance,

string endpointConfigurationName,

System.ServiceModel.EndpointAddress remoteAddress) :

base(callbackInstance, endpointConfigurationName, remoteAddress)

{

}

public ChatClient(System.ServiceModel.InstanceContext callbackInstance,

System.ServiceModel.Channels.Binding binding,

System.ServiceModel.EndpointAddress remoteAddress) :

base(callbackInstance, binding, remoteAddress)

{

}

public void Say(string msg)

{

base.Channel.Say(msg);

}

public void Whisper(string to, string msg)

{

base.Channel.Whisper(to, msg);

}

public Common.Person[] Join(Common.Person name)

{

return base.Channel.Join(name);

}

public void Leave()

{

base.Channel.Leave();

}

}

And here is the client App.Config that was auto-generated by svcutil.exe.

[pic]Collapse

So as you can see, these files can simply be used straightaway within your own client application to communicate with the WCF service. But wait, we are still not finished with svcutil.exe. Recall that I mentioned asynchronous delegates -- so why did I do that? Well the svcutil.exe also allows us to create asynchronous proxy code, using one of the command line switches. To do this, we use the following command line (notice the /a option):

svcutil *.wsdl *.xsd /a /language:C# /out:MyProxy.cs /config:app.config

...instead of:

svcutil *.wsdl *.xsd /language:C# /out:MyProxy.cs /config:app.config

...which we used previously. This will then change the format of the C# (or VB .NET) file we get out. What we get now for each WCF service method is an asynchronous one. So, we would get the following:

[pic]Collapse

[System.ServiceModel.OperationContractAttribute(IsOneWay=true,

IsInitiating=false, Action="")]

void Say(string msg);

[System.ServiceModel.OperationContractAttribute(IsOneWay=true,

IsInitiating=false, AsyncPattern=true,

Action="")]

System.IAsyncResult BeginSay(string msg, System.AsyncCallback callback,

object asyncState);

void EndSay(System.IAsyncResult result);

...instead of:

[pic]Collapse

[System.ServiceModel.OperationContractAttribute(IsOneWay=true,

IsInitiating=false, Action="")]

void Say(string msg);

Hopefully you can see where this ties in with the WCF: Asynchronous Delegates section mentioned earlier. But just to be sure, here's a more detailed description of what's going on. Using the /a switch of svcutil.exe, you can generate a proxy that contains asynchronous methods in addition to the synchronous ones. For each operation in the original contract, the asynchronous proxy and contract will contain two additional methods of this form:

The OperationContractAttribute offers the AsyncPattern Boolean property. AsyncPattern only has meaning on the client-side copy of the contract. You can only set AsyncPattern to true on a method with a Begin( )-compatible signature. The defining contract must also have a matching method with an End( )-compatible signature. These requirements are verified at proxy load time. What AsyncPattern does is bind the underlying synchronous method with the Begin/End pair, and correlates the synchronous execution with the asynchronous one.

When the client invokes a method of the form Begin( ) with AsyncPattern set to true, it tells WCF not to try to directly invoke a method by that name on the service. Instead, it will use a thread from the thread pool to synchronously call the underlying method (identified by the Action name). The synchronous call will block the thread from the thread pool, not the calling client. The client will only be blocked for the slightest moment it takes to dispatch the call request to the thread pool. The reply method of the synchronous invocation is correlated with the End( ) method.

As the attached demo application makes use of asynchronous methods for the Join operation, asynchronous delegates are using within the code to achieve this.

WCF: Configuration

As with all .NET applications, WCF applications allow the application to be configured via a configuration file. This will be discussed later on, for now you just need to know that the following items may be configured in an App.Config file for a WCF application:

• Service addresses

• Service type

• Behavior configuration

• Endpoints

• Binding types

• Service security

WPF: Styles / Templates

WPF styles and templates allow us to change how standard components look. This is quite a well documented feature, but what I will say is that by using a little bit of styling one is able to convert a rather plain ListView into a ListView that looks like the figure shown below. Quite nice, I think. So how is this done? Well, it's all down to styling. The figure below shows a standard ListView item which is styled and has some custom data templates assigned. Each item in the ListView is actually a Common.Person object, which will be discussed later.

[pic]

All that has been done is that I have applied a style to a standard .NET ListView control. Here is the XAML that does this. However, I say I am not going to dwell on these WPF features, as they are well documented and not really part of the main thrust of this article. I just wanted those people reading this that hadn't come across WPF before, to know what can be done with it.

[pic]Collapse

.....

.....

WPF: Animations

Animations are another element of WPF (again well documented, so I'll not go into it too much). I have not gone too overboard with animations in the demo application, but I do use animation twice (because one simply has to if they are developing WPF stuff).

Once to load the ChatControl and once to hide the ChatControl. The only thing that is special is the way that I am using animation. They are part of the Window1.xaml but the triggers are performed in code behind logic. As this may be useful to some people I'll give a small example.

In Window1.xaml I have declared an animation as follows, the one shown below loads the ChatControl by growing it from 0 X/Y scale to 100% X/Y scale over a period of 1 second, and is triggered when a user clicks a ListView item.

[pic]Collapse

I trigger this animation directly from code-behind. So how do I do that? Well, let's have a look at the code, shall we? It's fairly easy; the code to do that is as follows:

[pic]Collapse

//get Storyboard animation from window resources

((Storyboard)this.Resources["showChatWindow"]).Begin(this);

WPF: Databinding

The styled ListView that I am using within Window1.xaml utilizes data binding in order to bind a List of Person objects. This is done by using Templates as already shown in the WPF : Styles/Templates section. But just in case you're not so sure about all this WPF stuff, what actually happens is that I use a DataTemplate which specifies just how the ListView will display its data. To do that, I define the following DataTemplates and these DataTemplates include the Binding values. This allows a collection of Person objects to be bound to the ListView.

[pic]Collapse

WPF: Multithreading a WPF Application

Threading in WPF is quite similar to .NET 2.0/Win forms, you still have the issue of threads that are not on the same owner thread as a UI component needing to be marshaled to the correct thread. The only difference is the keywords. For example, in .NET 2.0, one would probably have done:

[pic]Collapse

if (this.InvokeRequired)

{

this.Invoke(new EventHandler(delegate

{

progressBar1.Value = e.ProgressValue;

}));

}

else

{

progressBar1.Value = e.ProgressValue;

}

...while in WPF we would (and I do) use the following syntax. Note : CheckAccess() is marked as false)> so don't be expecting to see it using Intellisense. However, it does work.

[pic]Collapse

///

/// A delegate to allow a cross UI thread call to be marshaled

/// to correct UI thread

///

private delegate void ProxySingleton_ProxyEvent_Delegate(

object sender, ProxyEventArgs e);

///

/// This method checks to see if the current thread needs to be

/// marshalled to the correct (UI owner) thread. If it does a new

/// delegate is created

/// which recalls this method on the correct thread

///

/// ProxySingleton

/// The event args

private void ProxySingleton_ProxyEvent(object sender,

ProxyEventArgs e)

{

if (!this.Dispatcher.CheckAccess())

{

this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,

new ProxySingleton_ProxyEvent_Delegate(

ProxySingleton_ProxyEvent),

sender, new object[] { e });

return;

}

//now marshalled to correct thread so proceed

foreach (Person person in e.list)

{

lstChatters.Items.Add(person);

}

this.ChatControl.AppendText("Connected at " +

DateTime.Now.ToString() + " with user name " +

currPerson.Name + Environment.NewLine);

}

Well, you know what, if you've got to this point without falling asleep, I think you're ready to deal with the inner workings of the attached demo application(s). It should be child's play now, as we've covered all the key elements. There's nothing new to say, apart from how the demo app uses all this stuff (though, some of it we've already discussed). So it should be just a question of explaining it all now.

How This All Works in the DEMO Application

Well how about we start with a sequence diagram (I know UML isn't that great for distributed apps, so I've annotated it with comments, but I hope you get the general idea).

[pic]

I apologize that the text on this diagram is so small, but that's down to the restrictions on image sizes here at The Code Project. I'll even give you some class diagrams, for those that prefer them. Remember that there are 3 separate assemblies (ChatService / Common / WPFChatter), which I talked about earlier:

ChatService

[pic]

In order for this service to work correctly, there is a special configuration file. It could also have been done in code, but App.Config is just more flexible. So, let's have a look at the ChatService.exe App.Config, shall we? Well, it looks like this:

[pic]Collapse

As you can see, this App.Config file contains all the information required to enable the service to operate. WCF supports a lot of different binding options, such as:

|Binding type |Description |

|BasicHttpBinding |A binding that is suitable for communicating with WS-Basic Profile |

| |conformant Web Services, for example, ASMX-based services. This |

| |binding uses HTTP as the transport and Text/XML as the default |

| |message encoding. |

|WSHttpBinding |A secure and interoperable binding that is suitable for non-duplex |

| |service contracts. |

|WSDualHttpBinding |A secure and interoperable binding that is suitable for duplex |

| |service contracts or communication through SOAP intermediaries. |

|WSFederationHttpBinding |A secure and interoperable binding that supports the WS-Federation |

| |protocol, enabling organizations that are in a federation to |

| |efficiently authenticate and authorize users. |

|NetTcpBinding |A secure and optimized binding suitable for cross-machine |

| |communication between WCF applications. |

|NetNamedPipeBinding |A secure, reliable, optimized binding that is suitable for on-machine|

| |communication between WCF applications. |

|NetMsmqBinding |A queued binding that is suitable for cross-machine communication |

| |between WCF applications. |

|NetPeerTcpBinding |A binding that enables secure, multi-machine communication. |

|MsmqIntegrationBinding |A binding that is suitable for cross-machine communication between a |

| |WCF application and existing MSMQ applications. |

For the demo application, I am using NetTcpBinding. For more information on bindings in WCF applications, you can see this link.

Common

This is a very simple class that is used by the ChatService and WPFChatter assemblies. This class represents a single chatter and may be sent over the WCF channel due the special WCF annotations that have been applied to this class. The entire class is listed below, as it's not so big:

[pic]

[pic]Collapse

using System

using System.Collections.Generic;

using System.Text;

using System.ServiceModel;

using System.Runtime.Serialization;

using ponentModel;

namespace Common

{

#region Person class

///

/// This class represnts a single chat user that can participate in

/// this chat application

/// This class implements INotifyPropertyChanged to support one-way

/// and two-way WPF bindings (such that the UI element updates when

/// the source has been changed dynamically)

/// [DataContract] specifies that the type defines or implements a

/// data contract and is serializable by a serializer, such as

/// the DataContractSerializer

///

[DataContract]

public class Person : INotifyPropertyChanged

{

#region Instance Fields

private string imageURL;

private string name;

public event PropertyChangedEventHandler PropertyChanged;

#endregion

#region Ctors

///

/// Blank constructor

///

public Person()

{

}

///

/// Assign constructor

///

/// Image url to allow a picture to be

/// created for this chatter

/// The name to use for this chatter

public Person(string imageURL, string name)

{

this.imageURL = imageURL;

this.name = name;

}

#endregion

#region Public Properties

///

/// The chatters image url

///

[DataMember]

public string ImageURL

{

get { return imageURL; }

set

{

imageURL = value;

// Call OnPropertyChanged whenever the property is updated

OnPropertyChanged("ImageURL");

}

}

///

/// The chatters Name

///

[DataMember]

public string Name

{

get { return name; }

set

{

name = value;

// Call OnPropertyChanged whenever the property is updated

OnPropertyChanged("Name");

}

}

#endregion

#region OnPropertyChanged (for correct well behaved databinding)

///

/// Notifies the parent bindings (if any) that a property

/// value changed and that the binding needs updating

///

/// The property which changed

protected void OnPropertyChanged(string propValue)

{

PropertyChangedEventHandler handler = PropertyChanged;

if (handler != null)

{

handler(this, new PropertyChangedEventArgs(propValue));

}

}

#endregion

}

#endregion

}

As you can see, this class has some special attributes which still haven't been talked about. It also inherits from a strange interface. So what's that all about, then? Well, [DataContract] specifies that the type defines or implements a data contract and is serializable by a serializer such as DataContractSerializer. The INotifyPropertyChanged interface is a special WPF interface that allows the class to notify and binding container of a change in a value. Thus, when a property is changed, the binding container will be informed. This applies to the Window1.xaml ListView in my case, as described in the WPF: Databinding section.

WPFChatter

[pic]

The app.config file and WCF proxy were created using the WCF scvutil.exe tool, as mentioned in the WCF: Creating the Proxy section. The finished WPF application is as shown below:

[pic][pic]

What I'm going to do now is explain a bit about how all the WCF/WPF stuff works in conjunction with each other, using the attached demo app. I think the best place to start would be to describe the actual ChatService (the server end) and why it's adorned the way it is. I will not go into all the inner workings of the ChatService.cs file just yet, as this will be visited later.

ChatService.cs

[pic]Collapse

[ServiceContract(SessionMode = SessionMode.Required,

CallbackContract = typeof(IChatCallback))]

interface IChat

{

[OperationContract(IsOneWay = true, IsInitiating = false,

IsTerminating = false)]

void Say(string msg);

[OperationContract(IsOneWay = true, IsInitiating = false,

IsTerminating = false)]

void Whisper(string to, string msg);

[OperationContract(IsOneWay = false, IsInitiating = true,

IsTerminating = false)]

Person[] Join(Person name);

[OperationContract(IsOneWay = true, IsInitiating = false,

IsTerminating = true)]

void Leave();

}

interface IChatCallback

{

[OperationContract(IsOneWay = true)]

void Receive(Person sender, string message);

[OperationContract(IsOneWay = true)]

void ReceiveWhisper(Person sender, string message);

[OperationContract(IsOneWay = true)]

void UserEnter(Person person);

[OperationContract(IsOneWay = true)]

void UserLeave(Person person);

}

public class ChatEventArgs : EventArgs

{

public MessageType msgType;

public Person person;

public string message;

}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession,

ConcurrencyMode = ConcurrencyMode.Multiple)]

public class ChatService : IChat

{

...

}

There are several things to note here:

1. There is an IChat interface which is implemented by the ChatService (server end) class. This is in order that the ChatService implements a common Chat interface that is known at compile time. Each of the interface methods are adorned with new WCF [OperationContract] attributes. I discussed all the possible values for this attribute earlier in this article. The general rule of thumb here is that if the method needs to return a value of IsOneWay = true. Otherwise, it will be IsOneWay = false. All these methods are also marked with the IsInitiating and IsTerminating properties. As the Join method is initialising the service, this is marked with IsInitiating = true and as the Leave method is quitting the service, this is marked with IsTerminating = true.

2. There is also another IChatCallBack interface included within the ChatService class. This is to allow the ChatService to make callbacks to the clients using a well-known interface that is also known at compile time. It can be seen that the server-implemented IChat interface actually includes extra annotation to state whether there is a callback required. Let's have a look at [ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IChatCallback))]. Notice how the CallbackContract is specified as being of type IChatCallback. That's how the WCF service knows how to communicate back to the client. Obviously, just having the CallbackContract property and not the actual IChatCallback interface is not enough. You need both.

3. Lastly, we need to note that the actual ChatService is also adorned with extra attributes that allow the service itself to be managed correctly. I am using InstanceContextMode = InstanceContextMode.PerSession. PerSession instructs the service application to create a new service object when a new communication session is established between a client and the service application. Subsequent calls in the same session are handled by the same object. I am also using ConcurrencyMode = ConcurrencyMode.Multiple. A value of Multiple means that service objects can be executed by multiple threads at any one time. In this case, you must ensure thread safety.

So that's the ChatService's WCF attributes explained, but what about the client-side code? Remember that we used svcutil.exe to create a proxy object the way I previously described. So I am not going to go into that in too much detail. However, the full proxy code (ChatService.cs, in the WPFChatter project in the demo code) is shown below. With the descriptions just given, and the previously mentioned attribute tables, you should be able to see what's going on I hope.

[pic]Collapse

using Common;

///

/// This class was auto generated by the svcutil.exe utility.

/// The article will explain how this class

/// was generated, for those readers that just need to know.

/// Basically, anyone like me.

///

#region IChat interface

[System.piler.GeneratedCodeAttribute("System.ServiceModel",

"3.0.0.0")]

[System.ServiceModel.ServiceContractAttribute(CallbackContract = typeof(

IChatCallback), SessionMode = System.ServiceModel.SessionMode.Required)]

public interface IChat

{

[System.ServiceModel.OperationContractAttribute(AsyncPattern = true,

Action = "">,

ReplyAction = "")]

System.IAsyncResult BeginJoin(Person name, System.AsyncCallback callback,

object asyncState);

Person[] EndJoin(System.IAsyncResult result);

[System.ServiceModel.OperationContractAttribute(IsOneWay = true,

IsInitiating = false, Action = "")]

void Leave();

[System.ServiceModel.OperationContractAttribute(IsOneWay = true,

IsInitiating = false, Action = "")]

void Say(string msg);

[System.ServiceModel.OperationContractAttribute(IsOneWay = true,

IsInitiating = false, Action = "")]

void Whisper(string to, string msg);

}

#endregion

#region IChatCallback interface

[System.piler.GeneratedCodeAttribute("System.ServiceModel",

"3.0.0.0")]

public interface IChatCallback

{

[System.ServiceModel.OperationContractAttribute(IsOneWay = true,

Action = "")]

void Receive(Person sender, string message);

[System.ServiceModel.OperationContractAttribute(IsOneWay = true,

Action = "")]

void ReceiveWhisper(Person sender, string message);

[System.ServiceModel.OperationContractAttribute(IsOneWay = true,

Action = "")]

void UserEnter(Person person);

[System.ServiceModel.OperationContractAttribute(IsOneWay = true,

Action = "")]

void UserLeave(Person person);

}

#endregion

#region IChatChannel interface

[System.piler.GeneratedCodeAttribute("System.ServiceModel",

"3.0.0.0")]

public interface IChatChannel : IChat, System.ServiceModel.IClientChannel

{

}

#endregion

#region ChatProxy class

[System.piler.GeneratedCodeAttribute("System.ServiceModel",

"3.0.0.0")]

public partial class ChatProxy : System.ServiceModel.DuplexClientBase,

IChat

{

public ChatProxy(System.ServiceModel.InstanceContext callbackInstance)

:

base(callbackInstance)

{

}

public ChatProxy(System.ServiceModel.InstanceContext callbackInstance,

string endpointConfigurationName)

:

base(callbackInstance, endpointConfigurationName)

{

}

public ChatProxy(System.ServiceModel.InstanceContext callbackInstance,

string endpointConfigurationName, string remoteAddress)

:

base(callbackInstance, endpointConfigurationName, remoteAddress)

{

}

public ChatProxy(System.ServiceModel.InstanceContext callbackInstance,

string endpointConfigurationName,

System.ServiceModel.EndpointAddress remoteAddress)

:

base(callbackInstance, endpointConfigurationName, remoteAddress)

{

}

public ChatProxy(System.ServiceModel.InstanceContext callbackInstance,

System.ServiceModel.Channels.Binding binding,

System.ServiceModel.EndpointAddress remoteAddress)

:

base(callbackInstance, binding, remoteAddress)

{

}

public System.IAsyncResult BeginJoin(Person name,

System.AsyncCallback callback, object asyncState)

{

return base.Channel.BeginJoin(name, callback, asyncState);

}

public Person[] EndJoin(System.IAsyncResult result)

{

return base.Channel.EndJoin(result);

}

public void Leave()

{

base.Channel.Leave();

}

public void Say(string msg)

{

base.Channel.Say(msg);

}

public void Whisper(string to, string msg)

{

base.Channel.Whisper(to, msg);

}

}

#endregion

OK, so now that I've described the skeleton, boiler plate code that both the Server/Client use to carry out their communications, let's dive in to see how the actual code utilizes the ChatService (server end) and the client side proxy object also called ChatService. I think the best way to do this is to list out one of the operations (such as Join) and follow it from start to finish, from client to server.

One thing to note though, before I start going into this stuff fully, is that I needed to be able to call the client side proxy object methods from several different windows/controls within the attached WPF project. To this end, there is a class called Proxy_Singleton.cs that is really just a singleton wrapper object around all the different operations that need to be carried out for the ChatService to work correctly. It simply enables the client code to obtain the current singleton object and call its methods without having to worry about instantiating a new proxy object, or keeping track of the correct proxy object.

Join

For the Join operation, the following sequence is observed

1. The user enters a name and assigns an image on the SignInControl.xaml control, where a new Person object is created which is accessible via a public property. This control then raises the AddButtonClickEvent where the container Window1.xaml is listening to this event. The container, Window1.xaml, then hides the SignInControl.xaml control. It grabs the new Person object that was created by the SignInControl.xaml and calls the Connect method of the Proxy_Singleton.cs, passing it the new Person object.

2. Both Window1.xaml and ChatControl.xaml are then wired up to subscribe to the Proxy_Singleton.cs ProxyEvent event and ProxyCallBackEvent event, such that when the Proxy_Singleton.cs receives a callback message from the server (another chatter), it will fire these events and all listening parties will know what to do with the received data.

3. The Proxy_Singleton.cs Connect method uses an asynchronous connection to call the Join method on the ChatService (local proxy). Recall that I told you about asynchronous delegates and how the asynchronous connections were handled by the svcutil.exe tool.

4. The Proxy_Singleton.cs Connect method eventually calls (through WCF magic, tcpBninding actually) the Join method of the ChatService (server). This will return the new List of chatters to all connected chatters. The server does this by maintaining a multicast delegate that holds a list of delegates of the type public delegate void ChatEventHandler(object sender, ChatEventArgs e);. There is 1 each per connected chatter. Basically what happens is that whenever one of the IChat methods on the server is called, the delegate for the client may also called (dependant on message type). Obviously, some messages (such as Whisper) are private. As such, only the particular delegate for the receiver of the message is invoked. Nevertheless, the mechanism is the same. When a message is received by the server, the message type is examined. If it's private, only the particular delegate for the receiver (client) of the message is invoked. Otherwise, all attached client delegates are invoked. When the delegate is invoked by the server, it will use the IChatCallback interface to make the call back to the client. As the Proxy_Singleton.cs Connect class implements IChatCallback, it is capable of receiving and dealing with the callback messages. It will generally make the callback messages available to other objects via internally generated events which, as mentioned earlier, Window1.xaml and ChatControl.xaml have subscribed to.

This probably needs some code snippets to help explain, so let's start with the Proxy_Singleton.cs Connect code:

[pic]Collapse

///

/// Begins an asynchronous join operation on the

/// underlying ChatProxy

/// which will call the OnEndJoin() method on completion

///

/// The chatter

/// to try and join with

public void Connect(Person p)

{

InstanceContext site = new InstanceContext(this);

proxy = new ChatProxy(site);

IAsyncResult iar = proxy.BeginJoin(p,

new AsyncCallback(OnEndJoin), null);

}

///

/// Is called as a callback from the asynchronous call,

/// so simply get the list of

/// Chatters that will

/// be yielded as part of the Asynch Join call

///

/// The asnch result

private void OnEndJoin(IAsyncResult iar)

{

try

{

Person[] list = proxy.EndJoin(iar);

HandleEndJoin(list);

}

catch (Exception e)

{

MessageBox.Show(e.Message, "Error",

MessageBoxButton.OK, MessageBoxImage.Error);

}

}

///

/// If the input list is not empty, then call the

/// OnProxyEvent() event, to raise the event for subscribers

///

/// The list of

/// Chatters

private void HandleEndJoin(Person[] list)

{

if (list == null)

{

MessageBox.Show("Error: List is empty", "Error",

MessageBoxButton.OK, MessageBoxImage.Error);

ExitChatSession();

}

else

{

ProxyEventArgs e = new ProxyEventArgs();

e.list = list;

OnProxyEvent(e);

}

}

///

/// Raises the event for connected subscribers

///

/// ProxyCallBackEventArgs

/// event args

protected void OnProxyCallBackEvent(ProxyCallBackEventArgs e)

{

if (ProxyCallBackEvent != null)

{

// Invokes the delegates.

ProxyCallBackEvent(this, e);

}

}

///

/// Raises the event for connected subscribers

///

/// ProxyEventArgs

/// event args

protected void OnProxyEvent(ProxyEventArgs e)

{

if (ProxyEvent != null)

{

// Invokes the delegates.

ProxyEvent(this, e);

}

}

.....

///

/// New chatter entered chat room, so call the

/// internal UserEnterLeave() method passing it the input parameters

/// and the CallBackType.UserEnter

/// type

///

/// The current chatter

/// The message

public void UserEnter(Person person)

{

....

}

So this ends up calling the Join method in the ChatService (server), which then uses IChatCallback to call the client back using the appropriate IChatCallback method. For example:

• A Join method in the ChatService will result in an IChatCallback UserEnter method being called.

• A Leave method in the ChatService will result in an IChatCallback UserLeave method being called.

• A Say method in the ChatService will result in an IChatCallback Receive method being called.

• A Whisper method in the ChatService will result in an IChatCallback ReceiveWhisper method being called.

So let's have a look at the Join/UserEnter operation at the server end:

[pic]Collapse

///

/// Takes a Person and allows them

/// to join the chat room, if there is not already a chatter with

/// the same name

///

/// Person joining

/// An array of Person

/// objects

public Person[] Join(Person person)

{

bool userAdded = false;

//create a new ChatEventHandler delegate, pointing to the

//MyEventHandler() method

myEventHandler = new ChatEventHandler(MyEventHandler);

//carry out a critical section that checks to see if the new

//chatter name is already in use, if its not allow the new

//chatter to be added to the list of chatters, using the

//person as the key, and the

//ChatEventHandler delegate as the value, for later invocation

lock (syncObj)

{

if (!checkIfPersonExists(person.Name) && person != null)

{

this.person = person;

chatters.Add(person, MyEventHandler);

userAdded = true;

}

}

//if the new chatter could be successfully added, get a

//callback instance create a new message, and broadcast it to

//all other chatters, and then return the list of al chatters

//such that connected clients may show a list of all the chatters

if (userAdded)

{

callback =

OperationContext.Current.GetCallbackChannel();

ChatEventArgs e = new ChatEventArgs();

e.msgType = MessageType.UserEnter;

e.person = this.person;

BroadcastMessage(e);

//add this newly joined chatters ChatEventHandler delegate,

//to the global multicast delegate for invocation

ChatEvent += myEventHandler;

Person[] list = new Person[chatters.Count];

//carry out a critical section that copy all chatters to

//a new list

lock (syncObj)

{

chatters.Keys.CopyTo(list, 0);

}

return list;

}

else

{

return null;

}

}

....

///

/// This method is called when ever one of the chatters

/// ChatEventHandler delegates is invoked. When this method

/// is called it will examine the events ChatEventArgs to see

/// what type of message is being broadcast, and will then

/// call the correspoding method on the clients callback interface

///

/// the sender, which is not used

/// The ChatEventArgs

private void MyEventHandler(object sender, ChatEventArgs e)

{

try

{

switch (e.msgType)

{

case MessageType.Receive:

callback.Receive(e.person, e.message);

break;

case MessageType.ReceiveWhisper:

callback.ReceiveWhisper(e.person, e.message);

break;

case MessageType.UserEnter:

callback.UserEnter(e.person);

break;

case MessageType.UserLeave:

callback.UserLeave(e.person);

break;

}

}

catch

{

Leave();

}

}

.....

///

///loop through all connected chatters and invoke their

///ChatEventHandler delegate asynchronously, which will firstly call

///the MyEventHandler() method and will allow a asynch callback to

///call

///the EndAsync() method on completion of the initial call

///

/// The ChatEventArgs to use to send to all

/// connected chatters

private void BroadcastMessage(ChatEventArgs e)

{

ChatEventHandler temp = ChatEvent;

//loop through all connected chatters and invoke their

//ChatEventHandler delegate asynchronously, which will firstly

//call the MyEventHandler() method and will allow a asynch

//callback to call the EndAsync() method on completion of the

//initial call

if (temp != null)

{

foreach (ChatEventHandler handler in temp.GetInvocationList())

{

handler.BeginInvoke(this, e, new AsyncCallback(EndAsync),

null);

}

}

}

................
................

In order to avoid copyright disputes, this page is only a partial summary.

Google Online Preview   Download