Sunday, March 16, 2008

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

No comments: