Misunderstood: Add Service Reference


Every so often, I get into a discussion about whether Add Service Reference is required in an application if the application owns both the service and one consumer. The short answer is No. But, you probably want to know Why? To understand that, we need to look at what Add Service Reference actually does and why you can do the same for yourself.

For the purposes of our discussion, the work actually being done by the service is unimportant. Let us assume the following interface:

    1 using System.ServiceModel;

    2 

    3 namespace BlogWCF

    4 {

    5     [ServiceContract]

    6     public interface ISimpleService

    7     {

    8         [OperationContract]

    9         string SayHello(string name);

   10     }

   11 }

The implementation of the interface is then

    1 namespace BlogWCF

    2 {

    3     public class SimpleService : ISimpleService

    4     {

    5         public string SayHello(string name)

    6         {

    7             return string.Format("Hello, {0}", name);

    8         }

    9     }

   10 }

So, nothing terribly complex going on. To be able to discover information about this service, we will also add a metadata endpoint (the configuration is at the end of this article). With the metadata endpoint exposed, we can point Add Service Reference at the endpoint and generate a consumer. Add Service Reference does not necessarily know that the other endpoint is implemented in Java, .NET, or some other language. It generates a bunch of bookkeeping files so that the client can be regenerated at will. If you click on Show All Files in your VS project, you will see many files under the reference you just added.

  • configuration.svcinfo: Contains a snapshot of the configuration generated for the client service endpoint for the local (app|web).config 
  • configuration91.svcinfo: For each property in config, contains an XPath to the setting and the original value stored in config.
  • *.wsdl: Copies of the WSDL files exposed by the WSDL endpoint.
  • *.xsd: Copies of any schema files exposed by the WSDL endpoint.
  • Reference.svcmap: Pointer to the service and pointers to any svcinfo, wsdl, and xsd files in the project. This file also stores any special settings selected by the user from when the ServiceReference was first generated. All of this is used when the user chooses to regenerate the reference.
  • Reference.(cs|vb): Contains a WCF compatible interface of the contract, a client channel capable of communicating with the contract, and a proxy class that can be instantiated.

All these files are needed to create and refresh a client when you do not control the client. When you do control the client, none of this is needed. You can share the configured binding in between the client and the service. However, you must put the service contract on an interface– that’s the only real requirement. If your client and service are in different binaries, I would recommend putting the Service and Data contracts into yet another class library that can be referenced from the service and the consumer. This reduces the coupling of the interface its implementation. Furthermore, you need to define the WCF contract on an interface, not on the implementation class. WCF cannot construct a client implementation based on a class definition. This is due to a number of details associated with how the CLR allows code to intercept method calls. The general rule is that you can intercept method calls on interfaces but not on classes.

To consume the service, WCF needs to know the address, the binding, and the contract, nothing more. WCF class libraries know nothing of the .svcmap file and its cohorts. Using this mechanism, we then talk to the service using code that many of us have seen before. If you own the service implementation, none of this is necessary. Instead, you can use stuff you know to create the client on your own. I’m going to show you some code that I have for a console application. This code will work no matter the context (Web, service, Windows app). The code shows three variations: reading the ServiceEndpoint from the ServiceHost, running the client from pure code, or reading the client from configuration. This is just a demonstration of what is possible.

    1 using System;

    2 using System.ServiceModel;

    3 

    4 namespace BlogWCF

    5 {

    6     class Program

    7     {

    8         static void Main(string[] args)

    9         {

   10             using (ServiceHost host = new ServiceHost(typeof(SimpleService)))

   11             {

   12                 host.Open();

   13                 Console.WriteLine("Service opened");

   14 

   15                 // Using the service description

   16                 using (ChannelFactory<ISimpleService> client = new

   17                     ChannelFactory<ISimpleService>(host.Description.Endpoints[0]))

   18                 {

   19                     ISimpleService channel = client.CreateChannel();

   20                     Console.WriteLine(channel.SayHello("Service Description"));

   21                 }

   22 

   23                 // Using stuff we know from config.

   24                 using (ChannelFactory<ISimpleService> client = new

   25                     ChannelFactory<ISimpleService>(new WSHttpBinding(),

   26                     new EndpointAddress("http://localhost:8731/Design_Time_Addresses/BlogWCF/SimpleService/&quot;)))

   27                 {

   28                     ISimpleService channel = client.CreateChannel();

   29                     Console.WriteLine(channel.SayHello("Binding"));

   30                 }

   31                 // Using a client in config.

   32                 using (ChannelFactory<ISimpleService> client = new

   33                     ChannelFactory<ISimpleService>("SampleClient"))

   34                 {

   35                     ISimpleService channel = client.CreateChannel();

   36                     Console.WriteLine(channel.SayHello("config"));

   37&
#160;                }

   38                 Console.ReadLine();

   39             }

   40         }

   41     }

   42 }

Config for the service:

    1 <?xml version="1.0" encoding="utf-8" ?>

    2 <configuration>

    3     <system.serviceModel>

    4         <client>

    5             <remove contract="IMetadataExchange" name="sb" />

    6             <endpoint address="" binding="netTcpRelayBinding" bindingConfiguration="metadataExchangeRelayBinding"

    7                 contract="IMetadataExchange" name="sb" />

    8             <endpoint address="http://localhost:8731/Design_Time_Addresses/BlogWCF/SimpleService/"

    9                 binding="wsHttpBinding" bindingConfiguration="" contract="BlogWCF.ISimpleService"

   10                 name="SampleClient" />

   11         </client>

   12         <behaviors>

   13             <serviceBehaviors>

   14                 <behavior name="BlogWCF.SimpleServiceBehavior">

   15                     <serviceMetadata httpGetEnabled="true" />

   16                 </behavior>

   17             </serviceBehaviors>

   18         </behaviors>

   19         <services>

   20             <service behaviorConfiguration="BlogWCF.SimpleServiceBehavior"

   21                 name="BlogWCF.SimpleService">

   22                 <endpoint address="" binding="wsHttpBinding" contract="BlogWCF.ISimpleService">

   23                     <identity>

   24                         <dns value="localhost" />

   25                     </identity>

   26                 </endpoint>

   27                 <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />

   28                 <host>

   29                     <baseAddresses>

   30                         <add baseAddress="http://localhost:8731/Design_Time_Addresses/BlogWCF/SimpleService/" />

   31                     </baseAddresses>

   32                 </host>

   33             </service>

   34         </services>

   35     </system.serviceModel>

   36 </configuration>