Introduction
If you are working with a WCF service application and the project contains a handful
of simple data contracts, then you possibly won't encounter any problem in
using the Add Service Reference feature of Visual Studio to generate service
proxies. However, there might come a time when you need to use inheritance or
circular references in your data contracts. In these cases, creating service
proxies manually might be a better option than using the Add Service Reference
dialog.
Getting Started
Let's create a simple WCF service application. Open up Visual Studio and create
a WCF service application project. The following files are created: IService1.cs,
Service1.svc, Service1.svc.cs and Web.config. Let's rename Service1 to MyService.
Run the application in Debug mode. This uses the ASP .NET Development Server
to host the WCF service. Click on the MyService.svc link and the browser will
display details on how to use the service, as shown in the following figure.
Figure 1. WCF Service in the Web Browser
Now, create the client application that will consume our service. I'll create
a simple console application. Right click on the project and select Add Service
Reference from the context menu. This opens the Add Service Reference dialog,
which is shown in Figure 2. Fill up the address field. The value you supply to
this field is the link you see in the WCF service’s web page, which is http://localhost:50127/MyService.svc?wsdl in our current example. You can also set the value of the namespace field to a more
appropriate namespace.
Figure 2. Adding a Service Proxy using the Add Service Reference Dialog
After clicking OK, our project will contain additional files, which are hidden by
default in Visual Studio. We can see these files in the Solution Explorer by
clicking on the Show All Files menu item from the Project menu. Most of these
files are XML files containing the details will enable us to invoke the WCF service,
like the address, binding configuration and the definition of the data needed
by the service. These XML files are useful when the consumer of the WCF service
is not a .NET client. This ensures interoperability with other languages. The
following figure shows the generated files. The files with the file extension
XSD contains the definition of the classes used by the service, but in XML format.
Common data types like string and integer are defined here. To invoke the WCF
service, we have to construct the required data using XML. However, we can create
code that will serialize and deserialize objects to and from XML so that we can
use the language of our choice.
Figure 3. Service Reference Files
If we are using a .NET language, then this is automatically done for us. If you open
the Reference.cs file, you will see the classes that correspond to those found
in the XML files. These classes will be quite different from the original classes
defined in the server project. Classes are annotated with some attributes. Data
contract classes also implement the interfaces IExtensibleDataObject and INotifyPropertyChanged.
namespace WCFServiceClientApp.MyServiceReference {
using System.Runtime.Serialization;
using System;
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="CompositeType", Namespace="http://schemas.datacontract.org/2004/07/WCFServiceApp")]
[System.SerializableAttribute()]
public partial class CompositeType : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {
[System.NonSerializedAttribute()]
private System.Runtime.Serialization.ExtensionDataObject extensionDataField;
[System.Runtime.Serialization.OptionalFieldAttribute()]
private bool BoolValueField;
[System.Runtime.Serialization.OptionalFieldAttribute()]
private string StringValueField;
[global::System.ComponentModel.BrowsableAttribute(false)]
public System.Runtime.Serialization.ExtensionDataObject ExtensionData {
get {
return this.extensionDataField;
}
set {
this.extensionDataField = value;
}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public bool BoolValue {
get {
return this.BoolValueField;
}
set {
if ((this.BoolValueField.Equals(value) != true)) {
this.BoolValueField = value;
this.RaisePropertyChanged("BoolValue");
}
}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public string StringValue {
get {
return this.StringValueField;
}
set {
if ((object.ReferenceEquals(this.StringValueField, value) != true)) {
this.StringValueField = value;
this.RaisePropertyChanged("StringValue");
}
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null)) {
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="MyServiceReference.IMyService")]
public interface IMyService {
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IMyService/GetData", ReplyAction="http://tempuri.org/IMyService/GetDataResponse")]
string GetData(int value);
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IMyService/GetDataUsingDataContract", ReplyAction="http://tempuri.org/IMyService/GetDataUsingDataContractResponse")]
WCFServiceClientApp.MyServiceReference.CompositeType GetDataUsingDataContract(WCFServiceClientApp.MyServiceReference.CompositeType
composite);
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public interface IMyServiceChannel : WCFServiceClientApp.MyServiceReference.IMyService, System.ServiceModel.IClientChannel {
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public partial class MyServiceClient : System.ServiceModel.ClientBase<WCFServiceClientApp.MyServiceReference.IMyService>,
WCFServiceClientApp.MyServiceReference.IMyService {
public MyServiceClient() {
}
public MyServiceClient(string endpointConfigurationName) :
base(endpointConfigurationName) {
}
public MyServiceClient(string endpointConfigurationName, string remoteAddress) :
base(endpointConfigurationName, remoteAddress) {
}
public MyServiceClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
base(endpointConfigurationName, remoteAddress) {
}
public MyServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress) {
}
public string GetData(int value) {
return base.Channel.GetData(value);
}
public WCFServiceClientApp.MyServiceReference.CompositeType GetDataUsingDataContract(WCFServiceClientApp.MyServiceReference.CompositeType
composite) {
return base.Channel.GetDataUsingDataContract(composite);
}
}
}
Listing 1. The Service and Data Contract Classes
We can now use the service proxy in our application. For our application to work,
the service should be running and the configuration files on both the server
and client are correct. These configuration files should contain the details
about the service address, binding configuration and contract. The client application’s
configuration file is automatically created when we used the Add Service Reference
dialog. It is important to note that the service’s binding configuration details,
like encoding and timeout, found in the application configuration file should
be the same with the configuration details in the web configuration file in the
server project. The following code listing shows an example on how to invoke
a WCF service using a service proxy.
class Program
{
static void Main(string[] args)
{
MyServiceClient myServiceClient = new MyServiceClient();
CompositeType compositeType = new CompositeType()
{
BoolValue = true,
StringValue = "Test"
};
CompositeType returnValue = myServiceClient.GetDataUsingDataContract(compositeType);
myServiceClient.Close();
// Outputs "TestSuffix"
Console.WriteLine(returnValue.StringValue);
Console.ReadKey();
}
}
Listing 2. Application using the WCF Service
Using the Add Service Reference dialog makes our work faster. Yet, it may not be
the best way if we want to use WCF services in a client application. If we are
creating a .NET client application that will consume a WCF service, we actually
do not need the generated XML files of the service reference. Try to delete these
files and the application will still run perfectly. Meanwhile, using inheritance
in our data contract classes might result in multiple definitions of a certain
type over different namespaces.
Reusing Types in Shared Assemblies
Let's say we have two classes, Customer and Employee, that inherit from a Person
class. These classes are used by two services, ICustomerService and IEmployeeService.
Listing 3 shows these classes and interfaces. As you can see, we have to mark
the inherited type with the KnownType attribute, wherein we specify the type
of the object that inherits from it. Take note that the ICustomerService only
uses the Customer data contract while the IEmployeeService only uses the Employee
data contract.
[DataContract]
[KnownType(typeof(Employee))]
[KnownType(typeof(Customer))]
public abstract class Person
{
}
[DataContract]
public class Customer : Person
{
[DataMember]
public int Id { get; set; }
}
[DataContract]
public class Employee : Person
{
[DataMember]
public int Id { get; set; }
}
[ServiceContract]
public interface ICustomerService
{
[OperationContract]
Customer GetCustomer(int id);
}
[ServiceContract]
public interface IEmployeeService
{
[OperationContract]
Employee GetEmployee(int id);
}
public class CustomerService : ICustomerService
{
#region ICustomerService Members
public Customer GetCustomer(int id)
{
throw new NotImplementedException();
}
#endregion
}
public class EmployeeService : IEmployeeService
{
#region IEmployeeService Members
public Employee GetEmployee(int id)
{
throw new NotImplementedException();
}
#endregion
}
Listing 3. The Customer and Employee Services
Let’s use again the Add Service Reference dialog in generating the service proxies
for the Customer and Employee services. Listing 4 shows the contents of the two
Reference.cs files. Notice that we have two different namespaces here, WCFServiceClientApp.CustomerServiceReference
and WCFServiceClientApp.EmployeeServiceReference. However, each of them has definitions
of the following classes: Person, Employee and Customer. This makes sense because
a service can be invoked without dependence on the other services. Yet, this
can get messy if there are a lot of data contracts that inherit from some base
class. Aside from it is hard to determine which namespace to use, you cannot
use a single base type for two service operations in different namespaces. In
our example client application, we cannot use the same Person class as the return
type of both GetCustomer and GetEmployee operations.
namespace WCFServiceClientApp.CustomerServiceReference {
using System.Runtime.Serialization;
using System;
public partial class Person : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {
...
}
public partial class Employee : WCFServiceClientApp.CustomerServiceReference.Person {
...
}
public partial class Customer : WCFServiceClientApp.CustomerServiceReference.Person {
...
}
public interface ICustomerService {
...
}
public interface ICustomerServiceChannel : WCFServiceClientApp.CustomerServiceReference.ICustomerService,
System.ServiceModel.IClientChannel {
}
public partial class CustomerServiceClient : System.ServiceModel.ClientBase<WCFServiceClientApp.CustomerServiceReference.ICustomerService>,
WCFServiceClientApp.CustomerServiceReference.ICustomerService {
...
}
}
namespace WCFServiceClientApp.EmployeeServiceReference {
using System.Runtime.Serialization;
using System;
public partial class Person : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {
...
}
public partial class Customer : WCFServiceClientApp.EmployeeServiceReference.Person {
...
}
public partial class Employee : WCFServiceClientApp.EmployeeServiceReference.Person {
...
}
public interface IEmployeeService {
...
}
public interface IEmployeeServiceChannel : WCFServiceClientApp.EmployeeServiceReference.IEmployeeService,
System.ServiceModel.IClientChannel {
}
public partial class EmployeeServiceClient : System.ServiceModel.ClientBase<WCFServiceClientApp.EmployeeServiceReference.IEmployeeService>,
WCFServiceClientApp.EmployeeServiceReference.IEmployeeService {
...
}
}
Listing 4. Customer and Employee Service References
One way to resolve these issues is to reuse types in shared assemblies. This means
we put all our data contracts in a separate assembly to be shared or referenced
by both the server and client applications. Note that this will only work when
we have a .NET client. Create a class library project and put all the data contracts
there. In our example, the classes Person, Employee and Customer will be put
in this project. If you want, you can put the project in separate directory to
differentiate shared assemblies from the client and server assemblies. Add a
reference to the shared assembly in the client and server projects. Assuming
that we have existing service references for the Customer and Employee services,
right click each of them on the solution explorer and select Configure Service
Reference... from the context menu. This will show the following dialog.
Figure 4. Configure Service Reference
We just need to make sure that the checkbox indicating whether to reuse types in
referenced assemblies is checked. You can also select to reuse types in all referenced
assemblies or just the types from the specified reference assemblies. After clicking
OK, update the service reference by right clicking on the service reference again
and selecting Update Service Reference from the context menu. Here is the updated
Reference.cs file of the Customer service.
namespace WCFServiceClientApp.CustomerServiceReference {
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="CustomerServiceReference.ICustomerService"
]
public interface ICustomerService {
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/ICustomerService/GetCustomer", ReplyAction="http://tempuri.org/ICustomerService/GetCustomerResponse")]
DataContracts.Customer GetCustomer(int id);
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public interface ICustomerServiceChannel : WCFServiceClientApp.CustomerServiceReference.ICustomerService,
System.ServiceModel.IClientChannel {
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public partial class CustomerServiceClient : System.ServiceModel.ClientBase<WCFServiceClientApp.CustomerServiceReference.ICustomerService>,
WCFServiceClientApp.CustomerServiceReference.ICustomerService {
public CustomerServiceClient() {
}
public CustomerServiceClient(string endpointConfigurationName) :
base(endpointConfigurationName) {
}
public CustomerServiceClient(string endpointConfigurationName, string remoteAddress) :
base(endpointConfigurationName, remoteAddress) {
}
public CustomerServiceClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
base(endpointConfigurationName, remoteAddress) {
}
public CustomerServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress) {
}
public DataContracts.Customer GetCustomer(int id) {
return base.Channel.GetCustomer(id);
}
}
}
Listing 5. The Updated Customer Service Reference
You will notice in the ICustomerService interface that the GetCustomer operation
now uses the data contract class in the shared assembly. This deals with the
issues we had earlier due to multiple definitions of data contract classes. Everything
might work fine already. However, you might encounter an error when adding service
references if the relationships between the data contract classes contain circular
references.
Manually Adding Service References
I’ve encountered a problem once wherein using Visual Studio’s Add Service Reference
feature generates an error or warning saying that the imported data contract
does not match the data contract defined in one of the XML files of the service
reference. This error occurs when I set the IsReference property of the DataContract attribute to true
of my base class. Setting the IsReference property to true resolves the problem
with circular references. Using the previous example, let’s say we have another
data contract called Child. The following figure shows the relationships of the
Child class with the existing classes.
Figure 5. Circular References
A circular reference happens when an object has a reference to an object that inherits
from it, which in turn has a reference to the former. The DataContractSerializer
class, used in serializing data contracts into XML, does not preserve circular
reference by default. Thus a runtime exception occurs when you try to invoke
the WCF service that involves passing of objects with circular references. So
in our example, we only need to set the IsReference property of the DataContract
attribute annotating the Person class to remove the exception. Going back to
my original problem, I haven’t found out what I did wrong with my data contract
classes. So I did a quick search and certainly I saw a similar problem in this
forum thread. Let’s change the previous example to match the problem described in the forum thread.
Figure 6. Updated Class Diagram
Assuming we have set the IsReference property to true, let’s update the Customer
service reference. You will see a warning as shown in the following figure. This
warning results in a custom tool error. Therefore, the contents of Reference.cs
won’t be generated automatically.
Figure 7. Update Service Reference Error
Recently, I’ve found a blog entry by Zafar Mohammed. It says that the tool that generates the service reference code
does not pick up the IsReference property for a certain configuration or structure
of data contract classes. There are also proposed workarounds like creating a
DataContractSerializer with preserveObjectReferences parameter set to true.
Since this problem arises when we reuse types from referenced assemblies, I think
this only happens when we have a .NET client. One way to resolve the problem
is not to use the Add Service Reference feature at all. To do this, we have to
put the service contracts in an assembly to be shared again by the client and
server. In our existing server project, we just need to update codes and web
configuration that references the service contract interfaces. In the client
project, we need to implement these interfaces. Here is the implementation of
the ICustomerService interface.
class CustomerServiceClient : ClientBase<ICustomerService>, ICustomerService
{
#region ICustomerService Members
public Customer GetCustomer(int id)
{
return Channel.GetCustomer(id);
}
#endregion
}
Listing 6. The ICustomerService Implementation
Actually, we just used the pattern used in the Reference.cs file. The service client
inherits from the ClientBase class to be able to access the channel to communicate
with the service. Another option is not to inherit from ClientBase. Instead,
we can use the ChannelFactory to manually create the channel. So aside from solving
our previous problem, we now have more control over the code. Here is the very
simple example of the final code.