Service oriented applications exchange messages.
WCF provides the ability to extend the channel to enable inspection of messages
which are created, transported, utilized and destroyed under the covers by the
service and client runtimes. This tutorial is intended to provide a entry into
the extensibity capabilities of WCF and demonstrate message inspection.
The Service
The service is exposed through its endpoints. A service endpoint consist of
three basic components: address (where the service is located), binding (how the
service communicates) and contract (operations performed by the service.)
Address
The address is a uri that identifies where the service, service endpoint, is located.
Address examples are the following:
- http://www.koolkode.net/DCS/
- Address of a service, located on the Internet, that calculates medicinal dosage
- http://localhost:50167/DemoService/
- Address of a service located locally on the development computer
The address consists of four parts: A scheme, in this case, http; a machine identification,
localhost; a port, 50167, which is optional; and a path, DemoService.
Binding
The binding describes how the endpoint communicates including the access protocol and encoding.
Built in bindings include the following:
- wsHttpBinding
- Provides secure and reliable sessions using Http transport
- basicHttpBinding
- Highly interoperable including support for Web services based on ASMX
- netNamedPipeBinding
- Efficient when the service is located on a single computer
- netTcpBinding
- Effective for peer-to-peer communication using TCP
- netMsmqBinding
- Enables communication using Microsoft Message Queue (MSMQ) transport
Contract
The
ServiceContractAttribute defines a class or interface as a service.
Best practice calls for defining the service as an interface. The
OperationContractAttribute defines a method that implements a service
operation in a service contract. Properties express the nature of the operation.
For example, the IsOneWay property describes an operation that has no output and
the AscynPattern property describes an operation that is ascy nchronous.
The following example illustrates the definition of a service contract with one
method.
using System.ServiceModel;
namespace DemoService
{
[ServiceContract]
public interface ITheContract
{
[OperationContract]
string TheMethod(string someData);
}
}
The contract is implemented by the class TheContract, shown below, that inherits
the interface ITheContract defining the service and operations.
using System.ServiceModel;
namespace DemoService
{
public class TheContract : DemoService.ITheContract
{
public string TheMethod(string someData)
{
return string.Format("You entered: {0}", someData);
}
}
}
Specifing the Endpoint
Endpoints can be specified imperatively using code or declaratively in a configuration file,
Web.config.
Best practice calls for specification in a configuration files that enables changes to the endpoint
without code compilaton. This is handy as the development environment and hosting environment are
generally different, requiring changes in the endpoint specification, the address for example.
The configuration file information is contained in a servicemodel element. The
endpoint specification for DemoService.TheContract is shown below:
<configuration>
<system.serviceModel>
<services>
<service name="DemoService.TheContract">
<endpoint address=""
binding="wsHttpBinding"
contract="DemoService.ITheContract">
</endpoint>
</service>
</services>
<system.serviceModel>
<configuration>
The service is specified in service element The service element's name attribute
is the type implementing the service. A service
can have multiple endpoints each of which is defined in an endpoint element. The address,
binding and contract are specified in the endpoint element's attributes.
Wiring-Up the Service>
When the host wires-up the service, the system looks in the configuration file
for the service specified in the @ServiceHost directive.
The
@ServiceHost directive is in an svc extension file that relates the
programming elements needed to compile and host the service. For example,
attributes identify the language and debug status. The Service attribute
identifies the type of the service. Note that the name is fully qualified.
< %@ ServiceHost
Language="C#"
Debug="true"
Service="DemoService.TheContract"
CodeBehind="~/App_Code/TheContract.cs"
%>
If the element is found the system loads the class, creates the endpoints
specified in the service's endpoint configuration element and starts the
service.
The Client
The ServiceModel Metadata Utility Tool (Svcutil.exe) extends
the ClientBase<TChannel> class to create a WCF client class that defines objects
that can call a service. When Visual Studio’s Add - Service Reference is
employed, Visual Studio uses Svcutil.exe to create a reference to the
service at a specified endpoint address. In this case the endpoint address is
http://localhost:50167/DemoService/Service.svc
and the reference's namspace is specified
as TheServiceReference.
The client class, included in the reference namespace, is the TheContractClient
which is an extension of the ClientBase<TChannel> class where TChannel is
TheClient.TheServiceReference.ITheContract
type.
This class, TheContractClient, can be used to instantiate a TheService proxy
object. An instance of a class that can call TheService is instantiated with the
statement
TheServiceReference.TheContractClient proxy =
new TheServiceReference.TheContractClient();
The instance of TheContractClient class, proxy, is created to accesses the
service with the following endpoint properties.
- Address
- //localhost:50167/DemoService/Service.svc (This service is running
locally, typically the service is at a remote location and the address would be
similar to the following: http://www.koolkode.net/dcs/Service.svc.)
- Binding
- System.ServiceModel.wsHttpBinding
- Contract
- ITheContract
- Behaviors
- Behaviors automatically added to the Behaviors collection by the
WCF Service template when the service was created.
These properties can be observed by inserting a breakpoint following creation
of the proxy object and opening the Local Window.
A program accessing TheService is shown below:
using System.ServiceModel;
using System.IO;
namespace TheClient
{
class Program
{
static void Main(string[] args)
{
TheServiceReference.TheContractClient proxy =
new TheServiceReference.TheContractClient();
proxy.Open();
string result = proxy.TheMethod("Input Data");
System.Console.WriteLine(result);
System.Console.ReadLine();
proxy.Close();
}
}
}
Following instantiation of the proxy the major program steps are the following:
Open is invoked,
TheMethod is called, and
Closed is invoked.
The program's output is shown below :
You entered: Input Data
Extending Client ‘Under the Hood’ Functionality
For example, how can I see the message that is created by the runtime for
transmission to the service?
The endpoint behaviors provide for extending the functionality of the client.
Shown
below is the Web.config file created by Visual Studio when the WCF Service
template is used to create a service.
<system.serviceModel>
<services>
<!-- name must refer to a type and the behaviorconfiguration (the
behavior used to instantiate the service) default is an empty
string -->
<service name="DemoService.TheContract"
behaviorConfiguration="ServiceBehavior">
<!-- Service Endpoints -->
<endpoint address=""
binding="wsHttpBinding"
contract="DemoService.ITheContract">
<!--
Upon deployment, the following identity element should be
removed or replaced to reflect the
identity under which
the deployed sce runs. If removed, WCF will infer an
appropriate identity
automatically.
-->
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<!-- To avoid disclosing metadata information, set the value
below to false and remove the metadata endpoint above before
deployment
-->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging
purposes, set the value below to true. Set to false before
deployment to avoid disclosing exception information
-->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Note the following:
First, the service has a
behaviorConfiguration attribute in addition to
the name attribute. The behaviorContribution identifies the name of the
behavior element specifing the service's behavior. In this case the name is
ServiceBehavior.
Second, the service has a second endpoint. The address is mex, the binding is
mexHttpBinding and the contract is
IMetadataExchange. The
IMetadataExchange interface exposes methods for retrieving service metadata,
mexHttpBinding enables metadata publication using Http and mex is a metadata
relative address.
Third, the behavior named ServiceBehavior has a
serviceMetadata element
with
httpGetEnabled attribute set to true. When the httpGetEnabled
attribute is set equal to true allows the service to respond to metadata
requests.
This is an example of extending the endpoint behavior to fulfill
metadata requests.
When the channel stack is built the operating system modifies the components
comprising the stack utilizing information contined in the configuration
behavior elements. This customization is performed before the service is started
or the proxy Open is invoked.
Extending Endpoint Behavior
Instances of a class that inherits the IEndpointBehavior interface can be added to the Behaviors collection.
The
IEndpointBehavior interface prototypes methods that are called by the operating
system while the channel is being built. Code within these methods can be used to
implement extension extensions.
- AddBindingParameters
- Customization of binding behavior
- ApplyClientBehavior
- Extension to client endpoint behavior
- ApplyDispatchBehavior
- Extension of service endpoint behavior
- Validate
- Confirmation of endpoint configuration settings
The parameters passed in the call make the extensions possible. For example, the
parameters passed to ApplyClientBehavior are the following:
- SeviceEndpoint -- The endpoint being customized
- ClientRuntime -- The runtime
The implementation shown below add and
public class TheEndpointBehavion : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint,
System.ServiceModel.Channels.BindingParameterCollection
bindingParameters)
{ }
public void ApplyClientBehavior(ServiceEndpoint endpoint,
ClientRuntime clientRuntime)
{ }
public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
EndpointDispatcher endpointDispatcher)
{ }
public void Validate(ServiceEndpoint endpoint)
{ }
}
An instance of the TheEndpointBehavior class can be added to the endpoint's
Behaviors collection.
TheEndpointBehavion theEndpointBehavior =
new TheEndpointBehavion();
proxy.Endpoint.Behaviors.Add(theEndpointBehavior);
This code is added after the proxy instance is created and before Open is
called.
Again, insertion of a breakpoint after the proxy object is instantiated will
stop program execution and properties of the object, including the added
TheEndpointBehavior in the Behaviors collection, are displayed in Visual
Studio's Locals Window.
Moreover, stepping through the code, after the proxy object is instantiated will
reveal calls to the client related methods in TheEndpointBehavior object.
When the ApplyClientBehavior is called it is passed objects representing the
endpoint and client runtime. The ClientRuntime type object exposes the
MessageInspectors property, a collection of IClientMessageInspector type objects.
The following code adds an instance of the TheMessageInspector class to the
MessageInspectors collection.
TheMessageInspector class implements the IClientMessageInspector interface.
public void ApplyClientBehavior(ServiceEndpoint endpoint,
ClientRuntime clientRuntime)
{
TheMessageInspector theMessageInspector =
new TheMessageInspector();
clientRuntime.MessageInspectors.Add(theMessageInspector);
}
}
Message
Messages transported between the service and client are modeled by the
Message class. The message is abstracted by the SOAP message and such
contains multiple headers and a body. The body is intended to be streamed and
thus access to a message is constrained in that it can only be accessed once.
Subsequent attempts to access the message will result in an exception
being thrown. The status of the message is characterized by its state. When it
is created it assigned the Created state. Reading, writing, copying or closing
the message results in a transistion to the Read, Written, Copied and Closed
state respectively. Any attempt to access the message when it is not in the
Created state causes an exception. A work-arround is provided by creating a
message buffer that contians a copy of the message. This buffer can than be used
to recreate the original message and create additional copies for other
processing.
Consider the code shown below. Assume that
requestMsg
is the Message object created by the framework. It can only be accessed once,
typically when streamed to the line connecting the client and service. The
Message class contains the CreateBufferedCopy method that returns a
MessageBuffer object which is a buffered copy of the message.
The MessageBuffer class contains a CreateMessage that returns a Message object
representing the buffered message. This message is identical to the message used
to create the message used to create the buffered copy.
Note the creation of a file stream and XmlWriter writing to the stream.
The MessageBuffer class also has a WriteMessage method that writes the buffered
message ot a stream passed as a parameter. The encoding of the WriteMessage
method is encoded to Xml by the XmlWriter object passed as a parameter.
Note that both the writer and streamed are flushed.
Finally, the buffered message is used to create, again, the original message.
The MessageBuffer, copy of the message, is used repeately to create a
new object identical to the original message in the Create state.
MessageBuffer messageBuffer =
requestMsg.CreateBufferedCopy(System.Int32.MaxValue);
requestMsg = messageBuffer.CreateMessage();
FileStream fileStream = new FileStream("theRequestMessageFile.xml",
FileMode.Create);
XmlWriterSettings xmlWriterSetting = new XmlWriterSettings();
xmlWriterSetting.Encoding = System.Text.Encoding.UTF8;
XmlWriter xmlWriter = XmlWriter.Create(fileStream, xmlWriterSetting);
rerequestMsg.WriteMessage(xmlWriter);
xmlWriter.Flush();
fifileStream.Flush();
requestMsg = messageBuffer.CreateMessage();
Remember the MessageInspector type object that was added to the
MessageInspectors collection.
TheMessageInspector class, shown below, inherits the
IClientMessageInspectore interface.The
IClientMessageInspectors interface contains two methods that are automatically
called by the framework:
- BeforeSendRequest
- AfterReceiveReply
Both contain a reference to the message that is to be sent or the message that
has just been received. Copies of this message object can be
processed, including being written to a file as shown below:
public class TheMessageInspector : IClientMessageInspector
{
public void AfterReceiveReply(
ref System.ServiceModel.Channels.Message replyMsg, object obj)
{
MessageBuffer messageBuffer =
replyMsg.CreateBufferedCopy(System.Int32.MaxValue);
replyMsg = messageBuffer.CreateMessage();
FileStream fileStream = new FileStream("theRequestMessageFile.xml",
FileMode.Create);
XmlWriterSettings xmlWriterSetting = new XmlWriterSettings();
xmlWriterSetting.Encoding = System.Text.Encoding.UTF8;
XmlWriter xmlWriter = XmlWriter.Create(fileStream, xmlWriterSetting);
replyMsg.WriteMessage(xmlWriter);
xmlWriter.Flush();
fileStream.Flush();
replyMsg = messageBuffer.CreateMessage();
}
public object BeforeSendRequest(
ref System.ServiceModel.Channels.Message requestMsg,
System.ServiceModel.IClientChannel clientChannel)
{
MessageBuffer messageBuffer =
requestMsg.CreateBufferedCopy(System.Int32.MaxValue);
requestMsg = messageBuffer.CreateMessage();
FileStream fileStream = new FileStream("theRequestMessageFile.xml",
FileMode.Create);
XmlWriterSettings xmlWriterSetting = new XmlWriterSettings();
xmlWriterSetting.Encoding = System.Text.Encoding.UTF8;
XmlWriter xmlWriter = XmlWriter.Create(fileStream, xmlWriterSetting);
rerequestMsg.WriteMessage(xmlWriter);
xmlWriter.Flush();
fileStream.Flush();
requestMsg = messageBuffer.CreateMessage();
}
}
The message sent to the service is shown below. Note the message body -- the
operation and parameter, as described in the contract, are clearly noted.:
<?xml version="1.0" encoding="utf-8" ?>
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing"
xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">
http://tempuri.org/ITheContract/TheMethod
</a:Action>
<a:MessageID>
urn:uuid:db2f06be-e678-43e4-89bc-0507c500e342
</a:MessageID>
<a:ReplyTo>
<a:Address>
http://www.w3.org/2005/08/addressing/anonymous
</a:Address>
</a:ReplyTo>
</s:Header>
<s:Body>
<TheMethod xmlns="http://tempuri.org/">
<someData>Input Data</someData>
</TheMethod>
</s:Body>
</s:Envelope>
The body element, contained in the message dispatched, by the service to the
client is shown below :
<s:Body u:Id="_0">
<TheMethodResponse xmlns="http://tempuri.org/">
<TheMethodResult>
You entered: Input Data
</TheMethodResult>
</TheMethodResponse>
</s:Body>
Author's Note
The above material has been gleaned from Microsoft's documentation.
I hope the tutorial has been informative. Plese forward any comment or observation and
I will add it to the tutorial with credit.
Dudley