Monday, July 5, 2010

WCF

WCF

Windows Communication Foundation (WCF) is an SDK for developing and deploying services on Windows. WCF provides a runtime environment for your services, enabling you to expose CLR types as services, and to consume other services as CLR types. Although in theory you could build services without WCF, in practice building services is significantly easier with WCF. WCF is Microsoft’s implementation of a set of industry standards defining service interactions, type conversion, marshaling, and various protocols’ management. Because of that, WCF provides interoperability between services. WCF provides developers with the essential off-the-shelf plumbing required by almost any application, and as such, it greatly increases productivity. The first release of WCF provides many useful facilities for developing services, such as hosting, service instance management, asynchronous calls, reliability, transaction management, disconnected queued calls, and security. WCF also has an elegant extensibility model that you can use to enrich the basic offering. In fact, WCF itself is written using this extensibility model. The rest of the chapters in this book are dedicated to those aspects and features. Most all of the WCF functionality is included in a single assembly called System.ServiceModel.dll in the System.ServiceModel namespace.

Services

A service is a unit of functionality exposed to the world. In that respect, it is the next evolutionary step in the long journey from functions to objects to components to services. Service-orientation (SO) is an abstract set of principles and best practices for building SO applications. If you are unfamiliar with the principles of service-orientation, Appendix A provides a concise overview and motivation for using service-orientation. The rest of this book assumes you are familiar with these principles. A service-oriented application (SOA) aggregates services into a single logical application similar to the way a component-oriented application aggregates components or an objectoriented application aggregates objects, as shown in Figure 1-1.



Figure 1-1. A service-oriented application


The services can be local or remote, developed by multiple parties using any technology, versioned independently, and even execute on different timelines. Inside a service, you will find concepts such as languages, technologies, platforms, versions, and frameworks, yet between services, only prescribed communication patterns are allowed.

The client of a service is merely the party consuming its functionality. The client can be literally anything—a Windows Forms class, an ASP.NET page, or another service.



Services' Execution Boundaries

With WCF, the client never interacts with the service directly, even when dealing with a local, in-memory service. Instead, the client always uses a proxy to forward the call to the service. The proxy exposes the same operations as the service, plus some proxy-management methods.

WCF allows the client to communicate with the service across all execution boundaries. On the same machine (see Figure 1-2), the client can consume services in the same app domain, across app domains in the same process, or across processes.



Figure 1-2. Same-machine communication using WCF

WCF and location transparency

In the past, distributed computing technologies such as DCOM or .NET Remoting aspired to provide the same programming model to the client whether the object was local or remote. In the case of a local call, the client used a direct reference, and when dealing with a remote object, the client used a proxy. The problem with this approach of trying to take the local programming model and make it the remote programming model is that there is much more to a remote call than an object with a wire. Complex issues such as life cycle management, reliability, state management, scalability, and security raised their heads, making the remote programming model significantly more complex, all because it tried to be what it is not—a local object. WCF also strives to provide the client with the same programming model regardless of the location of the service. However, the WCF approach is the exact opposite: it takes the remote programming model of instantiating and using a proxy and uses it even in the most local case. Because all interactions are done via a proxy, requiring the same configuration and hosting, WCF maintains the same programming model for the local and remote cases; thus it not only enables you to switch locations without affecting the client, but also significantly simplifies the application programming model.


Addresses

In WCF, every service is associated with a unique address. The address provides two important elements: the location of the service and the transport protocol or transport schema used to communicate with the service. The location portion of the address indicates the name of the target machine, site, or network; a communication port, pipe, or queue; and an optional specific path or URI. A URI is a Universal Resource Identifier, and can be any unique string, such as the service name or a GUID.

WCF 1.0 supports the following transport schemas:

  • HTTP
  • TCP
  • Peer network
  • IPC (Inter-Process Communication over named pipes)
  • MSMQ

Addresses always have the following format:

[base address]/[optional URI]

The base address is always in this format:

[transport]://[machine or domain][:optional port]

Here are a few sample addresses:

http://localhost:8001
http://localhost:8001/MyService

TCP Addresses

TCP addresses use net.tcp for the transport, and typically include a port number such as:

net.tcp://localhost:8002/MyService

When a port number is not specified, the TCP address defaults to port 808:

net.tcp://localhost/MyService

It is possible for two TCP addresses (from the same host, which will be discussed more later on in this chapter) to share a port:

net.tcp://localhost:8002/MyService
net.tcp://localhost:8002/MyOtherService

HTTP Addresses

HTTP addresses use http for transport, and can also use https for secure transport. You typically use HTTP addresses with outward-facing Internet-based services, and can specify a port such as:

http://localhost:8001

When the port number is unspecified, it defaults to 80. Similar to TCP addresses, two HTTP addresses from the same host can share a port, even on the same machine.

HTTP-based addresses are also used throughout this book.

IPC Addresses

IPC addresses use net.pipe for transport, to indicate the use of the Windows named pipe mechanism. In WCF, services that use named pipes can only accept calls from the same machine. Consequently, you must specify either the explicit local machine name or localhost for the machine name, followed by a unique string for the pipe name:

net.pipe://localhost/MyPipe

You can only open a named pipe once per machine, and therefore it is not possible for two named pipe addresses to share a pipe name on the same machine.

MSMQ Addresses

MSMQ addresses use net.msmq for transport, to indicate the use of the Microsoft Message Queue (MSMQ). You must specify the queue name. When you’re dealing with private queues, you must specify the queue type, but that can be omitted for public queues:

net.msmq://localhost/private/MyService
net.msmq://localhost/MyService

Peer Network Address

Peer network addresses use net.p2p for transport, to indicate the use of the Windows peer network transport. You must specify the peer network name as well as a unique path and port. Using and configuring peer networks is beyond the scope of this book, and you will see very little mention of peer networks in subsequent chapters.

Contracts

In WCF, all services expose contracts. The contract is a platform-neutral and standard way of describing what the service does. WCF defines four types of contracts.

Service contracts
Describe which operations the client can perform on the service. Service contracts are the subject of the next chapter, but are used extensively in every chapter in this book.
Data contracts
Define which data types are passed to and from the service. WCF defines implicit contracts for built-in types such as 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.
Fault contracts
Define which errors are raised by the service, and how the service handles and propagates errors to its clients. Chapter 6 is dedicated to defining and using fault contracts.
Message contracts
Allow the service to interact directly with messages. Message contracts can be typed or untyped, and are useful in interoperability cases and when there is an existing message format you have to comply with. As a WCF developer, you should use message contracts only rarely, so this book makes no use of message contracts.



The Service Contract

The 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 }

This attribute allows you to define a service contract. You can apply the attribute on an interface or a class, as shown in Example 1-1.

Example 1-1. Defining and implementing a service contract

[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";    } }

The ServiceContract attribute maps a CLR interface (or inferred interface, as you will see later on) to a technology-neutral service contract. The ServiceContract attribute exposes a CLR interface (or a class) as a WCF contract, independently of that type’s visibility. The type visibility has no bearing on WCF, because visibility is a CLR concept. Applying the ServiceContract attribute on an internal interface exposes that interface as a public service contract, ready to be consumed across the service boundary. Without the ServiceContract attribute, the interface is not visible to WCF clients, in line with the service-oriented tenet that service boundaries are explicit. To enforce that, all contracts must explicitly opt in: only interfaces (or classes) decorated with the ServiceContract attribute will be considered as WCF contracts. Other types will not.

In addition, none of the members of the type will ever be part of the contract when using the ServiceContract attribute. You must explicitly indicate to WCF which methods to expose as part of the WCF contract using the OperationContractAttribute, defined as:

[AttributeUsage(AttributeTargets.Method)] public sealed class OperationContractAttribute : Attribute {    public string Name    {get;set;}    //More members }

You can only apply the OperationContract attribute on methods, but not on properties, indexers, or events, which are CLR concepts. WCF only understands operations—logical functions—and the OperationContract attribute exposes a contract method as a logical operation to perform as part of the service contract. Other methods on the interface (or class) that do not have the OperationContract attribute will not be part of the contract. This enforces the explicit service boundary and maintains an explicit opt-in model for the operations themselves. In addition, a contract operation cannot use object references as parameters—only primitive types or data contracts are allowed.

Applying the ServiceContract attribute

WCF lets you apply the ServiceContract attribute on an interface or on a class. When you apply it on an interface, some class needs to implement the interface. In general, you use plain C# or VB to implement the interface, and nothing in the service class code pertains to it being a WCF service:

[ServiceContract] interface IMyContract {    [OperationContract]    string MyMethod( ); } class MyService : IMyContract {    public string MyMethod( )    {       return "Hello WCF";    } }

You can use implicit or explicit interface implementation:

class MyService : IMyContract {    string IMyContract.MyMethod( )    {       return "Hello WCF";    } }

A single class can support multiple contracts by deriving and implementing multiple interfaces decorated with the ServiceContract attribute.

[ServiceContract] interface IMyContract {    [OperationContract]    string MyMethod( ); } [ServiceContract] interface IMyOtherContract {    [OperationContract]    void MyOtherMethod( ); }   class MyService : IMyContract,IMyOtherContract {    public string MyMethod( )    {...}    public void MyOtherMethod( )    {...} }

There are, however, a few implementation constraints on the service implementation class. You should avoid parameterized constructors because only the default constructor will ever be used by WCF. Also, although the class can use internal properties, indexers, and static members, no WCF client will ever be able to access them.

WCF also lets you apply the ServiceContract attribute directly on the service class, without ever defining a separate contract first:

//Avoid [ServiceContract] class MyService {    [OperationContract]    string MyMethod( )    {       return "Hello WCF";    } }

Under the covers, WCF will infer the contract definition. You can apply the OperationContract attribute on any method of the class, be it private or public.

Names and namespaces

You can and should define a namespace for your contract. The contract namespace serves the same purpose in WCF as it does in .NET programming: to scope a type of contract and reduce the overall chance for a collision. You use the Namespace property of the ServiceContract attribute to provide a namespace:

[ServiceContract(Namespace = "MyNamespace")] interface IMyContract {...}

Unspecified, the contract namespace defaults to http://tempuri.org. For outwardfacing services you would typically use your company’s URL, and for intranet services you can use any meaningful unique name, such as MyApplication.

By default, the exposed name of the contract will be the name of the interface used. However, you could use an alias for a contract to expose a different name to the clients in the metadata using the Name property of the ServiceContract attribute:

[ServiceContract(Name = "IMyContract")] interface IMyOtherContract {...}

In similar manner, the name of the publicly exposed operation defaults to the method name, but you can use the Name property of the OperationContract attribute to alias it to a different publicly exposed name:

[ServiceContract] interface IMyContract {    [OperationContract(Name = "SomeOperation")]    void MyMethod(string text); }

You will see a use for these properties in the next chapter.



Hosting

The WCF service class cannot exist in a void. Every WCF service must be hosted in a Windows process called the host process. A single host process can host multiple services, and the same service type can be hosted in multiple host processes. WCF makes no demand on whether or not the host process is also the client process. Obviously, having a separate process advocates fault and security isolation. It is also immaterial who provides the process or what kind of a process is involved. The host can be provided by IIS, by the Widows Activation Service (WAS) on Windows Vista, or by the developer as part of the application.

A special case of hosting is in-process hosting, or in-proc for short, where the service resides in the same process as the client. The host for the in-proc case is, by definition, provided by the developer.

IIS Hosting

The main advantage of hosting a service in the Microsoft Internet Information Server (IIS) web server is that the host process is launched automatically upon the first client request, and you rely on IIS to manage the life cycle of the host process. The main disadvantage of IIS hosting is that you can only use HTTP. With IIS5, you are further restricted to having all services use the same port number.

Hosting in IIS is very similar to hosting a classic ASMX web service. You need to create a virtual directory under IIS and supply a .svc file. The .svc file functions similar to an .asmx file, and is used to identify the service code behind the file and class. Example 1-2 shows the syntax for the .svc file.

You can even inject the service code inline in the .svc file, but that is not advisable, as is the case with ASMX web services.

Using Visual Studio 2005

You can use Visual Studio 2005 to generate a boilerplate IIS-hosted service. From the File menu, select New Website and then select WCF Service from the New Web Site dialog box. This causes Visual Studio 2005 to create a new web site, service code, and matching .svc file. You can also use the Add New Item dialog to add another service later on.

The Web.Config file

The web site config file (Web.Config) must list the types you want to expose as services. You need to use fully qualified type names, including the assembly name, if the service type comes from an unreferenced assembly:

.serviceModel>         name = "MyNamespace.MyService">       ...         .serviceModel>

Self-Hosting

Self-hosting is the name for the technique used when the developer is responsible for providing and managing the life cycle of the host process. Self-hosting is used both in the case of wanting a process (or machine) boundary between the client and the service, and when using the service in-proc—that is, in the same process as the client. The process you need to provide can be any Windows process, such as a Windows Forms application, a Console application, or a Windows NT Service. Note that the process must be running before the client calls the service, which typically means you have to pre-launch it. This is not an issue for NT Services or in-proc. Providing a host can be done with only a few lines of code, and it does offer a few advantage over IIS hosting.

Similar to IIS hosting, the hosting application config file (App.Config) must list the types of the services you wish to host and expose to the world:

serviceModel>        "MyNamespace.MyService">       ...         serviceModel>

In addition, the host process must explicitly register the service types at runtime and open the host for client calls, which is why the host process must be running before the client calls arrive. Creating the host is typically done in the Main( ) method using the class ServiceHost, defined in Example 1-3.

Example 1-3. The ServiceHost class

public interface ICommunicationObject {    void Open( );    void Close( );    //More members } public abstract class CommunicationObject : ICommunicationObject    {...} public abstract class ServiceHostBase : CommunicationObject,IDisposable,...    {...} public class ServiceHost : ServiceHostBase,... {    public ServiceHost(Type serviceType,params Uri[] baseAddresses);    //More members }

Using Visual Studio 2005

Visual Studio 2005 allows you to add a WCF service to any application project by selecting WCF Service from the Add New Item dialog box. The service added this way is, of course, in-proc toward the host process, but can be accessed by out-ofproc clients as well.

Self-hosting and base addresses

You can launch a service host without providing any base address by omitting the base addresses altogether:

public static void Main( ) {    ServiceHost host = new ServiceHost(typeof(MyService));    host.Open( );    Application.Run(new MyForm( ));    host.Close( ); }

Do not provide a null instead of an empty list, because that will throw an exception:

ServiceHost host; host = new ServiceHost    (typeof(MyService),null);

You can also register multiple base addresses separated by a comma, as long as the addresses do not use the same transport schema, as in the following snippet (note the use of the params qualifier in Example 1-3):

Uri tcpBaseAddress = new Uri("net.tcp://localhost:8001/"); Uri httpBaseAddress = new Uri("http://localhost:8002/"); ServiceHost host = new ServiceHost(typeof(MyService),    tcpBaseAddress,httpBaseAddress);

WCF lets you also list the base addresses in the host config file:

serviceModel>        "MyNamespace.MyService">                           "net.tcp://localhost:8001/"/>           "http://localhost:8002/"/>                       ...         serviceModel>

Advanced hosting features

The ICommunicationObject interface supported by ServiceHost offers some advanced features, listed in Example 1-4.

Example 1-4. The ICommunicationObject interface

public interface ICommunicationObject {    void Open( );    void Close( );    void Abort( );      event EventHandler Closed;    event EventHandler Closing;    event EventHandler Faulted;    event EventHandler Opened;    event EventHandler Opening;      IAsyncResult BeginClose       (AsyncCallback callback,object state);    IAsyncResult BeginOpen       (AsyncCallback callback,object state);    void EndClose(IAsyncResult result);    void EndOpen(IAsyncResult result);      CommunicationState State    {get;}    //More members } public enum CommunicationState {    Created,    Opening,    Opened,    Closing,    Closed,    Faulted }

If opening or closing the host is a lengthy operation, you can do so asynchronously with the BeginOpen( ) and BeginClose( ) methods. You can subscribe to hosting events such as state changes or faults, and you can use the State property to query for the host status. Finally, the ServiceHost class also implements the Abort( ) method. Abort( ) is an ungraceful exit—when called, it immediately aborts all service calls in progress and shuts down the host. Active clients will get an exception.

The ServiceHost class

You can improve on the WCF-provided ServiceHost class by defining the ServiceHost class, as shown in Example 1-5.

Example 1-5. The ServiceHost class

public class ServiceHost : ServiceHost {    public ServiceHost( ) : base(typeof(T))    {}    public ServiceHost(params string[] baseAddresses) :       base(typeof(T),Convert(baseAddresses))    {}    public ServiceHost(params Uri[] baseAddresses) :       base(typeof(T),baseAddresses)    {}    static Uri[] Convert(string[] baseAddresses)    {       Converter convert = delegate(string address)       {          return new Uri(address);       };       return Array.ConvertAll(baseAddresses,convert);    } }

ServiceHost provides simple constructors that do not require the service type as a construction parameter, and can operate on raw strings instead of the cumbersome Uri. I’ll add quite a few extensions, features, and capabilities to ServiceHost in the rest of the book.

WAS Hosting

The Windows Activation Service (WAS) is a system service available with Windows Vista. WAS is part of IIS7, but can be installed and configured separately. To use the WAS for hosting your WCF service, you need to supply a .svc file, just as with IIS. The main difference between IIS and WAS is that the WAS is not limited to HTTP and can be used with any of the available WCF transports, ports, and queues.

WAS offers many advantages over self-hosting, including application pooling, recycling, idle time management, identity management, and isolation, and is the host process of choice when available; that is, when you can target either a Vista Server machine for scalability or a Vista client machine used as a server machine for a handful of clients only.

Still, the self-hosted process offers singular advantages such as in-proc hosting, dealing with unknown customer environments, and easy programmatic access to the advanced hosting features described previously.



Bindings

There are multiple aspects of communication with any given service, and there are many possible communication patterns: messages can be synchronous request/reply or asynchronous fire-and-forget; messages can be bidirectional; messages can be delivered immediately or queued; and the queues can be durable or volatile. There are many possible transport protocols for the messages, such as HTTP (or HTTPS), TCP, P2P (peer network), IPC (named pipes), or MSMQ. There are a few possible message encoding options: you can chose plain text to enable interoperability, binary encoding to optimize performance, or MTOM (Message Transport Optimization Mechanism) for large payloads. There are a few options for securing messages: you can choose not to secure them at all, to provide transport-level security only, to provide message-level privacy and security, and of course there are numerous ways for authenticating and authorizing the clients. Message delivery might be unreliable or reliable end-to-end across intermediaries and dropped connections, and the messages might be processed in the order they were sent or in the order they were received. Your service might need to interoperate with other services or clients that are only aware of the basic web service protocol, or they may be capable of using the score of WS-* modern protocols such as WS-Security and WS-Atomic Transactions. Your service may need to interoperate with legacy clients over raw MSMQ messages, or you may want to restrict your service to interoperate only with another WCF service or client.

If you start counting all the possible communication and interaction options, the number of permutations is probably in the tens of thousands. Some of those choices may be mutually exclusive, and some may mandate other choices. Clearly, both the client and the service must be aligned on all these options in order to communicate properly. Managing this level of complexity adds no business value to most applications, and yet the productivity and quality implications of making the wrong decisions are severe.

To simplify these choices and make them more manageable, WCF groups together a set of such communication aspects in bindings. A binding is merely a consistent, canned set of choices regarding the transport protocol, message encoding, communication pattern, reliability, security, transaction propagation, and interoperability. Ideally, you would extract all these "plumbing" aspects out of your service code and allow the service to focus solely on the implementation of the business logic. Binding enables you to use the same service logic over drastically different plumbing.

You can use the WCF-provided bindings as is, you can tweak their properties, or you can write your own custom bindings from scratch. The service publishes its choice of binding in its metadata, enabling clients to query for the type and specific properties of the binding because the client must use the exact same binding values as the service. A single service can support multiple bindings on separate addresses.

Standard Bindings

WCF defines nine standard bindings:

Basic binding
Offered by the BasicHttpBinding class, this is designed to expose a WCF service as a legacy ASMX web service, so that old clients can work with new services. When used by the client, this binding enables new WCF clients to work with old ASMX services.
TCP binding
Offered by the NetTcpBinding class, this uses TCP for cross-machine communication on the intranet. It supports a variety of features, including reliability, transactions, and security, and is optimized for WCF-to-WCF communication. As a result, it requires both the client and the service to use WCF.
Peer network binding
Offered by the NetPeerTcpBinding class, this uses peer networking as a transport. The peer network-enabled client and services all subscribe to the same grid and broadcast messages to it. Peer networking is beyond the scope of this book since it requires an understanding of grid topology and mesh computing strategies.
IPC binding
Offered by the NetNamedPipeBinding class, this uses named pipes as a transport for same-machine communication. It is the most secure binding since it cannot accept calls from outside the machine and it supports a variety of features similar to the TCP binding.
Web Service (WS) binding
Offered by the WSHttpBinding class, this uses HTTP or HTTPS for transport, and is designed to offer a variety of features such as reliability, transactions, and security over the Internet.
Federated WS binding
Offered by the WSFederationHttpBinding class, this is a specialization of the WS binding, offering support for federated security. Federated security is beyond the scope of this book.
Duplex WS binding
Offered by the WSDualHttpBinding class, this is similar to the WS binding except it also supports bidirectional communication from the service to the client.
MSMQ binding
Offered by the NetMsmqBinding class, this uses MSMQ for transport and is designed to offer support for disconnected queued calls.
MSMQ integration binding
Offered by the MsmqIntegrationBinding class, this converts WCF messages to and from MSMQ messages, and is designed to interoperate with legacy MSMQ clients. Using this binding is beyond the scope of this book.

Format and Encoding

Each of the standard bindings uses different transport and encoding, as listed in Table 1-1.

Table 1-1. Transport and encoding for standard bindings (default encoding is in bold)

Name

Transport

Encoding

Interoperable

BasicHttpBinding

HTTP/HTTPS

Text, MTOM

Yes

NetTcpBinding

TCP

Binary

No

NetPeerTcpBinding

P2P

Binary

No

NetNamedPipeBinding

IPC

Binary

No

WSHttpBinding

HTTP/HTTPS

Text, MTOM

Yes

WSFederationHttpBinding

HTTP/HTTPS

Text, MTOM

Yes

WSDualHttpBinding

HTTP

Text, MTOM

Yes

NetMsmqBinding

MSMQ

Binary

No

MsmqIntegrationBinding

MSMQ

Binary

Yes

Having a text-based encoding enables a WCF service (or client) to communicate over HTTP with any other service (or client) regardless of its technology. Binary encoding over TCP or IPC yields the best performance but at the expense of interoperability, by mandating WCF-to-WCF communication.



Choosing a Binding

Choosing a binding for your service should follow the decision-activity diagram shown in Figure 1-4.



Figure 1-4. Choosing a binding


The first question you should ask yourself is whether your service needs to interact with non-WCF clients. If the answer is yes, and if the client is a legacy MSMQ client, choose the MsmqIntegrationBinding that enables your service to interoperate over MSMQ with such a client. If you need to interoperate with a non-WCF client and that client expects basic web service protocol (ASMX web services), choose the BasicHttpBinding, which exposes your WCF service to the outside world as if it were an ASMX web service (that is, a WSI-basic profile). The downside is that you cannot take advantage of most of the modern WS-* protocols. However, if the non-WCF client can understand these standards, choose one of the WS bindings, such as WSHttpBinding, WSFederationBinding, or WSDualHttpBinding. If you can assume that the client is a WCF client, yet it requires offline or disconnected interaction, choose the NetMsmqBinding that uses MSMQ for transporting the messages. If the client requires connected communication, but could be calling across machine boundaries, choose the NetTcpBinding that communicates over TCP. If the client is on the same machine as the service, choose the NetNamedPipeBinding that uses named pipes to maximize performance. You may fine-tune binding selections based on additional criteria such as the need for callbacks (WSDualHttpBinding) or federated security (WSFederationBinding).

Using a Binding

Each binding offers literally dozens of configurable properties. There are three modes of working with bindings. You can use the built-in bindings as is if they fit your requirements. You can tweak and configure some of their properties such as transaction propagation, reliability, and security. You can also write your own custom bindings. The most common scenario is using an existing binding almost as is, and merely configuring two or three of its aspects. Application developers will hardly ever need to write a custom binding, but framework developers may need to.

Endpoints

Every service is associated with an address that defines where the service is, a binding that defines how to communicate with the service, and a contract that defines what the service does. This triumvirate governing the service is easy to remember as the ABC of the service. WCF formalizes this relationship in the form of an endpoint. The endpoint is the fusion of the address, contract, and binding (see Figure 1-5).



Figure 1-5. The endpoint


Every endpoint must have all three elements, and the host exposes the endpoint. Logically, the endpoint is the service’s interface, and is analogous to a CLR or COM interface. Note in Figure 1-5 the use of the traditional "lollipop" to denote an endpoint.

Conceptually, even in C# or VB, an interface is an endpoint: the address is the memory address of the type’s virtual table, the binding is CLR JIT compiling, and the contract is the interface itself. Because in classic .NET programming you never deal with addresses or bindings, you take them for granted. In WCF the address and the binding are not ordained, and need to be configured.

Every service must expose at least one business endpoint and each endpoint has exactly one contract. All endpoints on a service have unique addresses, and a single service can expose multiple endpoints. These endpoints can use the same or different bindings and can expose the same or different contracts. There is absolutely no relationship between the various endpoints a service provides.

It is important to point out that nothing in the service code pertains to its endpoints and they are always external to the service code. You can configure endpoints either administratively using a config file or programmatically.



Administrative Endpoint Configuration

Configuring an endpoint administratively requires placing the endpoints in the hosting process’ config file. For example, given this service definition:

namespace MyNamespace {    [ServiceContract]    interface IMyContract    {...}    class MyService : IMyContract    {...} }

Example 1-6 shows the required entries in the config file. Under each service type you list its endpoints.

Example 1-6. Administrative endpoint configuration

.serviceModel>         name = "MyNamespace.MyService">                address = "http://localhost:8000/MyService/"         binding = "wsHttpBinding"         contract = "MyNamespace.IMyContract"       />         .serviceModel>

When you specify the service and the contract type, you need to use fully qualified type names. I will omit the namespace in the examples throughout the remainder of this book, but you should use a namespace when applicable. Note that if the endpoint provides a base address, then that address schema must be consistent with the binding, such as HTTP with WSHttpBinding. A mismatch causes an exception at the service load time.

Example 1-7 shows a config file defining a single service that exposes multiple endpoints. You can configure multiple endpoints with the same base address as long as the URI is different.

Example 1-7. Multiple endpoints on the same service

"MyService">   "http://localhost:8000/MyService/"     binding = "wsHttpBinding"     contract = "IMyContract"   />   "net.tcp://localhost:8001/MyService/"     binding = "netTcpBinding"     contract = "IMyContract"   />   "net.tcp://localhost:8002/MyService/"     binding = "netTcpBinding"     contract = "IMyOtherContract"   /> 

Administrative configuration is the option of choice in the majority of cases because it provides the flexibility to change the service address, binding, and even exposed contracts without rebuilding and redeploying the service.

Using base addresses

In Example 1-7, each endpoint provided its own base address. When you provide an explicit base address, it overrides any base address the host may have provided.

You can also have multiple endpoints use the same base address, as long as the endpoint addresses differ in their URIs:

 name = "MyService">        address = "net.tcp://localhost:8001/MyService/"     binding = "netTcpBinding"     contract = "IMyContract"   />        address = "net.tcp://localhost:8001/MyOtherService/"     binding = "netTcpBinding"     contract = "IMyContract"   /> 

Alternatively, if the host provides a base address with a matching transport schema, you can leave the address out, in which case the endpoint address will be the same as the base address of the matching transport:

   binding = "wsHttpBinding"   contract = "IMyContract" />

If the host does not provide a matching base address, loading the service host will fail with an exception.

When you configure the endpoint address you can add just the relative URI under the base address:

   address = "SubAddress"   binding = "wsHttpBinding"   contract = "IMyContract" />

The endpoint address in this case will be the matching base address plus the URI, and, again, the host must provide a matching base address.

Binding configuration

You can use the config file to customize the binding used by the endpoint. To that end, add the bindingConfiguration tag to the endpoint section, and name a customized section in the bindings section of the config file. Example 1-8 demonstrates using this technique to enable transaction propagation. What the transactionFlow tag does will be explained in Chapter 7.

Example 1-8. Service-side binding configuration

.serviceModel>         name = "MyService">                address = "net.tcp://localhost:8000/MyService/"         bindingConfiguration = "TransactionalTCP"         binding = "netTcpBinding"         contract = "IMyContract"       />                address = "net.tcp://localhost:8001/MyService/"         bindingConfiguration = "TransactionalTCP"         binding = "netTcpBinding"         contract = "IMyOtherContract"       />                        name = "TransactionalTCP"         transactionFlow = "true"       />         .serviceModel>

As shown in Example 1-8, you can reuse the named binding configuration in multiple endpoints simply by referring to it.



Programmatic Endpoint Configuration

Programmatic endpoint configuration is equivalent to administrative configuration. Instead of resorting to a config file, you rely on programmatic calls to add endpoints to the ServiceHost instance. Again, these calls are always outside the scope of the service code. ServiceHost provides overloaded versions of the AddServiceEndpoint( ) method:

public class ServiceHost     : ServiceHostBase {    public ServiceEndpoint        AddServiceEndpoint(Type implementedContract,       Binding binding,        string address);     //Additional members }

You can provide AddServiceEndpoint( ) methods with either relative or absolute addresses, just as with a config file. Example 1-9 demonstrates programmatic configuration of the same endpoints as in Example 1-7.

Example 1-9. Service-side programmatic endpoint configuration

ServiceHost host = new ServiceHost(typeof(MyService)); Binding wsBinding = new WSHttpBinding( ); Binding tcpBinding = new NetTcpBinding( ); host.AddServiceEndpoint(typeof(IMyContract),wsBinding,    "http://localhost:8000/MyService"); host.AddServiceEndpoint(typeof(IMyContract),tcpBinding,    "net.tcp://localhost:8001/MyService"); host.AddServiceEndpoint(typeof(IMyOtherContract),tcpBinding,    "net.tcp://localhost:8002/MyService"); host.Open( );

When you add an endpoint programmatically, the address is given as a string, the contract as a Type, and the binding as one of the subclasses of the abstract class Binding, such as:

public class NetTcpBinding : Binding,... {...}

To rely on the host base address, provide an empty string if you want to use the base address, or just the URI to use the base address plus the URI:

Uri tcpBaseAddress = new Uri("net.tcp://localhost:8000/");   ServiceHost host = new ServiceHost(typeof(MyService),tcpBaseAddress);   Binding tcpBinding = new NetTcpBinding( );   //Use base address as address host.AddServiceEndpoint(typeof(IMyContract),tcpBinding,""); //Add relative address host.AddServiceEndpoint(typeof(IMyContract),tcpBinding,"MyService"); //Ignore base address host.AddServiceEndpoint(typeof(IMyContract),tcpBinding,    "net.tcp://localhost:8001/MyService");   host.Open( );

As with administrative configuration using a config file, the host must provide a matching base address; otherwise, an exception occurs. In fact, there is no difference between programmatic and administrative configuration. When you use a config file, all WCF does is parse the file and execute the appropriate programmatic calls in its place.



Retrieved from "http://en.csharp-online.net/WCF_Essentials%E2%80%94Programmatic_Endpoint_Configuration"




Personal tools


Binding configuration

You can programmatically set the properties of the binding used. For example, here is the code required to enable transaction propagation similar to Example 1-8:

ServiceHost host = new ServiceHost(typeof(MyService));   NetTcpBinding tcpBinding = new NetTcpBinding( );   tcpBinding.TransactionFlow = true;   host.AddServiceEndpoint(typeof(IMyContract),tcpBinding,    "net.tcp://localhost:8000/MyService");   host.Open( );

Note that when you’re dealing with specific binding properties, you typically interact with a concrete binding subclass such as NetTcpBinding, and not its abstract base class Binding as in Example 1-9.



Metadata Exchange

A service has two options for publishing its metadata. You can provide the metadata over the HTTP-GET protocol, or you can use a dedicated endpoint, discussed later. WCF can provide the metadata over HTTP-GET automatically for your service; all you need is to enable it by adding an explicit service behavior. Behaviors are described in subsequent chapters. For now, all you need to know is that a behavior is a local aspect of the service, such as whether or not it wants to exchange its metadata over HTTP-GET. You can add this behavior administratively or programmatically. Example 1-10 shows a host application config file, where both hosted services reference a custom behavior section that enables the metadata exchange over HTTPGET. The address the clients need to use for the HTTP-GET is the registered HTTP base address of the service. You can also specify in the behavior an external URL for this purpose.

Example 1-10. Enabling metadata exchange behavior using a config file

.serviceModel>             name = "MyService"            behaviorConfiguration = "MEXGET">                                  baseAddress = "http://localhost:8000/"/>                             ...               name = "MyOtherService"            behaviorConfiguration = "MEXGET">                                  baseAddress = "http://localhost:8001/"/>                               ...                          name = "MEXGET">          httpGetEnabled = "true"/>                .serviceModel>

Once you have enabled the metadata exchange over HTTP-GET, you can navigate to the HTTP base address (if present) using a browser. If all is well, you will get a confirmation page, such as the one shown in Figure 1-6, letting you know that you have successfully hosted a service. The confirmation page is unrelated to IIS hosting, and you can use a browser to navigate to the service address even when self-hosting.

Enabling Metadata Exchange Programmatically

To programmatically enable the metadata exchange over HTTP-GET, you first need to add the behavior to the collection of behaviors the host maintains for the service type. The ServiceHostBase class offers the Description property of the type ServiceDescription:

public abstract class ServiceHostBase : ... {    public ServiceDescription Description   {get;}     //More members }

The service description, as its name implies, is the description of the service with all its aspects and behaviors. ServiceDescription contains a property called Behaviors of the type KeyedByTypeCollection with IServiceBehavior as the generic parameter:

public class KeyedByTypeCollection :     KeyedCollection {    public T Find( );    public T Remove( );    //More members } public class ServiceDescription {    public KeyedByTypeCollection Behaviors    {get;} }

Metadata Exchange Endpoint

The service can also publish its metadata over a special endpoint called the metadata exchange endpoint, sometimes referred to as the MEX endpoint. Figure 1-7 shows a service with business and a metadata exchange endpoint. However, you typically do not show the metadata exchange endpoint in your design diagrams.



Figure 1-7. The metadata exchange endpoint


That endpoint supports an industry standard for exchanging metadata, represented in WCF by the IMetadataExchange interface:

[ServiceContract(...)] public interface IMetadataExchange {    [OperationContract(...)]    Message Get(Message request);    //More members }

The details of this interface are inconsequential. Like most of these industry standards, it is difficult to implement. Fortunately, WCF can have the service host automatically provide the implementation of IMetadataExchange and expose the metadata exchange endpoint. All you need to do is designate the address and the binding to use, as well as add the service metadata behavior. For the bindings, WCF provides dedicated binding transport elements for the HTTP, HTTPS, TCP, and IPC protocols. For the address, you can provide a full address or use any of the registered base addresses. There is no need to enable the HTTP-GET option, but there is no harm either. Example 1-12 shows a service that exposes three MEX endpoints, over HTTP, TCP, and IPC. For demonstration purposes, the TCP and IPC MEX endpoints use relative addresses and the HTTP one uses an absolute address.

Adding MEX endpoints programmatically

Like any other endpoint, you can only add a metadata exchange endpoint programmatically before opening the host. WCF does not offer a dedicated binding type for the metadata exchange endpoint. Instead, you need to construct a custom binding that uses the matching transport binding element, and then provide that binding element as a construction parameter to an instance of a custom binding. Finally, call the AddServiceEndpoint( ) method of the host providing it with the address, the custom binding, and the IMetadataExchange contract type. Example 1-13 shows the code required to add a MEX endpoint over TCP. Note that before adding the endpoint you must verify the presence of the metadata behavior.

Example 1-13. Adding TCP MEX endpoint programmatically

BindingElement bindingElement =     new TcpTransportBindingElement( ); CustomBinding binding =     new CustomBinding(bindingElement); Uri tcpBaseAddress =     new Uri("net.tcp://localhost:9000/"); ServiceHost host =     new ServiceHost(typeof(MyService),tcpBaseAddress); ServiceMetadataBehavior metadataBehavior; metadataBehavior =     host.Description.Behaviors.    Find( ); if(metadataBehavior == null) {    metadataBehavior = new ServiceMetadataBehavior( );    host.Description.Behaviors.Add(metadataBehavior); } host.AddServiceEndpoint(    typeof(IMetadataExchange),binding,"MEX"); host.Open( );

Streamlining with ServiceHost

You can extend ServiceHost to automate the code in Examples 1-11 and 1-13. ServiceHost offers the EnableMetadataExchange Boolean property that you can call to both add the HTTP-GET metadata behavior and the MEX endpoints:



public class ServiceHost : ServiceHost {    public bool EnableMetadataExchange    {get;set;}    public bool HasMexEndpoint    {get;}    public void AddAllMexEndPoints( );    //More members }

When set to true, EnableMetadataExchange adds the metadata exchange behavior, and if no MEX endpoint is available, EnableMetadataExchange adds a MEX endpoint for each registered base address scheme. Using ServiceHost, Examples 1-11 and 1-13 are reduced to:

ServiceHost host = new ServiceHost( ); host.EnableMetadataExchange = true; host.Open( );

ServiceHost also offers the HasMexEndpoint Boolean property, which returns true if the service has any MEX endpoint (regardless of transport protocol), and the AddAllMexEndPoints( ) method, which adds a MEX endpoint for each registered base address of the scheme type of HTTP, TCP, or IPC. Example 1-14 shows the implementation of these methods.

The Metadata Explorer

The metadata exchange endpoint provides metadata that describes not just contracts and operations, but also information about data contracts, security, transactions, reliability, and faults. To visualize the metadata of a running service I developed the Metadata Explorer tool, available along with the rest of the source code of this book. Figure 1-8 shows the Metadata Explorer reflecting the endpoints of Example 1-7. To use the Metadata Explorer, simply provide it with the HTTP-GET address or the metadata exchange endpoint of the running service to reflect the returned metadata.


Client-Side Programming

To invoke operations on the service, the client first needs to import the service contract to the client’s native representation. If the client uses WCF, the common way of invoking operations is to use a proxy. The proxy is a CLR class that exposes a single CLR interface representing the service contract. Note that if the service supports several contracts (over at least as many endpoints), the client needs a proxy per contract type. The proxy provides the same operations as service’s contract, but also Figure 1-8. The Metadata Explorer has additional methods for managing the proxy life cycle and the connection to the service. The proxy completely encapsulates every aspect of the service: its location, its implementation technology and runtime platform, and the communication transport.

Generating the Proxy

You can use Visual Studio 2005 to import the service metadata and generate a proxy. If the service is self-hosted, first launch the service and then select Add Service Reference… from the client project’s context menu. If the service is hosted in IIS or the WAS, there is no need to pre-launch the service. Interestingly enough, if the service is self-hosted in another project in the same solution as the client project, you can launch the host in Visual Studio 2005 and still add the reference, because unlike most project settings, this option is not disabled during a debug session (see Figure 1-9).



Figure 1-9. Generate a proxy using Visual Studio 2005


This brings up the Add Service Reference dialog box, where you need to supply the base address of the service (or a base address and a MEX URI) and the namespace to contain the proxy.

Instead of Visual Studio 2005, you can use the SvcUtil.exe command-line utility. You need to provide SvcUtil with the HTTP-GET address or the metadata exchange endpoint address and, optionally, with a proxy filename. The default proxy filename is output.cs but you can also use the /out switch to indicate a different name.

For example, if you’re hosting the service MyService in IIS or the WAS, and have enabled metadata public sharing over HTTP-GET, simply run this command line:

SvcUtil http://localhost/MyService/MyService.svc /out:Proxy.cs

When you are hosting in IIS and selecting a port other than port 80 (such as port 81), you must provide that port number as part of the base address:

SvcUtil http://localhost:81/MyService/MyService.svc /out:Proxy.cs

With self-hosting, assuming the self-hosted service enabled metadata publishing over HTTP-GET, registers these base addresses and exposes the matching metadata exchange endpoints with a relative address of MEX:

http://localhost:8002/ net.tcp://localhost:8003 net.pipe://localhost/MyPipe

After launching the host, you can use the following commands to generate the proxy:

SvcUtil http://localhost:8002/MEX /out:Proxy.cs SvcUtil http://localhost:8002/ /out:Proxy.cs SvcUtil net.tcp://localhost:8003/MEX /out:Proxy.cs SvcUtil net.pipe://localhost/MyPipe/MEX /out:Proxy.cs

The main advantage of using SvcUtil over Visual Studio 2005 is the numerous options it offers through switches for controlling the generated proxies, as you will see later in this book.

For this service definition:

[ServiceContract(Namespace = "MyNamespace")] interface IMyContract {    [OperationContract]    void MyMethod( ); } class MyService : IMyContract {    public void MyMethod( )    {...} }

SvcUtil generates the proxy shown in Example 1-15. You can safely remove the settings of Action and ReplyAction in most cases, since the default of using the method name is good enough.

Example 1-15. Client proxy file

[ServiceContract(Namespace = "MyNamespace")] public interface IMyContract {    [OperationContract(Action = "MyNamespace/IMyContract/MyMethod",    ReplyAction = "MyNamespace/IMyContract/MyMethodResponse")]    void MyMethod( ); } public partial class MyContractClient : ClientBase,IMyContract {    public MyContractClient( )    {}    public MyContractClient(string endpointName) : base(endpointName)    {}    public MyContractClient(Binding binding,EndpointAddress remoteAddress) :       base(binding,remoteAddress)    {}    /* Additional constructors */    public void MyMethod( )    {    Channel.MyMethod( );    } }

The most glaring aspect of the proxy class is that it has no reference to the serviceimplementing class, only to the contract exposed by the service. You can use the proxy in conjunction with a client-side config file that provides the address and the binding, or you can use it without a config file. Note that each proxy instance points at exactly one endpoint. The endpoint to interact with is provided to the proxy at construction time. As I mentioned previously, if the service-side contract does not provide a namespace, it will implicitly use the http://tempuri.org namespace.

Administrative Client Configuration

The client needs to know where the service is located and use the same binding as the service, and, of course, import the service contract definition. In essence, this is exactly the same information captured in the service’s endpoint. To reflect that, the client config file contains information about the target endpoints and even uses the same endpoint configuration schema as the host.

Example 1-16 shows the client configuration file required to interact with a service whose host is configured according to Example 1-6.

Example 1-16. Client config file

serviceModel>        "MyEndpoint"       address = "http://localhost:8000/MyService/"       binding = "wsHttpBinding"       contract = "IMyContract"     />    serviceModel>

The client config file may list as many endpoints as the services it deals with support, and the client may use any one of them. Example 1-17 shows the client config file matching the host config file of Example 1-7. Note that each endpoint in the client config file has a unique name.

Example 1-17. Client config file with multiple target endpoints

serviceModel>        "FirstEndpoint"       address = "http://localhost:8000/MyService/"       binding = "wsHttpBinding"       contract = "IMyContract"     />     "SecondEndpoint"       address = "net.tcp://localhost:8001/MyService/"       binding = "netTcpBinding"       contract = "IMyContract"     />     "ThirdEndpoint"       address = "net.tcp://localhost:8002/MyService/"       binding = "netTcpBinding"       contract = "IMyOtherContract"     />    serviceModel>

Binding configuration

You can customize the client-side standard bindings to match the service binding in a manner identical to the service configuration, as shown in Example 1-18.

Example 1-18. Client-side binding configuration

serviceModel>        "MyEndpoint"       address = "net.tcp://localhost:8000/MyService/"       bindingConfiguration = "TransactionalTCP"       binding = "netTcpBinding"       contract = "IMyContract"     />                  "TransactionalTCP"         transactionFlow = "true"         />         serviceModel>

Generating the client config file

By default, SvcUtil also auto-generates a client-side config file called output.config.

You can specify a config filename using the /config switch:

SvcUtil http://localhost:8002/MyService/ /out:Proxy.cs     /config:App.Config

And you can suppress generating the config file using the /noconfig switch:

SvcUtil http://localhost:8002/MyService/ /out:Proxy.cs     /noconfig

I recommend never letting SvcUtil generate the config file. The reason is that it generates fully articulated binding sections that often just state the default values, which tends to clutter the config file.



In-proc configuration

With in-proc hosting, the client config file is also the service host config file, and the same file contains both service and client entries, as shown in Example 1-19.

Example 1-19. In-proc hosting config file

serviceModel>        "MyService">       "net.pipe://localhost/MyPipe"         binding = "netNamedPipeBinding"         contract = "IMyContract"       />                "MyEndpoint"       address = "net.pipe://localhost/MyPipe"       binding = "netNamedPipeBinding"       contract = "IMyContract"     />    serviceModel>

Note the use of the named pipe binding for in-proc hosting.

The SvcConfigEditor

WCF provides a config file editor called SvcConfigEditor.exe that can edit both host and client configuration files (see Figure 1-10). You can also launch the editor from within Visual Studio by right-clicking on the configuration file (both the client and the host files) and selecting Edit WCF Configuration.

I have mixed feelings about SvcConfigEditor. On the one hand, it edits the config files nicely and it saves developers the need to know the configuration schema. On the other hand, it does not save the need to thoroughly understand WCF configuration, and for the most part, the light editing done in a config file is faster by hand than editing using Visual Studio 2005.


Creating and Using the Proxy

The proxy class derives from the class ClientBase, defined as:

public abstract class ClientBase :     ICommunicationObject,IDisposable {    protected ClientBase(string endpointName);    protected ClientBase       (Binding binding,EndpointAddress remoteAddress);    public void Open( );    public void Close( );    protected T Channel    {get;}    //Additional members }

ClientBase accepts a single generic type parameter identifying the service contract that this proxy encapsulates. The Channel property of ClientBase is of the type of that type parameter. The generated subclass of ClientBase simply delegates to Channel the method call (see Example 1-15).

To use the proxy, the client first needs to instantiate a proxy object and to provide the constructor with endpoint information: either the endpoint section name from the config file, or the endpoint address and binding objects if you’re not using a config file. The client can then use the proxy methods to call the service, and when the client is done, the client needs to close the proxy instance. For example, given the same definitions as in Examples 1-15 and 1-16, the client constructs the proxy, identifying the endpoint to use from the config file; invokes the method; and closes the proxy:

MyContractClient proxy = new MyContractClient("MyEndpoint"); proxy.MyMethod( ); proxy.Close( );

If only one endpoint is defined in the client config file for the type of contract the proxy is using, then the client can omit the endpoint name from the proxy’s constructor:

MyContractClient proxy = new MyContractClient(); proxy.MyMethod( ); proxy.Close( );

However, if multiple endpoints are available for the same contract type then the proxy throws an exception.

Closing the proxy

It is a recommended best practice to always close the proxy when the client is done using it. You will see in Chapter 4 why the client needs to close the proxy in certain cases, because closing the proxy terminates the session with the service and closes the connection.

Alternatively, you can use the Dispose( ) method of the proxy to close it. The advantage of the Dispose( ) method is that you can use the using statement to call it even in the face of exceptions:

using(MyContractClient proxy = new MyContractClient( )) {    proxy.MyMethod( ); }

If the client is declaring the contract directly instead of the concrete proxy class, the client can either query for the presence of IDisposable:

IMyContract proxy = new MyContractClient( )); proxy.MyMethod( ); IDisposable disposable = proxy as IDisposable; if(disposable != null) {    disposable.Dispose( ); }

or collapse the query inside the using statement:

IMyContract proxy = new MyContractClient( ); using(proxy as IDisposable) {    proxy.MyMethod( ); }

Call timeout

Each call made by a WCF client must complete within a configurable timeout. If for whatever reason the call duration exceeds the timeout, the call is aborted and the client gets a TimeoutException. The exact value of the timeout is a property of the binding, where the default timeout is one minute. To provide a different timeout, set the SendTimeout property of the abstract Binding base class:

public abstract class Binding : ... {    public TimeSpan SendTimeout    {get;set;}    //More members }

For example, when using the WSHttpBinding:

           ...       binding = "wsHttpBinding"       bindingConfiguration = "LongTimeout"       ...     />              name = "LongTimeout" sendTimeout = "00:05:00"/>     

Programmatic Client Configuration

Instead of relying on a config file, the client can programmatically construct address and binding objects matching the service endpoint and provide them to the proxy constructor. There is no need to provide the contract, since that was provided in the form of the generic type parameter of the proxy. To represent the address, the client needs to instantiate an EndpointAddress class, defined as:

public class EndpointAddress {   public EndpointAddress(string uri);   //More members }

Example 1-20 demonstrates this technique, showing the code equivalent to Example 1-16 targeting the service in Example 1-9.

Example 1-20. Programmatic client configuration

Binding wsBinding = new WSHttpBinding( ); EndpointAddress endpointAddress = new    EndpointAddress("http://localhost:8000/MyService/"); MyContractClient proxy =     new MyContractClient(wsBinding,endpointAddress); proxy.MyMethod( ); proxy.Close( );

Similar to using a binding section in a config file, the client can programmatically configure the binding properties:

WSHttpBinding wsBinding = new WSHttpBinding( ); wsBinding.SendTimeout = TimeSpan.FromMinutes(5); wsBinding.TransactionFlow = true; EndpointAddress endpointAddress = new    EndpointAddress("http://localhost:8000/MyService/"); MyContractClient proxy =     new MyContractClient(wsBinding,endpointAddress); proxy.MyMethod( ); proxy.Close( );

Again, note the use of the concrete subclass of Binding in order to access binding specific properties such as the transaction flow.

Programmatic Versus Administrative Configuration

The two techniques shown so far for configuring both the client and service complement each other. Administrative configuration gives you the option to change major aspects of the service and the client post-deployment, without even the need to rebuild or redeploy. The major downside of administrative configuration is that it is not type-safe, and configuration errors will only be discovered at runtime.

Programmatic configuration is useful when the configuration decision is either completely dynamic—when it is taken at runtime based on the current input or conditions— or when the decision is static and never changes, in which case you might as well hardcode it. For example, if you are interested in hosting in-proc calls only, you might as well hardcode the use of the NetNamedPipeBinding and its configuration. However, by and large, most clients and services do resort to using a config file.

WCF Architecture

So far in this chapter, I’ve covered all that is required to set up and consume simple WCF services. However, as described in the rest of the book, WCF offers immensely valuable support for reliability, transactions, concurrency management, security, and instance activation, all of which rely on the WCF interception-based architecture. Having the client interact with a proxy means that WCF is always present between the service and the client, intercepting the call and performing pre-and post-call processing. The interception starts when the proxy serializes the call stack frame to a message and sends the message down a chain of channels. The channel is merely an interceptor, whose purpose is to perform a specific task. Each client-side channel does pre-call processing of the message. The exact structure and composition of the chain depends mostly on the binding. For example, one of the channels may be responsible for encoding the message (binary, text, or MTOM), another for passing security call context, another for propagating the client transaction, another for managing the reliable session, another for encrypting the message body (if so configured), and so on. The last channel on the client side is the transport channel, which sends the message over the configured transport to the host.

On the host side, the message goes through a chain of channels as well, which perform host-side pre-call processing of the message. The first channel on the host side is the transport channel, which receives the message from the transport. Subsequent channels perform various tasks, such as decryption of the message body, decoding of the message, joining the propagated transaction, setting the security principal, managing the session, and activating the service instance. The last channel on the host side passes the message to the dispatcher. The dispatcher converts the message to a stack frame and calls the service instance. This sequence is depicted in Figure 1-11.



Figure 1-11. The WCF architecture


The service has no way of knowing it was not called by a local client. In fact, it was called by a local client—the dispatcher. The interception both on the client and the service side ensures that the client and the service get the runtime environment they require to operate properly. The service instance executes the call and returns control to the dispatcher, which then converts the returned values and error information (if any) to a return message. The process is now reversed: the dispatcher passes the message through the host-side channels to perform post-call processing, such as managing the transaction, deactivating the instance, encoding the reply, encrypting it, and so on. The returned message goes to the transport channel, which sends it to the client-side channels for client-side post-call processing, which consists of tasks such as decryption, decoding, committing or aborting the transaction, and so on. The last channel passes the message to the proxy. The proxy converts the returned message to a stack frame and returns control to the client.

Most noteworthy is that almost all the points in the architecture provide hooks for extensibility—you can provide custom channels for proprietary interaction, custom behaviors for instance management, custom security behavior, and so on. In fact, the standard facilities that WCF offers are all implemented using the same extensibility model. You will see many examples and uses for extensibility throughout this book.

Host Architecture

It is also interesting to explore how the transition is made from a technology-neutral, service-oriented interaction to CLR interfaces and classes. The bridging is done via the host. Each .NET host process can have many app domains. Each app domain can have zero or more service host instances. However, each service host instance is dedicated to a particular service type. When you create a host instance, you are in effect registering that service host instance with all the endpoints for that type on the host machine that correspond to its base addresses. Each service host instance has zero or more contexts. The context is the innermost execution scope of the service instance. A context is associated with at most one service instance, meaning it could also be empty, without any service instance. This architecture is shown in Figure 1-12.



Figure 1-12. The WCF host architecture



The WCF context is conceptually similar to the Enterprise Services context or the .NET context-bound object context.

It is the combined work of the service host and the context that exposes a native CLR type as a service. After the message is passed through the channels, the host maps that message to a new or existing context (and the object instance inside) and lets it process the call.

Working with Channels

You can use channels directly to invoke operations on the service without ever resorting to using a proxy class. The ChannelFactory class (and its supporting types), shown in Example 1-21, enables you to create a proxy on the fly.

Example 1-21. TheChannelFactory class

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 : 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 }

You need to provide the constructor of ChannelFactory 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 factory =     new ChannelFactory( );   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( );

You can also use the shorthand static CreateChannel( ) method to create a proxy given a binding and an address, without directly constructing an instance of ChannelFactory:

Binding binding = new NetTcpBinding( ); EndpointAddress address =     new EndpointAddress("net.tcp://localhost:8000"); IMyContract proxy =     ChannelFactory.CreateChannel(binding,address);   using(proxy as IDisposable) {    proxy1.MyMethod( ); }

The InProcFactory Class

To demonstrate the power of ChannelFactory, consider my static helper class InProcFactory, defined as:

public static class InProcFactory {    public static I CreateInstance( )        where I : class       where S : I;    public static void CloseProxy(I instance)        where I : class;    //More members }

InProcFactory is designed to streamline and automate in-proc hosting. The CreateInstance( ) method takes two generic type parameters: the type of the service S and the type of the supported contract I. CreateInstance( ) constrains S to derive from I. Using InProcFactory is straightforward:

IMyContract proxy =    InProcFactory.CreateInstance( ); proxy.MyMethod( ); InProcFactory.CloseProxy(proxy);

It literally takes a service class and hoists it up as a WCF service. It is as close as you can get in WCF to the old Win32 call of LoadLibrary( ).

Implementing InProcFactory

All in-proc calls should use named pipes, and should also flow all transactions. You can use programmatic configuration to automate the configurations of both the client and the service, and use ChannelFactory to avoid the need for a proxy. Example 1-22 shows the implementation of InProcFactory with some of the code removed for brevity.

Example 1-22. The InProcFactory class

public static class InProcFactory {    struct HostRecord    {       public HostRecord          (ServiceHost host,string address)       {          Host = host;          Address = address;       }       public readonly ServiceHost Host;       public readonly string Address;    }    static readonly Uri BaseAddress =        new Uri("net.pipe://localhost/");    static readonly Binding NamedPipeBinding;    static Dictionary m_Hosts =        new Dictionary( );    static InProcFactory( )    {       NetNamedPipeBinding binding = new NetNamedPipeBinding( );       binding.TransactionFlow = true;       NamedPipeBinding = binding;       AppDomain.CurrentDomain.ProcessExit += delegate       {          foreach(KeyValuePair pair in m_Hosts)          {             pair.Value.Host.Close( );          }       };    }    public static I CreateInstance( ) where I : class       where S : I    {       HostRecord hostRecord = GetHostRecord( );       return ChannelFactory.CreateChannel(NamedPipeBinding,          new EndpointAddress(hostRecord.Address));    }    static HostRecord GetHostRecord( ) where I : class       where S : I    {       HostRecord hostRecord;       if(m_Hosts.ContainsKey(typeof(S)))       {          hostRecord = m_Hosts[typeof(S)];       }       else       {          ServiceHost host = new ServiceHost(typeof(S), BaseAddress);          string address = BaseAddress.ToString() + Guid.NewGuid().ToString( );          hostRecord = new HostRecord(host,address);          m_Hosts.Add(typeof(S),hostRecord);          host.AddServiceEndpoint(typeof(I),NamedPipeBinding,address);          host.Open( );       }       return hostRecord;    }    public static void CloseProxy(I instance) where I : class    {       ICommunicationObject proxy = instance as ICommunicationObject;       Debug.Assert(proxy != null);       proxy.Close( );    } }

The main challenge facing InProcFactory is that CreateInstance( ) can be called to instantiate services of every type. For every service type, there should be a single matching host (an instance of ServiceHost). Allocating a host instance for each call is not a good idea. The problem is what should CreateInstance( ) do when it is asked to instantiate a second object of the same type:

IMyContract proxy1 = InProcFactory.CreateInstance( ); IMyContract proxy2 = InProcFactory.CreateInstance( );

The solution is to internally manage a dictionary that maps service types to a particular host instance. When CreateInstance( ) is called to create an instance of a particular type, it looks in the dictionary, using a helper method called GetHostRecord( ), which creates the host only if the dictionary does not already contain the service type. If it needs to create a host, GetHostRecord( ) programmatically adds to that host an endpoint, using a new GUID as a unique pipe name. CreateInstance( ) then grabs the address of the endpoint from the host record and uses ChannelFactory to create the proxy. In its static constructor, which is called upon the first use of the class, InProcFactory subscribes to the process exit event, using an anonymous method to close all hosts when the process shuts down. Finally, to help the clients close the proxy, InProcFactory provides the CloseProxy( ) method, which queries the proxy to ICommunicationObject and closes it.

Reliability

WCF and other service-oriented technologies make a distinction between transport reliability and message reliability. Transport reliability (such as the one offered by TCP) offers point-to-point guaranteed delivery at the network packet level, as well as guarantees the order of the packets. Transport reliability is not resilient to dropping network connections and a variety of other communication problems.

Message reliability, as the name implies, deals with reliability at the message level independent of how many packets are required to deliver the message. Message reliability provides for end-to-end guaranteed delivery and order of messages, regardless of how many intermediaries are involved, and how many network hops are required to deliver the message from the client to the service. Message reliability is based on an industry standard for reliable message-based communication that maintains a session at the transport level. It offers retries in case of transport failures such as dropping a wireless connection; it automatically deals with congestion, message buffering, and flow control; and it can adjust the number of messages accordingly. Message reliability also deals with managing the connection itself via connection verification and cleanup when no longer needed.

WCF Services and ASP.NET


This topic discusses hosting Windows Communication Foundation (WCF) services side-by-side with ASP.NET and hosting them in ASP.NET compatibility mode.

Hosting WCF Side-by-Side with ASP.NET

WCF services hosted in Internet Information Services (IIS) can be located with .ASPX pages and ASMX Web services inside of a single, common Application Domain. ASP.NET provides common infrastructure services such as AppDomain management and dynamic compilation for both WCF and the ASP.NET HTTP runtime. The default configuration for WCF is side-by-side with ASP.NET.

The ASP.NET HTTP runtime handles ASP.NET requests but does not participate in the processing of requests destined for WCF services, even though these services are hosted in the same AppDomain as is the ASP.NET content. Instead, the WCF Service Model intercepts messages addressed to WCF services and routes them through the WCF transport/channel stack.

The results of the side-by-side model are as follows:

  • ASP.NET and WCF services can share AppDomain state. Because the two frameworks can coexist in the same AppDomain, WCF can also share AppDomain state with ASP.NET (including static variables, events, and so on).

  • WCF services behave consistently, independent of hosting environment and transport. The ASP.NET HTTP runtime is intentionally coupled to the IIS/ASP.NET hosting environment and HTTP communication. Conversely, WCF is designed to behave consistently across hosting environments (WCF behaves consistently both inside and outside of IIS) and across transport (a service hosted in IIS 7.0 has consistent behavior across all endpoints it exposes, even if some of those endpoints use protocols other than HTTP).

  • Within an AppDomain, features implemented by the HTTP runtime apply to ASP.NET content but not to WCF. Many HTTP-specific features of the ASP.NET application platform do not apply to WCF Services hosted inside of an AppDomain that contains ASP.NET content. Examples of these features include the following:

    • HttpContext: Current is always null when accessed from within a WCF service. Use RequestContext instead.

    • File-based authorization: The WCF security model does not allow for the access control list (ACL) applied to the .svc file of the service when deciding if a service request is authorized.

    • Configuration-based URL Authorization: Similarly, the WCF security model does not adhere to any URL-based authorization rules specified in System.Web’s configuration element. These settings are ignored for WCF requests if a service resides in a URL space secured by ASP.NET’s URL authorization rules.

    • HttpModule extensibility: The WCF hosting infrastructure intercepts WCF requests when the PostAuthenticateRequest event is raised and does not return processing to the ASP.NET HTTP pipeline. Modules that are coded to intercept requests at later stages of the pipeline do not intercept WCF requests.

    • ASP.NET impersonation: By default, WCF requests always runs as the IIS process identity, even if ASP.NET is set to enable impersonation using System.Web’s configuration option.

These restrictions apply only to WCF services hosted in IIS application. The behavior of ASP.NET content is not affected by the presence of WCF.

WCF applications that require functionality traditionally provided by the HTTP pipeline should consider using the WCF equivalents, which are host and transport independent:

Alternatively, you can consider running your services in WCF’s ASP.NET compatibility mode.

Hosting WCF Services in ASP.NET Compatibility Mode

Although the WCF model is designed to behave consistently across hosting environments and transports, there are often scenarios where an application does not require this degree of flexibility. WCF’s ASP.NET compatibility mode is suitable for scenarios that do not require the ability to host outside of IIS or to communicate over protocols other than HTTP, but that use all of features of the ASP.NET Web application platform.

Unlike the default side-by-side configuration, where the WCF hosting infrastructure intercepts WCF messages and routes them out of the HTTP pipeline, WCF services running in ASP.NET Compatibility Mode participate fully in the ASP.NET HTTP request lifecycle. In compatibility mode, WCF services use the HTTP pipeline through an IHttpHandler implementation, similar to the way requests for ASPX pages and ASMX Web services are handled. As a result, WCF behaves identically to ASMX with respect to the following ASP.NET features:

  • HttpContext: WCF services running in ASP.NET Compatibility Mode can access Current and its associated state.

  • File-based authorization: WCF services running in ASP.NET compatibility mode can be secure by attaching file system access control lists (ACLs) to the service’s .svc file.

  • Configurable URL authorization: ASP.NET’s URL authorization rules are enforced for WCF requests when the WCF service is running in ASP.NET Compatibility Mode.

  • HttpModuleCollection extensibility: Because WCF services running in ASP.NET Compatibility Mode participate fully in the ASP.NET HTTP request lifecycle, any HTTP module configured in the HTTP pipeline is able to operate on WCF requests both before and after service invocation.

  • ASP.NET Impersonation: WCF services run using the current identity of the ASP.NET impersonated thread, which may be different than the IIS process identity if ASP.NET impersonation has been enabled for the application. If ASP.NET impersonation and WCF impersonation are both enabled for a particular service operation, the service implementation ultimately runs using the identity obtained from WCF.

WCF’s ASP.NET compatibility mode is enabled at the application level through the following configuration (located in the application’s Web.config file):

Copy

                

This value defaults to “false” if not specified. Setting this value to “true” indicates that all WCF services running in the application run in ASP.NET Compatibility Mode.

Because ASP.NET Compatibility Mode implies request processing semantics that are fundamentally different from the WCF default, individual service implementations have the ability to control whether they run inside of an application for which ASP.NET Compatibility Mode has been enabled. Services can use the AspNetCompatibilityRequirementsAttribute to indicate whether they support ASP.NET Compatibility Mode.

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]

public class CalculatorService : ICalculatorSession

{//Implement calculator service methods.}

The following table illustrates how the application-wide compatibility mode setting interacts with the individual service’s stated level of support:

Application-wide Compatibility Mode setting

[AspNetCompatibilityRequirementsMode] Setting

Observed Result

aspNetCompatibilityEnabled = “true

Required

Service activates successfully.

aspNetCompatibilityEnabled = “true

Allowed

Service activates successfully.

aspNetCompatibilityEnabled = “true

NotAllowed

An activation error occurs when the service receives a message.

aspNetCompatibilityEnabled = “false

Required

An activation error occurs when the service receives a message.

aspNetCompatibilityEnabled = “false

Allowed

Service activates successfully.

aspNetCompatibilityEnabled = “false

NotAllowed

Service activates successfully.

Note:

IIS 7.0 and WAS allows WCF services to communicate over protocols other than HTTP. However, WCF services running in applications that have enabled ASP.NET compatibility mode are not permitted to expose non-HTTP endpoints. Such a configuration generates an activation exception when the service receives its first message.

For more information about enabling ASP.NET compatibility mode for WCF services, see AspNetCompatibilityRequirementsMode.



Contracts

In WCF, all services expose contracts. The contract is a platform-neutral and standard way of describing what the service does. WCF defines four types of contracts.

Service contracts
Describe which operations the client can perform on the service. Service contracts are the subject of the next chapter, but are used extensively in every chapter in this book.
Data contracts
Define which data types are passed to and from the service. WCF defines implicit contracts for built-in types such as 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.
Fault contracts
Define which errors are raised by the service, and how the service handles and propagates errors to its clients. Chapter 6 is dedicated to defining and using fault contracts.
Message contracts
Allow the service to interact directly with messages. Message contracts can be typed or untyped, and are useful in interoperability cases and when there is an existing message format you have to comply with. As a WCF developer, you should use message contracts only rarely, so this book makes no use of message contracts.




No comments:

Post a Comment

Post a Comment