WCF
.Net 1 remoting and webservices
See also WCF Ajax/JSON
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]
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
Message Exchange Patterns
- Request/Response (default)
- OneWay - [OperationContract(IsOneWay=true)]
- Duplex - [ServiceContract(CallbackContract=typeof(x)] and the client will have a long running session. Not good at interop or scaling.
Behavior
Extensibility
endpoint.Behaviors.Add(myIEndpointBehavior)
- IParameterInspector (BeforeCall/ AfterCall):
- Server: add in IEndpointBehavior.ApplyDispatchBehavior
- Client: add in IEndpointBehavior.ApplyClientBehavior
Address
ServiceHost and endpoints
In svc:
<%@ ServiceHost Service="Service1" %>
In code:
//or WebServiceHost for web
var host = new ServiceHost(
//or pass new MyService() for singleton
typeof(MyService),
//params or array of base addresses
new Uri("http://localhost:8000/"));
host.AddServiceEndpoint(
typeof(IMyService),
new WebHttpBinding(),
//baseAddress + service name = full url.
//If no base address, can use full address here.
"MyService");
Hosts
- For non-HTTP IIS, make sure the appropriate service is running and use IIS 7's appcmd: %windir%\system32\inetsrv\appcmd.exe set app "x/service" /enabledProtocols:http,net.tcp
- For MSMQ, create a queue named MyServiceHost/service.svc : the endpoint has address="net.msmq://localhost/private/MyServiceHost/service.svc" binding="netMsmqBinding"
- In a hosted environment (IIS, WAS), a ServiceHost is configured automatically. To customize, replace the ServiceHostFactory to return your own dynamically configured ServiceHost: <%@ ServiceHost Factory="DerivedFactory"
Endpoints
- You can have multiple endpoints for one host (either different bindings to the same contract or different contracts with the same binding - don't new NetTcpBinding() in each )
- ClientViaBehavior is an intermediate route- the client uses the clientVia address, but the "To:" address is the final address.
- WS-Addressing (routing): var addressHeader = AddressHeader.CreateAddressHeader("name", "http://ns.org", valueobject);
Metadata
- Metadata support ?wsdl : <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
- HTTP GET: Service behavior: <serviceMetadata httpGetEnabled="true" />
WCF Clients
- Client proxies (via Visual Studio Service Reference, or svcutil, or manual) inherit ClientBase<IService> (if it has a callback, it's a DuplexClientBase<IService>)
- You can create the proxy dynamically using ChannelFactory<T>:
using (var factory = new ChannelFactory<IService>(
new NetTcpBinding(),
"net.tcp://localhost:13024/calculator/"))
{
var proxy = factory.CreateChannel();
var result = proxy.Add(1, 2);
} - Generate async code (in VS Service Reference/Advanced):
using(var proxy = new CalculatorClient())
{
var cb = new AsyncCallback(
(asyncResult) =>
{
var client = (CalculatorClient)asyncResult.AsyncState ;
var result = client.EndAdd(asyncResult);
});
proxy.BeginAdd(1, 2, cb, null);
} - For duplex clients, create an InstanceContext with your Callback class and pass it to the proxy (a DuplexClientBase)
Faults/Exceptions
- Add a [FaultContract(typeof(DivideByZeroException))] for types of faults that may be raised
- Throw a untyped FaultException or
throw new FaultException<DivideByZeroException>(new DivideByZeroException());- If you throw a faultException the service doesn't enter a faulted state
- you can also throw new FaultException<string>("Opps");
- Client can use faultException.Detail for a typed faultException.
Or: subscribe the client Faulted event.
Then rebuild the client (new Client())
client.Abort() as your client may throw on Close/Dispose when it is faulted
//don't do using for ClientBase
var client = new CalculatorClient();
try
{
var result = client.Add(1, 10);
//close can throw exceptions!
//and will if(client.State == CommunicationState.Faulted)
client.Close();
total = result.Total;
}
//catch typed exceptions
catch (FaultException<DivideByZeroException> exception)
{
var inner = exception.Detail;
client.Abort();
throw inner;
}
//catch untyped exception
catch (FaultException)
{
client.Abort();
throw;
}
//catch any other general error
catch (CommunicationException)
{
client.Abort();
}
//catch a timeout (slow connection)
catch (TimeoutException)
{
client.Abort();
}
Normal exceptions
IncludeExceptionDetailInFaults="True". Actually it returns a FaultException(typeof(ExceptionDetail)) but does not use a wsdl FaultContract
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceDebug includeExceptionDetailInFaults="true" />
ServiceContract code:
#if DEBUG
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
#endif
Logging and handling exceptions:
- Implement IErrorHandler to log (HandleError) and/or wrap (ProvideError) exceptions
- Subscribe to serviceHost.Faulted event.