Friday, January 31, 2014

WCF Tutorials - Part-1

WCF Tutorial 
Part 1 - Introduction to WCF
In this video we will discuss
What is WCF
Why should we use WCF



What is WCF?
WCF stands for Windows Communication Foundation and is part of .NET 3.0. WCF is Microsoft platform for building distributed and interoperable applications.



What is a distributed application?
In simple terms a distributed application, is an application where parts of it run on 2 or more computers. Distributed applications are also called as connected systems.

Examples:
A web application running on one machine and a web service that this application is consuming is running on another machine.
Distributed System

An enterprise web application may have the following tiers, and each tier may be running on a different machine
1. Presentation tier
2. Business tier
3. Data Access tier
Connected System

Why build distributed applications?
There are several reasons for this
1. An enterprise application may need to use the services provided by other enterprises. For example an ecommerce application may be using Paypal service for payments.
2. For better scalability. An enterprise web application may have Presentation tier, Business tier, and Data Access tier, and each tier may be running on a different machine.

What is an interoperable application?
An application that can communicate with any other application that is built on any platform and using any programming language is called as an interoperable application. Web services are interoperable, where as .NET remoting services are not.

Web services can communicate with any application built on any platform, where as a .NET remoting service can be consumed only by a .net application.

What technology choices did we have before WCF to build distributed applications?
Enterprise Services
Dot Net Remoting
Web Services

Why should we use WCF?
Let's take this scenario
We have 2 clients and we need to implement a service a for them. 
1. The first client is using a Java application to interact with our service, so for interoperability this client wants messages to be in XML format and the protocol to beHTTP.
2. The second client uses .NET, so for better performance this client wants messages formatted in binary over TCP protocol.

Without WCF
1. To satisfy the first client requirement we end up implementing an ASMX web service, and
asp.net web service example

2. To satisfy the second client requirement we end up implementing a remoting service
Dot Net Remoting Example

These are 2 different technologies, and have complete different programming models.So the developers have to learn different technologies.

So to unify and bring all these technologies under one roof Microsoft has come up with a single programming model that is called as WCF - Windows Communication Foundation.

With WCF,
You implement one service and we can configure as many end points as want to support all the client needs. To support the above 2 client requirements, we would configure 2 end points. In the endpoint configuration we can specify the protocols and message formats that we want to use.
WCF Example

In Part 2, we will discuss implementing
1. A web service to exchange messages in XML format using HTTP protocol forinteroperability.
2. A remoting service to exchange messages in binary format using TCP protocol forperformance.
Along the way, we will get a feel of how different these technologies are.

In Part 3, we will discuss implementing a single WCF Service and configuring different end points to support different transport protocols and message formats.
Part 2 - Creating a remoting service and a web service
Suggested Videos
Part 1 - Introduction to WCF

In this video we will discuss creating a simple remoting and a web service. This is continuation to Part 1. Please watch Part 1 from WCF video tutorial before proceeding.



We have 2 clients and we need to implement a service a for them. 
1. The first client is using a Java application to interact with our service, so for interoperability this client wants messages to be in XML format and the protocol to beHTTP.
2. The second client uses .NET, so for better performance this client wants messages formatted in binary over TCP protocol.

To satisfy the requirement of the first client let's create a web service. Web services use HTTP protocol and XML message format. So interoperability requirement of the first client will be met. Web services can be consumed by any client built on any platform.



To create the web service
1. Create an empty asp.net web application project and name it WebServicesDemo.
2. Right click on the project name in solution explorer and add a web service. Name itHelloWebService.
3. Copy and paste the following code HelloWebService.asmx.cs. 

using System.Web.Services;
namespace WebServicesDemo
{
    [
WebService(Namespace = "http://pragimtech.com/WebServices")]
    [
WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.
ToolboxItem(false)]
    [System.Web.Script.Services.
ScriptService]
    
public class HelloWebService : System.Web.Services.WebService
    {
        [
WebMethod]
        
public string GetMessage(string name)
        {
            
return "Hello " + name;
        }
    }
}

Build the solution.

Creating a client for the Web Service.
1. Right click on WebServicesDemo solution in solution explorer and add a new asp.net empty web application project and name it HelloWebApplication.
2. Right click on References folder in HelloWebApplication project and select Add Service Reference option. In Add Service Reference dialog box click the Discoverbutton. In the namespace textbox type HelloWebService and click OK. This should generate a proxy class to invoke the HelloWebService.
3. Right click on HelloWebApplication project name in solution explorer and a new web form. This should add WebForm1.aspx
4. Copy and paste the following HTML on WebForm1.aspx
<table style="font-family:Arial">
    <tr>
        <td>
            <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
            <asp:Button ID="Button1" runat="server" Text="Get Message"
            onclick="Button1_Click" />
        </td>
    </tr>
    <tr>
        <td>
            <asp:Label ID="Label1" runat="server" Font-Bold="true">
            </asp:Label>
        </td>      
    </tr>
</table>
5. Copy and paste the following code in WebForm1.aspx.cs
protected void Button1_Click(object sender, EventArgs e)
{
    HelloWebService.
HelloWebServiceSoapClient client = new
    HelloWebService.
HelloWebServiceSoapClient();

    Label1.Text = client.GetMessage(TextBox1.Text);
}

The ASP.NET web application is now able to communicate with the web service. Not just asp.net, a JAVA application can also consume the web service.

To satisfy the requirement of the second client let's create a .NET remoting service.
Creating a remoting service
1. Create a new Class Library project and name it IHelloRemotingService.
2. Rename Class1.cs file to IHelloRemotingService.cs. Copy and paste the following code in IHelloRemotingService.cs file.
namespace IHelloRemotingService
{
    
public interface IHelloRemtingService
    {
        
string GetMessage(string name);
    }
}
3. Right click on IHelloRemotingService solution in solution explorer and add new class library project, and name it HelloRemotingService.
4. We want to use interface IHelloRemotingService in HelloRemotingService project. So add a reference to IHelloRemotingService project.
5. Rename Class1.cs file to HelloRemotingService.cs. Copy and paste the following code in HelloRemotingService.cs file.
using System;
namespace HelloRemotingService
{
    
public class HelloRemotingService MarshalByRefObject,
        IHelloRemotingService.
IHelloRemtingService
    {
        
public string GetMessage(string name)
        {
            
return "Hello " + name;
        }
    }
}
6. Now we need to host the remoting service. To host it let's use a console application. A windows application or IIS can also be used to host the remoting service. Right click on IHelloRemotingService solution in solution explorer and add new Console Application  project, and name it RemotingServiceHost.
7. Add a reference to IHelloRemotingService and HelloRemotingService projects and System.Runtime.Remoting assembly.
8. Copy and paste the following code in Program.cs file
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;

namespace RemotingServiceHost
{
    
class Program
    {
        
static void Main()
        {
            HelloRemotingService.
HelloRemotingService remotingService =
                
new HelloRemotingService.HelloRemotingService();
            
TcpChannel channel = new TcpChannel(8080);
            
ChannelServices.RegisterChannel(channel);
            
RemotingConfiguration.RegisterWellKnownServiceType(
               
typeof(HelloRemotingService.HelloRemotingService), "GetMessage",
               
WellKnownObjectMode.Singleton);
            
Console.WriteLine("Host started @ " + DateTime.Now.ToString());
            
Console.ReadLine();
        }
    }
}
9. Now we need to create the client for our remoting service. Let's use windows application as the client. Right click on IHelloRemotingService solution in solution explorer and add new windows application.
10. Add a reference to IHelloRemotingService project and System.Runtime.Remotingassembly.
11. Drag and drop a textbox, button and a label control on Form1 in the windows application.
12. Double click the button to generate the click event handler. Copy and paste the following code in Form1.cs.
using System;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Windows.Forms;

namespace HelloRemotingServiceClient
{
    
public partial class Form1 Form
    {
        IHelloRemotingService.
IHelloRemtingService client;
        
public Form1()
        {
            InitializeComponent();
            
TcpChannel channel = new TcpChannel();
            
ChannelServices.RegisterChannel(channel);
            client = (IHelloRemotingService.
IHelloRemtingService)Activator.GetObject(
              
typeof(IHelloRemotingService.IHelloRemtingService),
              
"tcp://localhost:8080/GetMessage");
        }

        
private void button1_Click(object sender, EventArgs e)
        {
            label1.Text = client.GetMessage(textBox1.Text);
        }
    }
}

By this point you may have already realized how different web service and remoting programming models are. In Part 3, we will discuss implementing a single WCF servicethat can satisfy the requirements of both the clients.

Can we use .NET Remoting to build a Web service? 
Yes.

If the client is not built using .NET platform, will they be able to consume a .NET Remoting web service?
They can, but we need to be very careful in choosing the data types that we use in the service, and client-activated objects and events should be avoided.  But keep in mind .NET Remoting is not meant for implementing interoperable services.

If your goal is to build interoperable services use ASP.NET Web Services. But with introduction of WCF, both .NET Remoting and ASP.NET Web Services are legacy technologies
Part 3 - Creating a wcf service
Suggested Videos
Part 1 - Introduction to WCF
Part 2 - Creating a remoting service and a web service

This is continuation to Part 2. Please watch Part 2 from WCF video tutorial before proceeding.



In this video, we will discuss
1. Creating a WCF service
2. Hosting the WCF service using a console application
3. Exposing 2 service endpoints.
4. Creating a windows and a web Client applications.



Let's take the scenario that we discussed in Part 2
We have 2 clients and we need to implement a service a for them.
1. The first client is using a Java application to interact with our service, so for interoperability this client wants meesages to be in XML format and the protocl to be HTTP.
2. The second client uses .NET, so for better performance this client wants messages formmated in binary over TCP protocol.

In Part 2,
To meet the requirement of the first client, we implemented a web service and to meet the requirement of the second client we implemented a remoting service.

In this video, we will create a single WCF service, and configure 2 endpoints to meet the requirements of both the clients.

Creating the WCF Service:
1. Create a new Class Library Project and name it HelloService.
2. Delete Class1.cs file that is auto-generated.
3. Add a new WCF Service with name = HelloService. This should automatically generate 2 files (HelloService.cs & IHelloService.cs). Also a reference toSystem.ServiceModel assembly is added.
4. Copy and paste the following code in IHelloService.cs file
using System.ServiceModel;
namespace HelloService
{
    [
ServiceContract(Namespace="http://PragimTech.com/ServiceVersion1")]
    
public interface IHelloService
    {
        [
OperationContract]
        
string GetMessage(string name);
    }
}
5. Copy and paste the following code in HelloService.cs
namespace HelloService
{
    
public class HelloService IHelloService
    {
        
public string GetMessage(string name)
        {
            
return "Hello " + name;
        }
    }
}

That's it we are done implementing a WCF Service. The next step is to host the serviceusing a console application. A WCF service can also be hosted in a Windows application, or Windows Service or IIS. We will discuss these hosting options in a later video session.

Hosting the WCF service using a console application.
1. Right click on HelloService solution in Solution Explorer and add a new Console Application project with name = HelloServiceHost
2. Add a reference to System.ServiceModel assembly and HelloService project
3. Right click on HelloServiceHost project and add Application Configuration File.This should add App.config file to the project. Copy and paste the following XML. Notice that we have specified 2 endpoints in the configuration. One endpoint usesbasicHttpBinding, which communicates over HTTP protocol using XML messages. This endpoint will satisfy the requirement of the first client. The other endpoint usesnetTcpBinding, which communicates over TCP protocol using binary messages. This endpoint will satisfy the requirement of the second client.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="mexBehaviour">
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="HelloService.HelloService" behaviorConfiguration="mexBehaviour">
        <endpoint address="HelloService" binding="basicHttpBinding" contract="HelloService.IHelloService">
        </endpoint>
        <endpoint address="HelloService" binding="netTcpBinding" contract="HelloService.IHelloService">
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8080/" />
            <add baseAddress="net.tcp://localhost:8090"/>
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
</configuration>
4. Copy and paste the following code in Program.cs file
using System;
namespace HelloServiceHost
{
    
class Program
    {
        
static void Main()
        {
            
using(System.ServiceModel.ServiceHost host = new 
                System.ServiceModel.
ServiceHost(typeof(HelloService.HelloService)))
            {
                host.Open();
                
Console.WriteLine("Host started @ " + DateTime.Now.ToString());
                
Console.ReadLine();
            }
        }
    }
}

Build the solution. Set HelloServiceHost as startup project and run it by pressing CTRL + F5 keys.

Now let's build a web application that is going to consume the WCF service using the endpoint with basicHttpBinding. basicHttpBinding communicates over HTTP protocol using XML messages.
1. Create a new asp.net empty web application and name it HelloWebClient
2. Right click on References folder and select Add Service Reference option. In the address textbox type http://localhost:8080/ and click on GO button. In the namespace textbox type HelloService and click OK. This should generate a proxy class to communicate with the service.
3. Add a new webform. Copy and paste the following HTML in WebForm1.aspx
<div style="font-family:Arial">
    <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
    <asp:Button ID="Button1" runat="server" Text="Get Message"
        onclick="Button1_Click" />
    <br />
    <asp:Label ID="Label1" runat="server" Font-Bold="true"></asp:Label>
</div>
4. Copy and paste the following code in WebForm1.aspx.cs file
protected void Button1_Click(object sender, EventArgs e)
{
    HelloService.
HelloServiceClient client = new 
        HelloService.
HelloServiceClient("BasicHttpBinding_IHelloService");
    Label1.Text = client.GetMessage(TextBox1.Text);
}

Now let's build a windows application that is going to consume the WCF service using the endpoint with netTcpBinding. netTcpBinding communicated over TCP protocol using binary messages.
1. Create a new Windows Forms application and name it HelloWindowsClient
2. Right click on References folder and select Add Service Reference option. In the address textbox type http://localhost:8080/ and click on GO button. In the namespace textbox type HelloService and click OK. This should generate a proxy class to communicate with the service.
3. On Form1, drag and drop a textbox, a button and a label control. Double click the button to generate the click event handler.
4. Copy and paste the following code in Form1.cs file
private void button1_Click(object sender, EventArgs e)
{
    HelloService.
HelloServiceClient client = new 
        HelloService.
HelloServiceClient("NetTcpBinding_IHelloService");
    label1.Text = client.GetMessage(textBox1.Text);
}
Part 4 - WCF service implementing multiple service contracts
Suggested Videos
Part 1 - Introduction to WCF
Part 2 - Creating a remoting service and a web service
Part 3 - Creating a wcf service



In this video, we will discuss 
1. How a single WCF service can implement multiple service contracts and then
2. Expose each service contract using a different endpoint

Let's understand this with an example.



Creating a WCF service that implements multiple contracts
1. Create a new class library project with name = CompanyService
2. Delete Class1.cs file that is auto-generated
3. Add a new WCF service with name = CompanyService. This should generateCompanyService.cs and ICompanyService.cs files.
4. Copy and paste the following code in ICompanyService.cs file
using System.ServiceModel;
namespace CompanyService
{
    [
ServiceContract]
    
public interface IMyCompanyPublicService
    {
        [
OperationContract]
        
string GetPublicInformation();
    }

    [
ServiceContract]
    
public interface IMyCompanyConfidentialService
    {
        [
OperationContract]
        
string GetCofidentialInformation();
    }
}
5. Copy and paste the following code in CompanyService.cs file
namespace CompanyService
{
    
public class CompanyService IMyCompanyPublicService,IMyCompanyConfidentialService
    {
        
public string GetPublicInformation()
        {
            
return "This is public information and available over HTTP to all general public outside the FireWall";
        }

        
public string GetCofidentialInformation()
        {
            
return "This is confidential information and only available over TCP behind the company FireWall";
        }
    }
}

Hosting the WCF service using a console application.
1. Right click on CompanyService solution in Solution Explorer and add a new Console Application project with name = CompanyServiceHost
2. Add a reference to System.ServiceModel assembly and CompanyService project
3. Right click on CompanyServiceHost project and add Application Configuration File. This should add App.config file to the project. Copy and paste the following XML.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="CompanyService.CompanyService" behaviorConfiguration="mexBehaviour">
        <endpoint address="CompanyService" binding="basicHttpBinding" contract="CompanyService.IMyCompanyPublicService"></endpoint>
        <endpoint address="CompanyService" binding="netTcpBinding" contract="CompanyService.IMyCompanyConfidentialService"></endpoint>
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8080/"/>
            <add baseAddress="net.tcp://localhost:8090/"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="mexBehaviour">
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>
4. Copy and paste the following code in Program.cs file
using System;
namespace CompanyServiceHost
{
    
class Program
    {
        
static void Main()
        {
            
using(System.ServiceModel.ServiceHost host = new 
                System.ServiceModel.
ServiceHost(typeof(CompanyService.CompanyService)))
            {
                host.Open();
                
Console.WriteLine("Host started @ " + DateTime.Now.ToString());
                
Console.ReadLine();
            }
        }
    }
}

Build the solution. Set CompanyServiceHost as startup project and run it by pressing CTRL + F5 keys.

Now lte's build a web application that is going to consume the WCF service.
1. Create a new asp.net empty web application and name it CompanyClient
2. Right click on References folder and select Add Service Reference option. In the address textbox type http://localhost:8080/ and click on GO button. In the namespace textbox type CompanyService and click OK. This should generate a proxy class to communicate with the service.
3. Add a new webform. Copy and paste the following HTML in WebForm1.aspx
<div style="font-family:Arial">
    <table style="border:1px solid black">
        <tr>
            <td>
                <asp:Button ID="Button1" runat="server" Text="Get Public Information"
                    onclick="Button1_Click" Width="300px" />
            </td>
            <td>
                <asp:Label ID="Label1" runat="server" Font-Bold="true"></asp:Label>          
            </td>
        </tr>
        <tr>
            <td>
                <asp:Button ID="Button2" runat="server" Text="Get Confidential Information"
                    onclick="Button2_Click" Width="300px" />
            </td>
            <td>
                <asp:Label ID="Label2" runat="server" Font-Bold="true"></asp:Label>          
            </td>
        </tr>
    </table>
</div>
4. Copy and paste the following code in WebForm1.aspx.cs file
protected void Button1_Click(object sender, EventArgs e)
{
    CompanyService.
MyCompanyPublicServiceClient client1 = new
        CompanyService.
MyCompanyPublicServiceClient("BasicHttpBinding_IMyCompanyPublicService");
    Label1.Text = client1.GetPublicInformation();
}

protected void Button2_Click(object sender, EventArgs e)
{
    CompanyService.
MyCompanyConfidentialServiceClient client2 = new
        CompanyService.
MyCompanyConfidentialServiceClient("NetTcpBinding_IMyCompanyConfidentialService");
    Label2.Text = client2.GetCofidentialInformation();
}

Here is the requirement
1. MyCompanyPublicService should be available to everyone behind and outside the firewall
2. MyCompanyConfidentialService should be available only with in the company behind the firewall.

But with the client web application we are able to create proxy classes of both the services and invoke both the methods. That is because in our case we are having both the client and the service running on the same machine. When it comes to clients outside the firewall, they will be able to create proxy classes of both the services, but when they attempt to invoke the MyCompanyConfidentialService method they would get an exception.

Is it possible for a WCF service to implement multiple service contracts?
Yes, we make the service class implement multiple service interfaces, and then expose each service using a different endpoint.

Suggested Videos
Part 14 - Risks of implementing IExtensibleDataObject interface
Part 15 - Exception handling in WCF
Part 16 - SOAP faults in WCF



This is continuation to Part 16. Please watch Part 16, before proceeding.

An unhandled exception in the WCF service, will cause the communication channel to fault and the session will be lost. Once the communication channel is in faulted state, we cannot use the same instance of the proxy class any more. We will have to create a new instance of the proxy class.



BasicHttpBinding does not have sessions. So when there is an unhandled exception, it only faults the server channel. The client proxy is still OK, because with BasicHttpBinding the channel is not maintaining sessions, and when the client calls again it is not expecting the channel to maintain any session.

WSHttpBinding have secure sessions. So when there is an unhandled exception, it faults the server channel. At this point the existing client proxy is useless as it is also faulted, because with wsHttpBinding the channel is maintaining a secure session, and when the client calls again it expects the channel to maintain the same session. The same session does not exist at the server channel anymore, as the undhandled exception has already torn down the channel and the session along with it.

Part 5 - How to make changes to wcf service without breaking clients

Suggested Videos
Part 2 - Creating a remoting service and a web service
Part 3 - Creating a wcf service
Part 4 - Single wcf service implementing multiple service contracts



Use Name property of ServiceContractAttribute and give it an explicit name to prevent the clients from breaking when you change the service contract interface name.



We have not used Name property of the ServiceContractAttribute in the example below.
[
ServiceContract]
public interface IHelloService
{
    [
OperationContract]
    
string GetMessage(string name);
}

Later if we change the interface name from IHelloService to IHelloServiceChanged,this would break the existing clients consuming your wcf service. In order to prevent this from happening use Name property as shown below.
[
ServiceContract(Name = "IHelloService")]
public interface IHelloServiceChanged
{
    [
OperationContract]
    
string GetMessage(string name);
}

In WSDL document, we have something called portType. You can think of this portTypeas the interface the client uses to communicate with the wcf service. When you don't set the Name property on a service contract attribute, by default the name attribute of theportType xml element in WSDL will be the name of the service contract interface. If you set an explicit Name for the service contract using Name property then that Name will be used for the portType.

In a similar fashion you can set Name property for an OperationContract as shown below.
[
ServiceContract(Name = "IHelloService")]
public interface IHelloServiceChanged
{
    [
OperationContract(Name = "GetMessage")]
    
string GetMessageChanged(string name);
}
Part 6 - WCF DataContract and DataMember
Suggested Videos
Part 3 - Creating a wcf service
Part 4 - Single wcf service implementing multiple service contracts
Part 5 - How to make changes to wcf service without breaking clients



To understand DataContract and DataMember attributes in WCF, first let's understand what is meant by Serialization.

With respect to WCF, Serialization is the process of converting an object into an XML representation. The reverse process, that is reconstructing the same object from the XML is called as Deserialization.



By default, WCF uses DataContractSerializer. 

For a complex type like Customer, Employee, Student to be serialized, the complex type can either be decorated with
1. SerializableAttribute or
2. DataContractAttribute

With .NET 3.5 SP1 and above, we don't have to explicitly use DataContract or DataMember attributes. The Data Contract Serializer will serialize all public properties of your complex type in an alphabetical order. By default private field and properties are not serialized.

If we decorate a complex type, with [Serializable] attribute the DataContractSerializerserializes all fields. With [Serializable] attribute we don't have explicit control on what fields to include and exclude in serialized data.

If we decorate a complex type with [Datacontract] attribute, the DataContractSerializerserializes the fields marked with the [DataMember] attribute. The fields that are not marked with [DataMember] attribute are excluded from serialization. The [DataMember]attribute can be applied either on the private fields or public properties.

In WCF, the most common way of serialization is to mark the type with the DataContractattribute and mark each member that needs to be serialized with the DataMemberattribute.

If you want to have explicit control on what fields and properties get serialized then use DataContract and DataMember attributes.
1. Using DataContractAttribute, you can define an XML namespace for your data
2. Using DataMemberAttribute, you can
    a) Define Name, Order, and whether if a property or field IsRequired
    b) Also, serialize private fields and properties

Code used in the demo:
SQL Script:
Create Table tblEmployee
(
 Id 
int,
 Name 
nvarchar(50),
 Gender 
nvarchar(50),
 DateOfBirth 
datetime
)

Insert into tblEmployee values (1, 'Mark', 'Male', '10/10/1980')
Insert into tblEmployee values (2, 'Mary', 'Female', '11/10/1981')
Insert into tblEmployee values (3, 'John', 'Male', '8/10/1979')

Create procedure spGetEmployee
@Id 
int
as
Begin
 Select Id, Name, Gender, DateOfBirth
 
from tblEmployee
 
where Id = @Id
End

Create procedure spSaveEmployee
@Id 
int,
@Name 
nvarchar(50),
@Gender 
nvarchar(50),
@DateOfBirth 
DateTime
as
Begin
 Insert into tblEmployee
 
values (@Id, @Name, @Gender, @DateOfBirth)
End

Employee.cs file in EmployeeService project:
using System;
using System.Runtime.Serialization;

namespace EmployeeService
{
    [
DataContract(Namespace="http://pragimtech.com/Employee")]
    
public class Employee
    {
        
private int _id;
        
private string _name;
        
private string _gender;
        
private DateTime _dateOfBirth;

        [
DataMember(Order=1)]
        
public int Id
        {
            
get return _id; }
            
set { _id = value; }
        }

        [
DataMember(Order = 2)]
        
public string Name
        {
            
get { return _name; }
            
set { _name = value; }
        }

        [
DataMember(Order = 3)]
        
public string Gender
        {
            
get { return _gender; }
            
set { _gender = value; }
        }

        [
DataMember(Order = 4)]
        
public DateTime DateOfBirth
        {
            
get { return _dateOfBirth; }
            
set { _dateOfBirth = value; }
        }
    }
}

IEmployeeService.cs in EmployeeService project
using System.ServiceModel;

namespace EmployeeService
{
    [
ServiceContract]
    
public interface IEmployeeService
    {
        [
OperationContract]
        
Employee GetEmployee(int Id);

        [
OperationContract]
        
void SaveEmployee(Employee Employee);
    }
}

EmployeeService.cs in EmployeeService project
using System;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;

namespace EmployeeService
{
    
public class EmployeeService IEmployeeService
    {
        
public Employee GetEmployee(int Id)
        {
            
Employee employee = new Employee();
            
string cs = ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString;
            
using (SqlConnection con = new SqlConnection(cs))
            {
                
SqlCommand cmd = new SqlCommand("spGetEmployee", con);
                cmd.CommandType = 
CommandType.StoredProcedure;
                
SqlParameter parameterId = new SqlParameter();
                parameterId.ParameterName = 
"@Id";
                parameterId.Value = Id;
                cmd.Parameters.Add(parameterId);
                con.Open();
                
SqlDataReader reader = cmd.ExecuteReader();
                
while (reader.Read())
                {
                    employee.Id = 
Convert.ToInt32(reader["Id"]);
                    employee.Name = reader[
"Name"].ToString();
                    employee.Gender = reader[
"Gender"].ToString();
                    employee.DateOfBirth = 
Convert.ToDateTime(reader["DateOfBirth"]);
                }
            }
            
return employee;
        }

        
public void SaveEmployee(Employee employee)
        {
            
string cs = ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString;
            
using (SqlConnection con = new SqlConnection(cs))
            {
                
SqlCommand cmd = new SqlCommand("spSaveEmployee", con);
                cmd.CommandType = 
CommandType.StoredProcedure;
                
SqlParameter parameterId = new SqlParameter
                {
                    ParameterName = 
"@Id",
                    Value = employee.Id
                };
                cmd.Parameters.Add(parameterId);

                
SqlParameter parameterName = new SqlParameter
                {
                    ParameterName = 
"@Name",
                    Value = employee.Name
                };
                cmd.Parameters.Add(parameterName);

                
SqlParameter parameterName = new SqlParameter
                {
                    ParameterName = 
"@Gender",
                    Value = employee.Gender
                };
                cmd.Parameters.Add(parameterGender);

                
SqlParameter parameterName = new SqlParameter
                {
                    ParameterName = 
"@DateOfBirth",
                    Value = employee.DateOfBirth
                };
                cmd.Parameters.Add(parameterDateOfBirth);

                con.Open();
                cmd.ExecuteNonQuery();
            }
        }
    }
}

App.config in EmployeeServiceHost project
<?xml version="1.0"?>
<configuration>
  <connectionStrings>
    <add name="DBCS"
         connectionString="data source=.;Integrated Security=SSPI;database=Sample"
         providerName="System.Data.SqlClient" />
  </connectionStrings>
  <system.serviceModel>
    <services>
      <service name="EmployeeService.EmployeeService" behaviorConfiguration="mexBehaviour">
        <endpoint address="EmployeeService" binding="basicHttpBinding" contract="EmployeeService.IEmployeeService">
        </endpoint>
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8080/"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="mexBehaviour">
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Program.cs in EmployeeServiceHost project
using System;
using System.ServiceModel;

namespace EmployeeServiceHost
{
    
class Program
    {
        
public static void Main()
        {
            
using (ServiceHost host = newServiceHost(typeof(EmployeeService.EmployeeService)))
            {
                host.Open();
                
Console.WriteLine("Host started @ " + DateTime.Now.ToString());
                
Console.ReadLine();
            }
        }
    }
}

WebForm1.aspx in Client project
<table style="font-family:Arial; border:1px solid black;">
    <tr>
        <td>
            <b>ID</b>
        </td>
        <td>
            <asp:TextBox ID="txtID" runat="server">
            </asp:TextBox>
        </td>
    </tr>
    <tr>
        <td>
            <b>Name</b>
        </td>
        <td>
            <asp:TextBox ID="txtName" runat="server">
            </asp:TextBox>
        </td>
    </tr>
    <tr>
        <td>
            <b>Gender</b>
        </td>
        <td>
            <asp:TextBox ID="txtGender" runat="server">
            </asp:TextBox>
        </td>
    </tr>
    <tr>
        <td>
            <b>Date Of Birth</b>
        </td>
        <td>
            <asp:TextBox ID="txtDateOfBirth" runat="server">
            </asp:TextBox>
        </td>
    </tr>
    <tr>
        <td>
            <asp:Button ID="btnGetEmployee" runat="server"
                                 Text="Get Employee"
                                 onclick="btnGetEmployee_Click" />                                  
        </td>
        <td>
            <asp:Button ID="btnSave" runat="server"
                        Text="Save Employee"
                        onclick="btnSave_Click" />
        </td>
    </tr>
    <tr>
        <td colspan="2">
            <asp:Label ID="lblMessage" runat="server"
                        ForeColor="Green" Font-Bold="true">
            </asp:Label>
        </td>
    </tr>
</table>

WebForm1.aspx.cs in Client project
protected void btnSave_Click(object sender, EventArgs e)
{
    EmployeeService.
Employee employee = new EmployeeService.Employee();
    employee.Id = 
Convert.ToInt32(txtID.Text);
    employee.Name = txtName.Text;
    employee.Gender = txtGender.Text;
    employee.DateOfBirth = Convert.ToDateTime(txtDateOfBirth.Text);

    EmployeeService.
EmployeeServiceClient client = new
        EmployeeService.
EmployeeServiceClient();
    client.SaveEmployee(employee);

    lblMessage.Text = 
"Employee saved";
}

protected void btnGetEmployee_Click(object sender, EventArgs e)
{
    EmployeeService.
EmployeeServiceClient client = new
        EmployeeService.
EmployeeServiceClient();
           
    EmployeeService.
Employee employee =            
        client.GetEmployee(
Convert.ToInt32(txtID.Text));

    txtName.Text = employee.Name;
    txtGender.Text = employee.Gender;
    txtDateOfBirth.Text = employee.DateOfBirth.ToShortDateString();

    lblMessage.Text = 
"Employee retrieved";
}
Part 7 - KnownType attribute in WCF
Suggested Videos
Part 4 - Single wcf service implementing multiple service contracts
Part 5 - How to make changes to wcf service without breaking clients
Part 6 - WCF DataContract and DataMember



If we have classes related by inheritance, the wcf service generally accepts and returns the base type. If you expect the service to accept and return inherited types, then use KnownType attribute.

There are other ways of associating known types, which we will discuss in next video session.



Code used in the demo:
SQL:
Alter table tblEmployee Add 
EmployeeType 
int, AnnualSalary int, HourlyPay int, HoursWorked int

Alter procedure spGetEmployee
@Id 
int 
as  
Begin  
 Select Id, Name, Gender, DateOfBirth,
  EmployeeType, AnnualSalary, HourlyPay,
  HoursWorked
 
from tblEmployee where Id = @Id
End

Alter procedure spSaveEmployee
@Id 
int,
@Name 
nvarchar(50),
@Gender 
nvarchar(50),
@DateOfBirth 
DateTime,
@EmployeeType 
int,
@AnnualSalary 
int = null,
@HourlyPay 
int = null,
@HoursWorked 
int = null
as  
Begin  
 Insert into tblEmployee
 
values (@Id, @Name, @Gender, @DateOfBirth,
   @EmployeeType, @AnnualSalary, @HourlyPay,
   @HoursWorked)
End

FullTimeEmployee.cs
namespace EmployeeService
{
    
public class FullTimeEmployee Employee
    {
        
public int AnnualSalary { get; set; }
    }
}

PartTimeEmployee.cs
namespace EmployeeService
{
    
public class PartTimeEmployee Employee
    {
        
public int HourlyPay { get; set; }
        
public int HoursWorked { get; set; }
    }
}

Employee.cs
using System;
using System.Runtime.Serialization;

namespace EmployeeService
{
    [
KnownType(typeof(FullTimeEmployee))]
    [
KnownType(typeof(PartTimeEmployee))]
    [
DataContract(Namespace = "http://pragimtech.com/Employee")]
    
public class Employee
    {
        
private int _id;
        
private string _name;
        
private string _gender;
        
private DateTime _dateOfBirth;

        [
DataMember(Order = 1)]
        
public int Id
        {
            
get return _id; }
            
set { _id = value; }
        }

        [
DataMember(Order = 2)]
        
public string Name
        {
            
get { return _name; }
            
set { _name = value; }
        }

        [
DataMember(Order = 3)]
        
public string Gender
        {
            
get return _gender; }
            
set { _gender = value; }
        }

        [
DataMember(Order = 4)]
        
public DateTime DateOfBirth
        {
            
get return _dateOfBirth; }
            
set { _dateOfBirth = value; }
        }

        [
DataMember(Order = 5)]
        
public EmployeeType Type { get; set; }
    }

    
public enum EmployeeType
    {
        FullTimeEmployee = 1,
        PartTimeEmployee = 2
    }
}

EmployeeService.cs
using System;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;

namespace EmployeeService
{
    
public class EmployeeService IEmployeeService
    {
        
public Employee GetEmployee(int Id)
        {
            
Employee employee = null;
            
string cs = ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString;
            
using (SqlConnection con = new SqlConnection(cs))
            {
                
SqlCommand cmd = new SqlCommand("spGetEmployee", con);
                cmd.CommandType = 
CommandType.StoredProcedure;
                
SqlParameter parameterId = new SqlParameter();
                parameterId.ParameterName = 
"@Id";
                parameterId.Value = Id;
                cmd.Parameters.Add(parameterId);
                con.Open();
                
SqlDataReader reader = cmd.ExecuteReader();
                
while (reader.Read())
                {
                    
if ((EmployeeType)reader["EmployeeType"] ==EmployeeType.FullTimeEmployee)
                    {
                        employee = 
new FullTimeEmployee
                        {
                            Id = 
Convert.ToInt32(reader["Id"]),
                            Name = reader[
"Name"].ToString(),
                            Gender = reader[
"Gender"].ToString(),
                            DateOfBirth = 
Convert.ToDateTime(reader["DateOfBirth"]),
                            Type = 
EmployeeType.FullTimeEmployee,
                            AnnualSalary = 
Convert.ToInt32(reader["AnnualSalary"])
                        };
                    }
                    
else
                    {
                        employee = 
new PartTimeEmployee
                        {
                            Id = 
Convert.ToInt32(reader["Id"]),
                            Name = reader[
"Name"].ToString(),
                            Gender = reader[
"Gender"].ToString(),
                            DateOfBirth = 
Convert.ToDateTime(reader["DateOfBirth"]),
                            Type = 
EmployeeType.PartTimeEmployee,
                            HourlyPay = 
Convert.ToInt32(reader["HourlyPay"]),
                            HoursWorked = 
Convert.ToInt32(reader["HoursWorked"]),
                        };
                    }
                }
            }
            
return employee;
        }

        
public void SaveEmployee(Employee employee)
        {
            
string cs = ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString;
            
using (SqlConnection con = new SqlConnection(cs))
            {
                
SqlCommand cmd = new SqlCommand("spSaveEmployee", con);
                cmd.CommandType = 
CommandType.StoredProcedure;
                
SqlParameter parameterId = new SqlParameter
                {
                    ParameterName = 
"@Id",
                    Value = employee.Id
                };
                cmd.Parameters.Add(parameterId);

                
SqlParameter parameterName = new SqlParameter
                {
                    ParameterName = 
"@Name",
                    Value = employee.Name
                };
                cmd.Parameters.Add(parameterName);

                
SqlParameter parameterGender = new SqlParameter
                {
                    ParameterName = 
"@Gender",
                    Value = employee.Gender
                };
                cmd.Parameters.Add(parameterGender);

                
SqlParameter parameterDateOfBirth = new SqlParameter
                {
                    ParameterName = 
"@DateOfBirth",
                    Value = employee.DateOfBirth
                };
                cmd.Parameters.Add(parameterDateOfBirth);

                
SqlParameter parameterEmployeeType = new SqlParameter
                {
                    ParameterName = 
"@EmployeeType",
                    Value = employee.Type
                };
                cmd.Parameters.Add(parameterEmployeeType);

                
if (employee.GetType() == typeof(FullTimeEmployee))
                {
                    
SqlParameter parameterAnnualSalary = new SqlParameter
                    {
                        ParameterName = 
"@AnnualSalary",
                        Value = ((
FullTimeEmployee)employee).AnnualSalary
                    };
                    cmd.Parameters.Add(parameterAnnualSalary);
                }
                
else
                {
                    
SqlParameter parameterHourlyPay = new SqlParameter
                    {
                        ParameterName = 
"@HourlyPay",
                        Value = ((
PartTimeEmployee)employee).HourlyPay,
                    };
                    cmd.Parameters.Add(parameterHourlyPay);

                    
SqlParameter parameterHoursWorked = new SqlParameter
                    {
                        ParameterName = 
"@HoursWorked",
                        Value = ((
PartTimeEmployee)employee).HoursWorked
                    };
                    cmd.Parameters.Add(parameterHoursWorked);
                }

                con.Open();
                cmd.ExecuteNonQuery();
            }
        }
    }
}

WebForm1.aspx
<table style="font-family: Arial; border: 1px solid black;">
    <tr>
        <td>
            <b>ID</b>
        </td>
        <td>
            <asp:TextBox ID="txtID" runat="server">
            </asp:TextBox>
        </td>
    </tr>
    <tr>
        <td>
            <b>Name</b>
        </td>
        <td>
            <asp:TextBox ID="txtName" runat="server">
            </asp:TextBox>
        </td>
    </tr>
    <tr>
        <td>
            <b>Gender</b>
        </td>
        <td>
            <asp:TextBox ID="txtGender" runat="server">
            </asp:TextBox>
        </td>
    </tr>
    <tr>
        <td>
            <b>Date Of Birth</b>
        </td>
        <td>
            <asp:TextBox ID="txtDateOfBirth" runat="server">
            </asp:TextBox>
        </td>
    </tr>
    <tr>
        <td>
            <b>Employee Type</b>
        </td>
        <td>
            <asp:DropDownList ID="ddlEmployeeType" runat="server"
                OnSelectedIndexChanged="ddlEmployeeType_SelectedIndexChanged"
                AutoPostBack="True">
                <asp:ListItem Text="Select Employee Type" Value="-1">
                </asp:ListItem>
                <asp:ListItem Text="Full Time Employee" Value="1">
                </asp:ListItem>
                <asp:ListItem Text="Part Time Employee" Value="2">
                </asp:ListItem>
            </asp:DropDownList>
        </td>
    </tr>
    <tr id="trAnnualSalary" runat="server" visible="false">
        <td>
            <b>Annual Salary</b>
        </td>
        <td>
            <asp:TextBox ID="txtAnnualSalary" runat="server">
            </asp:TextBox>
        </td>
    </tr>
    <tr id="trHourlPay" runat="server" visible="false">
        <td>
            <b>Hourly Pay</b>
        </td>
        <td>
            <asp:TextBox ID="txtHourlyPay" runat="server">
            </asp:TextBox>
        </td>
    </tr>
    <tr id="trHoursWorked" runat="server" visible="false">
        <td>
            <b>Hours worked</b>
        </td>
        <td>
            <asp:TextBox ID="txtHoursWorked" runat="server">
            </asp:TextBox>
        </td>
    </tr>
    <tr>
        <td>
            <asp:Button ID="btnGetEmployee" runat="server"
            Text="Get Employee" OnClick="btnGetEmployee_Click" />
        </td>
        <td>
            <asp:Button ID="btnSave" runat="server"
            Text="Save Employee" OnClick="btnSave_Click" />
        </td>
    </tr>
    <tr>
        <td colspan="2">
            <asp:Label ID="lblMessage" runat="server"
                ForeColor="Green" Font-Bold="true">
            </asp:Label>
        </td>
    </tr>
</table>

WebForm1.aspx.cs
protected void btnGetEmployee_Click(object sender, EventArgs e)
{
    EmployeeService.
EmployeeServiceClient client =
        
new EmployeeService.EmployeeServiceClient();
    EmployeeService.
Employee employee =
        client.GetEmployee(
Convert.ToInt32(txtID.Text));

    
if (employee.Type == EmployeeService.EmployeeType.FullTimeEmployee)
    {
        txtAnnualSalary.Text =
            ((EmployeeService.
FullTimeEmployee)employee).AnnualSalary.ToString();
        trAnnualSalary.Visible = 
true;
        trHourlPay.Visible = 
false;
        trHoursWorked.Visible = 
false;
    }
    
else
    {
        txtHourlyPay.Text =
            ((EmployeeService.
PartTimeEmployee)employee).HourlyPay.ToString();
        txtHoursWorked.Text =
            ((EmployeeService.
PartTimeEmployee)employee).HoursWorked.ToString();
        trAnnualSalary.Visible = 
false;
        trHourlPay.Visible = 
true;
        trHoursWorked.Visible = 
true;
    }
    ddlEmployeeType.SelectedValue = ((
int)employee.Type).ToString();

           
           
    txtName.Text = employee.Name;
    txtGender.Text = employee.Gender;
    txtDateOfBirth.Text = employee.DateOfBirth.ToShortDateString();
    lblMessage.Text = 
"Employee retrieved";
}

protected void btnSave_Click(object sender, EventArgs e)
{
    EmployeeService.
EmployeeServiceClient client = new 
        EmployeeService.
EmployeeServiceClient();
    EmployeeService.
Employee employee = null;

    
if(((EmployeeService.EmployeeType)Convert.ToInt32(ddlEmployeeType.SelectedValue))
        == EmployeeService.
EmployeeType.FullTimeEmployee)
    {
        employee = 
new EmployeeService.FullTimeEmployee
        {
            Id = 
Convert.ToInt32(txtID.Text),
            Name = txtName.Text,
            Gender = txtGender.Text,
            DateOfBirth = 
Convert.ToDateTime(txtDateOfBirth.Text),
            Type = EmployeeService.
EmployeeType.FullTimeEmployee,
            AnnualSalary = 
Convert.ToInt32(txtAnnualSalary.Text),
        };
        client.SaveEmployee(employee);
        lblMessage.Text = 
"Employee saved";
    }
    
else if(((EmployeeService.EmployeeType)Convert.ToInt32(ddlEmployeeType.SelectedValue))
        == EmployeeService.
EmployeeType.PartTimeEmployee)
    {
        employee = 
new EmployeeService.PartTimeEmployee
        {
            Id = 
Convert.ToInt32(txtID.Text),
            Name = txtName.Text,
            Gender = txtGender.Text,
            DateOfBirth = 
Convert.ToDateTime(txtDateOfBirth.Text),
            Type = EmployeeService.
EmployeeType.PartTimeEmployee,
            HourlyPay = 
Convert.ToInt32(txtHourlyPay.Text),
            HoursWorked = 
Convert.ToInt32(txtHoursWorked.Text),
        };
        client.SaveEmployee(employee);
        lblMessage.Text = 
"Employee saved";
    }
    
else
    {
        lblMessage.Text = 
"Please select Employee Type";
    }
}

protected void ddlEmployeeType_SelectedIndexChanged
    (
object sender, EventArgs e)
{
    
if (ddlEmployeeType.SelectedValue == "-1")
    {
        trAnnualSalary.Visible = 
false;
        trHourlPay.Visible = 
false;
        trHoursWorked.Visible = 
false;
    }
    else if (ddlEmployeeType.SelectedValue == "1")
    {
        trAnnualSalary.Visible = 
true;
        trHourlPay.Visible = 
false;
        trHoursWorked.Visible = 
false;
    }
    
else
    {
        trAnnualSalary.Visible = 
false;
        trHourlPay.Visible = 
true;
        trHoursWorked.Visible = 
true;
    }
}
Part 8 - Different ways of associating known types in wcf
Suggested Videos
Part 5 - How to make changes to wcf service without breaking clients
Part 6 - WCF DataContract and DataMember
Part 7 - KnownType attribute in WCF



In this video, we will discuss different options that are available for associating known types in WCF.

There are 4 different ways to associate KnownTypes
1. Use KnownType attribute on the base type. This option is global, that is all service contracts and all operation contracts will respect the known types.
[
KnownType(typeof(FullTimeEmployee))]
[
KnownType(typeof(PartTimeEmployee))]
[
DataContract]
public class Employee
{
}

[
DataContract]
public class FullTimeEmployee Employee
{
    
public int AnnualSalary { get; set; }
}

[
DataContract]
public class PartTimeEmployee Employee
{
    
public int HourlyPay { get; set; }
    
public int HoursWorked { get; set; }
}



2. Apply ServiceKnownType attribute on the service contract. With this option the known types are respected by all operation contracts with in this service contract only.
[
ServiceKnownType(typeof(PartTimeEmployee))]
[
ServiceKnownType(typeof(FullTimeEmployee))]
[
ServiceContract]
public interface IEmployeeService
{
    [
OperationContract]
    
Employee GetEmployee(int Id);

    [
OperationContract]
    
void SaveEmployee(Employee Employee);
}

3. If you want even more granular control, then apply ServiceKnownType attribute on specific operation contracts. With this option, only the operation contracts that are decorated with ServiceKnownType attribute respect known types.
[
ServiceContract]
public interface IEmployeeService
{
    [
ServiceKnownType(typeof(PartTimeEmployee))]
    [
ServiceKnownType(typeof(FullTimeEmployee))]
    [
OperationContract]
    
Employee GetEmployee(int Id);

    [
OperationContract]
    
void SaveEmployee(Employee Employee);
}

4. You can also specify known types in the configuration file. This is equivalent to applyingKnownType attribute on the base type, in the sense that it is applicable globally. All service contracts and operation contracts respect the known types.
<system.runtime.serialization>
  
<dataContractSerializer>
    
<declaredTypes>
      
<add type="EmployeeService.Employee, EmployeeService, Version=1.0.0.0, 
            Culture=Neutral, PublicKeyToken=null">
        
<knownType type="EmployeeService.FullTimeEmployee, EmployeeService, 
                    Version=1.0.0.0, Culture=Neutral, PublicKeyToken=null"/>
        
<knownType type="EmployeeService.PartTimeEmployee, EmployeeService, 
                    Version=1.0.0.0, Culture=Neutral, PublicKeyToken=null"/>
      
</add>
    
</declaredTypes>
  
</dataContractSerializer>
</system.runtime.serialization>
Part 9 - How to enable tracing and message logging in wcf
Suggested Videos
Part 6 - WCF DataContract and DataMember
Part 7 - KnownType attribute in WCF
Part 8 - Different ways of associating known types in wcf



Use Microsoft Service Configuration Editor to enable tracing and message loggingin WCF. This can be done either on the client or the wcf service.



Enable Tracing and Message Logging in WCF
https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDfW-4SFtFKG74ZLhEwLq6x1vJgAuVURDQA_IZ1hgMxY39s15z31jpsDBNpj1HIx6YLaR26796yruadpjvypAM8vxSbe79Bf4CHfHT8uDbyyroa5Vh3YsS7o7OC5LWv45dCbVRBNprFT0M/s1600/How+to+enable+tracing+and+message+logging+in+wcf.png

1. Right click on the config file and select "Edit WCF Configuration" option from the context menu. If you don't see this option, click on Tools menu item and then selecctWCF Configuration Editor and then point to the config file.
2. Select Diagnostics folder
3. Click on Enable Log Auto Flush link.
4. Then click on Enable Message Logging link. This should automatically add file, to which messages will be logged. To enable tracing click on Enable Tracing link.
5. Expand Diagnostics folder on the left hand side
6. Select Message Logging item that is present under Diagnostics folder. On the right hand side set LogEntireMessage option to true.
7. Close Microsoft Service Configuration Editor tool. This will ask you to Save Changes. Click Yes.

The config file should updated with the settings we have made using the tool. At this point we have enabled tracing and message logging.

Run the wcf service and the client. Make a request from the client. Look for the log files in the client or service folder depending on where you have enabled tracing and message logging.

To open the log files use Service Trace Viewer utility that ships with .NET. To open Service Trace Viewer utility, there are 2 options
1. Click on Start
2. Click All Programs.
3. Open Microsoft Visual Studio 2010 folder
4. Open Windows SDK Tools folder and then select Service Trace Viewer tool

Open Service Trace Viewer from visual studio command prompt
1. Open visual studio command prompt
2. Type SVCTRACEVIEWER and press enter

Point the Service Trace Viewer utility to the log file and you should see the messages exchanged between the service and the client. 
Part 10 - Message Contract in WCF
Suggested Videos
Part 7 - KnownType attribute in WCF
Part 8 - Different ways of associating known types in wcf
Part 9 - How to enable tracing and message logging in wcf



With Data Contracts we have very limited control over the SOAP XML request and response messages that are generated. Use Message Contracts, if you want to have full control over the generated XML SOAP messages. 



Few examples of when Message Contracts can be handy
1. Include some custom data in the SOAP header. In general SOAP headers are used to pass user credentials, license keys, session keys etc.
2. Change the name of the wrapper element in the SOAP message or to remove it altogether.

SOAP Request message that is generated without using Message Contracts
message contract example

SOAP Response message that is generated without using Message Contracts
message contract attribute example

In this demo, we will discuss how to use MessageContracts 
1. To include LicenseKey in the SOAP Header
2. Change the Name of the wrapper element in the SOAP Body

SOAP Request message that is generated using Message Contracts
 messagecontractattribute in wcf

SOAP Response message that is generated using Message Contracts
message contract in wcf

Decorate a class with MessageContract attribute, and then use that class as an operation contract parameter or return type. MessageContract attribute has the following parameters.
1. IsWrapped
2. WrapperName
3. WrapperNamespace
4. ProtectionLevel

MessageHeader attribute is applied on a property of the class that you want to include in the SOAP header. MessageHeader attribute has the following parameters.
1. Name
2. Namespace
3. ProtectionLevel
4. Actor
5. MustUnderstand
6. Relay

MessageBodyMember attribute is applied on a property of the class that you want to include in the SOAP body section. MessageBodyMember attribute has the following parameters.
1. Name
2. Namespace
3. Order
4. ProtectionLevel

In general, use MessageContract only if there is a reason to tweak the structure of the soap XML message.

Employee.cs
using System;
using System.Runtime.Serialization;
using System.ServiceModel;
namespace EmployeeService
{
    [
MessageContract(IsWrapped=true,
                     WrapperName=
"EmployeeRequestObject",
                     WrapperNamespace=
"http://MyCompany.com/Employee")]
    
public class EmployeeRequest
    {
        [
MessageBodyMember(Namespace = "http://MyCompany.com/Employee")]
        
public int EmployeeId { get; set; }

        [
MessageHeader(Namespace = "http://MyCompany.com/Employee")]
        
public string LicenseKey { get; set; }
    }

    [
MessageContract(IsWrapped=true,
                     WrapperName=
"EmployeeInfoObject",
                     WrapperNamespace=
"http://MyCompany.com/Employee")]
    
public class EmployeeInfo
    {
        
public EmployeeInfo()
        {
        }
        
public EmployeeInfo(Employee employee)
        {
            
this.ID = employee.Id;
            
this.Name = employee.Name;
            
this.Gender = employee.Gender;
            
this.DOB = employee.DateOfBirth;
            
this.Type = employee.Type;
            
if (this.Type == EmployeeType.FullTimeEmployee)
            {
                this.AnnualSalary = ((
FullTimeEmployee)employee).AnnualSalary;
            }
            
else
            {
                
this.HourlyPay = ((PartTimeEmployee)employee).HourlyPay;
                
this.HoursWorked = ((PartTimeEmployee)employee).HoursWorked;
            }
        }

        [
MessageBodyMember(Order = 1,Namespace ="http://MyCompany.com/Employee")]
        
public int ID { get; set; }
        [
MessageBodyMember(Order = 2,Namespace = "http://MyCompany.com/Employee")]
        
public string Name { get; set; }
        [
MessageBodyMember(Order = 3,Namespace = "http://MyCompany.com/Employee")]
        
public string Gender { get; set; }
        [
MessageBodyMember(Order = 4,Namespace = "http://MyCompany.com/Employee")]
        
public DateTime DOB { get; set; }
        [
MessageBodyMember(Order = 5,Namespace = "http://MyCompany.com/Employee")]
        
public EmployeeType Type { get; set; }
        [
MessageBodyMember(Order = 6,Namespace = "http://MyCompany.com/Employee")]
        
public int AnnualSalary { get; set; }
        [
MessageBodyMember(Order = 7,Namespace = "http://MyCompany.com/Employee")]
        
public int HourlyPay { get; set; }
        [
MessageBodyMember(Order = 8,Namespace = "http://MyCompany.com/Employee")]
        
public int HoursWorked { get; set; }
    }
   
    [
DataContract(Namespace = "http://MyCompany.com/Employee")]
    
public class Employee
    {
        [
DataMember(Order = 1)]
        
public int Id { get; set; }

        [
DataMember(Order = 2)]
        
public string Name { get; set; }

        [
DataMember(Order = 3)]
        
public string Gender { get; set; }

        [
DataMember(Order = 4)]
        
public DateTime DateOfBirth { get; set; }

        [
DataMember(Order = 5)]
        
public EmployeeType Type { get; set; }
    }

    
public enum EmployeeType
    {
        FullTimeEmployee = 1,
        PartTimeEmployee = 2
    }
}

IEmployeeService.cs
using System.ServiceModel;
namespace EmployeeService
{
    [
ServiceContract]
    
public interface IEmployeeService
    {
        [
OperationContract]
        
EmployeeInfo GetEmployee(EmployeeRequest employeeRequest);

        [
OperationContract]
        
void SaveEmployee(EmployeeInfo Employee);
    }
}

EmployeeService.cs
using System;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
namespace EmployeeService
{
    
public class EmployeeService IEmployeeService
    {
        
public EmployeeInfo GetEmployee(EmployeeRequest employeeRequest)
        {
            
Console.WriteLine("License Key = " + employeeRequest.LicenseKey);
            
Employee employee = null;
            
string cs = ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString;
            
using (SqlConnection con = new SqlConnection(cs))
            {
                
SqlCommand cmd = new SqlCommand("spGetEmployee", con);
                cmd.CommandType = 
CommandType.StoredProcedure;
                
SqlParameter parameterId = new SqlParameter();
                parameterId.ParameterName = 
"@Id";
                parameterId.Value = employeeRequest.EmployeeId;
                cmd.Parameters.Add(parameterId);
                con.Open();
                
SqlDataReader reader = cmd.ExecuteReader();
                
while (reader.Read())
                {
                    if ((
EmployeeType)reader["EmployeeType"] ==EmployeeType.FullTimeEmployee)
                    {
                        employee = 
new FullTimeEmployee
                        {
                            Id = 
Convert.ToInt32(reader["Id"]),
                            Name = reader[
"Name"].ToString(),
                            Gender = reader[
"Gender"].ToString(),
                            DateOfBirth = 
Convert.ToDateTime(reader["DateOfBirth"]),
                            Type = 
EmployeeType.FullTimeEmployee,
                            AnnualSalary = 
Convert.ToInt32(reader["AnnualSalary"])
                        };
                    }
                    
else
                    {
                        employee = 
new PartTimeEmployee
                        {
                            Id = 
Convert.ToInt32(reader["Id"]),
                            Name = reader[
"Name"].ToString(),
                            Gender = reader[
"Gender"].ToString(),
                            DateOfBirth = 
Convert.ToDateTime(reader["DateOfBirth"]),
                            Type = 
EmployeeType.PartTimeEmployee,
                            HourlyPay = 
Convert.ToInt32(reader["HourlyPay"]),
                            HoursWorked = 
Convert.ToInt32(reader["HoursWorked"]),
                        };
                    }
                }
            }
            
return new EmployeeInfo(employee);
        }

        
public void SaveEmployee(EmployeeInfo employee)
        {
            
string cs = ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString;
            
using (SqlConnection con = new SqlConnection(cs))
            {
                
SqlCommand cmd = new SqlCommand("spSaveEmployee", con);
                cmd.CommandType = 
CommandType.StoredProcedure;
                
SqlParameter parameterId = new SqlParameter
                {
                    ParameterName = 
"@Id",
                    Value = employee.ID
                };
                cmd.Parameters.Add(parameterId);

                
SqlParameter parameterName = new SqlParameter 
                {
                    ParameterName = 
"@Name",
                    Value = employee.Name
                };
                cmd.Parameters.Add(parameterName);

                
SqlParameter parameterGender = new SqlParameter 
                {
                    ParameterName = 
"@Gender",
                    Value = employee.Gender
                };
                cmd.Parameters.Add(parameterGender);

                
SqlParameter parameterDateOfBirth = new SqlParameter 
                {
                    ParameterName = 
"@DateOfBirth",
                    Value = employee.DOB
                };
                cmd.Parameters.Add(parameterDateOfBirth);

                
SqlParameter parameterEmployeeType = new SqlParameter 
                {
                    ParameterName = 
"@EmployeeType",
                    Value = employee.Type
                };
                cmd.Parameters.Add(parameterEmployeeType);

                if (employee.Type == 
EmployeeType.FullTimeEmployee)
                {
                    
SqlParameter parameterAnnualSalary = new SqlParameter 
                    {
                        ParameterName = 
"@AnnualSalary",
                        Value = employee.AnnualSalary
                    };
                    cmd.Parameters.Add(parameterAnnualSalary);
                }
                else
                {
                    
SqlParameter parameterHourlyPay = new SqlParameter 
                    {
                        ParameterName = 
"@HourlyPay",
                        Value = employee.HourlyPay,
                    };
                    cmd.Parameters.Add(parameterHourlyPay);

                    
SqlParameter parameterHoursWorked = new SqlParameter 
                    {
                        ParameterName = 
"@HoursWorked",
                        Value = employee.HoursWorked
                    };
                    cmd.Parameters.Add(parameterHoursWorked);
                }

                con.Open();
                cmd.ExecuteNonQuery();
            }
        }
    }
}

Client WebForm1.aspx.cs
protected void btnGetEmployee_Click(object sender, EventArgs e)
{
    EmployeeService.
IEmployeeService client =
        new EmployeeService.
EmployeeServiceClient();
    EmployeeService.
EmployeeRequest request = newEmployeeService.EmployeeRequest("XYZ120FABC"Convert.ToInt32(txtID.Text));

    EmployeeService.
EmployeeInfo employee = client.GetEmployee(request);

    
if (employee.Type == EmployeeService.EmployeeType.FullTimeEmployee)
    {
        txtAnnualSalary.Text = employee.AnnualSalary.ToString();
        trAnnualSalary.Visible = 
true;
        trHourlPay.Visible = 
false;
        trHoursWorked.Visible = 
false;
    }
    
else
    {
        txtHourlyPay.Text = employee.HourlyPay.ToString();
        txtHoursWorked.Text = employee.HoursWorked.ToString();
        trAnnualSalary.Visible = 
false;
        trHourlPay.Visible = 
true;
        trHoursWorked.Visible = 
true;
    }
    ddlEmployeeType.SelectedValue = ((
int)employee.Type).ToString();

    txtName.Text = employee.Name;
    txtGender.Text = employee.Gender;
    txtDateOfBirth.Text = employee.DOB.ToShortDateString();
    lblMessage.Text = 
"Employee retrieved";
}

protected void btnSave_Click(object sender, EventArgs e)
{
    EmployeeService.
IEmployeeService client = new
        EmployeeService.
EmployeeServiceClient();
    EmployeeService.
EmployeeInfo employee = new EmployeeService.EmployeeInfo();

    
if (ddlEmployeeType.SelectedValue == "-1")
    {
        lblMessage.Text = 
"Please select Employee Type";
    }
    
else
    {
        
if(((EmployeeService.EmployeeType)Convert.ToInt32(ddlEmployeeType.SelectedValue))
            == EmployeeService.
EmployeeType.FullTimeEmployee)
        {
            employee.AnnualSalary = 
Convert.ToInt32(txtAnnualSalary.Text);
            employee.Type = EmployeeService.
EmployeeType.FullTimeEmployee;
        }
        
else if(((EmployeeService.EmployeeType)Convert.ToInt32(ddlEmployeeType.SelectedValue))
            == EmployeeService.
EmployeeType.PartTimeEmployee)
        {
            employee.HourlyPay = 
Convert.ToInt32(txtHourlyPay.Text);
            employee.HoursWorked = 
Convert.ToInt32(txtHoursWorked.Text);
            employee.Type = EmployeeService.
EmployeeType.PartTimeEmployee;
        }

        employee.ID = 
Convert.ToInt32(txtID.Text);
        employee.Name = txtName.Text;
        employee.Gender = txtGender.Text;
        employee.DOB = 
Convert.ToDateTime(txtDateOfBirth.Text);

        client.SaveEmployee(employee);
        lblMessage.Text = 
"Employee saved";
    }
}

protected void ddlEmployeeType_SelectedIndexChanged
    (
object sender, EventArgs e)
{
    
if (ddlEmployeeType.SelectedValue == "-1")
    {
        trAnnualSalary.Visible = 
false;
        trHourlPay.Visible = 
false;
        trHoursWorked.Visible = 
false;
    }
    
else if (ddlEmployeeType.SelectedValue == "1")
    {
        trAnnualSalary.Visible = 
true;
        trHourlPay.Visible = 
false;
        trHoursWorked.Visible = 
false;
    }
    else
    {
        trAnnualSalary.Visible = 
false;
        trHourlPay.Visible = 
true;
        trHoursWorked.Visible = 
true;
    }
}
Suggested Videos
Part 8 - Different ways of associating known types in wcf
Part 9 - How to enable tracing and message logging in wcf
Part 10 - Message Contract in WCF



DataContract gives very limited control over the SOAP messages. DataContract allows us to control the Name and Order of XML elements in the body section of the SOAP message. Beyond this we don't have much control over the SOAP messages.

On the other hand, MessageContract gives full control over the SOAP messages by providing access to the SOAP header and body sections using MessageHeader andMessageBodyMember attributes. Use MessageContract if there is a reason to tweak the structure of the soap XML i.e if you want to include any additional information in the SOAP header.



WCF Interview Questions related to MessageContract that were asked in an interview attended by one of our youtube subscribers
1. Why do use MessageContract in WCF?
MessageContract gives full control over the SOAP messages. For example, it allows us to include custom information in the SOAP header.

2. What kind of custom information?
User credentials to invoke the service.

3. Why do you need to pass user credentials in the header? Can't you pass them as method parameters?
We can, but user credentials are periphery to what the method has to do. So, it would make more sense to pass them out of band in the header, rather than as additional parameters.

4. SOAP messages are in xml format, so anyone can read the credentials? How you do you protect sensitive data?
Using MessageContract we can sign and encrypt messages. Use ProtectionLevel named parameter. 




Suggested Videos
Part 10 - Message Contract in WCF
Part 11 - Difference between datacontract and messagecontract in wcf
Part 12 - Backward compatible WCF contract changes



In this video we will discuss the use of ExtensionDataObject. We will be working with the same example that we discussed in Part 12. Please watch Part 12 before proceeding.



Let us understand the use of ExtensionDataObject with an example. For some reason we want to remove Gender property from Employee DataContract. Notice that we have commented wherever Gender property is referenced.
[DataContract(Namespace = "http://pragimtech.com/Employee")]
public class Employee
{
    [DataMember(Order = 1)]
    public int Id { get; set; }

    [DataMember(Order = 2)]
    public string Name { get; set; }

    //[DataMember(Order = 3)]
    //public string Gender { get; set; }

    [DataMember(Order = 4)]
    public DateTime DateOfBirth { get; set; }

    [DataMember(Order = 5)]
    public EmployeeType Type { get; set; }

    [DataMember(Order = 6)]
    public string City { get; set; }
}

Since we have removed Gender property property from Employee DataContract, we also need to change EmployeeService.cs file as shown below. Changes are in 
Redcolour.
public class EmployeeService : IEmployeeService
{
public Employee GetEmployee(int Id)
{
    Employee employee = null;
    string cs = ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString;
    using (SqlConnection con = new SqlConnection(cs))
    {
        SqlCommand cmd = new SqlCommand("spGetEmployee", con);
        cmd.CommandType = CommandType.StoredProcedure;
        SqlParameter parameterId = new SqlParameter();
        parameterId.ParameterName = "@Id";
        parameterId.Value = Id;
        cmd.Parameters.Add(parameterId);
        con.Open();
        SqlDataReader reader = cmd.ExecuteReader();
        while (reader.Read())
        {
            if ((EmployeeType)reader["EmployeeType"] == EmployeeType.FullTimeEmployee)
            {
                employee = new FullTimeEmployee
                {
                    Id = Convert.ToInt32(reader["Id"]),
                    Name = reader["Name"].ToString(),
                    //Gender = reader["Gender"].ToString(),
                    DateOfBirth = Convert.ToDateTime(reader["DateOfBirth"]),
                    Type = EmployeeType.FullTimeEmployee,
                    AnnualSalary = Convert.ToInt32(reader["AnnualSalary"]),
                    City = reader["City"].ToString()
                };
            }
            else
            {
                employee = new PartTimeEmployee
                {
                    Id = Convert.ToInt32(reader["Id"]),
                    Name = reader["Name"].ToString(),
                    //Gender = reader["Gender"].ToString(),
                    DateOfBirth = Convert.ToDateTime(reader["DateOfBirth"]),
                    Type = EmployeeType.PartTimeEmployee,
                    HourlyPay = Convert.ToInt32(reader["HourlyPay"]),
                    HoursWorked = Convert.ToInt32(reader["HoursWorked"]),
                    City = reader["City"].ToString()
                };
            }
        }
    }
    return employee;
}

public void SaveEmployee(Employee employee)
{
    string cs = ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString;
    using (SqlConnection con = new SqlConnection(cs))
    {
        SqlCommand cmd = new SqlCommand("spSaveEmployee", con);
        cmd.CommandType = CommandType.StoredProcedure;
        SqlParameter parameterId = new SqlParameter
        {
            ParameterName = "@Id",
            Value = employee.Id
        };
        cmd.Parameters.Add(parameterId);

        SqlParameter parameterName = new SqlParameter
        {
            ParameterName = "@Name",
            Value = employee.Name
        };
        cmd.Parameters.Add(parameterName);

        //SqlParameter parameterGender = new SqlParameter
        //{
        //    ParameterName = "@Gender",
        //    Value = employee.Gender
        //};
        //cmd.Parameters.Add(parameterGender);

        SqlParameter parameterCity = new SqlParameter
        {
            ParameterName = "@City",
            Value = employee.City
        };
        cmd.Parameters.Add(parameterCity);
               
        SqlParameter parameterDateOfBirth = new SqlParameter
        {
            ParameterName = "@DateOfBirth",
            Value = employee.DateOfBirth
        };
        cmd.Parameters.Add(parameterDateOfBirth);

        SqlParameter parameterEmployeeType = new SqlParameter
        {
            ParameterName = "@EmployeeType",
            Value = employee.Type
        };
        cmd.Parameters.Add(parameterEmployeeType);

        if (employee.GetType() == typeof(FullTimeEmployee))
        {
            SqlParameter parameterAnnualSalary = new SqlParameter
            {
                ParameterName = "@AnnualSalary",
                Value = ((FullTimeEmployee)employee).AnnualSalary
            };
            cmd.Parameters.Add(parameterAnnualSalary);
        }
        else
        {
            SqlParameter parameterHourlyPay = new SqlParameter
            {
                ParameterName = "@HourlyPay",
                Value = ((PartTimeEmployee)employee).HourlyPay,
            };
            cmd.Parameters.Add(parameterHourlyPay);

            SqlParameter parameterHoursWorked = new SqlParameter
            {
                ParameterName = "@HoursWorked",
                Value = ((PartTimeEmployee)employee).HoursWorked
            };
            cmd.Parameters.Add(parameterHoursWorked);
        }

        con.Open();
        cmd.ExecuteNonQuery();
    }
}
}

We also need to change spGetEmployee and spSaveEmployee procedures to remove Gender.
Alter procedure spGetEmployee      
@Id int      
as      
Begin      
 Select Id, Name, DateOfBirth, EmployeeType,
        AnnualSalary, HourlyPay, HoursWorked, City  
 from   tblEmployee where Id = @Id      
End

Alter procedure spSaveEmployee      
@Id int,      
@Name nvarchar(50),      
@Gender nvarchar(50) = null,      
@DateOfBirth DateTime,    
@EmployeeType int,    
@AnnualSalary int = null,    
@HourlyPay int = null,    
@HoursWorked int = null,
@City nvarchar(50) = null  
as      
Begin      
 Insert into tblEmployee      
 values (@Id, @Name, @Gender, @DateOfBirth,      
         @EmployeeType, @AnnualSalary, @HourlyPay,
         @HoursWorked, @City)      
End

At this point run the service. Run the client application. The client application still references Gender property. This is because the client proxy classes were generated before we have modified the service. So, when we Save an employee, we also also passing value for Gender property. This property value is unknown to the service and by default the value will be lost at the service.

Try to get the same employee and notice that, Gender property value is lost. Let's say we want to preserve unknown elements that are sent by client and then return them back to the client. To achieve this the Employee DataContract has to implementIExtensibleDataObject interface. This interface has one property called ExtensionDatathat we need to implement.

Modify Employee DataContract in Employee.cs file as shown below. Changes in Red colour.
[DataContract(Namespace = "http://pragimtech.com/Employee")]
//Implement IExtensibleDataObject interface
public class Employee 
: IExtensibleDataObject
{
    [DataMember(Order = 1)]
    public int Id { get; set; }

    [DataMember(Order = 2)]
    public string Name { get; set; }

    //[DataMember(Order = 3)]
    //public string Gender { get; set; }

    [DataMember(Order = 4)]
    public DateTime DateOfBirth { get; set; }

    [DataMember(Order = 5)]
    public EmployeeType Type { get; set; }

    [DataMember(Order = 6)]
    public string City { get; set; }

    public ExtensionDataObject ExtensionData { get; set; }
}

Modify EmployeeService in EmployeeService.cs file as shown below.
// First change
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class EmployeeService : IEmployeeService
{
    
// Second change
    private Employee _lastSavedEmployee;
       
    public Employee GetEmployee(int Id)
    {
        Employee employee = null;
        string cs = ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString;
        using (SqlConnection con = new SqlConnection(cs))
        {
            SqlCommand cmd = new SqlCommand("spGetEmployee", con);
            cmd.CommandType = CommandType.StoredProcedure;
            SqlParameter parameterId = new SqlParameter();
            parameterId.ParameterName = "@Id";
            parameterId.Value = Id;
            cmd.Parameters.Add(parameterId);
            con.Open();
            SqlDataReader reader = cmd.ExecuteReader();
            while (reader.Read())
            {
                if ((EmployeeType)reader["EmployeeType"] == EmployeeType.FullTimeEmployee)
                {
                    employee = new FullTimeEmployee
                    {
                        Id = Convert.ToInt32(reader["Id"]),
                        Name = reader["Name"].ToString(),
                        //Gender = reader["Gender"].ToString(),
                        DateOfBirth = Convert.ToDateTime(reader["DateOfBirth"]),
                        Type = EmployeeType.FullTimeEmployee,
                        AnnualSalary = Convert.ToInt32(reader["AnnualSalary"]),
                        City = reader["City"].ToString()
                    };
                }
                else
                {
                    employee = new PartTimeEmployee
                    {
                        Id = Convert.ToInt32(reader["Id"]),
                        Name = reader["Name"].ToString(),
                        //Gender = reader["Gender"].ToString(),
                        DateOfBirth = Convert.ToDateTime(reader["DateOfBirth"]),
                        Type = EmployeeType.PartTimeEmployee,
                        HourlyPay = Convert.ToInt32(reader["HourlyPay"]),
                        HoursWorked = Convert.ToInt32(reader["HoursWorked"]),
                        City = reader["City"].ToString()
                    };
                }
            }
        }
        
// Third change
        if (_lastSavedEmployee != null && Id == _lastSavedEmployee.Id)
        {
            employee.ExtensionData = _lastSavedEmployee.ExtensionData;
        }
        return employee;
    }

    public void SaveEmployee(Employee employee)
    {
       
 // Fourth change
        _lastSavedEmployee = employee;
        string cs = ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString;
        using (SqlConnection con = new SqlConnection(cs))
        {
            SqlCommand cmd = new SqlCommand("spSaveEmployee", con);
            cmd.CommandType = CommandType.StoredProcedure;
            SqlParameter parameterId = new SqlParameter
            {
                ParameterName = "@Id",
                Value = employee.Id
            };
            cmd.Parameters.Add(parameterId);

            SqlParameter parameterName = new SqlParameter
            {
                ParameterName = "@Name",
                Value = employee.Name
            };
            cmd.Parameters.Add(parameterName);

            //SqlParameter parameterGender = new SqlParameter
            //{
            //    ParameterName = "@Gender",
            //    Value = employee.Gender
            //};
            //cmd.Parameters.Add(parameterGender);

            SqlParameter parameterCity = new SqlParameter
            {
                ParameterName = "@City",
                Value = employee.City
            };
            cmd.Parameters.Add(parameterCity);
               
            SqlParameter parameterDateOfBirth = new SqlParameter
            {
                ParameterName = "@DateOfBirth",
                Value = employee.DateOfBirth
            };
            cmd.Parameters.Add(parameterDateOfBirth);

            SqlParameter parameterEmployeeType = new SqlParameter
            {
                ParameterName = "@EmployeeType",
                Value = employee.Type
            };
            cmd.Parameters.Add(parameterEmployeeType);

            if (employee.GetType() == typeof(FullTimeEmployee))
            {
                SqlParameter parameterAnnualSalary = new SqlParameter
                {
                    ParameterName = "@AnnualSalary",
                    Value = ((FullTimeEmployee)employee).AnnualSalary
                };
                cmd.Parameters.Add(parameterAnnualSalary);
            }
            else
            {
                SqlParameter parameterHourlyPay = new SqlParameter
                {
                    ParameterName = "@HourlyPay",
                    Value = ((PartTimeEmployee)employee).HourlyPay,
                };
                cmd.Parameters.Add(parameterHourlyPay);

                SqlParameter parameterHoursWorked = new SqlParameter
                {
                    ParameterName = "@HoursWorked",
                    Value = ((PartTimeEmployee)employee).HoursWorked
                };
                cmd.Parameters.Add(parameterHoursWorked);
            }

            con.Open();
            cmd.ExecuteNonQuery();
        }
    }
}

In short, use IExtensibleDataObject to preserve unkown elements during serialization and deserialization of DataContracts. On the service side, at the time of deserialization the unknown elements from the client are stored in ExtensionDataObject. To send data to the client, the service has to serialize data into XML. During this serialization process the data from ExtensionDataObject is serialized into XML as it was provided at the time of service call.
Suggested Videos
Part 11 - Difference between datacontract and messagecontract in wcf
Part 12 - Backward compatible WCF contract changes
Part 13 - ExtensionDataObject in WCF



In Part 13, we discussed, how to implement IExtensibleDataObject to preserve unknown elements during serialization and deserialization of DataContracts. Please watch Part 13, before proceeding.

The downside of implementing IExtensibleDataObject interface is the risk of Denial of Service attack. Since, the extension data is stored in memory, the attacker may flood the server with requests that contains large number of unknown elements which can lead to system out of memory and DoS.



How to turn off IExtensibleDataObject feature?
One way is to remove the implementation of IExtensibleDataObject interface from all the DataContracts. This should work fine as long as we have a few data contracts on which IExtensibleDataObject interface is implemented. The downside of changing the application code is that we have to rebuild and redeploy services to the production server.

What if there are large number of DataContracts that have implemented IExtensibleDataObject interface?
IExtensibleDataObject can be enabled or disabled using service behavior configuration as shown below. With this option later if we want to enable support, all we need to do is set ignoreExtensionDataObject to false.
<behaviors>
  
<serviceBehaviors>
    
<behavior name="ignoreExtensionData">
      
<dataContractSerializer ignoreExtensionDataObject="true"/>
    
</behavior>
  
</serviceBehaviors>
</behaviors>

This can also be done programatically using ServiceBehaviorAttribute. Set IgnoreExtensionDataObject property to true.
[
ServiceBehavior(IgnoreExtensionDataObject = true)]
public class EmployeeService IEmployeeService

When IExtensibleDataObject feature is turned off, the deserializer will not populate the ExtensionData property. 

Suggested Videos
Part 12 - Backward compatible WCF contract changes
Part 13 - ExtensionDataObject in WCF
Part 14 - Risks of implementing IExtensibleDataObject interface



When an exception occurs in a WCF service, the service serializes the exception into a SOAP fault, and then sends the SOAP fault to the client.

By default unhandled exception details are not included in SOAP faults that are propagated to client applications for security reasons. Instead a generic SOAP fault is returned to the client.



For debugging purpose, if you want to include exception details in SOAP faults, enableIncludeExceptionDetailInFaults setting. This can be done in 2 ways as shown below.
1. In the config file using service behavior configuration
<behaviors>
  
<serviceBehaviors>
    
<behavior name="inculdeExceptionDetails">
      
<serviceDebug includeExceptionDetailInFaults="true"/>
    
</behavior>
  
</serviceBehaviors>
</behaviors>

2. In code using ServiceBehavior attribute
[
ServiceBehavior(IncludeExceptionDetailInFaults=true)]
public class CalculatorService ICalculatorService
{
    
public int Divide(int Numerator, int Denominator)
    {
        
return Numerator / Denominator;
    }
}

Frequently asked WCF interview questions
What happens when an exception occurs in a WCF service?
OR
What is a SOAP fault?
OR
How are WCF service exceptions reported to client applications?

Code used in the demo:
ICalculatorService.cs in CalculatorService project
using System.ServiceModel;
namespace CalculatorService
{
    [
ServiceContract]
    
public interface ICalculatorService
    {
        [
OperationContract]
        
int Divide(int Numerator, int Denominator);
    }
}

CalculatorService.cs in CalculatorService project
namespace CalculatorService
{
    
public class CalculatorService ICalculatorService
    {
        
public int Divide(int Numerator, int Denominator)
        {
            
return Numerator / Denominator;
        }
    }
}

App.config in CalculatorServiceHost project
<?xml version="1.0"?>
<configuration>
<system.serviceModel>
  
<services>
    
<service name="CalculatorService.CalculatorService"
              
behaviorConfiguration="mexBehavior">
      
<endpoint address="CalculatorService" binding="basicHttpBinding"
                
contract="CalculatorService.ICalculatorService">
      
</endpoint>
      
<host>
        
<baseAddresses>
          
<add baseAddress="http://localhost:8080/"/>
        
</baseAddresses>
      
</host>
    
</service>
  
</services>
  
<behaviors>
    
<serviceBehaviors>
      
<behavior name="mexBehavior">
        
<serviceMetadata httpGetEnabled="true"/>
        
<serviceDebug includeExceptionDetailInFaults="true"/>
      
</behavior>
    
</serviceBehaviors>
  
</behaviors>
</system.serviceModel>
</configuration>

Program.cs in CalculatorServiceHost project
using System;
using System.ServiceModel;
namespace CalculatorServiceHost
{
    
class Program
    {
        
static void Main()
        {
            
using (ServiceHost host = new ServiceHost
                (
typeof(CalculatorService.CalculatorService)))
            {
                host.Open();
                
Console.WriteLine("Host started @ " + DateTime.Now.ToString());
                
Console.ReadLine();
            }
        }
    }
}

WebForm1.aspx in CalculatorClient project
<table style="font-family:Arial; border:1px solid black">
    <tr>
        <td>
            <b>Numerator:</b>
        </td>
        <td>
            <asp:TextBox ID="txtNumerator" runat="server"></asp:TextBox>
        </td>
    </tr>
    <tr>
        <td>
            <b>Denominator:</b>
        </td>
        <td>
            <asp:TextBox ID="txtDenominator" runat="server"></asp:TextBox>
        </td>
    </tr>
    <tr>
        <td>
            <asp:Button ID="btnDivide" runat="server" Text="Divide" Font-Bold="True"                                   onclick="btnDivide_Click" />
        </td>  
        <td>
            <asp:Label ID="lblResult" runat="server" Font-Bold="true">
            </asp:Label>
        </td>
    </tr>
</table>

WebForm1.aspx.cs in CalculatorClient project
protected void btnDivide_Click(object sender, EventArgs e)
{
    
int numerator = Convert.ToInt32(txtNumerator.Text);
    
int denominator = Convert.ToInt32(txtDenominator.Text);
    CalculatorService.
CalculatorServiceClient client =
        
new CalculatorService.CalculatorServiceClient();
    lblResult.Text = client.Divide(numerator, denominator).ToString();

Suggested Videos
Part 13 - ExtensionDataObject in WCF
Part 14 - Risks of implementing IExtensibleDataObject interface
Part 15 - Exception handling in WCF



This is continuation to Part 15. Please watch Part 15, before proceeding. In Part 15, we have learnt that, WCF serializes exceptions to SOAP faults before reporting the exception information to the client. This is because exceptions are not allowed to be passed through a wcf service channel.



SOAP faults are in XML format and are platform independent. A typical SOAP fault contains
1. FaultCode
2. FaultReason
3. Detail elements etc.

The Detail element can be used to include any custom xml. We will discuss more about Detail element in a later video session.

SOAP faults are formatted based on SOAP 1.1 or SOAP 1.2 speficications. SOAP format depends on the binding. BasicHttpBinding uses SOAP 1.1 whereas the other built-in WCF bindings use SOAP 1.2.

For the differences between SOAP 1.1 and 1.2 please refer to the following article.
http://www.w3.org/2003/06/soap11-soap12.html

The differences are not that important from a developer perspective, as WCF formats the messages automatically based on the binding we have used to expose the service.

To view SOAP Fault messages, please enable message logging in WCF. We have discussed enabling message logging in Part 9 of WCF video series.

To view SOAP 1.1 fault message, set binding to basicHttpBinding.

SOAP 1.1 Fault with includeExceptionDetailInFaults set to false:
<s:Envelope xmlns:s="http://...">
  <s:Body>
    <s:Fault>
      <faultcode xmlns:a="http://..." xmlns="">a:InternalServiceFault</faultcode>
      <faultstring xml:lang="en-GB" xmlns="http://...">
        The server was unable to process the request due to an internal error.
        For more information about the error, either turn on IncludeExceptionDetailInFaults
        (either from ServiceBehaviorAttribute or from the <serviceDebug>configuration behavior)
        on the server in order to send the exception information back to the client, or turn
        on tracing as per the Microsoft .NET Framework 3.0 SDK documentation and inspect the
        server trace logs.
      </faultstring>
    </s:Fault>
  </s:Body>
</s:Envelope>

SOAP 1.1 Fault with includeExceptionDetailInFaults set to true:
<s:Envelope xmlns:s="http://...">
  <s:Body>
    <s:Fault>
      <faultcode xmlns:a="http://..." xmlns="">a:InternalServiceFault</faultcode>
      <faultstring xml:lang="en-GB" xmlns="">Attempted to divide by zero.</faultstring>
      <detail xmlns="">
        <ExceptionDetail xmlns="http://..." xmlns:i="http://...">
          <HelpLink i:nil="true"></HelpLink>
          <InnerException i:nil="true"></InnerException>
          <Message>Attempted to divide by zero.</Message>
          <StackTrace>
            at CalculatorService.CalculatorService.Divide(Int32 Numerator, Int32 Denominator)
            in C:\CalculatorService\CalculatorService\CalculatorService.cs:line 7
          </StackTrace>
          <Type>System.DivideByZeroException</Type>
        </ExceptionDetail>
      </detail>
    </s:Fault>
  </s:Body>
</s:Envelope>

To view SOAP 1.2 fault message, set binding to wsHttpBinding. By default Message Security is turned on for wsHttpBinding. Set the security mode for wsHttpBinding to None, so we could view the SOAP 1.2 fault message.
<bindings>
  <wsHttpBinding>
    <binding>
      <security mode="None">
      </security>
    </binding>
  </wsHttpBinding>
</bindings>

SOAP 1.2 Fault with includeExceptionDetailInFaults set to false:
<s:Envelope xmlns:s="http://..." xmlns:a="http://...">
  <s:Header>
    <a:Action s:mustUnderstand="1">http://...</a:Action>
    <a:RelatesTo>urn:uuid:3ea53f96-6e2f-48b3-83d8-ff81c8171153</a:RelatesTo>
  </s:Header>
  <s:Body>
    <s:Fault>
      <s:Code>
        <s:Value>s:Receiver</s:Value>
        <s:Subcode>
          <s:Value xmlns:a="http://...">a:InternalServiceFault</s:Value>
        </s:Subcode>
      </s:Code>
      <s:Reason>
        <s:Text xml:lang="en-GB">
          The server was unable to process the request due to an internal error.
          For more information about the error, either turn on IncludeExceptionDetailInFaults
          (either from ServiceBehaviorAttribute or from the <serviceDebug>configuration behavior)
          on the server in order to send the exception information back to the client, or turn
          on tracing as per the Microsoft .NET Framework 3.0 SDK documentation and inspect the server trace logs.
        </s:Text>
      </s:Reason>
    </s:Fault>
  </s:Body>
</s:Envelope>

SOAP 1.2 Fault with includeExceptionDetailInFaults set to true:
<s:Envelope xmlns:s="http://..." xmlns:a="http://...">
  <s:Header>
    <a:Action s:mustUnderstand="1">http://...</a:Action>
    <a:RelatesTo>urn:uuid:d649ab47-2813-4dd1-a97a-6ceea71c3e9c</a:RelatesTo>
  </s:Header>
  <s:Body>
    <s:Fault>
      <s:Code>
        <s:Value>s:Receiver</s:Value>
        <s:Subcode>
          <s:Value xmlns:a="http://...">a:InternalServiceFault</s:Value>
        </s:Subcode>
      </s:Code>
      <s:Reason>
        <s:Text xml:lang="en-GB">Attempted to divide by zero.</s:Text>
      </s:Reason>
      <s:Detail>
        <ExceptionDetail xmlns="http://..." xmlns:i="http://...">
          <HelpLink i:nil="true"></HelpLink>
          <InnerException i:nil="true"></InnerException>
          <Message>Attempted to divide by zero.</Message>
          <StackTrace>
            at CalculatorService.CalculatorService.Divide(Int32 Numerator,
            Int32 Denominator) in C:\CalculatorService\CalculatorService
            \CalculatorService.cs:line 7 ...
          </StackTrace>
          <Type>System.DivideByZeroException</Type>
        </ExceptionDetail>
      </s:Detail>
    </s:Fault>
  </s:Body>
</s:Envelope> 
Suggested Videos
Part 14 - Risks of implementing IExtensibleDataObject interface
Part 15 - Exception handling in WCF
Part 16 - SOAP faults in WCF



This is continuation to Part 16. Please watch Part 16, before proceeding.

An unhandled exception in the WCF service, will cause the communication channel to fault and the session will be lost. Once the communication channel is in faulted state, we cannot use the same instance of the proxy class any more. We will have to create a new instance of the proxy class.



BasicHttpBinding does not have sessions. So when there is an unhandled exception, it only faults the server channel. The client proxy is still OK, because with BasicHttpBinding the channel is not maintaining sessions, and when the client calls again it is not expecting the channel to maintain any session.

WSHttpBinding have secure sessions. So when there is an unhandled exception, it faults the server channel. At this point the existing client proxy is useless as it is also faulted, because with wsHttpBinding the channel is maintaining a secure session, and when the client calls again it expects the channel to maintain the same session. The same session does not exist at the server channel anymore, as the undhandled exception has already torn down the channel and the session along with it.
Part 18 - Throwing fault exceptions from a WCF service
Suggested Videos
Part 15 - Exception handling in WCF
Part 16 - SOAP faults in WCF
Part 17 - Unhandled exceptions in WCF



This is continuation to Part 17. Please watch Part 17, before proceeding.

In this video we will discuss throwing FaultException from a WCF service. The first question that normally comes to our mind at this point is that, why can't we simply throw .NET exceptions instead of Fault exceptions.



A WCF service should be throwing a FaultException or FaultException<T> instead ofDot Net exceptions. This is because of the following 2 reasons.
1. An unhandled .NET exception will cause the channel between the client and the server to fault. Once the channel is in a faulted state we cannot use the client proxy anymore. We will have to re-create the proxy. We discussed this in Part 17 of the WCF tutorial. On the other hand faultexceptions will not cause the communication channel to fault.

2. As .NET exceptions are platform specific, they can only be understood by a client that is also .NET. If you want the WCF service to be interoperable, then the service should be throwing FaultExceptions.

Please Note: FaultException<T> allows us to create strongly typed SOAP faults. We will discuss this in our next video.

CalculatorService.cs:
using System.ServiceModel;
namespace CalculatorService
{
    
public class CalculatorService ICalculatorService
    {
        
public int Divide(int Numerator, int Denominator)
        {
            
if (Denominator == 0)
                
throw new FaultException("Denomintor cannot be ZERO", new FaultCode("DivideByZeroFault"));

            
return Numerator / Denominator;
        }
    }
}

Client application:
using System;
using System.ServiceModel;
using System.Windows.Forms;

namespace WidowsClient
{
    
public partial class Form1 Form
    {
        CalculatorService.
CalculatorServiceClient client = null;
       
        
public Form1()
        {
            InitializeComponent();
            client = 
new CalculatorService.CalculatorServiceClient();
        }

        
private void button1_Click(object sender, EventArgs e)
        {
            
try
            {
                
int numerator = Convert.ToInt32(textBox1.Text);
                
int denominator = Convert.ToInt32(textBox2.Text);

                label1.Text = client.Divide(numerator, denominator).ToString();
            }
            
catch (FaultException faultException)
            {
                label1.Text = faultException.Code.Name + 
" - " + faultException.Message;
            }
        }

        
private void button2_Click(object sender, EventArgs e)
        {
            client = 
new CalculatorService.CalculatorServiceClient();
            MessageBox.Show(
"A new instance of the proxy class is created");
        }
    }
}
Part 19 - Creating and throwing strongly typed SOAP faults
Suggested Videos
Part 16 - SOAP faults in WCF
Part 17 - Unhandled exceptions in WCF
Part 18 - Throwing fault exceptions from a WCF service



This is continuation to Part 18, please watch Part 18 before proceeding.

In Part 18, we discussed throwing a generic SOAP fault using FaultException class. Instead of throwing a generic SOAP fault, we can create strongly typed SOAP faults and throw them. Creating our own strongly typed SOAP faults allow us to include any additional custom information about the exception that has occurred. Here are the steps to create a strongly typed SOAP fault.



Step 1: Riglt click on the CalcualtorService and add a class file with name =DivideByZeroFault.cs. Copy and paste the following code. DivideByZeroFault is a strongly typed fault.
using System.Runtime.Serialization;
namespace CalculatorService
{
    [
DataContract]
    
public class DivideByZeroFault
    {
        [
DataMember]
        
public string Error { get; set; }

        [
DataMember]
        
public string Details { get; set; }
    }
}

Step 2: Make the following changes to ICalculatorService.cs. Notice the change highlighted in yellow. We have used FaultContract attribute to specify that the Divide()method can throw DivideByZeroFault.
using System.ServiceModel;
namespace CalculatorService
{
    [
ServiceContract]
    
public interface ICalculatorService
    {
        [FaultContract(typeof(DivideByZeroFault))]
        [
OperationContract]
        
int Divide(int Numerator, int Denominator);
    }
}

Step 3: Copy and paste the following code in CalculatorService.cs. Notice that instead of throwing a generic SOAP fault, we are throwing a strongly typed SOAP fault usingFaultException<T> class, where <T> is DivideByZeroFault.
using System.ServiceModel;
using System;
namespace CalculatorService
{
    
public class CalculatorService ICalculatorService
    {
        
public int Divide(int Numerator, int Denominator)
        {
            
try
            {
                
return Numerator / Denominator;
            }
            
catch (DivideByZeroException ex)
            {
                
DivideByZeroFault divideByZeroFault = new DivideByZeroFault();
                divideByZeroFault.Error = ex.Message;
                divideByZeroFault.Details =
 "Denominator cannot be ZERO";

                
throw new FaultException<DivideByZeroFault>(divideByZeroFault);
            }
        }
    }
}

As we have changed the WCF service, update the service reference in the client application.

Step 4: Finally in the client application, modify the catch block as shown below.
catch (FaultException<CalculatorService.DivideByZeroFault> faultException)
{
    label1.Text = faultException.Detail.Error + 
" - " + faultException.Detail.Details;
}

So in short, to create a strongly typed SOAP fault
1. Create a class that represents your SOAP fault. Decorate the class withDataContract attribute and the properties with DataMember attribute.
2. In the service data contract, use FaultContractAttribute to specify which operations can throw which SOAP faults.
3. In the service implementation create an instance of the strongly typed SOAP fault and throw it using FaultException<T>
Suggested Videos
Part 17 - Unhandled exceptions in WCF
Part 18 - Throwing fault exceptions from a WCF service
Part 19 - Creating and throwing strongly typed SOAP faults



This is continuation to Part 19, please watch Part 19 before proceeding.

In this video, we will discuss - How to handle all WCF service exceptions in one central location. This is a very common interview question.

In an ASP .NET web applications we can use Application_Error() event handler method in Global.asax to log all the exceptions and redirect the user to a custom error page.

In WCF, to centralize exception handling and to return a general faultreason to the client, we implement IErrorHandler interface. Let's now look at the 3 steps involved in centralizing exception handling in WCF. We will be continuing with the same example, that we worked with in Part 19.



Step 1: Implement IErrorHandler interface. To do this, right click on CalculatorService project and add a class file with name = GlobalErrorHandler.cs. Copy and paste the following code.
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
namespace CalculatorService
{
    
public class GlobalErrorHandler IErrorHandler
    {
        
public bool HandleError(Exception error)
        {
            
// log the actual exception for the IT Team to investigate
            // return true to indicate that the exception is handled
            
return true;
        }

        
public void ProvideFault(Exception error,
                                 System.ServiceModel.Channels.
MessageVersion version,
                                 ref System.ServiceModel.Channels.
Message fault)
        {
            
if (error is FaultException)
                
return;

            // Return a general service error message to the client
            
FaultException faultException = new FaultException("A general service error occured");
            
MessageFault messageFault = faultException.CreateMessageFault();
            fault = 
Message.CreateMessage(version, messageFault, null);
        }
    }
}

IErrorHandler interface has 2 methods for which we need to provide implementation.
1. ProvideFault() - This method gets called automatically when there is an unhandled exception or a fault. In this method we have the opportunity to write code to convert the unhandled exception into a generic fault that can be returned to the client. ProvideFault() gets called before HandleError() method.

2. HandleError() - This method gets called asynchronously after ProvideFault() method is called and the error message is returned to the client. This means that this method allows us to write code to log the exception without blocking the client call.

Step 2: Create a custom Service Behaviour Attribute to let WCF know that we want to use the GlobalErrorHandler class whenever an unhandled exception occurs. To do this, right click on the CalculatorService project and add a class file with name =  GlobalErrorHandlerBehaviourAttribute.cs. Copy and paste the following code.
using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace CalculatorService
{
    
public class GlobalErrorHandlerBehaviourAttribute AttributeIServiceBehavior
    {
        
private readonly Type errorHandlerType;

        
public GlobalErrorHandlerBehaviourAttribute(Type errorHandlerType)
        {
            
this.errorHandlerType = errorHandlerType;
        }

        
public void Validate(ServiceDescription serviceDescription,
                             
ServiceHostBase serviceHostBase)
        {
        }

        
public void AddBindingParameters(ServiceDescription serviceDescription,
                                         
ServiceHostBase serviceHostBase,
                                         
Collection<ServiceEndpoint> endpoints,
                                         
BindingParameterCollection bindingParameters)
        {
        }

        
public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
                                          
ServiceHostBase serviceHostBase)
        {
            
IErrorHandler handler = (IErrorHandler)Activator.CreateInstance(this.errorHandlerType);

            
foreach (ChannelDispatcherBase channelDispatcherBase inserviceHostBase.ChannelDispatchers)
            {
                
ChannelDispatcher channelDispatcher = channelDispatcherBase asChannelDispatcher;
                
if (channelDispatcher != null)
                    channelDispatcher.ErrorHandlers.Add(handler);
            }
        }
    }
}

Notice that the GlobalErrorHandlerBehaviourAttribute class
1. Inherits from Attribute abstract class.

2. Implements IServiceBehavior interface. This interface has 3 methods (Validate(), AddBindingParameters(), ApplyDispatchBehavior()). The implementation for Validate() and AddBindingParameters() method can be left blank. In the ApplyDispatchBehavior() method, we create an instance of the GlobalErrorHandler class and associate the instance with each channelDispatcher.

3. Has a constructor that contains one Type parameter. We will use this constructor in Step 3.

Step 3: Decorate CalculatorService class in CalculatorService.cs file withGlobalErrorHandlerBehaviourAttribute. Notice that this attribute has one constructor that expects a single Type parameter. Pass GlobalErrorHandler class created in Step 1 as the argument.
[
GlobalErrorHandlerBehaviour(typeof(GlobalErrorHandler))]
public class CalculatorService ICalculatorService
{
    
public int Divide(int Numerator, int Denominator)
    {
    }
}

For testing the global centralized error handler
1. Comment try-catch blocks in the Divide() method in CalculatorService.cs file
public int Divide(int Numerator, int Denominator)
{
    
//try
    //{
        
return Numerator / Denominator;
    
//}
    //catch (DivideByZeroException ex)
    //{
    //    DivideByZeroFault divideByZeroFault = new DivideByZeroFault();
    //    divideByZeroFault.Error = ex.Message;
    //    divideByZeroFault.Details = "Denominator cannot be ZERO";

    //    throw new FaultException<DivideByZeroFault>(divideByZeroFault);
    //}
}

2. Put a break point in ProvideFault() and HandleError() methods

3. Run the service in Debug mode

4. In the client application change the 
catch in button1_Click() event handler as shown below.
catch (FaultException faultException)
{
    label1.Text = faultException.Message;

}

5. Run the client application. Pass 0 as the denominator and click Divide button.

Notice that ProvideFault() and HandleError() methods in GlobalErrorHandler class are automatically called


2 comments: