To invoke operations on a service, a 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 one proxy per contract type. The proxy provides the same operations as the service's contract, but also has additional methods for managing the proxy lifecycle 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.
You can use Visual Studio 2008 to import the service metadata and generate a proxy. If the service is self-hosted outside the solution, first launch the service and then select Add Service Reference from the client project's context menu. If the service is self-hosted in the same solution, first launch it without the debugger, and then select the Add Service Reference option from the context menu.
If the service is hosted in IIS 5/6 or WAS, there is no need to prelaunch the service; simply select Add Service Reference from the client project's context menu, and Visual Studio will bring up the Add Service Reference dialog, shown in Figure 1-9.
Tip
In order for the Add Service Reference option to appear in a project's context menu, the project must be configured to target .NET Framework 3.0 or later.
In the Add Service Reference dialog, specify the service metadata address (not the
service URL, as the dialog states) and click Go to view the available service endpoints
(not "Services" as labeled). Specify a namespace (such as MyService
) to contain the generated proxy, then click OK to generate the
proxy and update the config file. The Discover button lets you discover WCF services in
your own solution, as long as they are hosted either in a website project or in one of the
WCF service library project types. In the case of a website project, Visual Studio 2008
will either retrieve the metadata from IIS or launch the ASP.NET filesystem-based
development server. In the case of a WCF service library, WCF will automatically launch
its host (WcfSvcHost, described in the sidebar The WCF-Provided Host) to
get the metadata.
The Advanced button brings up the settings dialog, where you can tweak the proxy generation options (see Figure 1-10).
The more intuitive options let you configure the visibility of the generated proxy and contracts (public or internal), and you can generate message contracts for your data types for advanced interoperability scenarios, where you have to comply with an existing (typically custom) message format. You can also click the Add Web Reference button to convert the reference to an old ASMX web service reference, as long as the service is using the basic binding.
Once you've added a reference, your project will have a new folder called Service References. In it, you'll find a service reference item for each referenced service (see Figure 1-11).
At any point, you can right-click on a reference and select Update Service Reference to regenerate the proxy. This is possible because each service reference item also contains a file that records the original metadata address used. You can also select Configure Service Reference to bring up a dialog similar to the advanced settings dialog used when adding a reference. The Configure Service Reference dialog lets you change the service metadata address, as well as the rest of the advanced proxy settings.
As an alternative to Visual Studio 2008, you can use the
SvcUtil.exe command-line utility to import the service metadata
and generate a proxy. You need to provide SvcUtil with the metadata exchange address
and, optionally, with a proxy filename. The default proxy filename is
output.cs, but you can use the /out
switch to indicate a different name.
For example, if you're hosting the service MyService
in IIS 5/6 or WAS and have enabled metadata publishing over
HTTP-GET, you can simply run this command line:
SvcUtil http://localhost/MyService/MyService.svc
/out:Proxy.cs
With self-hosting, suppose that the self-hosted service has enabled metadata publishing over HTTP-GET on this base address:
http://localhost:8000/
and has exposed MEX endpoints using these addresses:
http://localhost:8000/MEX http://localhost:8001/MEX net.tcp://localhost:8002/MEX net.pipe://localhost/MyPipe/MEX
After launching the host, you'll be able to use the following commands to generate the proxy:
SvcUtil http://localhost:8000 /out:Proxy.cs SvcUtil http://localhost:8000/MEX /out:Proxy.cs SvcUtil http://localhost:8001/MEX /out:Proxy.cs SvcUtil net.tcp://localhost:8002/MEX /out:Proxy.cs SvcUtil net.pipe://localhost/MyPipe/MEX /out:Proxy.cs
Tip
The main advantage of using SvcUtil over Visual Studio 2008 is that you can include the command line for generating the proxy as a pre-build event.
SvcUtil offers numerous command-line switches that correspond to the options in the Visual Studio advanced settings dialog shown in Figure 1-10.
Regardless of whether you use Visual Studio 2008 or SvcUtil to generate the proxy, Example 1-15 shows the imported contract and generated proxy for this service definition:
[ServiceContract(Namespace = "MyNamespace")] interface IMyContract { [OperationContract] void MyMethod( ); } class MyService : IMyContract { public void MyMethod( ) {...} }
You can safely remove many of the gunk attributes the tools generate, which merely state the defaults, so that you end up with the cleaned-up proxy shown in Example 1-15.
Example 1-15. Client proxy file
[ServiceContract(Namespace = "MyNamespace")] interface IMyContract { [OperationContract] void MyMethod( ); } partial class MyContractClient :ClientBase
<IMyContract>,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 service-implementing 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 default to the http://tempuri.org namespace.
The client needs to know where the service is located, 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 can contain 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
<system.serviceModel><client>
<endpoint name = "MyEndpoint" address = "http://localhost:8000/MyService" binding = "wsHttpBinding" contract = "IMyContract" /></client>
</system.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. There is no relationship between the various endpoints in the client's config file: they could all be pointing at the same endpoint on the service, at different endpoints on the service, at different endpoints on different services, or any mix and match in between. Note also that on the client side you typically name endpoints with unique names (you will see why shortly). Naming the endpoints on the client side is optional, just as it is optional on the service side, yet on the service side you typically do not name the endpoints, while on the client side you typically do.
Example 1-17. Client config file with multiple target endpoints
<system.serviceModel> <client> <endpoint name = "FirstEndpoint" address = "http://localhost:8000/MyService" binding = "wsHttpBinding" contract = "IMyContract" /> <endpoint name = "SecondEndpoint" address = "net.tcp://localhost:8001/MyService" binding = "netTcpBinding" contract = "IMyContract" /> <endpoint name = "ThirdEndpoint" address = "net.tcp://localhost:8002/MyService" binding = "netTcpBinding" contract = "IMyOtherContract" /> </client> </system.serviceModel>
You can customize the client-side 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
<system.serviceModel>
<client>
<endpoint name = "MyEndpoint"
address = "net.tcp://localhost:8000/MyService"
bindingConfiguration = "TransactionalTCP"
binding = "netTcpBinding"
contract = "IMyContract"
/>
</client>
<bindings>
<netTcpBinding>
<binding name = "TransactionalTCP"
transactionFlow = "true"
/>
</netTcpBinding>
</bindings>
</system.serviceModel>
When you add a service reference in Visual Studio 2008, it will also try to
automatically edit the client's config file and insert in it the required client
section describing the service's endpoints. However,
in most cases Visual Studio 2008 is not smart enough to infer the cleanest binding
values, and it will therefore butcher the config file by stating all the default values
for the bindings, which effectively renders the file unreadable. (This issue will be
addressed in a future release of Visual Studio.) Visual Studio 2008 similarly butchers
the config file when updating a service reference. If you care about maintaining the
client's config file, before adding (or updating) a reference you should open the file,
add (or update) the reference, and then perform a single Undo (Ctrl-Z) and manually add
the config file entries in the client
section.
Like Visual Studio 2008, SvcUtil also autogenerates a client-side config file called
output.config. You can specify a different config filename using
the /config
switch:
SvcUtil http://localhost:8002/MyService/ /out:Proxy.cs /config:App.Config
The config files SvcUtil produces are also unreadable, as it's no better at
inferring binding values. However, unlike with Visual Studio, with SvcUtil you can
suppress generating the config file by using the /noconfig
switch:
SvcUtil http://localhost:8002/MyService/ /out:Proxy.cs /noconfig
I recommend never letting SvcUtil or Visual Studio 2008 control the config file.
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
<system.serviceModel> <services> <service name = "MyService"> <endpoint address = "net.pipe://localhost/MyPipe" binding = "netNamedPipeBinding" contract = "IMyContract" /> </service> </services> <client> <endpoint name = "MyEndpoint" address = "net.pipe://localhost/MyPipe" binding = "netNamedPipeBinding" contract = "IMyContract" /> </client> </system.serviceModel>
Note the use of the named pipe binding for in-proc hosting.
WCF provides a config file editor called SvcConfigEditor.exe that can edit both host and client configuration files (see Figure 1-12). You can launch the editor from within Visual Studio by right-clicking on the configuration file (for either the client or the host) and selecting Edit WCF Configuration.
Tip
Due to a bug in Visual Studio 2008, you may have to launch the editor first from the Tools menu.
I have mixed feelings about SvcConfigEditor. On the one hand, it edits the config files nicely, and it saves developers the need to learn the configuration schema. But on the other hand, it does not shield developers from needing to thoroughly understand WCF configuration, and it's often faster to do the light editing that's typically required in a config file by hand than it is using Visual Studio 2008.
The proxy class derives from the class ClientBase<T>
, defined as:
public abstract class ClientBase<T> : 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<T>
accepts a single generic type
parameter identifying the service contract that this proxy encapsulates. The Channel
property of ClientBase<T>
is of that type parameter. As shown in Example 1-15, the generated subclass of ClientBase<T>
simply delegates the method call to Channel
. Calling the method on the Channel
property sends the appropriate WCF message to the service.
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 it is done, the client needs to close the proxy instance. For example, given the same definitions as in Example 1-15 and Example 1-16, the client constructs the proxy, identifying the endpoint to use from the config file, and then invokes the method and closes the proxy:
MyContractClient proxy = new MyContractClient("MyEndpoint"); proxy.MyMethod( ); proxy.Close( );
When specifying the endpoint name to the proxy, its constructor also verifies that the contract configured for that endpoint matches the proxy's type parameter. Because of this verification ability, if exactly one endpoint is defined in the client config file for the type of contract the proxy is using, the client can omit the endpoint name from the proxy's constructor:
MyContractClient proxy = new MyContractClient( )
;
proxy.MyMethod( );
proxy.Close( );
The proxy will simply look up that endpoint (named or not in the config file) and use it. However, if you use this technique when multiple (or zero) endpoints are available for the same contract type, the proxy will throw an exception.
It is a recommended best practice to always close the proxy when the client is done using it. Closing the proxy releases the connection held toward the service, which is particularly important to do in the presence of a transport session (as discussed later in this chapter). It also helps ensure that the threshold for the maximum number of connections on the client's machine is not reached. Furthermore, as you will see in Chapter 4, closing the proxy terminates the session with the service instance.
Instead of closing the proxy, you can use its Dispose(
)
method. Internally, Dispose( )
just
calls Close( )
. 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( )) { //Any exception here automatically closes the proxy; }
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( );
}
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
. This behavior is very
handy, since it offers an elegant way to deal with deadlocks on the service side or just
poor availability. In traditional .NET, the client would have to spin a worker thread
and have the worker thread call the class (and potentially hang), and the client would
then monitor some timed-out event that the worker thread would have to signal when done.
This is obviously a complicated programming model. The advantage of using a proxy for
every call is that the proxy can do all this for you. The exact value of the timeout is
a property of the binding, and 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, here's how to configure the WSHttpBinding
with a five-minute call timeout:
<client>
<endpoint
...
binding = "wsHttpBinding"
bindingConfiguration = "LongTimeout"
...
/>
</client>
<bindings>
<wsHttpBinding>
<binding name = "LongTimeout" sendTimeout
= "00:05:00"/>
</wsHttpBinding>
</bindings>
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 of 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.
Visual Studio 2008 ships with a simple general-purpose test client for rudimentary testing that you can use to invoke operations on most services. The test client is called WcfTestClient.exe, and after a normal installation it is found under C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE. You can provide WcfTestClient with a single command-line argument containing the metadata address of the service to test:
WcfTestClient.exe http://localhost:9000/
You can specify any metadata address (be it an HTTP-GET address, or a metadata exchange endpoint over HTTP, TCP, or IPC). You can also specify multiple metadata addresses:
WcfTestClient.exe http://localhost:8000/ net.tcp://localhost:9000/MEX
You can also launch the test client without a command-line parameter. Once it's running, you can add a new service by selecting Add Service from the File menu and then specify the metadata address in the Add Service dialog. You can also remove a service by right-clicking on it in the services tree.
WcfTestClient is a Windows Forms 3.5 application. The tree control in the left pane contains the tested services and their endpoints. You can drill into an endpoint's contract and select an operation to display a dedicated tab for that invocation in the pane on the right. For example, for this simple contract and implementation:
[ServiceContract] interface IMyContract { [OperationContract] string MyMethod(int someNumber,string someText); } class MyService : IMyContract { public string MyMethod(int someNumber,string someText) { return "Hello"; } }
the method tab will let you provide an integer and a string as operation parameters in the Request section, as shown in Figure 1-13.
When you click the Invoke button, WcfTestClient will dispatch the call to the service and display the returned value or out-parameters in the Response section. In case of an exception, WcfTestClient will display the exception information in a message box and allow you to issue additional calls. All calls are made on new proxy instances. In addition, all calls are made asynchronously, so that the UI is kept responsive. However, while the calls are asynchronous, WcfTestClient will let you dispatch only one call at a time.
The way WcfTestClient functions is by silently creating an assembly from a proxy file, complete with a config file, and then loading it for use from a temporary location. If you click on the Config File item in the tree on the left, you can actually grab that config file (the same config file generated when adding a service reference) and display the config file in its own tab. You can even edit the config file using SvcConfigEditor.
WcfTestClient allows you to invoke operations with enumerations, composite parameters such as classes or structures (each of which is a composite of other classes or structures), and even collections and arrays of parameters. Simply expand the items in the Request section, set their values from the drop-down lists (e.g., enum values), and invoke the call. If the operation accepts a collection or an array, you will also need to set the length. Again, the Response pane will contain any composite returned value or out-parameters.
As with WcfSvcHost (see the earlier sidebar The WCF-Provided Host), you can integrate WcfTestClient directly into your Visual Studio 2008 solution. First, add a class library project to the solution, and delete from it all references, folders, and source files, since you have no need for those. Next, set WcfTestClient.exe as the external start program, and provide the metadata address (or addresses) of the tested service (or services). This may be the .svc address of an IIS 5/6- or WAS-hosted project, or, for that matter, any other metadata address of a host project, whether inside or outside your solution.
Of course, you can combine the use of WcfTestClient and WcfSvcHost in a single step to automatically host a service in a service library and test it:
WcfSvcHost.exe /service:MyService.dll /config:App.config /client:WcfTestClient.exe /clientArgs:http://localhost:9000/
However, with WcfSvcHost, specifying the metadata arguments is optional. By default, WcfSvcHost will pipe into the specified client application any metadata addresses it finds in the service config file. You should specify a service's metadata address explicitly only if the service (or services) does not provide its own metadata, or if you would like the test client to use different addresses. If the service config file contains multiple metadata endpoints for a given service, they will be provided in this precedence order: HTTP, TCP, IPC, HTTP-GET.
You can incorporate these steps into Visual Studio 2008 for a seamless hosting and
testing experience. To do this, specify WcfSvcHost.exe as the startup program along with the config file, and
specify WcfTestClient.exe as the client. When you
invoke WcfTestClient using /client
, closing the test
client also terminates the host.
Get Programming WCF Services, 2nd Edition 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.