WCF Contracts
- Service Contracts define the service and (OperationContract) methods
- Data Contracts define the message data (request/response) passing to/from the OperationContracts
- Message Contracts are low level data contracts that change the underlying SOAP xml
Service Contracts
//best practice to add name and namespace
[ServiceContract(Name = "Calculator", Namespace = "http://x.com/")]
public interface IService
{
[OperationContract]
Response Add(int number, [MessageParameter(Name = "Number2")]int number2);
}
Data Contracts
The messages that are passing to and from the OperationContract are data contracts.
//DataContract must be on classes/enums (and is not inherited).
[DataContract(Namespace = "http://y.com/")]
public class Response
{
//Defaults: IsRequired=false (optional), EmitDefaultValue=true (passes nil for null/0)
//IsRequired=true is not compatible with EmitDefaultValue=false
[DataMember(IsRequired = false)]
public int Total { get; set; }
}
There's also an [EnumMember] and a [CollectionDataContract]
When you have polymorphic objects/collections, add KnownTypes: [KnownType(typeof(Gorilla))]. Generic types must be returned in a Type[] from a method.
Message Contracts
For detailed control of serialization (wrapping/ headers) use [MessageContract] over dataContract. See MSDN
//default wrapping: <s:Body><SpecificRequest><Description>...
//no wrapping (IsWrapped=false): <s:Body><Description>...
//renamed (WrapperName=): <s:Body><specificRequest>...
[MessageContract(WrapperName = "specificRequest")]
public class SpecificRequest
{
//appears in the header, encrypted and signed
[MessageHeader(ProtectionLevel =
System.Net.Security.ProtectionLevel.EncryptAndSign)]
public string User { get; set; }
//add a specific namespace instead of the service contract
[MessageBodyMember(Namespace = "http://mw.com/")]
public string Description { get; set; }
}
More control
- For forward-proof versioning, implement IExtensibleDataObject with an ExtensionData property.
- For RPC, use [DataContractFormat(Style = OperationFormatStyle.Rpc)]
- For specific XML schemas, use [XmlSerializerFormat] on the ServiceContract.
- Custom headers: In config, endpoints can have a <headers> element
POX
You handle things on the XML level. By default, a client puts an Action on a request which maps to the OperationContract (the SOAP Action name is {method}Request and {method}Response e.g. DoWorkRequest). You can do it manually:
//specify a specific action
[OperationContract(Action = "http://calculator/add")]
Message DoWork(Message message);
//or use a wildcard to handle all request with or without an action
[OperationContract(Action = "*")]
Message DoEverything(Message message);
Working with System.ServiceModel.Channels.Message
public Message DoWork(Message message)
{
if (message.IsEmpty) return NoBodyFound();
//message.State == Created
XmlReader body = message.GetReaderAtBodyContents();
//message.State == Read
//or (if message.State == Created - if Read, InvalidOperationException)
var request = message.GetBody<SpecificRequest>();
//to read without setting Read state
var messageBuffer = message.CreateBufferedCopy(int.MaxValue);
var copy = messageBuffer.CreateMessage();
foreach (var headerInfo in message.Headers)
{
//read the header info
}
//response - use static CreateMesage (with XmlReader, a derived BodyWriter, a MessageFault or ...)
return Message.CreateMessage(MessageVersion.Soap12, "actionName", new SpecificRequest());
}
You can send POX messages with no SOAP envelope: add MessageVersion.None to the TextMessageEncodingBindingElement.
Behavior/Contract
Some "behaviors" (optional features) can be expressed in the contract.
- FaultContracts: [FaultContract(typeof(DivideByZeroException))]
- Sessions: [ServiceContract(SessionMode = SessionMode.Required)]
[OperationContract(IsInitiating = false, IsTerminating = true)] - ProtectionLevel: [OperationContract(ProtectionLevel = ProtectionLevel.EncryptAndSign)]
- Transactions: See below
Transactions
TCP and WS Bindings (not basicHttpBinding) support transactions.
Contract
[OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)] //or Mandatory (NB: not OneWay!)
void DoWork();
Service Implementation
//requires transaction and will commit when finished
[OperationBehavior(TransactionScopeRequired = true,
TransactionAutoComplete = true)]
public void DoWork()
Client binding
Must be manual!
var binding = new WSHttpBinding();
binding.TransactionFlow = true;