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. 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 2010 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-14.
Note
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 box, specify the service
metadata address (not the service URL, as the dialog box 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. Use
the Discover button to 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 2010 will either retrieve the metadata from IIS or
launch the ASP.NET file system–based development server. In the case
of a WCF service library, WCF will automatically launch its host
(WcfSvcHost, described in the sidebar titled The WCF-Provided Test Host) to get the metadata.
Click the Advanced button to bring up the settings dialog box, where you can tweak the proxy generation options (see Figure 1-15).
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-16).
At any point, you can right-click 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 box similar to the advanced settings dialog box used when adding a reference. The Configure Service Reference dialog box lets you change the service metadata address, as well as the rest of the advanced proxy settings.
As an alternative to Visual Studio 2010, 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 the 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 the 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
Note
The main advantage of using SvcUtil over Visual Studio 2010 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 box shown in Figure 1-15.
Regardless of whether you use Visual Studio 2010 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();
}
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 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 and must 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 scheme 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 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>
The client can rely on a default binding configuration, just as the service does. Note that if the config file contains both client and service sections, the default binding will affect both.
When you add a service reference in Visual Studio 2010, it will also try to automatically
edit the client’s config file and insert the required client
section
describing the service’s endpoints in it. However, in most cases
Visual Studio 2010 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. Visual Studio 2010 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 2010, 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 2010 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-17). 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.
Note
Due to a limitation in the design of Visual Studio 2010, you will 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 scheme. 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 2010.
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 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 query for the presence of
IDisposable
:
IMyContract proxy = new MyContractClient(); proxy.MyMethod(); IDisposable disposable = proxy as IDisposable; if(disposable != null) { disposable.Dispose(); }
Alternatively, the client can collapse the query inside the
using
statement:
IMyContract proxy = new MyContractClient();
using(proxy as IDisposable
)
{
proxy.MyMethod();
}
Note
While the results of calling Dispose()
and Close()
are identical, you will see in
Chapter 6 that it is always better to call
Close()
than to use the
using
statement.
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 has to spin a worker thread and have the worker thread
call the class (and potentially hang), and the client then monitors
some timed-out event that the worker thread has 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 2010 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 in C:\Program Files\Microsoft Visual Studio 10.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 specifying the metadata address in the Add Service dialog box. You can also remove a service by right-clicking it in the services tree.
WcfTestClient is a Windows Forms 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-18.
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.
WcfTestClient functions 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 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 sidebar The WCF-Provided Test Host), you can integrate WcfTestClient directly into your Visual Studio 2010 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 /clientArg: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 2010 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, 3rd 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.