Monday, March 17, 2008

Exception Handling

Let’s make a small change in the application

Open the connection string configuration and type in a wrong password. This is to ensure that you deliberately introduce a failure. Compile your service, execute your service host and then your client application.

Application will have no compilation issues. As soon as it tries to fetch the product list, you will get an exception (FaultException) as mentioned below, or something similar to this

Error message
“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 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”

Though our changes were in the connection string, .Net should have re-casted it, but what actually happened is, .NET consumes the error message, converted it into a generic FaultException and there to client so that they can be handled…..but wait……we don’t need this error. We want to see, what actually caused this problem……to see the actual error, go ahead with the following changes……we will continue on the same application we created earlier

Crete a new Data Contract with the following structure
Service side changes

[DataContract]
public class ServiceFaults
{

private string _operation;

[DataMember]
public string Operation
{
get { return _operation; }
set { _operation = value; }
}

private string _reason;

[DataMember]
public string Reason
{
get { return _reason; }
set { _reason = value; }
}


private string _message;

[DataMember]
public string Message
{
get { return _message; }
set { _message = value; }
}
}

Add this class as a Fault contract for methods that will throw error. In our case we have 3 mtthods exposed, we will add to all three methods.
Note: If needed, you can create as many Data contract classes separately and have them associated with methods, there can me multiple attributes for each method

[ServiceContract]
public interface IProductService
{
[FaultContract(typeof(ServiceFaults))]
[OperationContract]
List ListProducts();

[FaultContract(typeof(ServiceFaults))]
[OperationContract]
MyProduct GetProduct(string sProdNumber);

[FaultContract(typeof(ServiceFaults))]
[OperationContract]
int GetCurrentStockLevel(string sProdNumber);

[FaultContract(typeof(ServiceFaults))]
[FaultContract(typeof(ConfigFaults))]
[FaultContract(typeof(CustomFaults))]
[OperationContract]
bool ChangeStockLevel(string sProdNumber,
int newStockLevel, string shelf, int bin);
// TODO: Add your service operations here
}

If you take a look at the method declaration for ChangeStockLevel, there are multiple FaultContracts defined. You can follow similar mechanism for your custom error handling (one on to error logs, other to event viewer, to console, configuration error, DB errors…etc)

Let’s capture the error from the DB query…that’s where we would receive this error. Remember we changed the password for DB connection in the connection string to a wrong one. Capture the exception, get the message, add custom messages, if needed and re-cast it to client applications

public List ListProducts()
{
try
{
ProductDao pDao = new ProductDao();
return pDao.ListProducts();
}
catch (Exception ex)
{
ServiceFaults sf = new ServiceFaults();
sf.Operation = "List Products";
sf.Reason = "Exception querying Db";
sf.Message = ex.Message;
throw new FaultException(sf);
}
}


Let’s move on to client application to see if we can catch the error message
As there have been changes to the service (new Data contracts added), it will be wise to regenerate the service reference. You can do that from IDE, by clicking on the Reference file in the folder Service Reference, right-click on the file select “Update Service Reference”

It’ll take a while to get updated and from then on, it’s an easy game (as though others were tough :-))

Client side changes
private void OpenTcpClient()
{
try
{
ProductServiceClient proxy =
new ProductServiceClient("NetTcpBinding_IProductService");
List lstProducts = proxy.ListProducts().ToList();
foreach (string prod in lstProducts)
{
Console.WriteLine(prod);
}
}
catch (FaultException sf)
{
string sMessage;
sMessage = sf.Detail.Operation + "\n";
sMessage += sf.Detail.Reason + "\n";
sMessage += sf.Detail.Message;
MessageBox.Show(sMessage);
}
}

You will see appropriate error message from service

In the coming days, I’ll explain you how to secure your application through encryption, authentication and authorization….until then sit tight :-)

Sunday, March 16, 2008

Configuring Multiple Endpoints

For Tcpbinding the following changes are to be doneAdd a new endpoint to your service host application, below your original Http configuration
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service behaviorConfiguration="ProductsBehavior" name="ProductService.ProductService">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8000/ProductService"/>
</baseAddresses>
</host>

<endpoint address="ProductService.svc" binding="basicHttpBinding"
name="ProductServiceHttpEndPoint" contract="ProductService.IProductService" />

<endpoint address="net.tcp://localhost:8080/TcpProductService"
binding="netTcpBinding" bindingConfiguration="" name="ProductServiceTcpEndPoint"
contract="ProductService.IProductService" />

</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ProductsBehavior">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>

When ever ServiceHost is instantiated, a separate listener for each endpoint will be created. Nothing more needed from service host.

Move on to the client application
Open App.config, and in the client section, add a new tcp end point

<client>

<endpoint address="http://localhost:8000/ProductService/ProductService.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IProductService"
contract="ProdServiceReference.IProductService" name="BasicHttpBinding_IProductService" />

<endpoint address="net.tcp://localhost:8080/TcpProductService"
binding="netTcpBinding" bindingConfiguration="" contract="ProdServiceReference.IProductService"
name="NetTcpBinding_IProductService" />

</client>

Move on to your code behind file, to execute service through newly configured Tcp connection, write the following code and invoke it in the place of your choice.

private void OpenTcpClient()
{
ProductServiceClient proxy =
new ProductServiceClient("NetTcpBinding_IProductService");
List lstProducts = proxy.ListProducts().ToList();
foreach (string prod in lstProducts)
{
Console.WriteLine(prod);
}
}

Creating a WCF application

This contains of 3 sections
Service
Service Host
Client

Service
Application that will be consumed by clients.

A simple service will contain Data Contracts and Service Contracts

Service Contracts is the interface defined for communication. All exposed methods should be contained in this interface. There are no restrictions on the number of data contracts that will be exposed by a single WCF application.

Services necessarily need to communicate information, back to the caller. Return values can be basid data types (int, char, string, float…) or can be some typed objects (product, employee, order….). If the return value falls in the later category, then we should expose those types as DataMembers.

Enough of talking let me take you through a simple application that I created for use

Following class acts as a service contract and Data contract. If the application has a number of typed objects you may want to create separate files for each of the data contracts. I have kept things simple here

IProductService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace ProductService
{
// NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in App.config.
[ServiceContract]
public interface IProductService
{
[OperationContract]
List ListProducts();

[OperationContract]
MyProduct GetProduct(string sProdNumber);

[OperationContract]
int GetCurrentStockLevel(string sProdNumber);

[OperationContract]
bool ChangeStockLevel(string sProdNumber,
int newStockLevel, string shelf, int bin);
// TODO: Add your service operations here
}

// Use a data contract as illustrated in the sample below to add composite types to service operations
[DataContract]
public class MyProduct
{
string _name;
string _productNumber;
string _color;
decimal _listPrice;

[DataMember]
public string Name
{
get { return _name; }
set { _name = value; }
}

[DataMember]
public string ProductNumber
{
get { return _productNumber; }
set { _productNumber = value; }
}

[DataMember]
public string Color
{
get { return _color; }
set { _color = value; }
}

[DataMember]
public decimal ListPrice
{
get { return _listPrice; }
set { _listPrice = value; }
}
}
}

This class does the underlying job of communication with DB/Mock data/XML Data for information, and publishes it to the caller/invoker

ProductService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace ProductService
{
// NOTE: If you change the class name "Service1" here, you must also update the reference to "Service1" in App.config.
[ServiceBehavior (InstanceContextMode=InstanceContextMode.PerSession)]
public class ProductService : IProductService
{
public List ListProducts()
{
ProductDao pDao = new ProductDao();
return pDao.ListProducts();
}

public MyProduct GetProduct(string sProdNumber)
{
ProductDao pDao = new ProductDao();
return pDao.GetProduct(sProdNumber);
}

public int GetCurrentStockLevel(string sProdNumber)
{
ProductDao pDao = new ProductDao();
return pDao.GetCurrentStockLevel(sProdNumber);
}

public bool ChangeStockLevel(string sProdNumber,
int newStockLevel, string shelf, int bin)
{
ProductDao pDao = new ProductDao();
return pDao.ChangeStockLevel(sProdNumber,
newStockLevel, shelf, bin);
}
}
}

Watch out for the Services and behavior section of app.config. As the number of servicecs grow, we will have as many services
App.Config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
</configSections>
<connectionStrings>
<add name="ProductService.Properties.Settings.AdventureWorksConnectionString" connectionString="Data Source=.;Initial Catalog=AdventureWorks;Persist Security Info=True;User ID=sa;Password=1234kris$" providerName="System.Data.SqlClient" />
</connectionStrings>
<system.web>
<compilation debug="true" />
</system.web>
<!-- When deploying the service library project, the content of the config file must be added to the host's app.config file. System.Configuration does not support config files for libraries. -->
<system.serviceModel>
<services>
<service name="ProductService.ProductService" behaviorConfiguration="ProductsBehavior">
<!-- <endpoint address="ProductService.svc" binding="basicHttpBinding" contract="ProductService.IProductService" />
-->
<endpoint address="ProductService.svc" binding="wsHttpContextBinding" contract="ProductService.IProductService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</service>
<service name="ProductService.Service1" behaviorConfiguration="ProductService.Service1Behavior">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8731/Design_Time_Addresses/ProductService/Service1/" />
</baseAddresses>
</host>
<!-- Service Endpoints -->
<!-- Unless fully qualified, address is relative to base address supplied above -->
<endpoint address="" binding="wsHttpBinding" contract="ProductService.IService1">
<!-- Upon deployment, the following identity element should be removed or replaced to reflect the identity under which the deployed service runs. If removed, WCF will infer an appropriate identity automatically. -->
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<!-- Metadata Endpoints -->
<!-- The Metadata Exchange endpoint is used by the service to describe itself to clients. -->
<!-- This endpoint does not use a secure binding and should be secured or removed before deployment -->
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
<service name="ProductService.ProductService" behaviorConfiguration="ProductsBehavior">
<endpoint address="" binding="basicHttpBinding" contract="ProductService.IProductService" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ProductsBehavior">
<serviceMetadata httpGetEnabled="true" />
</behavior>
<behavior name="ProductService.Service1Behavior">
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="True"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
<behavior name="ProductsBehavior">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>


Once done you are all set to launch the service for your client. But wait. Untill .NET 3.0, we have been using IIS, doe it work the same way even now….yes.

Copy all project files (your workspace) and paste it in the Virtual directory and execute the same from IE. You are in for a sharp surprise, application does not seem to work…..Alright, I’ll break the ice

Create a new .svc file (a text file renamed to .svc) with following contents
<%@ ServiceHost Language="C#" Debug="true"
Service="ProductService.ProductService" %>

Open IE and execute the application. Now things will work as expected. So the next step is to access it from client application

Here is a simple client application that I have put togather for use

As this is a web-based service (HttpBinding, we have many types of binding, we will shortly take a look about tcpbinding too), add a web reference. The following is the link I used for reference
http://ml-krishnananr.virtusa.com/AdventureWorksService/ProductService.svc?wsdl

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;


namespace ServiceConsumer
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
ProdServiceReference.ProductServiceClient psClient =
new ServiceConsumer.ProdServiceReference.ProductServiceClient("BasicHttpBinding_IProductService");
List lstProducts = psClient.ListProducts().ToList();
foreach(string prod in lstProducts)
{
Console.WriteLine(prod);
}
}
}
}

Execute the application. This move like butter

Alright…..let’s say that you don’t like IIS to act as a mediator for you. Rather, you want to have a granular control to host your service. We can do that by the following mechanism

Create a simple Windows application with start stop button and a label to update the status of service

Reference the web service dll (Add references, bowse to the location of the dll and refer it in this project)
Following is the code that I have for the windows application

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;
using System.Net;
using ProductService;

namespace ProductServiceHost
{
public partial class Form1 : Form
{
private ServiceHost productServiceHost;
public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{

}

private void btnStart_Click(object sender, EventArgs e)
{
//for http connections
productServiceHost = new ServiceHost
(typeof(ProductService.ProductService),
new Uri("http://localhost:8000/ProductService"));
productServiceHost.Open();
btnStart.Enabled = false;
btnStop.Enabled = true;
lblStatus.Text = "Service Started";
}

private void btnStop_Click(object sender, EventArgs e)
{
productServiceHost.Close();
btnStop.Enabled = false;
btnStart.Enabled = true;
lblStatus.Text = "Service Stopped";
}
}
}


App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IProductService" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None" realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:8000/ProductService/ProductService.svc" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IProductService" contract="ProdServiceReference.IProductService" name="BasicHttpBinding_IProductService" />
</client>
</system.serviceModel>
</configuration>


Execute the application, flick on the start button, Host application would expose the service in the location http://localhost:8000/ProductService/ProductService.svc

Execute the client application and ensure it’s make it’s request to the location mentioned above
You are all done with Basic WCF. In the following section I'll take you through multiple endpoint configuration