By Juval Löwy
Book Price: $44.99 USD
£31.99 GBP
PDF Price: $31.99
Cover | Table of Contents | Colophon
System.ServiceModel namespace.System.ServiceModel namespace.
[base address]/[optional URI]
[transport]://[machine or domain][:optional port]
http://localhost:8001 http://localhost:8001/MyService net.tcp://localhost:8002/MyService net.pipe://localhost/MyPipe net.msmq://localhost/private/MyService net.msmq://localhost/MyService
http://localhost:8001
http://localhost:8001/MyService
MyService is waiting for my calls."net.tcp for the transport, and typically include a port number such as:
net.tcp://localhost:8002/MyService
net.tcp://localhost/MyService
net.tcp://localhost:8002/MyService net.tcp://localhost:8002/MyOtherService
http for transport, and can also use int and string, but you can easily define explicit opt-in data contracts for custom types. Chapter 3 is dedicated to defining and using data contracts, and subsequent chapters make use of data contracts as required.ServiceContractAttribute is defined as:
[AttributeUsage(AttributeTargets.Interface|AttributeTargets.Class,
Inherited = false)]
public sealed class ServiceContractAttribute : Attribute
{
public string Name
{get;set;}
public string Namespace
{get;set;}
//More members
}
[ServiceContract] interface IMyContract { [OperationContract] string MyMethod(string text); //Will not be part of the contract string MyOtherMethod(string text); } class MyService : IMyContract { public string MyMethod(string text) { return "Hello " + text; } public string MyOtherMethod(string text) { return "Cannot call this method over WCF"; } }
<%@ ServiceHost
Language = "C#"
Debug = "true"
CodeBehind = "~/App_Code/MyService.cs"
Service = "MyService"
%>
namespace MyNamespace
{
[ServiceContract]
interface IMyContract
{...}
class MyService : IMyContract
{...}
}
<system.serviceModel>
<services>
<service name = "MyService" behaviorConfiguration = "MEXGET">
<host>
<baseAddresses>
<add baseAddress = "http://localhost:8000/"/>
</baseAddresses>
</host>
...
</service>
<service name = "MyOtherService" behaviorConfiguration = "MEXGET">
<host>
<baseAddresses>
<add baseAddress = "http://localhost:8001/"/>
</baseAddresses>
</host>
...
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name = "MEXGET">
<serviceMetadata httpGetEnabled = "true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
/out switch to indicate a different name.MyService in IIS or the WAS, and have enabled metadata public sharing over HTTP-GET, simply run this command line:NetNamedPipeBinding and its configuration. However, by and large, most clients and services do resort to using a config file.
ChannelFactory<T> class (and its supporting types), shown in Example 1-21, enables you to create a proxy on the fly.
public class ContractDescription
{
public Type ContractType
{get;set;}
//More members
}
public class ServiceEndpoint
{
public ServiceEndpoint(ContractDescription contract,Binding binding,
EndpointAddress address);
public EndpointAddress Address
{get;set;}
public Binding Binding
{get;set;}
public ContractDescription Contract
{get;}
//More members
}
public abstract class ChannelFactory : ...
{
public ServiceEndpoint Endpoint
{get;}
//More members
}
public class ChannelFactory<T> : ChannelFactory,...
{
public ChannelFactory(ServiceEndpoint endpoint);
public ChannelFactory(string configurationName);
public ChannelFactory(Binding binding,EndpointAddress endpointAddress);
public static T CreateChannel(Binding binding,EndpointAddress endpointAddress);
public T CreateChannel( );
//More Members
}
ChannelFactory<T> with the endpoint—either the endpoint name from the client config file, or the binding and address objects, or a ServiceEndpoint object. Next, use the CreateChannel( ) method to obtain a reference to the proxy and use its methods. Finally, close the proxy by either casting it to IDisposable and calling the Dispose( ) method or to ICommunicationObject and calling the Close( ) method:
ChannelFactory<IMyContract> factory = new ChannelFactory<IMyContract>( );
IMyContract proxy1 = factory.CreateChannel( );
using(proxy1 as IDisposable)
{
proxy1.MyMethod( );
}
IMyContract proxy2 = factory.CreateChannel( );
proxy2.MyMethod( );
ICommunicationObject channel = proxy2 as ICommunicationObject;
Debug.Assert(channel != null);
channel.Close( );
CreateChannel( )|
Name
|
Supports reliability
|
Default reliability
|
Supports ordered |
|---|
ServiceContract attribute presented in the previous chapter exposes an interface (or a class) as a service-oriented contract, allowing you to program in languages like C#, using constructs like interfaces, while exposing the constructs as WCF contracts and services. This chapter starts by discussing how to better bridge the gap between the two programming models by enabling operation overloading
and contract inheritance. Next you will see a few simple yet powerful service contract design and factoring guidelines and techniques. The chapter ends by showing how to interact programmatically at run runtime with the metadata of the exposed contracts.
interface ICalculator
{
int Add(int arg1,int arg2);
double Add(double arg1,double arg2);
}
InvalidOperationException at the service host load time:
//Invalid contract definition:
[ServiceContract]
interface ICalculator
{
[OperationContract]
int Add(int arg1,int arg2);
[OperationContract]
double Add(double arg1,double arg2);
}
Name property of the OperationContract attribute to alias the operation:
[AttributeUsage(AttributeTargets.Method)]
public sealed class OperationContractAttribute : Attribute
{
public string Name
{get;set;}
//More members
}
[ServiceContract]
interface ICalculator
{
[OperationContract(Name = "AddInt")]
int Add(int arg1,int arg2);
[OperationContract(
interface ICalculator
{
int Add(int arg1,int arg2);
double Add(double arg1,double arg2);
}
InvalidOperationException at the service host load time:
//Invalid contract definition:
[ServiceContract]
interface ICalculator
{
[OperationContract]
int Add(int arg1,int arg2);
[OperationContract]
double Add(double arg1,double arg2);
}
Name property of the OperationContract attribute to alias the operation:
[AttributeUsage(AttributeTargets.Method)]
public sealed class OperationContractAttribute : Attribute
{
public string Name
{get;set;}
//More members
}
[ServiceContract]
interface ICalculator
{
[OperationContract(Name = "AddInt")]
int Add(int arg1,int arg2);
[OperationContract(Name = "AddDouble")]
double Add(double arg1,double arg2);
}
[ServiceContract]
public interface ICalculator
{
[OperationContract]
int AddInt(int arg1,int arg2);
[OperationContract]
double AddDouble(double arg1,double arg2);
}
public partial class CalculatorClient : ClientBase<ICalculator>,ICalculator
{
public int AddInt(int arg1,int arg2)
{
return Channel.AddInt(arg1,arg2);
}
public double AddDouble(double arg1,double arg2)
{
return Channel.AddDouble(arg1,arg2);
}
//Rest of the proxy
}
ServiceContract attribute is not inheritable:
[AttributeUsage(Inherited = false,...)]
public sealed class ServiceContractAttribute : Attribute
{...}
ServiceContract attribute, as shown in Example 2-3.
[ServiceContract]
interface ISimpleCalculator
{
[OperationContract]
int Add(int arg1,int arg2);
}
[ServiceContract]
interface IScientificCalculator : ISimpleCalculator
{
[OperationContract]
int Multiply(int arg1,int arg2);
}
class MyCalculator : IScientificCalculator
{
public int Add(int arg1,int arg2)
{
return arg1 + arg2;
}
public int Multiply(int arg1,int arg2)
{
return arg1 * arg2;
}
}
<service name = "MyCalculator">
<endpoint
address = "http://localhost:8001/MyCalculator/"
binding = "basicHttpBinding"
contract = "IScientificCalculator"
/>
</service>
Action and ResponseAction properties of the OperationContract attribute, the name of the original contract that defined each operation:
[AttributeUsage(AttributeTargets.Method)]
public sealed class OperationContractAttribute : Attribute
{
public string Action
{get;set;}
public string ReplyAction
{get;set;}
//More members
}
IDog service contract and have different kinds of services, such as the PoodleService and the GermanShepherdService implement the IDog contract:
[ServiceContract]
interface IDog
{
[OperationContract]
void Fetch( );
[OperationContract]
void Bark( );
[OperationContract]
long GetVetClinicNumber( );
[OperationContract]
void Vaccinate( );
}
class PoodleService : IDog
{...}
class GermanShepherdService : IDog
{...}
IDog service contract is not well factored. Even though all the operations are things a dog should support, ?wsdl). To ease the task of parsing the returned metadata, WCF offers a few helper classes, available in the System.ServiceModel.Description namespaces, as shown in Example 2-7.
public enum MetadataExchangeClientMode
{
MetadataExchange,
HttpGet
}
class MetadataSet : ...
{...}
public class ServiceEndpointCollection : Collection<ServiceEndpoint>
{...}
public class MetadataExchangeClient
{
public MetadataExchangeClient( );
public MetadataExchangeClient(Binding mexBinding);
public MetadataSet GetMetadata(Uri address,MetadataExchangeClientMode mode);
//More members
}
public abstract class MetadataImporter
{
public abstract ServiceEndpointCollection ImportAllEndpoints( );
//More members
}
public class WsdlImporter : MetadataImporter
{
public WsdlImporter(MetadataSet metadata);
//More members
}
public class ServiceEndpoint
{
public EndpointAddress Address
{get;set;}
public Binding Binding
{get;set;}
public ContractDescription Contract
{get;}
//More members
}
public class ContractDescription
{
public string Name
{get;set;}
public string Namespace
{get;set;}
//More members
}
Serializable attribute is workable, it is inadequate for service-oriented interaction between clients and services. It denotes all members in the type as serializable and therefore part of the data schema for that type. It is much better to have an opt-in approach, where only members the contract developer wants to explicitly include in the data contract are included. The Serializable attribute forces the data type to be serializable in order to be used as a parameter in a contract operation, and does not offer clean separation between the serviceness aspect of the type (ability to use it as a WCF operation parameter) and the ability to serialize it. The attribute offers no support for aliasing type name or members, or for mapping a new type to a predefined data contract. The attribute operates directly on member fields, and completely bypasses any logical properties used to access those fields. It would be better to allow those properties to add their values when accessing the fields. Finally, there is no direct support for versioning because any versioning information is supposedly captured by the formatters. Consequently, it is difficult to deal with versioning over time.DataContractAttribute defined in the System.Runtime.Serialization namespace:
[AttributeUsage(AttributeTargets.Enum |
AttributeTargets.Struct|
AttributeTargets.Class,
Inherited = false)]
public sealed class DataContractAttribute : Attribute
{
public string Name
{get;set;}
public string Namespace
{get;set;}
}
DataContract attribute on a class or struct does not cause WCF to serialize any of its members:
[DataContract]
struct Contact
{
//Will not be part of the data contract
public string FirstName;
public string LastName;
}
DataContract attribute does is merely opt-in the type, indicating that the type is willing to be marshaled by value. To serialize any of its members, you must apply the DataContract attribute is not inheritable:
[DataContract]
class Contact
{
[DataMember]
public string FirstName;
[DataMember]
public string LastName;
}
[DataContract]
class Customer : Contact
{
[DataMember]
public int OrderNumber;
}
InvalidDataContractException at the service load time. WCF lets you mix the Serializable and DataContract attribute in the class hierarchy:
[Serializable]
class Contact
{...}
[DataContract]
class Customer : Contact
{..}
Serializable attribute will be at the root of the class hierarchy, if at all, because new classes should use the DataContract attribute. When you export a data contract hierarchy, the metadata maintains the hierarchy, and all levels of the class hierarchy are exported when making use of the subclass in a service contract:
[ServiceContract]
interface IContactManager
{
[OperationContract]
void AddCustomer(Customer customer);//Contact is exported as well
...
}
[ServiceContract]
interface IContactManager
{
//Cannot accept Customer object here:
[OperationContract]
void AddContact(Contact contact);
//Cannot return Customer objects here:
[OperationContract]
Contact[] GetContacts( );
}
Customer class as well:
[DataContract]
class Customer : Contact
{
[DataMember]
public int OrderNumber;
}
Contact contact = new Customer( );
contact.FirstName = "Juval";
contact.LastName = "Lowy";
ContactManagerClient proxy = new ContactManagerClient( );
//Service call will fail:
proxy.AddContact(contact);
proxy.Close( );
Name property of the DataContract or DataMember attribute to map one data contract to another. In the case of the DataContract attribute, the Name property defaults to the type's name, so these two definitions are identical:
[DataContract]
struct Contact
{...}
[DataContract(Name = "Contact")]
struct Contact
{...}
DataMember attribute, the Name defaults to member name, so these two definitions are identical:[DataMember] public string FirstName; [DataMember(Name = "FirstName")] public string FirstName;
[DataContract]
struct Contact
{
[DataMember]
public string FirstName;
[DataMember]
public string LastName;
}
[DataContract(Name = "Contact")]
struct Person
{
[DataMember(Name = "FirstName")]
public string Name;
[DataMember(Name = "LastName")]
public string Surname;
}