Adding WCF Service References

Using the Visual Studio's Add Service Reference dialog enables us to quickly use WCF services in a client application. Sometimes, it is better not to use it due to some problems it presents.

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.

By Michael Detras   Popularity  (35208 Views)
Biography - Michael Detras
.NET developer. Interested in WPF, Silverlight, and XNA.
My blog