Introduction
Contracts in WCF, provide interoperability they need to communicate with the client. In this article i am going to explain the type of contracts, and how these contracts can be used in a real life application. in this article i have tried to explain all these things, using a simple example, in this example i have taken a real world cuboid, which has 3 properties (length, width and height), our service calculates volume and total surface area of the cuboid. demonstrating both way, using message contract and data contract.
Background
It is contracts that that client and service agree as to the type of operation and structure they will use during communication. it is a formal agreement between a client and service to define a platform-neutral and standard way of describing what the service does. WCF defines four types of contracts
- Service Contract
- Data Contract
- Message Contract
- Fault Contract
Service Contract
Service contract describes the operations, or methods, that are available on the service endpoint, and exposed to the outside world. A Service contract describes the client-callable operations (functions) exposed by the service, apart from that it also describes.
- location of operations, interface and methods of your service to a platform-independent description
- message exchange patterns that the service can have with another party. might be one-way/request-reply/duplex.
To create a service contract you define an interface with related methods representative of a collection of service operations, and then decorate the interface/class with the ServiceContract Attribute to indicate it is a service contract. Methods in the interface that should be included in the service contract are decorated with the OperationContract Attribute.
[ServiceContract]
interface ICuboidService
{
[OperationContract]
CuboidDetail CalculateDetails2(CuboidInfo cInfo);
}
Data Contract
In one line, data contract describes the data to be exchanged. it is formal agreement between service and client, that contains information about the data they will be exchanging. the important point, which needs to be mentioned here is that the two parties don’t have to share the same data types to communicate, they only need the share the same data contracts. Data contract defines which parameter & return type will be serialized/de-serialized to and from (Binary <==> XML) in order to be transmitted between one party to another. Data contracts can be defined by annotating a class, enumeration, or even a structure, but not an interface.
To define a data contract, you simply decorate a class or enumeration with [DataContract] attribute, as shown below.
[DataContract]
public class CuboidInfo
{
}
Message Contract
WCF uses SOAP message for communication. Most of the time developer concentrates more on developing the DataContract, Serializing the data, etc. Some time developer will also require control over the SOAP message format. In that case WCF provides Message Contract to customize the message as per requirement.
A Message Contract is used to control the structure of a message body and serialization process. It is also used to send / access information in SOAP headers. By default WCF takes care of creating SOAP messages
according to service DataContracts and OperationContracts.
So when you need full control on SOAP messages structure and how serialization happens specially when your service needs to be interoperable for consuming by different types of clients or service needs to provide extra layer of security on messages and message parts, Amessage is nothing but a packet and WCF uses this packet to transfer information from source to destination. This message contains an envelope, header and body. There are some rules, which one needs to follow, while working with message contract.
- When using Message contract type as parameter, Only one parameter can be used in Operation.
- Service operation either should return MessageContract type or it should not return any value.
- Operation will accept and return only message contract type. Other data types are not allowed.
Choosing between DataContract and MessageContract.
90% of the time, DataContract will be sufficient, You'll only need message contracts if you need to very closely and very specifically control the layout of your SOAP messages. In most of the time, you don't need to.
A message contract allows you to specifically say which elements (scalar types or compound types as DataContracts) will be in the SOAP header, and which will be in the SOAP body.
You might need this if you have a communication partner, with whom you have agreed to a very specific format and you have to tweak your SOAP messages to match that given layout exactly. That's just about the only valid scenario when you'll need to and should use message contracts.
However, sometimes complete control over the structure of a SOAP message is just as important as control over its contents. This is especially true when interoperability is important or to specifically control security issues at the level of the message or message part. In these cases, you can create a message contract that enables you to use a type for a parameter or return value that serializes directly into the precise SOAP message that you need.
So, to making the long story short:
always use
data contracts, practically never use message contracts (
unless you really have to).
Fault Contract
Fault Contract provides documented view for error accorded in the service to client. This help as to easy identity the what error has occurred, and where. By default when we throw any exception from service, it will not reach the client side. The less the client knows about what happened on the server side, the more dissociated the interaction will be, this phenomenon (not allowing the actual cause of error to reach client). is known as error masking. By default all exceptions thrown on the service side always reach the client as FaultException, as by having all service exceptions indistinguishable from one another, WCF decouples the client from service.
While the default policy of error-masking is best practice of WCF, but there are times when client is required to respond to these exceptions in a prescribed and meaningful way. WCF provides the option to handle and convey the error message to client from service using SOAP Fault contract. SOAP faults are based on industry standard that is independent of any technology specific exceptions, it is a way to map technology specific exceptions to some neutral error information. So when service meets as unexpected error, instead of throwing a raw CLR exception (technology specific), service can throw an instance of the FaultException <T> class.
Using the code
This article has been divided into 4 modules:
- WCF Service Library (ContractLib.dll): Actual Service logic, which defines and implements the service, and exposes few functions to the world to use them
- Console based Host Application to host the WCF Service Library (ContractLibHost.exe): Host the WCF library
- Console Client Application (ContractClient.exe): Client Application which will use this service
- Web Client (ContractWebClient) Web site which will use this service
First Module: WCF Service Library (ContractLib.dll)
To create this project, you can simply take a "Class Library" project, while choosing from project wizard option. let’s name it "ContractLib
", it is the actual service which implements the business logic. The project already contains a file Class1.cs, let us do some house keeping, before we write any code for the service library.
Little House Keeping
- Delete the file Class1.cs from the project workspace.
- Add a new Interface named
ICuboidService
to the project, a new file ICuboidService.cs will be added to the project. - Add a new Class named
CuboidService
,
to the project. that will implement the ICuboidService
interface, a new file CuboidService.
cs will be added to the project.
Defining Service Interface - Data Contract(s)
Definition of CuboidFaulltException
[DataContract]
public class CuboidFaultException
{
private string _reason;
private string _source;
private string _detail;
private string _helpLink;
[DataMember]
public string Reason
{
get { return _reason; }
set { _reason = value; }
}
[DataMember]
public string Source
{
get { return _source; }
set { _source = value; }
}
[DataMember]
public string Detail
{
get { return _detail; }
set { _detail = value; }
}
[DataMember]
public string HelpLink
{
get { return _helpLink; }
set { _helpLink = value; }
}
}
Explanation
This
DataContract defines the data the data to be exchanged when a
CuboidFaultException is thrown from service. As the service is also implementing Fault contract, this information is sent to client from the service. I have defined 4 fields, to send the error information to the client, you can define more fields you you need to to send some other contextual information to client.
Reason: The actual cause of the exception, why the service has thrown an
CuboidFaultException.
Source : The source information, which has thrown the exception, in this field you can send the name of the function, or line number where exception occurred, or what ever information you think will fit as source
field of the error.
Detail: here you can pack the information regarding, the actual detail of the error.
HelpLink: a web link about the documentation about this kind of error, any thing which you can think can be a help.
Definition of CuboidDimension
[DataContract]
public class CuboidDimension
{
[DataMember] public int Length;
[DataMember] public int Width;
[DataMember] public int Height;
}
Explanation
This Data contract simply defines the 3 dimensions of a cuboid, which is length, width and height.
Definition of CuboidInfo
[DataContract]
public class CuboidInfo
{
[DataMember] public int ID;
[DataMember] public CuboidDimension Dimension = new CuboidDimension();
}
Explanation
This Data contract defines a class which contains two fields, first field is the ID of the Cuboid and second field is an object of CuboidDimension class, which was defined previously, that contains the dimensions (length, width and height) of the cuboid.
Definition of CuboidDetail
[^__strong__^DataContract]
public class CuboidDetail
{
[DataMember] public int ID;
[DataMember] public double SurfaceArea;
[DataMember] public double Volume;
}
Explanation
This Data contract simply defines a class which contains 3 fields, first field is the
ID of the Cuboid and second field is a double, that will hold the value of
surface area of the Cuboid, and the 3rd field is a
double that will contain the
volume of the cuboid.
Defining Service Interface - Message Contract(s)
Definition of CuboidInfoRequest
[MessageContract]
public class CuboidInfoRequest
{
private int m_nID ;
private CuboidDimension m_Dims = new CuboidDimension();
[MessageHeader]
public int ID
{
get { return m_nID; }
set { m_nID = value; }
}
[MessageBodyMember]
public CuboidDimension Dimension
{
get { return m_Dims; }
set { m_Dims = value; }
}
}
Explanation
This Message contract simply defines a class which contains two fields, first field is the ID of the Cuboid, which has been defined as Message Header. In the message body contains a single member
CuboidDimension, that is details (dimensions) of the cuboid.
Defining Service Interface - Message Contract(s)
Definition of CuboidInfoResponse
[MessageContract]
public class CuboidDetailResponse
{
private int m_nID;
private double m_dblArea;
private double m_dblVolume;
[MessageHeader]
public int ID
{
get { return m_nID; }
set { m_nID = value; }
}
[MessageBodyMember]
public double SurfaceArea
{
get { return m_dblArea ; }
set { m_dblArea = value ; }
}
[MessageBodyMember]
public double Volume
{
get { return m_dblVolume; }
set { m_dblVolume = value; }
}
}
Explanation
This Message contract defines a class which contains 3 fields, ID of the cuboid has been defined as Message Header. Message body contains a two members, that is the Volume and Area of the Cuboid.
Defining Service Interface – Service Contract, Operation Contract, Fault Contract
Defining ServiceContract ICuboidService
[ServiceContract]
interface ICuboidService
{
[OperationContract]
[FaultContract(typeof(CuboidFaultException))]
CuboidDetailResponse CalculateDetails1(CuboidInfoRequest cInfo);
[OperationContract]
[FaultContract(typeof(CuboidFaultException))]
CuboidDetail CalculateDetails2(CuboidInfo cInfo);
[OperationContract]
[FaultContract(typeof(CuboidFaultException))]
CuboidDetail CalculateDetails3(int nID, CuboidDimension cInfo);
}
Explanation
Service defines 3 methods as Exposed Operation(s) of the service.
The first method CalculateDetails1 of the service takes a Message Contract type CuboidInfoRequest as an input parameter, and returns a Message Contract type CuboidDetailResponse.
Abiding by the rules.
- When using Message contract type as parameter, Only one parameter can be used in Operation.
- Service operation either should return MessageContract type or it should not return any value.
- Operation will accept and return only message contract type. Other data types are not allowed.
implements a FaultContract, which raises CuboidFaultException, if there is something not in order, as it supposed to be.
The second method CalculateDetails2 of the service takes a Data Contract type CuboidInfo as an input parameter, and returns a Data Contract type CuboidDetail. although method takes only one parameter.
implements a FaultContract, which raises CuboidFaultException, if there is something not the way, as it supposed to be. using DataContract does not impose any rules, as in case of MessageContract.
The third method CalculateDetails3 of the service takes a 2 parameters, first paraeter is an int, and 2nd parameter is Data Contract type CuboidDimension as an input parameter, and returns a Data Contract type CuboidDetail. implements a FaultContract, which raises CuboidFaultException, if there is something not the way, as it supposed to be.
using DataContract does not impose any rules, as in case of MessageContract.
Implementing Service Interface
Implementing method CalculateDetails1
public CuboidDetailResponse CalculateDetails1(CuboidInfoRequest cInfo)
{
if ((cInfo.Dimension.ID <=0) || (cInfo.Dimension.Length <= 0) || (cInfo.Dimension.Width <= 0) || (cInfo.Dimension.Height <= 0))
{
CuboidFaultException faultEx = new CuboidFaultException ();
faultEx.Reason = "Any dimension of Cuboid (length/width/height) can not be zero or negative value";
faultEx.Source = "CalculateDetails1()";
faultEx.HelpLink = "http://praveenkatiyar.wordpress.com";
StringBuilder sbDetail = new StringBuilder ("");
if (cInfo.Dimension.ID <= 0) sbDetail.Append ( "[ID is <= 0] ");
if (cInfo.Dimension.Length <= 0) sbDetail.Append ( "[Length is <= 0] ");
if (cInfo.Dimension.Width <= 0) sbDetail.Append ( "[Width is <= 0] ");
if (cInfo.Dimension.Height <= 0) sbDetail.Append ( "[Height is <= 0]") ;
faultEx.Detail = sbDetail.ToString();
throw new FaultException<CuboidFaultException>(faultEx);
}
CuboidDetailResponse dInfo = new CuboidDetailResponse();
dInfo.ID = cInfo.ID ;
dInfo.Volume = (cInfo.Dimension.Length * cInfo.Dimension.Width * cInfo.Dimension.Height);
dInfo.SurfaceArea = 2 * ( (cInfo.Dimension.Length * cInfo.Dimension.Width) +
(cInfo.Dimension.Width * cInfo.Dimension.Height) +
(cInfo.Dimension.Length * cInfo.Dimension.Height ));
return dInfo;
}
Explanation
Operation takes a MessageContrat (CuboidInfoRequest) type parameter, Operation checks whether that ID and attributes (length, width, height) of the cuboid is not zero or negative. if yes then raises a FaultException of type CuboidFaultExeception, assigning the values for the field of CuboidFaultExeception. and if everything is in place, calculates the desired values, creates a new MessageContract object (CuboidDetailResponse) and assigns its field and returns that object.
Implementing method CalculateDetails2
public CuboidDetail CalculateDetails2(CuboidInfo cInfo)
{
if ((cInfo.Dimension.ID <=0) || (cInfo.Dimension.Length <= 0) || (cInfo.Dimension.Width <= 0) || (cInfo.Dimension.Height <= 0))
{
CuboidFaultException faultEx = new CuboidFaultException ();
faultEx.Reason = "Any dimension of Cuboid (length/width/height) can not be zero or negative value";
faultEx.Source = "CalculateDetails2()";
faultEx.HelpLink = "http://praveenkatiyar.wordpress.com";
StringBuilder sbDetail = new StringBuilder ("");
if (cInfo.Dimension.ID <= 0) sbDetail.Append ( "[ID is <= 0] ");
if (cInfo.Dimension.Length <= 0) sbDetail.Append ( "[Length is <= 0] ");
if (cInfo.Dimension.Width <= 0) sbDetail.Append ( "[Width is <= 0] ");
if (cInfo.Dimension.Height <= 0) sbDetail.Append ( "[Height is <= 0]") ;
faultEx.Detail = sbDetail.ToString();
throw new FaultException<CuboidFaultException>(faultEx);
}
CuboidDetail dInfo = new CuboidDetail();
dInfo.ID = cInfo.ID;
dInfo.Volume = (cInfo.Dimension.Length * cInfo.Dimension.Width * cInfo.Dimension.Height);
dInfo.SurfaceArea = 2 * ((cInfo.Dimension.Length * cInfo.Dimension.Width) +
(cInfo.Dimension.Width * cInfo.Dimension.Height) +
(cInfo.Dimension.Length * cInfo.Dimension.Height));
return dInfo;
}
Explanation
Operation takes a DataContrat (CuboidInfo) type parameter, Method checks whether that ID and attributes (length, width, height) of the cuboid is not zero or negative. if yes then simply creates and raises a FaultException of type CuboidFaultExeception, assigning the values for the field of CuboidFaultExeception. and if everything is in place, calculates the desired values, creates a new DataContract object (CuboidDetail) and assigns its field and returns that object.
Implementing method CalculateDetails3
public CuboidDetail CalculateDetails3(int nID, CuboidDimension cDims)
{
if ((nID <=0) || (cDims.Length <= 0) || (cDims.Width <= 0) || (cDims.Height <= 0))
{
CuboidFaultException faultEx = new CuboidFaultException();
faultEx.Reason = "Any dimension of Cuboid (length/width/height) can not be zero or negative value";
faultEx.Source = "CalculateDetails3()";
faultEx.HelpLink = "http://praveenkatiyar.wordpress.com";
StringBuilder sbDetail = new StringBuilder("");
if (nID <= 0) sbDetail.Append ( "[ID is <= 0] ");
if (cDims.Length <= 0) sbDetail.Append("[Length is <= 0] ");
if (cDims.Width <= 0) sbDetail.Append("[Width is <= 0] ");
if (cDims.Height <= 0) sbDetail.Append("[Height is <= 0]");
faultEx.Detail = sbDetail.ToString();
throw new FaultException<CuboidFaultException>(faultEx);
}
CuboidDetail dInfo = new CuboidDetail();
dInfo.ID = nID;
dInfo.Volume = (cDims.Length * cDims.Width * cDims.Height);
dInfo.SurfaceArea = 2 * ((cDims.Length * cDims.Width) + (cDims.Width * cDims.Height) + (cDims.Length * cDims.Height));
return dInfo;
}
Explanation
Operation takes two parameters,
- First parameter is an (integer) ID of the Cuboid
- Second parameter is an object of DataContract CuboidDimension, this demonstrates, that when using DataContract, you can pass more than one parameter.
Operation checks whether that ID and attributes (length, width, height) of the cuboid is not zero or negative. if yes then simply creates and raises a FaultException of type CuboidFaultExeception, assigning the values for the field of CuboidFaultExeception. and if everything is in place, calculates the desired values, creates a new DataContract object (CuboidDetail) and assigns its field and returns that object.
build the project (library), this complete the first module.
Second Module: Host Application (ContractLibHost.exe)
I have used self hosting in this project, To create this project, you can simply take a "Console Application" project, while choosing from project wizard option. let’s name it "ContractLibHost
", this application will self host the service. let us do some house keeping, before we write any code for the host application
Adding application configuration
- Add an Application configuration (App.config) file to the project.
Defining configuration
before we write any code for the host, let’s define the configuration.
Assumption
The Host application will expose the following endpoints for the service.
- CuboidService will expose HTTP endpoint at Port 9011
- Corresponding mex End Point (IMetadatExchange) for the HTTP end point.
Defining configuration for CuboidService
Defining Endpoints
<service name="ContractLib.CuboidService" behaviorConfiguration="CuboidServiceBehavior">
<host>
<baseAddresses>
<add baseAddress="http://localhost:9011/CuboidService"/>
</baseAddresses>
</host>
<endpoint address="http://localhost:9011/CuboidService" binding="wsHttpBinding" contract="ContractLib.ICuboidService"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
Explanation
The service defines, one endpoint with wsHttpBinding, at 9011 another endpoint is defined as mex, are defined for publication of metadata, IMetadataExchange is standard contract for mex endpoints.
Defining Behavior
<behaviors>
<serviceBehaviors>
<!– CalcService Behavior –>
<behavior name="CuboidServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true "/>
</behavior>
</serviceBehaviors>
</behaviors>
Explanation
service behaviour defines two attributes,
<serviceMetadata httpGetEnabled="true" />
Gets or sets a value that indicates whether to publich service medtadata for retrieval using an HTTP/GET request, true means yes, metadata is available for retrieval using a HTTP/GET request.
<serviceDebug includeExceptionDetailInFaults="true "/>
Set IncludeExceptionDetailsInFaults
to true
to enable clients to obtain information about internal service method exceptions; it is only recommended as a way of temporarily debugging a service application. this property must be set to false on production servers.
Hosting Service
As the configuration for the service has been defined, before we write any code
- add reference of System.ServiceModel to the project.
Let’s write code to host the service.
try
{
ServiceHost host = new ServiceHost(typeof(ContractLib.CuboidService));
host.Open();
Console.WriteLine("Service is Hosted as http://localhost:9011/CuboidService");
Console.WriteLine("\nPress key to stop the service.");
Console.ReadKey();
host.Close();
}
catch (Exception eX)
{
Console.WriteLine("There was en error while Hosting Service [" + eX.Message + "]");
Console.WriteLine("\nPress key to close.");
Console.ReadKey();
}
Explanation
ServiceHost class provides a host for services.
Build and Execute the Host.
Open a command prompt in administrator mode (Run as Administrator) and execute the host. here is the output.
Creating Client (ContractClient.exe)
Creating the base project
Take a console based application. name it ContractClient.
Generating proxies
While the host application (ContractLibHost.exe) is running, open another command prompt,
- switch to folder, where Client Project is created, let say (E:\ContractClient)
- Issue the following command to generate the proxy for the service.
SvcUtil http: <return>
this command will generate two files.
- CuboidServiceProxy.cs
- App.Config
Add these two files to the client project.
before we write any code, let’s examine the app.config file.
="1.0"="utf-8"
<configuration>
<system.serviceModel>
<bindings>
. . . .
</bindings>
<client>
. . . .
</client>
</system.serviceModel>
</configuration>
The binding section holds the collection of standard and custom bindings. Each entry is a binding element that can be identified by its unique name.
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_ICuboidService" />
</wsHttpBinding>
</bindings>
The client section contains a list of endpoints a client uses to connect to a service.
<client>
<endpoint address="http://localhost:9011/CuboidService" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_ICuboidService" contract="CuboidServiceReference.ICuboidService" name="WSHttpBinding_ICuboidService">
<identity>
<userPrincipalName value="PRAVEEN-WIN7\BriskTech" />
</identity>
</endpoint>
</client>
Note:
<userPrincipalName value="PRAVEEN-WIN7\BriskTech" />
will be different on your machine.
Calling the service, using the Proxy
Declare some temporary variable ro read the input.
int nID = 0;
int nLength = 0;
int nWidth = 0;
int nHeight = 0;
Read ID, length, wdith and height of the quboid.
Console.WriteLine();
Console.Write("Enter ID : ");
string strID = Console.ReadLine();
bool bID = int.TryParse(strID, out nID);
Console.WriteLine();
Console.Write("Enter Length : ");
string strLength = Console.ReadLine();
bool bLength = int.TryParse(strLength, out nLength);
Console.WriteLine();
Console.Write("Enter Width : ");
string strWidth = Console.ReadLine();
bool bWidth = int.TryParse(strWidth, out nWidth);
Console.WriteLine();
Console.Write("Enter Height : ");
string strHeight = Console.ReadLine();
bool bHeight = int.TryParse(strHeight, out nHeight);
Make sure that all inputs are valid, if not then quit.
bool bValid = ((bID) && (bWidth) && (bLength) && (bHeight));
if (bValid == false )
{
if (bID == false) Console.WriteLine("ID should be a positive numeric value");
if (bLength == false) Console.WriteLine("Length should be a positive numeric value");
if (bWidth == false) Console.WriteLine("Width should be a positive numeric value");
if (bHeight == false) Console.WriteLine("Height should be a positive numeric value");
return ;
}
Create Service proxy object, quit if failed.
CuboidServiceClient objClient = null ;
try
{
objClient = new CuboidServiceClient ();
}
catch (Exception eX)
{
objClient = null;
Console.WriteLine("\nFailed while creating Service proxy [" + eX.Message + "]" );
return;
}
Call Method 1, using Message Contract
enclose every thing in try block.
try {
Create object to hold the request parameter, CuboidInfoRequest is defined as Message Contract
CuboidInfoRequest msgRequest = new CuboidInfoRequest();
msgRequest. Dimension = new CuboidDimension ();
assign input values to request object
msgRequest.ID = nID;
msgRequest.Dimension.Length = nLength;
msgRequest.Dimension.Width = nWidth;
msgRequest.Dimension.Height = nHeight;
Display input values, packed in message request.
Console.WriteLine("\nCalling Method : CalculateDetails1");
Console.WriteLine("Message Request");
Console.WriteLine("Cuboid ID ............. : {0:D2}", msgRequest.ID);
Console.WriteLine("Cuboid Length ......... : {0:F2}", msgRequest.Dimension.Length);
Console.WriteLine("Cuboid Width .......... : {0:F2}", msgRequest.Dimension.Width);
Console.WriteLine("Cuboid Height ......... : {0:F2}", msgRequest.Dimension.Height);
Create an object to hold the Message Response.
CuboidDetailResponse has been defined as
MessageContract
CuboidDetailResponse msgResponse = new CuboidDetailResponse ();
msgResponse.SurfaceArea = objClient.CalculateDetails1(ref msgRequest.ID, msgRequest.Dimension, out msgResponse.Volume);
Display Output of first service method.
Console.WriteLine("\nMessage Response");
Console.WriteLine("Total Surface Area .. : {0:F2}", msgResponse.SurfaceArea);
Console.WriteLine("Total Volume ........ : {0:F2}", msgResponse.Volume);
If something goes wrong at the service side, or you passed 0 or -1 as any of the dimension, display information sent by service, remember service has implemented FaultContract
catch (FaultException<CuboidFaultException> eX)
{
Console.WriteLine("\nThere was an error while fetching details . . .");
Console.WriteLine("Reason .............. : " + eX.Detail.Reason);
Console.WriteLine("Source .............. : " + eX.Detail.Source);
Console.WriteLine("Details ............. : " + eX.Detail.Detail);
Console.WriteLine("Help Link ........... : " + eX.Detail.HelpLink);
}
Call Method 2, using Data Contract
enclose every thing in try block.
try {
Create object to hold the input values, CuboidInfo is defined as Data Contract
CuboidInfo dataRequest = new CuboidInfo();
dataRequest.Dimension = new CuboidDimension ();
assign input values to input data object
dataRequest.ID = nID;
dataRequest.Dimension.Length = nLength;
dataRequest.Dimension.Width = nWidth;
dataRequest.Dimension.Height = nHeight;
Display input values, packed in a DataContract object.
Console.WriteLine("\nCalling Method : CalculateDetails2");
Console.WriteLine("Input Data ");
Console.WriteLine("Cuboid ID ............. : {0:D2}", dataRequest.ID);
Console.WriteLine("Cuboid Length ......... : {0:F2}", dataRequest.Dimension.Length);
Console.WriteLine("Cuboid Width .......... : {0:F2}", dataRequest.Dimension.Width);
Console.WriteLine("Cuboid Height ......... : {0:F2}", dataRequest.Dimension.Height);
Create an object to hold the Data Contract object returned by method
CalculateDetails2,
CuboidDetail has been defined as
DataContract.
CuboidDetail dataResponse = new CuboidDetail();
dataResponse = objClient.CalculateDetails2(dataRequest);
Display Output of second service method.
Console.WriteLine("\nData Returned");
Console.WriteLine("Total Surface Area .. : {0:F2}", dataResponse.SurfaceArea);
Console.WriteLine("Total Volume ........ : {0:F2}", dataResponse.Volume);
If something goes wrong at the service side, or you passed 0 or -1 as any of the dimension, display fault information sent by service, remember service has implemented FaultContract
catch (FaultException<CuboidFaultException> eX)
{
Console.WriteLine("\nThere was an error while fetching details . . .");
Console.WriteLine("Reason .............. : " + eX.Detail.Reason);
Console.WriteLine("Source .............. : " + eX.Detail.Source);
Console.WriteLine("Details ............. : " + eX.Detail.Detail);
Console.WriteLine("Help Link ........... : " + eX.Detail.HelpLink);
}
Call Method 3, using Data Contract
enclose every thing in try block.
try {
Create object to hold the input values, CuboidInfo is defined as Data Contract
CuboidDimension dimsCubiod = new CuboidDimension();
assign input values to input data object
dimsCubiod.Length = nLength;
dimsCubiod.Width = nWidth;
dimsCubiod.Height = nHeight;
Display input values, packed in a DataContract object.
Console.WriteLine("\nCalling Method : CalculateDetails3");
Console.WriteLine("Input Data ");
Console.WriteLine("Cuboid ID ............. : {0:D2}", nID);
Console.WriteLine("Cuboid Length ......... : {0:F2}", dimsCubiod.Length);
Console.WriteLine("Cuboid Width .......... : {0:F2}", dimsCubiod.Width);
Console.WriteLine("Cuboid Height ......... : {0:F2}", dimsCubiod.Height);
Create an object to hold the Data Contract object returned by method
CalculateDetails3,
CuboidDetail has been defined as
DataContract
CuboidDetail dataResponse = new CuboidDetail();
dataResponse = objClient.CalculateDetails3(nID, dimsCubiod);
Display Output of second service method.
Console.WriteLine("\nData Returned");
Console.WriteLine("Total Surface Area .. : {0:F2}", dataResponse.SurfaceArea);
Console.WriteLine("Total Volume ........ : {0:F2}", dataResponse.Volume);
If something goes wrong at the service side, or you passed zero or negative value, as any of the dimension, display fault information sent by service, remember service has implemented FaultContract
catch (FaultException<CuboidFaultException> eX)
{
Console.WriteLine("\nThere was an error while fetching details . . .");
Console.WriteLine("Reason .............. : " + eX.Detail.Reason);
Console.WriteLine("Source .............. : " + eX.Detail.Source);
Console.WriteLine("Details ............. : " + eX.Detail.Detail);
Console.WriteLine("Help Link ........... : " + eX.Detail.HelpLink);
}
Build and execute the client, here is the output for different inputs.
Output
Output when you provide all valid inputs.
Output with one invalid input.
With two invalid inputs.
Creating Web Client
Creating the base project
Create a web site.
Generating proxies
while the host application is running, right click on the client application project, right click on the website
Add Service Reference
in the address bar type the address of the mex endpoint address of CuboidService as shown below.
Designing UI for web client
Add some controls to the page as shown below.
List of controls, and their properties to be altered, are given below.
ID | Type | Text |
lblID | Label | ID : |
txtID | TextBox | |
lblLength | Label | Length : |
txtLength | TextBox | |
lblWidth | Label | Width: |
txtWidth | TextBox | |
lblHeight | Label | Height : |
txtHeight | TextBox | |
btnCalc1 | Button | CalculateDetails1 |
btnCalc2 | Button | CalculateDetails2 |
btnCalc3 | Button | CalculateDetails3 |
lblMethod | Label | |
lblArea | Label | |
lblVolume | Label | |
lblStatus | Label | |
Writing event for Page_Load
once all the controls has been added, let's write the Page_Load handler for the web page, it is useful, when you want to initialize controls, with some initial values. double click on the page, IDE will add an handler for the Page_Load event, and open it, below is the code.
protected void Page_Load(object sender, EventArgs e)
{
if (!(Page.IsPostBack))
{
txtID.Text = "1";
txtLength.Text = "10";
txtWidth.Text = "20";
txtHeight.Text = "30";
Session["ID"] = "1";
Session["Length"] = "10";
Session["Width"] = "20";
Session["Height"] = "30";
}
}
Explanation
On Page_Load, if it is loaded, the first time (not because of a postback), we initialize the controls with some initial values. and store the, same values to session, so that we can initialize controls from the values stored in Session, during postbacks.
Writing helper function to make life easy
before we write, code for click event of buttons, lets write 2 helper functions, first function is ParseAndValidate function as shown below.
protected bool ParseAndValidateInputs(ref int nID, ref int nLength, ref int nWidth, ref int nHeight)
{
bool bID = int.TryParse(txtID.Text, out nID);
bool bLength = int.TryParse(txtLength.Text, out nLength);
bool bWidth = int.TryParse(txtWidth.Text, out nWidth);
bool bHeight = int.TryParse(txtHeight.Text, out nHeight);
bool bValid = ((bID) && (bWidth) && (bLength) && (bHeight));
if (bValid == false)
{
StringBuilder sbTmp = new StringBuilder();
sbTmp.Append("<Font Color=#ff0000>");
if (bID) sbTmp.Append("<p>ID should be a positive numeric value");
if (bLength) sbTmp.Append("<p>Length should be a positive numeric value");
if (bWidth) sbTmp.Append("<p>Width should be a positive numeric value");
if (bHeight) sbTmp.Append("<p>Height should be a positive numeric value");
sbTmp.Append("</Font>");
lblStatus.Text = sbTmp.ToString();
return false ;
}
return true ;
}
Explanation
validates the values entered in the page controls, uses the standard library function TryParse to parse a numeric value from a string. values are stored in the ref variable passed through the function. in case of non-numeric input, function returns false, and displays the cause to the Label (lblStatus) , assigned for Status.
Second function creates the Service Proxy object, and returns the newly created object, as shown below.
protected CuboidServiceClient GetServiceClientObject()
{
CuboidServiceClient objClient = null;
try
{
objClient = new CuboidServiceClient();
}
catch (Exception eX)
{
objClient = null;
lblStatus.Text = "<Font color=#ff0000>Failed while creating Service proxy ..... </font>";
}
return objClient ;
}
Explanation
Simply creates an object of Service proxy, and returns the new created object, if the creation failed, then cause of the error is shown to the Label (lblStatus) , assigned for Status Display. and null is returned.
Calling Method CalculateDetails1
This methods will be called when Button btnCalc1 is clicked. here is the handler for the same. in this event, we simply parse the inputs, entered through the web page controls, and call the method. below is the breakup of the event handler
protected void btnCalc1_Click(object sender, EventArgs e)
{
declare variables to hold different values, and parse the input from the controls on page (ID, Length, Width and Height). return if invaid (non-numeric values are entered, 0 and -1 is allowed).
int nID=0, nLength=0, nWidth=0, nHeight=0;
if (ParseAndValidateInputs ( ref nID, ref nLength, ref nWidth, ref nHeight ) == false) return;
you got permissible values, so store them to session, it is useful during postback you can initialize your controls, with the values stored in the session,
Session["ID"] = nID.ToString () ;
Session["Length"] = nLength .ToString ();
Session["Width"] = nWidth .ToString ();
Session["Height"] = nHeight.ToString () ;
Create Service proxy object, if object creation failed, then return;
CuboidServiceClient objClient = GetServiceClientObject();
if (objClient == null) return;
once the input has been collected, and the proxy object is ready, its time to call the service. Enclose every thing in the try block, for error trapping, and as well as catching FaultException thrown by the service.
try
{
Create MessageRequest object, and assign input values to the request.
cuboidInfoRequest msgRequest = new CuboidInfoRequest ();
msgRequest.Dimension = new CuboidDimension();
msgRequest.ID = nID ;
msgRequest.Dimension.Length = nLength ;
msgRequest.Dimension.Width = nWidth ;
msgRequest.Dimension.Height = nHeight ;
allocate space for Message Response object. call the service function and save the response.
CuboidDetailResponse msgResponse = new CuboidDetailResponse ();
msgResponse.SurfaceArea = objClient.CalculateDetails1 ( ref msgRequest.ID, msgRequest.Dimension, out msgResponse.Volume );
Display Response output to designated controls.
lblMethod.Text = "<Font color=#0000ff>Method Called ..... : CalculateDetails1 </font>";
lblStatus.Text = "<font Color=#008000>Status : Success </font>";
lblArea.Text = "<Font color=#000080>Total Surface Area .. : " + msgResponse.SurfaceArea.ToString ("F2") + "</font>" ;
lblVolume.Text = "<Font color=#000080>Total Volume .. : " + msgResponse.Volume.ToString ("F2") + "</font>" ;
Close the try block.
}
See, if you got any error (CuboidFaultException) from the service.
catch (FaultException<CuboidFaultException> eX)
{
lblMethod.Text = "<Font color=#ff0000>Method Called ..... : CalculateDetails1 </font>";
StringBuilder sbErr = new StringBuilder ("<font Color=#ff0000>Status : Error ");
sbErr .Append ( "<br>Reason : " ); sbErr.Append ( eX.Detail.Reason );
sbErr .Append ( "<br>Source : " ); sbErr.Append ( eX.Detail.Source );
sbErr .Append ( "<br>Details : " ); sbErr.Append ( eX.Detail.Detail );
sbErr .Append ( "<br>HelpLink : " ); sbErr.Append ( eX.Detail.HelpLink );
sbErr .Append ( "</Font>");
lblStatus.Text = sbErr.ToString(); ;
lblArea.Text = "" ;
lblVolume.Text = "";
}
Explanation
if you received the CuboidFaultException, then it contains all the information, about which client might be interested. here you simply display that information on the label, designated for the Status.
Calling Method CalculateDetails2
This methods will be called when button btnCalc2 is clicked. here is the handler for the same. in this event, we simply parse the inputs, entered through the web page controls, and call the method. below is the breakup of the event handler.
protected void btnCalc2_Click(object sender, EventArgs e)
{
declare variables to hold different values, and parse the input from the controls on page (ID, Length, Width and Height). return if invaid (non-numeric values are entered), 0 and -1 is allowed.
int nID=0, nLength=0, nWidth=0, nHeight=0;
if (ParseAndValidateInputs ( ref nID, ref nLength, ref nWidth, ref nHeight ) == false) return;
you got permissible values, so store them to session, it is useful during postback, as you can initialize your controls, with the values stored in the postback.
Session["ID"] = nID.ToString () ;
Session["Length"] = nLength .ToString ();
Session["Width"] = nWidth .ToString ();
Session["Height"] = nHeight.ToString () ;
Create Service proxy object, if object creation failed, then return;
CuboidServiceClient objClient = GetServiceClientObject();
if (objClient == null) return;
once the input has been collected, and the proxy object is ready, its time to call the service. Enclose every thing in the try block, for error trapping, and as well as catching
FaultException thrown by the service.
try
{
enclose every thing in the try block.
CuboidInfo dataRequest = new CuboidInfo();
dataRequest.Dimension = new CuboidDimension();
dataRequest.ID = nID;
dataRequest.Dimension.Length = nLength;
dataRequest.Dimension.Width = nWidth;
dataRequest.Dimension.Height = nHeight;
Collect the return object.
CuboidDetail dataResponse = new CuboidDetail();
dataResponse = objClient.CalculateDetails2(dataRequest);
Display values in returned object, at designated labels.
lblMethod.Text = "<Font color=#0000ff>Method Called ..... : CalculateDetails2 </font>"
lblArea.Text = "<Font color=#000080>Total Surface Area .. : " + dataResponse.SurfaceArea.ToString("F2") + "</font>";
lblVolume.Text = "<Font color=#000080>Total Volume .. : " + dataResponse.Volume.ToString("F2") + "</font>";
lblStatus.Text = "<font Color=#008000>Status : Success </font>";
}
Close the try block, and see, if the service has thrown CuboidFaultException, if yes then simply display the details of CuboidFaultException on the label labels designated for status, other labels designated for displaying output values (surface area, volume) are cleared.
catch (FaultException< CuboidFaultException> eX)
{
lblMethod.Text = "<Font color=#ff0000>Method Called ..... : CalculateDetails2 </font>";
StringBuilder sbErr = new StringBuilder("<font Color=#ff0000>Status : Error ");
sbErr.Append("<br>Reason : "); sbErr.Append(eX.Detail.Reason);
sbErr.Append("<br>Source : "); sbErr.Append(eX.Detail.Source);
sbErr.Append("<br>Details : "); sbErr.Append(eX.Detail.Detail);
sbErr.Append("<br>HelpLink : "); sbErr.Append(eX.Detail.HelpLink);
sbErr.Append("</Font>");
lblArea.Text = "";
lblVolume.Text = "";
lblStatus.Text = sbErr.ToString();
}
Displays the CuboidFaultException details on the label designated for status, labels designated for displaying output values (surface area, volume) are cleared.
Calling Method CalculateDetails3
This methods will be called when button
btnCalc3 is clicked. here is the handler for the same. in this event, we simply parse the inputs, entered through the web page controls, and call the method. below is the breakup of the event handler
protected void btnCalc3_Click(object sender, EventArgs e)
{
declare variables to hold different values, and parse the input from the controls on page (ID, Length, Width and Height). return if invaid (non-numeric values are entered), 0 and -1 is allowed).
int nID=0, nLength=0, nWidth=0, nHeight=0;
if (ParseAndValidateInputs ( ref nID, ref nLength, ref nWidth, ref nHeight ) == false) return;
you got permissible values, so store them to session, it is useful, during postback you can initialize your controls, with the values stored in the session,
Session["ID"] = nID.ToString () ;
Session["Length"] = nLength .ToString ();
Session["Width"] = nWidth .ToString ();
Session["Height"] = nHeight.ToString () ;
Create Service proxy object, if object creation failed, then return;
CuboidServiceClient objClient = GetServiceClientObject();
if (objClient == null) return;
Once the input has been collected, and the proxy object is ready, its time to call the service. Enclose every thing in the try block, for error trapping, and as well as catching FaultException thrown by the service.
try
{
Create CuboidDimension object, assign input values to the object, to be sent as input parameter.
CuboidDimension dimData = new CuboidDimension();
dimData.Length = nLength;
dimData.Width = nWidth;
dimData.Height = nHeight;
Create object to collect the value from the object returned from service.
CuboidDetail dataResponse = new CuboidDetail();
dataResponse = objClient.CalculateDetails3(nID, dimData);
Display output on designated labels.
lblMethod.Text = "<Font color=#0000ff>Method Called ..... : CalculateDetails3 </font>";
lblArea.Text = "<Font color=#000080>Total Surface Area .. : " + dataResponse.SurfaceArea.ToString("F2") + "</font>";
lblVolume.Text = "<Font color=#000080>Total Volume .. : " + dataResponse.Volume.ToString("F2") + "</font>";
lblStatus.Text = "<font Color=#008000>Status : Success </font>";
Close the try block.
}
Handle the CuboidFaultException in case something is wrong at the service side. and display the field values of CuboidFaultExcepti on the label designated for status in red color.
catch (FaultException<CuboidFaultException> eX)
{
lblMethod.Text = "<Font color=#ff0000>Method Called ..... : CalculateDetails3 </font>";
StringBuilder sbErr = new StringBuilder("<font Color=#ff0000>Status : Error ");
sbErr.Append("<br>Reason : ");
sbErr.Append(eX.Detail.Reason);
sbErr.Append("<br>Source : ");
sbErr.Append(eX.Detail.Source);
sbErr.Append("<br>Details : ");
sbErr.Append(eX.Detail.Detail);
sbErr.Append("<br>HelpLink : ");
sbErr.Append(eX.Detail.HelpLink);
sbErr.Append("</Font>");
lblArea.Text = "";
lblVolume.Text = "";
lblStatus.Text = sbErr.ToString();
}
Output
here is the output of the web based client for CuboidService,
when all inputs are valid, and CalculateDetails1 is called.
when all inputs are valid, and CalculateDetails2 is called.
when all inputs are valid, and CalculateDetails3 is called.
when one ore more inputs are invalid, and CalculateDetails1 is called.
when all inputs are valid, and CalculateDetails2 is called.
when all inputs are valid, and CalculateDetails3 is called.
Points of Interest
In this article i have described, all three contract types in a very simple manner, i have also explained, the suitability of each contract type.
when you should use I hope you will enjoy reading it as much as I enjoyed writing it.
thanks alot for reading.
History
Initial version.