Windows Communication Foundation Tutorial - Part 3 - Messaging & MessageContracts
[NOTE: This article currently targets the pre-release version of WCF. It is currently being updated to RTM. Much of the content and most of the concepts still hold, though]
So far in this series we have looked at creating a simple Hello World service that simply returned a string. We then built upon this by returning a customer business object via the 1.x and 2.0 Serializable attribute) and then contrasted it with the new WCF System.Runtime.Serialization DataContract and DataMember attributes. In order to get them from the service to the client we passed in an integer ID directly and received a Customer object directly in response. Were this system currently in production, having a number of globally distributed clients, we would be faced with a problem if the requirements for this particular method changed...
It is for this reason, amongst others, that in the services world we make use of Message objects that act as containers for our objects on the wire. An example will make this clearer. Our method currently looks like this:
Customer GetCustomer(int id);
If we suddenly decided we needed to split customers amongst our client affiliates, we would require not only a customer ID but an Affiliate ID as well. There is no way to change this RPC-style method signature without breaking all of our existing customers. This is even worse if we suddenly require more than one object to be returned as well, such as the products this customers is allowed to view, something along the lines of the following:
Customer, Product GetCustomer(int id, int affiliateID);
which is not even possible in the framework. What we require is containers for our request and corresponding response parameters. These can be implemented using specific extensible, strongly-typed request and response objects and this is by and large the recommended approach in the services world. For this reason, WCF explicitly provides standard support for this approach and it does this using the same model is has used in our series thus far: through the use of attributes. Using this new approach, our method signature would look like the following:
GetCustomerResponse GetCustomer(GetCustomerRequest request);
The corresponding code for our new objects is :
using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
namespace HG.RnD.WinFX.WCF.Messages
{
public class GetCustomerRequest
{
private int _id;
public int ID
{
get { return _id; }
set { _id = value; }
}
}
}
and
using System;
using System.Collections.Generic;
using System.Text;
using BusinessLayer;
using System.ServiceModel;
namespace HG.RnD.WinFX.WCF.Messages
{
public class GetCustomerResponse
{
private Customer _customer;
public Customer Customer
{
get { return _customer; }
set { _customer = value; }
}
}
}
In order to make use of these Message objects, they need to be tagged with the WCF MessageContract attribute. As with the other xContract attributes we have seen in WCF, we also need to mark certain members within the class with corresponding attributes. What differs with the MessageContract attributes is that the members have 2 possible attribute options: MessageBody and MessageHeader. Applying the MessageHeader attribute results in that field being placed into the header of the SOAP envelope, whereas the MessageBody attribute puts it into the SOAP message body. If you're not sure which one you want, by and large MessageBody is most likely to be sufficient. The header option is useful because different security settings, for example, might be applied to the header of the SOAP message. We extend our classes to be used as Messages like so:
namespace HG.RnD.WinFX.WCF.Messages
{
[MessageContract]
public class GetCustomerRequest
{
...
[MessageBody]
public int ID
{
...
}
}
}
and
namespace HG.RnD.WinFX.WCF.Messages
{
[MessageContract]
public class GetCustomerResponse
{
...
[MessageBody]
public Customer Customer
{
...
}
}
}
On a note of personal style, one way of returning information from the operation that relates directly to the operation itself is via an additional property on the xResponse class. An example would be the use of a string property called ResponseMessage. It must have a matching private field and be marked with MessageBody as well and it can used to transmit success of failure information, for example, in an update, of the fact that a search criterion is invalid instead of simply zero results from a valid search. In WCF we have greater flexibility in the use of explicit exception throwing which we can also make use of but it is arguable whether some results require an exception per se so this is still a valid model in many instances.
The final stages on the service-side are to make use of these new request-response message objects. To do this, we need to change our method signature to match our new version above and we need to change the code itself to retrieve the "parameters" contained in the request object and to wrap our response "parameters" in a response object. A very simple implementation of this might look as follows:
public GetCustomerResponse GetCustomer(GetCustomerRequest request)
{
GetCustomerResponse response = new GetCustomerResponse();
Customer customer = new Customer();
// Get Customer from datastore using request.ID
// Dummy If for testing for now:
if (request.ID == 1)
{
customer.FirstName = "Hilton";
}
else
{
customer.FirstName = "Bob";
}
response.Customer = customer;
return response;
}
We can make use of the request object's properties directly and to return our results we create a new GetCustomerResponse object and assign our return values to its properties. To finish on the sevice-side requires a simple recompile and from there we move onto the client proxy.
We can generate our proxy either by hand using svcutil.exe or Visual Studio and either way we end up with a similar file, as we have seen in previous tutorials (except for some DataContract naming issues - see Tutorial 2 for more). If we examine the code for this proxy we can see some interesting points. However, before we do this, let's take a step back and look a bit more closely at the proxy we used for Tutorial 2 and take a very short detour into binding. Binding is a topic which we will cover in upcoming tutorials, suffice to say that it is controlled in large part through our config files and that it has a considerable affect on the security offered and enforced in our service. If we change the "binding" option of our service to "basicHttpBinding", for example, we are disabling WS-* security, whereas if we simply change it to "wsHttpBinding" we enable a variety of basic security options. These include security options and if we use the later "ws" option, we see the following method signatures:
public HG.RnD.WinFX.WCF.BusinessLayer.Customer GetCustomer(int id)
{
GetCustomerRequest inValue = new GetCustomerRequest();
inValue.Body = new GetCustomerRequestBody();
inValue.Body.id = id;
GetCustomerResponse retVal = ((ICMSService)(this)).GetCustomer(inValue);
return retVal.Body.GetCustomerResult;
}
GetCustomerResponse ICMSService.GetCustomer(GetCustomerRequest request)
{
return base.InnerProxy.GetCustomer(request);
}
The first method is the one we were expecting and thus it was the one we coded against. However, we can see that an additional method was generated using a Request and Response object! Even more strange is that this method maps to the interface directly. If we look at the interface itself, it appears thus:
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute()]
public interface ICMSService
{
// CODEGEN: Generating message contract since message part GetCustomerResult requires protection.
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/ICMSService/GetCustomer", ReplyAction="http://tempuri.org/ICMSService/GetCustomerResponse")]
GetCustomerResponse GetCustomer(GetCustomerRequest request);
...
}
and we see that the interface is indeed using the Message-based approach, with a specific note about protection. The actual request response objects for the GetCustomer method appear below:
[System.ServiceModel.MessageContractAttribute()]
public class GetCustomerRequest
{
[System.ServiceModel.MessageBodyAttribute(Name="GetCustomer", Namespace="http://tempuri.org/", ProtectionLevel=System.Net.Security.ProtectionLevel.EncryptAndSign, Order=0)]
public GetCustomerRequestBody Body;
public GetCustomerRequest()
{
}
public GetCustomerRequest(GetCustomerRequestBody Body)
{
this.Body = Body;
}
}
[System.Runtime.Serialization.DataContractAttribute(Namespace="http://tempuri.org/")]
public class GetCustomerRequestBody
{
[System.Runtime.Serialization.DataMemberAttribute(Order=0)]
public int id;
public GetCustomerRequestBody()
{
}
public GetCustomerRequestBody(int id)
{
this.id = id;
}
}
[System.ServiceModel.MessageContractAttribute()]
public class GetCustomerResponse
{
[System.ServiceModel.MessageBodyAttribute(Name="GetCustomerResponse", Namespace="http://tempuri.org/", ProtectionLevel=System.Net.Security.ProtectionLevel.EncryptAndSign, Order=0)]
public GetCustomerResponseBody Body;
public GetCustomerResponse()
{
}
public GetCustomerResponse(GetCustomerResponseBody Body)
{
this.Body = Body;
}
}
[System.Runtime.Serialization.DataContractAttribute(Namespace="http://tempuri.org/")]
public class GetCustomerResponseBody
{
[System.Runtime.Serialization.DataMemberAttribute(Order=0)]
public HG.RnD.WinFX.WCF.BusinessLayer.Customer GetCustomerResult;
public GetCustomerResponseBody()
{
}
public GetCustomerResponseBody(HG.RnD.WinFX.WCF.BusinessLayer.Customer GetCustomerResult)
{
this.GetCustomerResult = GetCustomerResult;
}
}
We can see that some sort of message security has indeed been applied in the form of the EncryptAndSign ProtectionLevel enumeration option. Security is not a topic I want to delve into at this point as it is something that deserves a variety of articles! The first two of the remaining MessageBodyAttribute properties are quite straightforward. The Order property specifies exactly what order the actual properties should be created in during serialization and we shall see one of its uses shortly.
One aspect that puzzles me about the above is that there is an additional level of objects. Whereas we created a single request object, for example, which directly contained the members we wanted, this autogenerated one created a request which contained a single RequestBody member which finally contained the id member. The only explanation I can think of is that it might be to split the body and header aspects of the message, except that there are not going to be any header attributes because this is for a pattern I did not explicity specify. Has anyone got any suggestions?
Aside from the extra level of objects, we can see that WCF tried to generate a request-response pair for us earlier which closely matched what we have created explicitly in this tutorial. From this we can see first of all that we seem to be on the right track if that's what the team think is worthwhile (;->) and also that this approach can be used to assist with security and making it more explicit. One minor additional point of interest is that generated proxy versions use public fields instead of private field - public property pairs, which was a complaint in the asmx 1.x time. For those who are interested, using the wsHttpBinding in our first tutorial would have resulted in a similar Request - Response pair being created even for our simple Hello World sample.
We can turn now to our client proxy for the explicit messages we created earlier, and we see the following has been generated:
[System.ServiceModel.MessageContractAttribute()]
public class GetCustomerRequest
{
[System.ServiceModel.MessageBodyAttribute(Namespace="http://tempuri.org/", ProtectionLevel=System.Net.Security.ProtectionLevel.EncryptAndSign, Order=0)]
public int ID;
public GetCustomerRequest()
{
}
public GetCustomerRequest(int ID)
{
this.ID = ID;
}
}
[System.ServiceModel.MessageContractAttribute()]
public class GetCustomerResponse
{
[System.ServiceModel.MessageBodyAttribute(Namespace="http://tempuri.org/", ProtectionLevel=System.Net.Security.ProtectionLevel.EncryptAndSign, Order=0)]
public ConsoleClient.CMSService.Customer Customer;
[System.ServiceModel.MessageBodyAttribute(Namespace="http://tempuri.org/", ProtectionLevel=System.Net.Security.ProtectionLevel.EncryptAndSign, Order=1)]
public string ResponseMessage;
public GetCustomerResponse()
{
}
public GetCustomerResponse(ConsoleClient.CMSService.Customer Customer, string ResponseMessage)
{
this.Customer = Customer;
this.ResponseMessage = ResponseMessage;
}
}
In this instance, we can see that we have Request and Response objects that match what we created. There are only 2 points of interest. First of all, I've used the wsHttpBinding option, so I gain the ProtectionLevel property. Using the basicHttpBinding instead results only in a change to the properties of this attribute and the classes themselves remain unchanged. In addition, the second level of classes we noticed above (using the GetCustomerRequestBody object, for instance) no longer appears. Also, notice that I've included the ResponseMessage string property as well and through it we can see some interesting additional points. For now, notice that svcutil has automatically decided to give my properties an order. This might not be order we want, as we shall see shortly, but it's the tradeoff we get for not being explicit about it ourselves.
Aside from the representation of our new message object pair, let us look at what the proxy has done regarding our interface and its implementation. If we look at the interface method, it appears as follows:
// CODEGEN: Generating message contract since the operation GetCustomer is neither RPC nor document wrapped.
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/ICMSService/GetCustomer", ReplyAction="http://tempuri.org/ICMSService/GetCustomerResponse")]
GetCustomerResponse GetCustomer(GetCustomerRequest request);
We can see that the CODEGEN comment has changed and it is no longer telling us that the response part "requires protection", although it is telling us that it is not RPC. Leaving aside the "document wrapped" comment and the Action and ReplyAction for now, we can see that the interface now matches our service side operation. If we have a look at the implementation in the actual proxy class itself, we see that WCF has generated two methods, however:
GetCustomerResponse ICMSService.GetCustomer(GetCustomerRequest request)
{
return base.InnerProxy.GetCustomer(request);
}
public ConsoleClient.CMSService.Customer GetCustomer(int ID, out string ResponseMessage)
{
GetCustomerRequest inValue = new GetCustomerRequest();
inValue.ID = ID;
GetCustomerResponse retVal = ((ICMSService)(this)).GetCustomer(inValue);
ResponseMessage = retVal.ResponseMessage;
return retVal.Customer;
}
In fact it did this with the sample we looked at from Tutorial 2 above. However, this time it is the RPC-style second method that we do not expect. What we see is that the first method explicitly implements the request-response method from the interface. In fact, because it is explicitly implemented we actually need to cast our proxy to ICMSService to use this method! We can see that the code itself does exactly this in the next method. The default implementation (the second method listed above) actually unwraps the request and response objects back into parameters, with the input parameters taken in directly and the return values implemented as a return on the method (for one of the parameters, at least) and as output parameters from the method. Recall that one of the reasons we implemented the request-response approach in the first place was so as not to break existing clients as the requirements change. From the client perspective, however, unless a change is designed to break clients explicitly (e.g. for a new parameter that is mandatory), the client can implement the call in whatever manner is easiest. From a coding perspective, it is definitely easier to code against a simple method with simple return values and so WCF automatically unwraps the message details for us. This approach is not uncommon in the pre-WCF services world through the use of service helper or service agent classes, where this is done by hand, or at least not in the tools directly.
An important note on the implementation method - we see that the method itself returns a Customer class, whereas the ResponseMessage class is returned as an output parameter. My own preference is to return the ResponseMessage as the method return and have the Customer class and subsequent parameters as output parameters. This can be accomplished through setting the Order property explicitly on the service side in the MessageBody contract rather than letting WCF set its own defaults, as it has done in this case.
That's all we need to do to make our WCF services messages-based. WCF provides support for a variety of other Message Exchange Patterns (MEPs), including duplex and one-way. In addition, we have looked at Parameter-based communication (our RPC-style original calls) and now at the Typed Messages approach. A third option, Untyped Messages, makes use of the plain Message class. This gives tremendous flexibility, but it is like programming against a direct XML packet and it can get quite painful very quickly. The Typed Messages approach is the one I would recommend for most regular situations. Worth noting is that the models cannot be mixed, though, at least not for the same operation.
Well, in terms of the prinicipal so-called "ABC's" of WCF endpoints (Address, Binding and Contract), we have covered some of the key concepts in WCF so far in the form of the various Contract attributes. We will be looking at the A's and B's soon, all being well, but it is worth summarising these contract concepts. The table below is based on some of the media from Microsoft and it provides an incomplete but quick reference summary so far:
| Contract Type |
Member Attributes |
Description |
| ServiceContract |
OperationContract |
Describes the operations a service can perform. Maps CLR types to WSDL. |
| DataContract |
DataMember |
Describes a data structure. Maps CLR types to XSD. |
| MessageContract |
MessageBody, MessageHeader |
Defines the structure of the message on the wire. Maps CLR types to SOAP messages. |