WCF Contract Gross Anatomy


Service contracts, data contracts, and message contracts differ by the attributes used in the contract definition. The important attributes names are ServiceContractAttribute, OperationContractAttribute, DataContractAttribute, DataMemberAttribute, MessageContractAttribute, MessageHeaderAttribute, and MessageBodyMemberAttribute. These attributes are part of the System.ServiceModel namespace, and the names of each attribute adequately describe the category of contract they can define.

Note 

Remember that attribute annotations change the metadata of a type definition. By themselves, attribute annotations are completely inert. For attributes to have any value, another set of objects must interrogate this metadata via the reflection application programming interface (API) and use the presence of that metadata to drive behavior. The WCF infrastructure uses reflection to interrogate contract metadata and uses the contract metadata and other type information during the construction of an endpoint.

Service Contracts

Service contracts describe a service. This includes defining facets of the service, the operations of the service, the MEP of each operation, and the messages that each operation uses. The first step in creating a service contract is to establish the names of the operations and the MEPs that they use. In our restaurant example, the service contains three operations: RequestReservation, ChangeReservation, and CancelReservation. Let’s assume that the RequestReservation and ChangeReservation operations use the request/reply MEP and that the CancelReservation operation uses the datagram MEP. Given the complexion of this service, our service contract becomes the following:

 [ServiceContract] public interface IRestaurantService {     [OperationContract]     Int32? RequestReservation(DateTime? resDateTime,                              String restaurantName,                              String partyName);     [OperationContract]     void ChangeReservation(Int32? reservationId, DateTime? resDateTime);     [OperationContract(IsOneWay=true)]     void CancelReservation(Int32? reservationId); }

Note 

I am taking a few liberties with the method parameters and return types in these interface methods. We will revisit the method signatures in the sections “Data Contracts” and “Message Contracts” later in this chapter.

At the surface, this type definition looks like any other .NET interface. In fact, the only differentiating factor between this interface and a normal .NET interface is the addition of the ServiceContractAttribute and the OperationContractAttribute definitions. The addition of the ServiceContractAttribute to the interface means that the WCF infrastructure can use the interface as a service contract. The addition of the OperationContractAttribute to each interface method means that each method is an operation in the service.

The ServiceContractAttribute and OperationContractAttribute types define several instance properties. When used in a service contract, these instance properties offer control over the contract. The ServiceContractAttribute is defined as the following:

 [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class,                 Inherited=false, AllowMultiple=false)] public sealed class ServiceContractAttribute : Attribute {       public Type CallbackContract { get; set; }       public String ConfigurationName { get; set; }       public Boolean HasProtectionLevel { get; }       public String Name { get; set; }       public String Namespace { get; set; }       public ProtectionLevel ProtectionLevel { get; set; }       public SessionMode SessionMode { get; set; } }

The CallbackContract property is for duplex contracts. The ConfigurationName property is the alias that can be used in a configuration file to reference the service. The aptly named Name and Namespace properties are the name and namespace of the service, and these values propagate to the XML name and namespace of the service, as well as the messages.

Note 

Notice that the ServiceContractAttribute can be applied to an interface definition and a class definition. I greatly prefer the use of an interface for a service contract because an interface forces the separation of the contract from implementation.

The ProtectionLevel Property

The ProtectionLevel property indicates the level of message security that a binding must have when using the contract. This property is of type System.Net.Security.ProtectionLevel, and the three values of the enumeration are None, Sign, and EncryptAndSign. When the ServiceContractAttribute.ProtectionLevel property is set to Sign, all messages that the service sends and receives must be signed. When the property is set to EncryptAndSign, all of the messages that the service sends and receives must be encrypted and signed. When the property is None, the contract indicates that no message security is needed for the service.

Note 

The ProtectionLevel property impacts only the security applied to the body of the message. It has no impact on the infrastructure headers present in a message. Examples of these infrastructure headers are WS-Addressing and WS-ReliableMessage headers.

Each binding has security capabilities, and the ProtectionLevel property in the ServiceContractAttribute can force the use of those security capabilities. This ability to set the minimum security requirements in a contract has immense practical application. It means that the contract developer can establish minimum message security requirements, and any endpoint that uses the contract must meet or exceed those minimum requirements. Without this level of control at the service contract level, it is possible that an application developer or application administrator could add an endpoint that has no message-based security on it, and this might not be something that the contract developer ever intended. Conceptually, control over security at the contract blurs the line between a binding and a contract, because a binding is the primary means by which developers express their intent for how a messaging application functions. The blurring of this line might seem like a design problem to the purist. In my opinion, the practical value of this capability is worth the blurring of the boundary.

The SessionMode Property

The SessionMode property indicates whether the channels used in the application must, can, or cannot use sessionful channel shapes. The SessionMode property is of type System. ServiceModel.SessionMode, and the three values of the enumeration are Allowed, Required, and NotAllowed. In Chapter 8, “Bindings,” you saw how the Binding type can create channel managers and that a channel manager has the capability to create a channel that implements a particular channel shape. If the SessionMode property is set to Required, the BuildChannelFactory and BuildChannelListener methods on a binding are invoked with sessionful shapes. If the binding cannot support sessionful channel shapes, an exception is thrown at run time. The default value of the SessionMode property is Allowed. When the SessionMode property is set to the default value, there is no session-based restriction on the application.

Operations in a Service Contract

Service contracts include a description of the operations in the service. When describing an operation in a service contract, it is necessary to describe the MEP of the operation, the structure of the messages that the operation will receive, and the structure of the messages that the operation will return (if any). Because service contracts are annotated class or interface definitions, operations are annotated method definitions within a service contract. Let’s take another look at the restaurant reservation service contract:

 [ServiceContract] public interface IRestaurantService {     [OperationContract]     Int32? RequestReservation(DateTime? resDateTime,                              String restaurantName,                              String partyName);     [OperationContract]     void ChangeReservation(Int32? reservationId, DateTime resDateTime);     [OperationContract(IsOneWay=true)]     void CancelReservation(Int32? reservationId); }

The OperationContractAttribute annotation has several instance properties that control the MEP, security, sessionful capabilities, and message structure of the operation. The OperationContractAttribute is valid only on methods. The following is the public API of the OperationContractAttribute:

 [AttributeUsage(AttributeTargets.Method)] public sealed class OperationContractAttribute : Attribute {   public Boolean AsyncPattern { get; set; }   public Boolean HasProtectionLevel { get; }   public ProtectionLevel ProtectionLevel { get; set; }   public Boolean IsOneWay { get; set; }   public Boolean IsInitiating { get; set; }   public Boolean IsTerminating { get; set; }   public String Name { get; set; }   public String Action { get; set; }   public String ReplyAction { get; set; } }

The AsyncPattern Property

The AsyncPattern property indicates whether the operation is part of the Asynchronous Programming Model (APM) pattern. When this property is set to true, the attribute must be applied to the Begin<methodname> method in the Begin/End pair. The End<methodname> method does not need the OperationContractAttribute applied to it. If, for some reason, the End<methodname> method is not present, the contract will not be used. When the AsyncPattern property is set to true, the receiving infrastructure will asynchronously invoke the Begin<methodname> method. Receiving applications that perform I/O within their operations should set this property to true because it will make the receiving application more scalable. For more information on this topic, see Jeffrey Richter’s CLR via C#. An operation should not, however, set this property to true if the operation is performing computationally bound tasks, because this will result in a suboptimal performance. The AsyncPattern property is completely transparent to sending applications.

The ProtectionLevel Property

The ProtectionLevel property on the OperationContractAttribute is very similar to the same property on the ServiceContractAttribute, but at a different scope. The ProtectionLevel property on the ServiceContractAttribute sets the minimum security for all operations in the service, and the ProtectionLevel property on the OperationContractAttribute sets the minimum security level for that operation. The ProtectionLevel property on the OperationContractAttribute can be less secure than the ProtectionLevel property on the ServiceContractAttribute.

The IsOneWay Property

By default, all operations are assumed to use the request/reply MEP. As you saw in Chapter 2, “Service Orientation,” this is by far the most pervasive and familiar MEP. At first glance, it might appear that defining an operation with a void return type is enough to create a datagram operation. A void return type on a method means that the receiving application will generate a reply, and that reply will not contain any information in the body of the message. If you want to use the datagram MEP in an operation, the method must have a void return type and the IsOneWay property must be set to true. As I mentioned in Chapter 2, I am a big fan of the datagram MEP, and I encourage you to embrace this MEP because of the scalability and advanced messaging scenarios it allows.

However, error handling is markedly simpler with the request/reply MEP than it is with the datagram MEP, and this was a contributing factor in the team’s decision to make request/ reply the default MEP. When the receiver processes a request/reply message and an error occurs, the receiver can automatically send a fault back to the sender. This is particularly simple when the messaging participants are using the HTTP transport. In the case of a fault, the receiver sends the sender a fault via the transport back channel. In a contract, errors from a datagram MEP operation must be returned to the sender via the address specified in the WS-Addressing FaultTo header block.

For security reasons, this behavior is not enabled by default. Consider a message sender that sends a message to the receiver and specifies an address in the FaultTo header block. Using the WS-Addressing mindset, if this message creates a fault, the receiver will route the fault to the address specified in the FaultTo header block. A malicious sender could specify a third-party address in the FaultTo and then send a high volume of these messages to that address, thereby flooding the third-party address with network traffic, and the source of that network traffic would be the WS-Addressing–compliant service. This type of exploitation is a form of a smurfing attack, and the team did not want to allow this sort of behavior by default. I would not let this deter you from using the datagram MEP. Safely using the datagram MEP requires the receiver to interrogate the FaultTo address before sending a fault to that address. Given the nature of trading relationships in business, the domain names of the possible recipients of a fault might be known. In this case, you simply allow faults to propagate to those addresses, and you could even do similar work to validate the sender.

The IsInitiating and IsTerminating Properties

The IsInitiating and IsTerminating properties impact the sessionful behavior of an endpoint. If the IsInitiating property is set to true, the receipt of a message at that operation will start a new session on the receiver. If the IsTerminating property is set to true, the receipt of a message at that operation will terminate the existing session. An operation can have both the IsInitiating and the IsTerminating properties set to true. Setting either of these properties to true is possible only if the SessionMode property on the ServiceContractAttribute is set to Required.

These properties are most applicable in services where there is a natural start and end of the session. Consider a purchasing service that defines operations for creating a purchase order, adding items to the purchase order, and submitting a purchase order. The natural flow of these operations from creating a purchase order to submission lends itself to making the purchase order creation operation an initiating operation and the submission a terminating operation.

The impact of these properties depends on the type of session created via the binding. There are four kinds of sessions possible in WCF: security sessions, WS-ReliableMessaging sessions, MSMQ sessions, and socket-based sessions. The choice of binding determines the type of session the application uses at run time. Within one binding, it is possible to combine sessions. For example, the NetTcpBinding normally uses socket-based sessions. In the constructor of the NetTcpBinding type, however, you can add support for WS-ReliableMessaging sessions.

In security and WS-ReliableMessaging sessions, an initiating operation creates a context on the sender and the receiver. This context is the result of a message choreography between the sender and the receiver. With these types of sessions, the terminating operation invalidates the context, thereby requiring the sender and the receiver to establish a new context before future message exchanges can begin.

With socket-based communication, like the kind resulting from the NetTcpBinding, the sender and receiver must establish a socket connection before any communication can begin. When a service defines an operation that has the IsInitiating property set to true, the first message sent to the receiver must be to that operation; otherwise, an exception is thrown. After the sender sends a message to the initiating operation, the sender is free to send messages to other operations on the receiver. When the sender sends a message to an operation that has the IsTerminating property set to true, the socket is closed after the receiver receives the message.

MSMQ sessions are distinctly different from other sessions. Other types of sessions rely on some form of interactive communication between the sender and the receiver. With security and WS-ReliableMessaging sessions, this involves a message choreography. With socket-based sessions, the sender and the receiver must establish a socket connection. Neither of these types of sessions will work for MSMQ because MSMQ is a connectionless transport. Due to the nature of the transport, MSMQ sessions are the combination of several messages into one message. Like other sessions, operations in a service can have the IsInitiating and IsTerminating properties set. When an operation has the IsInitiating property set to true, the operation begins a new session. When the sender sends a message to an IsInitiating operation, a message is stored in memory rather than sent through the entire channel stack and out to the MSMQ transport. Subsequent message sends to other operations add messages to the existing message. When the sender sends a message to a terminating operation, the entire aggregated message is sent through the entire channel stack and to an MSMQ queue.

The Name, Action, and ReplyAction Properties

The Name property provides the capability to map the name of an operation to the name of an interface method. By default, this property is set to the name of the interface or class method that the annotation is associated with. The Action property sets the WS-Addressing action associated with received messages, and the ReplyAction property sets the WS-Addressing action associated with reply messages. If the Action property is set to *, that operation can be the target of messages with any WS-Addressing action header block. This setting can be useful in scenarios where an operation needs to receive many different kinds of messages, like a router.

Operation Method Arguments

The method definition of an operation in a service contract indicates the structure of the

messages that the operation receives and sends as a reply. Examine the RequestReservation method from our service contract:

     [OperationContract]     Int32? RequestReservation(DateTime? resDateTime,                              String restaurantName,                              String partyName); 

The resDateTime, restaurantName, and partyName parameters are just normal interface method parameters. However, when they are part of an operation contract, they become the basic structure for a received message. At run time, the parameters in an operation contract are used to build a data contract dynamically, and that data contract is used as the template for the body of a message. The definition for the dynamic data contract is built during service initialization, and not each time it is needed. The same paradigm holds true for a method return type. In the preceding example, the return type Int32? is actually used as the basis for a dynamic data contract, and ultimately as a template for the reply message body.

Mapping a Service Contract to a Service Object

Received messages must be processed by a type that contains some business logic for the receiving application to have any value. If a service contract is the embodiment of an agreement between messaging participants, there must be a way for the receiving application to ensure that it complies with the service contract. If we choose to implement a service contract as an interface, we can rely on interface inheritance for enforcement. Here is an example of a type definition that meets the criterion of the service contract via interface inheritance:

 internal sealed class RestaurantService : IRestaurantService {   public Int32? RequestReservation(DateTime? resDateTime,                                   String restaurantName,                                   String partyName) {     // do the work to request reservation     // return a reservation ID     return 5; // we can change the 5 later   }   public void ChangeReservation(Int32? reservationId,                                 DateTime? resDateTime) {     // try to change a reservation to a new datetime   }   public void CancelReservation(Int32? reservationId) {     // use the reservation ID to cancel that reservation   } }

The methods in the RestaurantService type are the implementation of the IRestaurantService interface. Because the IRestaurantService interface is the service contract, the RestaurantService type is an implementation of the service contract. At run time, the WCF infrastructure creates a RestaurantService object when it receives a message at an endpoint (assuming that the endpoint references the service contract), and the lifetime of that object is configurable. You’ll learn more about how the WCF infrastructure creates one of these objects in the next chapter. For now, it is important to see that the WCF infrastructure builds an instance of the RestaurantService type and invokes one of its instance methods when a message is received. The method invoked on a RestaurantService object depends on the Action of the message. Because each operation will have a unique WS-Addressing Action header block, the WCF infrastructure can use the Action header block to route messages to the appropriate method. If the application is not using a binding that forces the addition of a WS-Addressing Action header block, routing can occur based on the body of the message, assuming that the body of the message is unique.

Data Contracts

Data contracts map .NET types to the body of a message and are a key component of message serialization and deserialization. A data contract can stand on its own, but it is often referred to by an operation in a service contract. Like service contracts, data contracts are annotated type definitions. The important attributes in a data contract are the DataContractAttribute and the DataMemberAttribute. As mentioned in the section “Operation Method Arguments” earlier in this chapter, the arguments in a service contract operation are used to create a data contract dynamically when an operation contract contains .NET primitives. The dynamic data contract that the WCF infrastructure creates at run time for the RequestReservation operation has a definition similar to the following:

 [DataContract] public sealed class RequestReservationParams {    [DataMember(Name="resDateTime")]    private DateTime? _resDateTime;    [DataMember(Name="restaurantName")] private String _restaurantName;    [DataMember(Name="partyName")]      private String _partyName;       public RequestReservationParams(DateTime? resDateTime, String restaurantName, String partyName) {     this._partyName = partyName;     this._resDateTime = resDateTime;     this._restaurantName = restaurantName;   }      public DateTime? ResDateTime {     get { return _resDateTime; }   }      public String RestaurantName {     get { return _restaurantName; }   }      public String PartyName {     get { return _partyName; }   } }

I have taken some liberties with the name of the type, the constructor, and the properties. (The actual form of the type generated by the WCF infrastructure is not documented.) The important point is that the data contract contains members that can hold all of the state of the arguments in the RequestReservation operation. Notice also that the only items different from the data contract definition and a regular .NET class definition are the DataContractAttribute and DataMemberAttribute annotations. The presence of the DataContractAttribute indicates to the WCF serialization infrastructure that the type can be serialized, and the presence of the DataMemberAttribute on the stateful members of the type indicates which members should be serialized. Notice that the two String members and the DateTime member use the private access modifier. Object-oriented visibility has no impact on whether a member can be serialized by the default WCF serialization infrastructure.

Even though the WCF infrastructure creates a type like the RequestReservationParams type automatically, it is sometimes necessary to create an explicit data contract and to reference that data contract in an operation contract. Reasons for creating an explicit data contract include needing to reference several explicit data contracts from one data contract and encapsulating the state passed to an operation. I’ll offer some guidance to help you choose in the section “My Philosophy on Contracts” later in this chapter. For now, I simply want to make the point7 that explicit data contracts are a viable option for defining a service contract. The service contract shown here illustrates how to use the RequestReservationParams type in a service contract:

 [OperationContract]     Int32? RequestReservation(RequestReservationParams resParams);

The DataContractAttribute Type

The DataContractAttribute can be applied to enumerated types, structures, and classes. The Name and Namespace properties are the only two instance properties defined on the DataContractAttribute. The Name property maps the name of the data contract to the name of the annotated type, and the Namespace property sets the XML namespace of the data contract, as shown here:

 [DataContract(Name="ReservationInformation",               Namespace="http://contoso.com/Restaurant")] public sealed class RequestReservationParams {   [DataMember(Name="resDateTime")]  private DateTime? _resDateTime;   [DataMember(Name="restaurantName")] private String _restaurantName;   [DataMember(Name="partyName")]    private String _partyName;   // other implementation omitted for clarity }

The DataMemberAttribute Type

The DataMemberAttribute can be applied to fields and properties. It defines several instance properties: EmitDefaultValue, IsRequired, Name, and Order. The EmitDefaultValue property indicates whether the default value should be emitted or extracted from the serialized data. For reference types, the default value is null, and for value types, the default value is 0. The IsRequired property indicates whether the member must be present in the serialized data. The Name property maps the name of the type member to an element name in the serialized data. The Order property indicates the order of the members in the serialized data.

The EmitDefaultValue and IsRequired properties are important in situations where a field must have a value. If the field in the data contract does not need to be present in the serialized data, set the IsRequired property to false. With this setting, the absence of a value for a field does not create any data in the resultant serialized data. If the field is required and the default value has meaning (for example, it is null or 0), two paths are possible. The first path is to manually set the field to its default value before serialization. The second option is to set the EmitDefaultValue property to true. When the EmitDefaultValue property is true, the serialized data will contain the default value, even though the field did not have a value in the data contract. If a field in a data contract is a nullable type, the default value is null.

Message Contracts

The last type of WCF contract is the message contract. A message contract offers more control over the content of the serialized data than a data contract, because a message contract defines message headers and the message body. In addition, message contracts also provide the means to express the security requirements of a member during serialization. The paradigm for creating a message contract is similar to the paradigm for creating a data contract in that a message contract is an annotated type definition and a service contract references a message contract in an operation.

Note 

All message contracts must implement a public parameterless constructor.

The attributes used in a message contract are the MessageContractAttribute, the MessageHeaderAttribute, and the MessageBodyMemberAttribute. The following code snippet shows a message contract that encapsulates the parameters of the ChangeReservation operation:

 [MessageContract(WrapperName = "ChangeReservationNewDateTime",                  WrapperNamespace="http://contoso.com/Restaurant")] public sealed class ChangeReservationNewDateTime {   [MessageHeader(Name="reservationId", MustUnderstand = true)]   private Int32? _reservationId;   [MessageBodyMember(Name="newDateTime")]   private DateTime? _newDateTime;   public ChangeReservationNewDateTime() {  }   public ChangeReservationNewDateTime(Int32? reservationId,                                       DateTime? newDateTime) {     this._newDateTime = newDateTime.Value;     this._reservationId = reservationId;   }   public Int32? ReservationId {     get { return _reservationId; }   }   public DateTime? NewDateTime {     get { return _newDateTime; }   } }

Notice that the _reservationId field is annotated with the MessageHeaderAttribute. As its name implies, a field annotated with the MessageHeaderAttribute will be serialized as a message header. The primary reason for adding a field as a message header is to make it available to messaging infrastructures. I show the reservationId field as a header for illustrative purposes only. In real life, values that messaging routers or other intermediaries act on are good candidates for message headers. If the illustrated restaurant reservation system used the reservationId field to route the reservation to a restaurant for confirmation of the change, then and only then would the reservationId field make sense as a header.

Note 

Adding a message header to a message contract should be done with caution, because message headers are applicable only in message formats that allow headers. Some message formats like Plain Old XML (POX) do not allow message headers, so forcing a field to be a message header throws an InvalidOperationException.

The MessageHeaderAttribute defines several instance properties that map to standard SOAP header attributes: Actor, MustUnderstand, and Relay. Setting these properties changes the serialized data as well as how the message contract is used after a receiving application receives a message.

The MessageBodyMemberAttribute annotation indicates the fields placed in the body of a message. One message can include multiple body members, and the MessageBodyMemberAttribute defines an Order property that specifies the order of the body members.

The MessageContractAttribute, MessageHeaderAttribute, and MessageBodyMemberAttribute types define a ProtectionLevel property. This property indicates the minimum security that must be applied to that member; the paradigm for this property follows the ProtectionLevel property on the OperationContractAttribute. In effect, this property provides granular control over the minimum security level for the entire contract, a header, or a member in the body.

Operation Compatibility

The operations in a service contract define the structure for the messages sent to the operation and the messages that the operation sends as a reply. WCF categorizes these messages into two broad categories: typed and untyped. Typed messages are message contracts and the System.ServiceModel.Channels.Message type. Untyped messages are data contracts and serializable types. Typed messages cannot be commingled with untyped messages. The following are examples of viable operations in a service:

 [ServiceContract] public interface ISomeService {   // BEGIN TYPED MESSAGES   [OperationContract]   void SomeOperation(Message input);   [OperationContract]   Message SomeOperation2(Message input);   [OperationContract]   Message SomeOperation3(SomeMessageContract input);   [OperationContract]   void SomeOperation4(SomeMessageContract input);   // END TYPED MESSAGES   // BEGIN UNTYPED MESSAGES   [OperationContract]   void SomeOperation5(Int32? input);   [OperationContract]   Int32? SomeOperation6(Int32? input, String otherInput);   [OperationContract]   Int32? SomeOperation7(SomeDataContract input);   [OperationContract]   Int32? SomeOperation8(SomeDataContract input, Int32? input2);   // END UNTYPED MESSAGES }

Pay close attention to the last operation in the preceding code snippet. An operation’s parameter can be the combination of a data contract and another serializable type. The WCF infrastructure treats typed and untyped messages differently. Typed messages can include header definitions, whereas untyped messages cannot. If an operation uses typed messages, there would be ambiguity about where other parameters should go. Rather than make an arbitrary decision, the team opted to keep a clean separation between typed and untyped messages.

My Philosophy on Contracts

WCF is fundamentally a platform that can handle a wide variety of messaging functionality. I view WCF as a progression from distributed object platforms, and I believe that forcing yesterday’s paradigms of distributed computing will not work in the long run in WCF. Over time, I have seen a few observations about distributed computing hold true, and these observations have shaped my view on how to approach WCF contracts:

  • Complex object-oriented type hierarchies are hard to manage in the long term, especially in distributed computing.

  • What seems simple today becomes complex tomorrow, and what seems complex today becomes unmanageable tomorrow.

  • Any single-vendor environment becomes a multiple-vendor environment over time.

As a result of these observations, I offer the following recommendations about contracts.

Avoid Defining Methods in Data Contracts and Message Contracts

Data contracts and message contracts are fundamentally state containers. As they are serialized and sent over the proverbial wire, method implementations are not sent with them. In my view, this simple fact is enough to bolster the case for dumbing down the definition of a data or message contract to simply stateful members. Any implementation that I add to a data or message contract is for the purpose of simplifying the instantiation of a contract or extracting state from the object.

Obviously, data and message contracts reside in more complex type hierarchies either at the sender or the receiver. Adding implementation to these contracts rather than to other parts of your type hierarchies means that the line between a contract and an implementation is blurred. Blurring this line can lead to major versioning problems and should be avoided.

In my view, a better approach is to build factories that can build a stateful data contract or message contract on demand. These factory types should also include a facility to parse an object and do meaningful work based on the state of that object. This sort of design ensures that the objects that are serialized and sent over the wire adhere to the contract they must uphold.

Seal Contracts

I like sealed classes, and I think contracts should be sealed. Sealed classes simplify testing and make the behavior of classes more predictable, and invoking methods on sealed classes is more efficient than on unsealed classes. In fact, I think that the Microsoft Visual C# team should have made classes sealed by default and offered up an unsealed keyword instead. If a class is sealed today and it needs to be unsealed tomorrow, the change is not a breaking one. In type hierarchies (other than contracts), inheritance can come in quite handy. Among other things, it paves the way for virtual methods, and this gives our type hierarchies tremendous flexibility and extensibility. With contracts, however, I do not think inheritance makes sense.

If you buy off on the idea that contracts should not contain implementation, the only viable reason to need inheritance is to serialize members in a base class. It is important to keep in mind that a contract maps .NET constructs to messaging constructs. In essence, a contract maps a vendor-specific type system that has full object-oriented support to what should be a vendor-agnostic type system with questionable object-oriented support. While XSD has some low fidelity means to express inheritance, what about messaging structures that are not XML based? How can you express inheritance in messaging structures like JSON (JavaScript Object Notation)? You simply can’t do this with any reliability. If this is true, there is no way to reliably express the complexities of a contract type hierarchy in a truly vendor-agnostic way.

In some organizations, there might be a view that their applications need to work only with other WCF applications. In these scenarios, a contract type hierarchy might make sense, but I urge caution. Businesses change, businesses buy other businesses, and trading alliances that seem impossible today have a way of becoming reality tomorrow. While there is never any guarantee that an application can deal with tomorrow’s changes, making contract types sealed does offer much more protection against the inevitable changes of tomorrow than does a complex contract type hierarchy.

Use Nullable Types

If a WCF application needs to interoperate, contract members that are value types should be nullable value types. In my view, all WCF applications should be designed to interoperate because of the possibilities that offers for the future. The prototypical example is the DateTime type. In Java, the Date type is a reference type. In the .NET Framework, it is a value type. If such a date representation is used as a field in a contract, the Java application can send a value of null for it. Since a null value for a DateTime has no meaning in the .NET Framework, an exception will be thrown. If the DateTime is set to a nullable DateTime, the WCF application can deal with a null DateTime field.




Inside Windows Communication Foundation
Inside Windows Communication Foundation (Pro Developer)
ISBN: 0735623066
EAN: 2147483647
Year: 2007
Pages: 106
Authors: Justin Smith

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net