So far in this chapter, I have shown you different ways to create services, how to expose a service endpoint and metadata exchange endpoint, how to generate client proxies, how to work with metadata, and how to configure service behaviors. In this section, I’ll place the emphasis on endpoints, binding configuration, and allocation of assemblies for a more complex solution.
WCF includes a number of standard bindings that allow you to quickly configure a service for a particular set of protocols. Clients must use compatible protocols when they communicate with each endpoint. Services can expose multiple endpoints for the same service contract in order to expose functionality over different protocols. For example, a service may be called by internal clients over TCP, but by external clients over HTTP. In addition to supporting different protocols, internal and external clients may not have access to the same service contracts. Some operations may be allowed only by clients within the domain, while others are publicly available to remote clients on the Internet.
In this lab you will configure multiple endpoints for a service to support different endpoint and binding configurations. In the process you’ll explore the following concepts:
Hosting multiple services
Configuring multiple endpoints for a service
Accessing a service from a Windows client application
Initializing proxies from multiple endpoint and binding configurations
Comparing proxy generation to sharing types between services and clients
In this lab, you’ll modify an existing solution to implement a service contract and an administrative contract on two distinct services. You’ll then host each service in the same host process, a console application. An internal client presumed to be behind the firewall will consume each service using network protocols such as TCP and named pipes. This client will have access to service operations exposed by the service contract and administrative contract. An external client, presumed to be accessing the service over the Internet will have access only to operations exposed by the service contract over HTTP. The internal client will share class libraries to access service contracts, while the external client will use traditional methods for generating service proxies.
In this section, you’re going to implement the predefined service contracts on two distinct services. Each service will expose two contracts: one for business functionality core to the service, the other for administrative functionality. Both services will implement the same administrative contract. This illustrates an example of contract factoring for reuse.
Start by opening the solution <YourLearningWCFPath>\Labs\Chapter1\ MultiContractService\MultiContractService.sln. This solution contains several shell projects, including a service library, a host, and two Windows client applications as follows:
BusinessServiceContracts
A class library containing three contracts:
IAdmin
,IServiceA
, andIServiceB
.IAdmin
defines administrative operations.IServiceA
andIServiceB
respectively describe functionality to be exposed byServiceA
andServiceB
.BusinessServices
A class library that will contain two services:
ServiceA
andServiceB
.Host
A console application that will host
ServiceA
andServiceB
.InternalClient
A windows client application that will access services behind the firewall.
ExternalClient
A windows client application that will access services over the Internet.
Tip
Putting service contracts into a separate class library facilitates sharing metadata with client applications when you own both sides of the development effort.
The first thing you’ll do is provide an implementation for
ServiceA
, which is located in theBusinessServices
class library. First, take a look at the contracts you will implement. Go to theBusinessServiceContracts
project and open IServiceA.cs; you’ll see a contract with two operations. Now open IAdmin.cs and you’ll see another contract with two different operations.To implement these contracts, go to the
BusinessServices
project. First, add a reference to theBusinessServiceContracts
project so you can access the contracts it defines. Then open ServiceA.cs and add ausing
statement for theBusinessServiceContracts
namespace, as shown here:using BusinessServiceContracts;
Modify the definition of
ServiceA
so that it derives fromIServiceA
andIAdmin
as follows:public class ServiceA : IServiceA, IAdmin
Implement both contracts implicitly. You can use a shortcut by hovering your mouse over
IServiceA
and using the smart tag to select “Implement interface IServiceA,” as shown in Figure 1-29.Complete the implementation by adding the code shown in Example 1-17.
Example 1-17. Implementation for ServiceA
public class ServiceA : IServiceA, IAdmin { publice string Operation1() { return "IServiceA.Operation1() invoked."; } publice string Operation2() { return "IServiceA.Operation2() invoked."; } public string AdminOperation1() { return "IAdmin.AdminOperation1 invoked."; } public string AdminOperation2() { return "IAdmin.AdminOperation2 invoked."; } }
Follow a similar set of steps to implement
ServiceB
. Open ServiceB.cs and derive the class fromIServiceB
andIAdmin
. Implement both interfaces implicitly so that the result looks like the code in Example 1-18. Don’t forget to add theusing
statement for theBusinessServiceContracts
namespace.Example 1-18. Implementation for ServiceB
using BusinessServiceContracts; public class ServiceB: IServiceB, IAdmin { public string Operation3() { return "IServiceB.Operation3() invoked."; } public string AdminOperation1() { return "IAdmin.AdminOperation1 invoked."; } public string AdminOperation2() { return "IAdmin.AdminOperation2 invoked."; } }
Verify that the
BusinessServices
project compiles without error.
Now you will host both services in a single console application. This will require
you to create two ServiceHost
instances and provide
two <service>
configuration sections, one for
each service type.
First, make sure the
Host
project can access the service contracts and service types. Go to theHost
project and add assembly references to two projects:BusinessServiceContracts
andBusinessServices
.In the application configuration file provided for the
Host
, provide configuration settings for both services. Open the app.config file and add the<system.serviceModel>
section shown in Example 1-19. This section belongs inside the<configuration>
section of the file.The configuration section for
ServiceA
exposes two endpoints for the service contractIServiceA
: one for Internet access over HTTP, another for TCP access behind the firewall.ServiceB
also exposes two endpoints for the service contractIServiceB
: one for Internet access and another for named pipe access restricting communications to the same machine.Both services expose the
IAdmin
contract over TCP and named pipes, respectively, allowing callers on the same machine, or on remote machines behind the firewall.Each service configuration also provides the appropriate base addresses for the protocols they support across all endpoints.
Example 1-19. Service model configuration for ServiceA and ServiceB
<system.serviceModel> <services> <service name="BusinessServices.ServiceA" behaviorConfiguration="serviceBehavior"> <host> <baseAddresses> <add baseAddress="http://localhost:8000"/> <add baseAddress="net.tcp://localhost:9000"/> </baseAddresses> </host> <endpoint address="Admin" contract="BusinessServiceContracts.IAdmin" binding="netTcpBinding" /> <endpoint address="ServiceA" contract="BusinessServiceContracts.IServiceA" binding="basicHttpBinding" /> <endpoint address="ServiceA" contract="BusinessServiceContracts.IServiceA" binding="netTcpBinding" /> </service> <service name="BusinessServices.ServiceB" behaviorConfiguration="serviceBehavior"> <host> <baseAddresses> <add baseAddress="http://localhost:8001"/> <add baseAddress="net.pipe://localhost"/> </baseAddresses> </host> <endpoint address="Admin" contract="BusinessServiceContracts.IAdmin" binding="netNamedPipeBinding" /> <endpoint address="ServiceB" contract="BusinessServiceContracts.IServiceB" binding="basicHttpBinding" /> <endpoint address="ServiceB" contract="BusinessServiceContracts.IServiceB" binding="netNamedPipeBinding" /> </service> </services> <behaviors> <serviceBehaviors> <behavior name="serviceBehavior"> <serviceMetadata httpGetEnabled="true"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel>
Tip
Each
<service>
section holds configuration settings for its own base addresses and endpoints. Recall that the configuration for a particular service is used to initialize aServiceHost
instance for that service type. Be mindful that base addresses across all sections must have unique ports since a port can be opened only once per machine.Now that service model configuration has been provided for each service, you will write code to initialize and open a
ServiceHost
instance for both. Go to theHost
project and open Program.cs. Modify theMain( )
entry point so that it includes the code shown in Example 1-20. You will also need to add ausing
statement forSystem.ServiceModel
.This code creates two distinct
ServiceHost
instances, one for each service. They are both constructed and opened within a try...finally block to ensure thatClose( )
is called for each when the host shuts down or if a fatal exception occurs. In addition, the code callsAbort()
if theServiceHost
instance is in a faulted state, sinceClose()
would throw an exception in this case.Example 1-20. Initializing the ServiceHost for ServiceA and ServiceB
using System.ServiceModel; static void Main(string[] args) { ServiceHost hostA = null; ServiceHost hostB = null; try { hostA = new ServiceHost(typeof(BusinessServices.ServiceA)); hostB = new ServiceHost(typeof(BusinessServices.ServiceB)); hostA.Open(); hostB.Open(); Console.WriteLine(); Console.WriteLine("Press <ENTER> to terminate Host"); Console.ReadLine(); } finally { if (hostA.State == CommunicationState.Faulted) hostA.CloseAbort(); else hostA.Close(); if (hostB.State == CommunicationState.Faulted) hostB.Abort(); else hostB.Close(); } }
Compile and run the
Host
project once to verify that no errors occur.
Because metadata browsing is enabled in the configuration section for each service type, you can browse to the WSDL document for each service by providing the HTTP base address for each service. Note that each service has its own distinct WSDL document, but for each individual service, all endpoints for the service are included in its WSDL document.
In this part of the lab, you will implement the internal client application and invoke service operations over TCP and named pipe protocols. The purpose of this exercise is to illustrate how you might share service metadata when you own both sides of the development effort for intranet clients. Sharing contract libraries ensures both sides are compiling against the latest contract versions throughout the development cycle. In addition, this exercise will illustrate the use of different standard bindings for TCP and named pipes.
First, go to the
InternalClient
project and add a reference to theBusinessServiceContracts
project. This will give the client application direct access to the service contract necessary to invoke the service.The client application requires prior knowledge of the service endpoints it can reach before it can configure a proxy. Under the assumption that you own both sides, client developers will manually configure client endpoints to reach each service endpoint. In this case, you’ll consume
ServiceA
over TCP andServiceB
over named pipes.Open the app.config file and add the
<system.serviceModel>
section shown in Example 1-21. This includes two endpoints for each service: one for the main service contract, the other for the administrative contract. The binding for each endpoint matches the binding configuration for the same endpoint at the service. In this case, TCP endpoints usenetTcpBinding
, and named pipe endpoints usenetNamedPipeBinding
.Example 1-21. Service model configuration for the InternalClient
<system.serviceModel> <client> <endpoint address="net.tcp://localhost:9000/ServiceA" contract="BusinessServiceContracts.IServiceA" binding="netTcpBinding" /> <endpoint address="net.tcp://localhost:9000/Admin" contract="BusinessServiceContracts.IAdmin" binding="netTcpBinding" name="TCP_IAdmin" /> <endpoint address="net.pipe://localhost/ServiceB" contract="BusinessServiceContracts.IServiceB" binding="netNamedPipeBinding" /> <endpoint address="net.pipe://localhost/Admin" contract="BusinessServiceContracts.IAdmin" binding="netNamedPipeBinding" name="IPC_IAdmin"/> </client> </system.serviceModel>
Tip
Remember that when you select a binding, you are selecting a transport protocol, a message encoding format, and possibly other messaging protocols for security and reliability, for example. The details of what goes into a binding will be discussed in Chapter 3. The important thing is that if you use the defaults on both sides, the communication channel at each end will be compatible.
Now you will write some code to invoke each service endpoint on both services. To do this, you will need four proxy references: one for each service and contract.
Open Form1.cs and add a
using
statement forBusinessServiceContracts
. In addition, declare a proxy for each contract, scoped to the lifetime of the application, adding the following definitions as members ofForm1
:using BusinessServiceContracts;
public partial class Form1 : Form {IServiceA m_proxyA;
IAdmin m_adminProxyA;
IServiceB m_proxyB;
IAdmin m_adminProxyB;
// more code }In the form constructor, initialize each proxy using
ChannelFactory<T>
. The proxies forIServiceA
andIServiceB
can use the default client endpoint for each contract. Because there is only one endpoint for each contract type, you don’t have to specify which<endpoint>
section will initialize the proxy.Since
IAdmin
has two endpoints defined, one for TCP and another for named pipes, you must specify which endpoint will initialize the proxy when you constructChannelFactory<T>
. Do this by passing the appropriate endpoint name from Example 1-21.You’ll see the resulting code for the form constructor in Example 1-22. You must also add a reference to
System.ServiceModel
, as shown.Example 1-22. Code to initialize ServiceA and ServiceB proxies
using System.ServiceModel;public Form1() { InitializeComponent();ChannelFactory<IServiceA> factoryA = new ChannelFactory<IServiceA>(""); m_proxyA = factoryA.CreateChannel(); ChannelFactory<IAdmin> adminFactoryA = new ChannelFactory<IAdmin>("TCP_IAdmin"); m_adminProxyA = adminFactoryA.CreateChannel(); ChannelFactory<IServiceB> factoryB = new ChannelFactory<IServiceB>(""); m_proxyB = factoryB.CreateChannel(); ChannelFactory<IAdmin> adminFactoryB = new ChannelFactory<IAdmin>("IPC_IAdmin"); m_adminProxyB = adminFactoryB.CreateChannel(); }
The user interface for the client has already been created. If you look at Form1.cs in design view, you’ll see a button to test each service and administrative operation. Now you’ll add code to invoke each operation using the appropriate proxy. For each button on the form, add a handler for the
Click
event; you can do this by double-clicking each button from design view. Inside eachClick
event handler, add code to invoke the appropriate operation and show the result in a message box. The resulting code for each of these handlers is shown in Example 1-23.Warning
Just adding the code from Example 1-23 will not be sufficient to hook up the event handlers. When you double-click each button in design view, this generates designer code to hook up each event handler to its Button control.
Example 1-23. Code to invoke all service operations
private void button1_Click(object sender, EventArgs e) { string s = m_adminProxyA.AdminOperation1(); MessageBox.Show(s); } private void button2_Click(object sender, EventArgs e) { string s = m_adminProxyA.AdminOperation2(); MessageBox.Show(s); } private void button3_Click(object sender, EventArgs e) { string s = m_adminProxyB.AdminOperation1(); MessageBox.Show(s); } private void button4_Click(object sender, EventArgs e) { string s = m_adminProxyB.AdminOperation2(); MessageBox.Show(s); } private void button5_Click(object sender, EventArgs e) { string s = m_proxyA.Operation1(); MessageBox.Show(s); } private void button6_Click(object sender, EventArgs e) { string s = m_proxyA.Operation2(); MessageBox.Show(s); } private void button7_Click(object sender, EventArgs e) { string s = m_proxyB.Operation3(); MessageBox.Show(s); }
It is always good practice to release resources when you are finished with them. To make sure that each of the channels are properly disposed of when the application exits, add code to explicitly close each proxy. First, add a new private function named
CloseProxy( )
that will cast each proxy toICommunicationObject
and invoke itsAbort()
operation if the proxy is in the faulted state, and call theClose( )
operation otherwise. Then, add an event handler for theFormClosing
event ofForm1
and add code to callCloseProxy( )
for each proxy. The code to add is shown in Example 1-24.Example 1-24. Code to close each proxy
private void CloseProxy(ICommunicationObject proxy) { if (proxy != null) if (proxy.State == CommunicationState.Faulted) proxy.Abort(); else proxy.Close(); } } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { CloseProxy(m_proxyA as ICommunicationObject); CloseProxy(m_proxyB as ICommunicationObject); CloseProxy(m_adminProxyA as ICommunicationObject); CloseProxy(m_adminProxyB as ICommunicationObject); }
Compile the solution and test the internal client. Run the
Host
project first, and then runInternalClient
. Click each button to invoke operations exposed byServiceA
andServiceB
.
Now you will implement the external client application and invoke service operations over HTTP. In this case, the client will rely on the WSDL document to generate a proxy and related configuration to call the service. This exercise will illustrate how the proxy generation process handles multiple contracts and endpoints.
Start by running the
Host
project without debugging so that you can generate proxies for each service.Go to the
ExternalClient
project in Solution Explorer and add a service reference forServiceA
. Provide the base address http://localhost:8000 and provide the namespaceServiceA
. This will generate a proxy for each contract exposed byServiceA
and create an application configuration file with client endpoints.Now add a service reference for
ServiceB
. This time, provide the base address http://localhost:8001 and provide the namespaceServiceB
. This will generate a proxy for each contract exposed byServiceB
, adding new client endpoints to the application configuration file.Tip
The application configuration file is not overwritten when you add new service references. A merge is performed to add to configuration settings. SvcUtil also supports merge through command-line options.
This application will invoke each service using the proxies generated by SvcUtil. Like with the
InternalClient
application, you’ll create a proxy reference for each service contract, initialize them in the form constructor, and then close them in theFormClosing
event. The code will be simplified somewhat since the generated proxy hides some of the complexity of creating the communication channel, and directly exposes close functionality. The resulting code is shown in Example 1-25.Example 1-25. Code to initialize and close generated proxies for ServiceA and ServiceB
public partial class Form1 : Form { ServiceA.ServiceAClient m_proxyA; ServiceB.ServiceBClient m_proxyB; public Form1() { InitializeComponent(); m_proxyA = new ExternalClient.ServiceA.ServiceAClient ("BasicHttpBinding_IServiceA"); m_proxyB = new ExternalClient.ServiceB.ServiceBClient ("BasicHttpBinding_IServiceB"); } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { if (this.m_proxyA.State == CommunicationState.Faulted) this.m_proxyA.Abort(); else this.m_proxyA.Close(); if (this.m_proxyB.State == CommunicationState.Faulted) this.m_proxyB.Abort(); else this.m_proxyB.Close(); } }
You’ll notice that each proxy is initialized by passing the name of a particular endpoint from the application configuration file. Each service exposes multiple endpoints, but because this is an Internet client, the assumption is that the client won’t have permissions to call the TCP nor be able to invoke services over named pipes (named pipes require same-machine calls). Still, add service reference generated configuration for all endpoints because the WSDL document includes all endpoints for a service.
Now you can add code to invoke each operation. If you look at the form in design view, you’ll see that only three buttons are present to invoke the collective operations of both service contracts. Create Click event handlers for each button and add code to invoke each operation through the appropriate proxy. Example 1-26 shows the resulting code.
Example 1-26. Code to invoke each service operation
private void button1_Click(object sender, EventArgs e) { string s = m_proxyA.Operation1(); MessageBox.Show(s); } private void button2_Click(object sender, EventArgs e) { string s = m_proxyA.Operation2(); MessageBox.Show(s); } private void button3_Click(object sender, EventArgs e) { string s = m_proxyB.Operation3(); MessageBox.Show(s); }
Compile and test the external client application. First run the
Host
, and then runExternalClient
. Click each button to invoke the service operations exposed byServiceA
andServiceB
.
Let’s look at the new concepts and tools introduced in this lab.
Designing service contracts is not as simple as just exposing existing business components as services. In all likelihood, services will aggregate calls to many logically related business components. This requires forethought into the use cases through each service. Irrespective of this aggregation and design effort, it is still possible that the functionality exposed by a single service should not be lumped into one big service contract. Here are some cases in which multiple contracts could exist on a single service:
To separate logically related operations for different features
To separate queued operations from non-queued operations
To provide a different entry point for external and internal consumers of the service
If your service contracts are implemented on CLR interfaces (as I’ve recommended) then
implementing multiple contracts on a service is as simple as implementing multiple
interfaces. In this lab, each service implements a main service contract (IServiceA
and IServiceB
,
respectively) and an administrative contract (IAdmin
).
Contracts can facilitate the logical separation of functionality exposed by a service. For example, in the lab the main service contract for each service holds core business operations for the service. The administrative contract provides a consistent set of administrative functions that any service can expose. Both services implement the same administrative contract, which means both services expose a consistent set of operations, although the internal implementation may be quite different. Because of the presumed sensitivity of administrative operations, the lab exposes these operations over TCP or named pipes, which implies access behind the firewall. To enable internal applications and business partners to access the core service functionality, the main service contracts are exposed over two endpoints: one for TCP or named pipes, the other for Internet access over HTTP.
A ServiceHost
instance is required for each service
type in order to expose endpoints to calling clients. When you host in IIS or WAS, a
.svc endpoint is supplied for each service type,
with a @ServiceHost
directive that links the .svc endpoint to the actual service type. Thus, if you have
multiple services to host in IIS, you provide a .svc
for each to support message-based activation and configure the service model section as
you would for any service type.
In self-hosting environments, you are responsible for initializing each ServiceHost
instance. You can initialize a ServiceHost
for each service type as shown here (the complete
code listing is shown in Example 1-20):
ServiceHost hostA = new ServiceHost(typeof(BusinessServices.ServiceA)); ServiceHost hostB = new ServiceHost(typeof(BusinessServices.ServiceB)); hostA.Open(); hostB.Open();
Each ServiceHost
is initialized with its own base
addresses and service endpoints according to the <service>
section with the matching type (see Example 1-19). Each ServiceHost
can also be programmatically initialized using the
techniques illustrated earlier in this chapter.
Adding a service reference generates the proxy and configuration settings necessary to
access a particular service. If the service implements multiple contracts, a proxy type is
generated for each contract. For example, in this lab when you add a service reference to
ServiceA
in the ExternalClient
project, the following proxies are generated—one for IServiceA
, another for IAdmin
:
public partial classServiceAClient
: System.ServiceModel.ClientBase<ExternalClient.ServiceA.IServiceA>, ExternalClient.ServiceA.IServiceA public partial classAdminClient
: System.ServiceModel.ClientBase<ExternalClient.ServiceA.IAdmin>, ExternalClient.ServiceA.IAdmin
Likewise, when you add a service reference to ServiceB
a proxy is generated for both contracts: IServiceB
and IAdmin
.
Tip
In theory, because the IAdmin
service contract is
the same for both services, they could share a proxy, but SvcUtil generates proxy types
for all contracts and has no knowledge of the code you have already generated.
In addition to generating proxies, SvcUtil generates the configuration necessary for
each endpoint exposed by each service. SvcUtil always provides a name for each <endpoint>
element, so you can specify the correct
endpoint to use by name when constructing each proxy. Example 1-27 shows the client
endpoints generated for ServiceA
and ServiceB
; the endpoints used in the lab for the ExternalClient
are shown in bold.
Example 1-27. Client endpoints generated for ServiceA and ServiceB
<client> <endpoint address="net.tcp://localhost:9000/Admin" binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IAdmin" contract="ExternalClient.ServiceA.IAdmin" name="NetTcpBinding_IAdmin" /><endpoint address="http://localhost:8000/ServiceA" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IServiceA" contract="ExternalClient.ServiceA.IServiceA" name="BasicHttpBinding_IServiceA" /> <endpoint address="net.tcp://localhost:9000/ServiceA" binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IServiceA" contract="ExternalClient.ServiceA.IServiceA" name="NetTcpBinding_IServiceA" /> <endpoint address="net.pipe://localhost/Admin" binding="netNamedPipeBinding" bindingConfiguration="NetNamedPipeBinding_IAdmin" contract="ExternalClient.ServiceB.IAdmin" name="NetNamedPipeBinding_IAdmin" /> <endpoint address="http://localhost:8001/ServiceB" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IServiceB" contract="ExternalClient.ServiceB.IServiceB" name="BasicHttpBinding_IServiceB" /> <endpoint address="net.pipe://localhost/ServiceB" binding="netNamedPipeBinding" bindingConfiguration="NetNamedPipeBinding_IServiceB" contract="ExternalClient.ServiceB.IServiceB" name="NetNamedPipeBinding_IServiceB" /> </client>
Although the client may not have network rights to invoke the TCP or named pipe endpoints, these endpoints are still part of the service description (WSDL) and therefore are visible to the client.
Recall that a WSDL document is created for each service type. Thus, to prevent remote clients from seeing internal endpoints that they should not access, you can create different service types for internal and external use—funneling them to the same implementation code. On external service types, you can expose endpoints only for supported contracts over HTTP. For internal service types, you can expose internal contracts and TCP and named pipes endpoints. To modify the lab in support of this scenario, you might see the following service types:
public class ServiceA : IServiceA {...} public class InternalServiceA : IServiceA, IAdmin {...} public class ServiceB : IServiceB {...} public class InternalServiceB : IServiceB, IAdmin {...}
In the host configuration, each service type would be defined in a separate <service>
element, exposing only the required endpoints.
A compressed view of the required <service>
elements is shown here:
<service name="BusinessServices.ServiceA" ...> <service name="BusinessServices.InternalServiceA" ...> <service name="BusinessServices.ServiceB" ...> <service name="BusinessServices.InternalServiceB" ...>
Tip
The following sample illustrates this scenario: <YourLearningWCFPath>\Samples\ServiceContracts\MultiContractServices_UniqueServiceTypes.
Each client proxy opens a communication channel to invoke a service endpoint. The proxy can be programmatically initialized in code or declaratively initialized per the client’s service model configuration. If there is only one endpoint configured for a particular service contract, there is no need to specify an endpoint configuration name to the constructor of the channel factory or proxy.
When you use ChannelFactory<T>
to create the
channel from the default endpoint, you pass empty quotes to the constructor. This example
expects that only one endpoint is configured for IServiceA
:
ChannelFactory<IServiceA> factoryA = new ChannelFactory<IServiceA>(""); m_proxyA = factoryA.CreateChannel();
Generated proxies provide a default constructor to achieve the same result:
m_proxyA = new ExternalClient.ServiceA.ServiceAClient();
On the other hand, when multiple endpoints exist for the same contract, you must
provide a configuration name as shown here for ChannelFactory<T>
and for a generated proxy:
ChannelFactory<BusinessServiceContracts.IServiceA> factoryA = new
ChannelFactory<BusinessServiceContracts.IServiceA>("BasicHttpBinding_IServiceA
");
proxyA = factoryA.CreateChannel();
m_proxyA = new
ExternalClient.ServiceA.ServiceAClient("BasicHttpBinding_IServiceA");
In either case, the lifetime of the communication channel is controlled by the proxy reference. If the client application intends to invoke the service endpoint repeatedly, it is better not to recreate the proxy each time an operation is invoked. Instead, the proxy should be scoped to the lifetime of the application.
When the application is shutting down, you should close the proxy to speed up the
release of resources. When you are working with a channel factory to create the proxy
reference, you must cast to ICommunicationObject
in
order to call its Close( )
method (see Example 1-24). The equivalent inline steps would be as
follows:
ICommunicationObject proxyACommunication = m_proxyA as ICommunicationObject; ... proxyACommunication.Close();
This step is required because the channel factory returns a reference to the service
contract, which doesn’t expose a Close( )
method.
Still, the underlying object is a CommunicationObject
that implements ICommunicationObject
.
Proxies generated with SvcUtil include code to wrap the inner communication channel.
In addition, each generated proxy type implements ICommunicationObject
directly, and thus provides a Close( )
method.
Warning
Be aware that the channel stack beneath the proxy reference can be put into a faulted or invalid state. For example, if the service is no longer available, or if the service throws an exception, or if a timeout occurs at either end. In this case Close()will throw an exception and Abort()should be called to clean up channel resources. In Chapter 8, I’ll discuss exception handling.
Another point to note is that the lifetime of the communication channel should not be confused with the lifetime of the service instance instantiated by the host to handle a request. In fact, a different service instance may be allocated for every call even if the client uses the same channel. This behavior is controlled by the service. Service instancing and throttling behaviors are covered in Chapter 5.
This lab illustrates an alternate approach for sharing metadata with the client. Instead of generating a proxy using SvcUtil, a class library containing only service contracts is shared by the service library and the internal client application. This approach is useful in an environment where you own both sides: client and service. This approach can simplify steps in development, help you avoid the internal complexity of types generated by SvcUtil, and even allow you to exercise more control over service contract versioning on both ends.
Realistically, remote clients such as Internet clients may not be owned, which is why the more traditional approach of sharing contracts via add service reference is used.
This lab illustrates exposing two different contracts on each service. These contracts each have unique operations: a set for the business functionality exposed by the service, and a set for administrative functions. You may also want to expose a subset of business operations for external clients while exposing the complete set of business functionality to internal clients.
To achieve this, you could create internal and external interfaces for the service
contract, for example: IServiceA
and IInternalServiceA
. The external interface, IServiceA
, would in this case contain a subset of the
functionality exposed by IInternalServiceA
(see Example 1-28).
Example 1-28. Internal and external service contracts with duplicate operations
[ServiceContract(Namespace = "http://www.thatindigogirl.com/samples/2006/06")] public interface IServiceA { [OperationContract] string Operation1(); [OperationContract] string Operation2(); } [ServiceContract(Namespace = "http://www.thatindigogirl.com/samples/2006/06")] public interface IInternalServiceA { [OperationContract] string Operation1(); [OperationContract] string Operation2(); [OperationContract] string Operation3(); }
If you expose each of these contracts on their own service types (ServiceA
and InternalServiceA
, respectively), external clients will never see the
functionality exposed to internal clients because they work from a different WSDL
document. However, the implementation can still be the same for each service
implementation.
Tip
The following sample illustrates the scenario: <YourLearningWCFPath>\Samples\ServiceContracts\InternalExternalServiceTypes.sln.
Get Learning WCF 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.