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).
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.
Tip
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.
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.
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
<service name = "MyService"><endpoint address = "http://localhost:8000/MyService/" binding = "wsHttpBinding" contract = "IMyContract" /> <endpoint address = "net.tcp://localhost:8001/MyService/" binding = "netTcpBinding" contract = "IMyContract" /> <endpoint address = "net.tcp://localhost:8002/MyService/" binding = "netTcpBinding" contract = "IMyOtherContract" /> </service>
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.
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:
<service name = "MyService"> <endpoint address = "net.tcp://localhost:8001/MyService/
" binding = "netTcpBinding" contract = "IMyContract" /> <endpoint address = "net.tcp://localhost:8001/MyOtherService/
" binding = "netTcpBinding" contract = "IMyContract" /> </service>
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:
<endpoint 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:
<endpoint
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.
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
<system.serviceModel> <services> <service name = "MyService"> <endpoint address = "net.tcp://localhost:8000/MyService/"bindingConfiguration = "TransactionalTCP" binding = "netTcpBinding" contract = "IMyContract" /> <endpoint address = "net.tcp://localhost:8001/MyService/" bindingConfiguration = "TransactionalTCP" binding = "netTcpBinding" contract = "IMyOtherContract" /> </service> </services> <bindings> <netTcpBinding> <binding name = "TransactionalTCP" transactionFlow = "true" /> </netTcpBinding> </bindings> </system.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 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.
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));NetTcp
Binding tcpBinding = newNetTcp
Binding( ); 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.
Get Programming WCF Services now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.