Introduction
In this article, I am going to explain how to host a WCF library in a Windows service.
Background
There are several ways to host a WCF service library (IIS, Windows Service, Self Hosting), Windows Service is one of them. Windows service hosting option is suitable for a long-running WCF service hosted outside of IIS in a secure environment that is not message activated. The lifetime of the service is controlled instead by the operating system.
Using the Code
This article has been divided into 4 modules:
- WCF Service Library (WCFCalcLib.dll): Actual Service logic, which defines a Service Contract Interface,
OperationContract
, and implements them, and exposes few functions to the world to use them - Windows Service To Host the WCF Service Library WinSvcHostedCalcService.exe): Host the WCF library
- Console Client Application (CalcClient.exe): Client Application which will use this service
- Web based Client Application (CalcWebClient.exe): Web Application which will use this service
First Module: WCF Service Library (WCFCalcLib.dll)
To create this project, you can simply take a "Class Library" project, while choosing from project wizard option. let's name it "WCFCalcLib
", it is the actual service which implements the business logic. The project already contains a file Class1.cs, rename that file as CalcService.cs, add one more file (ICalcService.cs) to define the interface, although you can define interface in this file also.
Definition of Service Contract
[ServiceContract]
public interface ICalcService
{
[OperationContract]
double Add(double dblNum1, double dblNum2);
[OperationContract]
double Subtract(double dblNum1, double dblNum2);
[OperationContract]
double Multiply(double dblNum1, double dblNum2);
[OperationContract]
double Divide(double dblNum1, double dblNum2);
}
Explanation
Interface simply defines 4 operations supported by this service.
Implementation of Service Contract
public class CalcService : ICalcService
{
public double Add(double dblNum1, double dblNum2)
{
return (dblNum1 + dblNum2);
}
public double Subtract(double dblNum1, double dblNum2)
{
return (dblNum1 - dblNum2);
}
public double Multiply(double dblNum1, double dblNum2)
{
return (dblNum1 * dblNum2);
}
public double Divide(double dblNum1, double dblNum2)
{
return ((dblNum2 == 0) ? 0 : (dblNum1 / dblNum2));
}
}
ServiceContract Attribute
Service Contract describes which related operations can be tied together as a single functional unit that the client can perform on the service.
OperationContract Attribute
An Operation contract specifies that the said operation is exposed by the service, service defines the parameters and return type of an operation.
As one can see, there is nothing special here, just a Service Contract definition and its implementation.
Second Module: WCF Service Library (WinSvcHostedCalcService.exe)
To create this project, you can simply take a "Windows Service", while choosing from project wizard option. let's name it "WinSVCHostedCalcService
". This module is a Windows service, which is going to host our WCF Library (business logic), providing endpoints to the outside world to use WCF library. In this module at the start of the service, an infrastructure will be created to expose the service, and when the service is stopped, all objects are disposed.
Little housekeeping
Rename Service1.cs as MyCalcWinService.cs.
Add a member variable ServiceHost
type. As the name implies, ServiceHost
provides a host for services. Name it, for instance m_svcHost
.
private ServiceHost m_svcHost = null ;
Implement the OnStart ()
and OnStop ()
handler for the service.
Creating end points when Service is started
protected override void OnStart(string[] args)
{
if (m_svcHost != null) m_svcHost.Close();
string strAdrHTTP = "http://localhost:9001/CalcService";
string strAdrTCP = "net.tcp://localhost:9002/CalcService";
Uri[] adrbase = { new Uri(strAdrHTTP), new Uri(strAdrTCP) };
m_svcHost = new ServiceHost(typeof(WCFCalcLib.CalcService), adrbase);
ServiceMetadataBehavior mBehave = new ServiceMetadataBehavior();
m_svcHost.Description.Behaviors.Add(mBehave);
BasicHttpBinding httpb = new BasicHttpBinding();
m_svcHost.AddServiceEndpoint(typeof(WCFCalcLib.ICalcService), httpb, strAdrHTTP);
m_svcHost.AddServiceEndpoint(typeof(IMetadataExchange),
MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
NetTcpBinding tcpb = new NetTcpBinding();
m_svcHost.AddServiceEndpoint(typeof(WCFCalcLib.ICalcService), tcpb, strAdrTCP);
m_svcHost.AddServiceEndpoint(typeof(IMetadataExchange),
MetadataExchangeBindings.CreateMexTcpBinding(), "mex");
m_svcHost.Open();
}
Explanation
The OnStart()
handler is called, when the said service is started (through service control panel, or command line or otherwise), it does the following things.
Defines two end points.
string strAdrHTTP = "http://localhost:9001/CalcService";
string strAdrTCP = "net.tcp://localhost:9002/CalcService";
Creates and initializes a new instance of ServiceHost
class, with the instance of the service (WCFCalcLib.CalcService
) and its base addresses (adrbase
) specified.
Uri[] adrbase = { new Uri(strAdrHTTP), new Uri(strAdrTCP) };
m_svcHost = new ServiceHost(typeof(WCFCalcLib.CalcService), adrbase)
Adding Service Behavior
ServiceMetadataBehavior
class controls the publication of service metadata and associated information, although.
ServiceMetadataBehavior mBehave = new ServiceMetadataBehavior();
m_svcHost.Description.Behaviors.Add(mBehave);
Adding service endpoints to the hosted service
Add Basic TCP end point
NetTcpBinding tcpb = new NetTcpBinding();
m_svcHost.AddServiceEndpoint(typeof(WCFCalcLib.ICalcService), tcpb, strAdrTCP);
Add Basic HTTP end point
BasicHttpBinding httpb = new BasicHttpBinding();
m_svcHost.AddServiceEndpoint(typeof(WCFCalcLib.ICalcService), httpb, strAdrHTTP);
Adding just behavior to the service is not sufficient for publication of metadata, you must add an IMetadataExchange
endpoint to your service for each supported binding type.
m_svcHost.AddServiceEndpoint(typeof(IMetadataExchange),
MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
m_svcHost.AddServiceEndpoint(typeof(IMetadataExchange),
MetadataExchangeBindings.CreateMexTcpBinding(), "mex");
Finally open the host, and we are ready to go.
m_svcHost.Open();
Disposing, when Service is Stopped
protected override void OnStop()
{
if (m_svcHost != null)
{
m_svcHost.Close();
m_svcHost = null;
}
}
When the service is stopped either by service console/command prompt, or by any other means, Service host is closed, and object is assigned null
.
Adding an installer to the Service
open the MathWndowsService in design mode, by right clicking on the MyWinCalcService.cs node. right click on the blank space on the designer view of the service, and select the Add Installer from the context menu. as shown below.
adding an installer will create an installer class named as ProjectInstaller, which in turn also create dependancy files ProjectInstaller.Designer.cs and ProjectInstaller.resx, just do little house keeping to get things in line,
select rename from the shortcut menu, for ProjectInstaller.cs to rename the installer class as CalcServiceInstaller.cs.
now switch to the code view of the installer class. and add the following code to the contructor of the installer class, which is basically setting the properties of the service.
public CalcServiceInstaller()
{
serviceProcessInstaller1 = new ServiceProcessInstaller();
serviceProcessInstaller1.Account = ServiceAccount.LocalSystem;
serviceInstaller1 = new ServiceInstaller();
serviceInstaller1.ServiceName = "WinSvcHostedCalcService";
serviceInstaller1.DisplayName = "WinSvcHostedCalcService";
serviceInstaller1.Description = "WCF Calc Service Hosted by Windows NT Service";
serviceInstaller1.StartType = ServiceStartMode.Automatic;
Installers.Add(serviceProcessInstaller1);
Installers.Add(serviceInstaller1);
}
build the windows service.
Installing the Service
Windows service needs to be installed, and should be running, you can install the service using InstallUtil.exe utility. This utility is part of the SDK, the path is set, when you run this utility through Visual Studio command prompt.
Open Visual Studio Command Prompt through:
Start -> All Programs -> Microsoft Visual Studio 2010 -> Visual Studio Tools -> Visual Studio Command Prompt
Switch to the folder, where WinSvcHostedCalcService.exe has been built, or preferably where you want to deploy it.
Issue the following command to Install the service:
Command Prompt > InstallUtil WinSvcHostedCalcService.exe <return>
Starting the Service
You can start the service using command prompt using the following command:
Command Prompt > sc start WinSvcHostedCalcService <return>
Another way to start the service is to:
Computer -> Manage -> Services and Applications -> Services
Locate the Service named as WinSvcHostedCalcService
, right click and select Start.
and should be running, you can install the service using InstallUtil.exe utility. This utility is the part of the SDK, the path is set, when you run this utility through Visual Studio.
Third Module: Console Client Application (CalcClient.exe)
Once the service has been built, and deployed, it is time to create the client. I have chosen the console based application, to keep things simple. Select the project template "Console Application", name it CalcClient
.
Generating the Service Proxy
To use the service, we have to generate the proxy. Switch to the folder where client project has been created. Open a command prompt and issue a command to generate the proxy.
Command Prompt > SvcUtil http://localhost:9001/CalcService /out:CalcServiceProxy.cs /config:App.config <return>
Two files - a Service Proxy file and a client configuration file is generated.
- App.config
- CalcServiceProxy.cs
Add these to files to the newly generated client project. When we closely examine the configuration file, we find the following sections:
="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>
<basicHttpBinding>
<binding name="BasicHttpBinding_ICalcService" />
</basicHttpBinding>
<netTcpBinding>
<binding name="NetTcpBinding_ICalcService" />
</netTcpBinding>
</bindings>
The client section contains a list of endpoints a client uses to connect to a service.
<client>
<endpoint address="http://localhost:9001/CalcService"
binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_ICalcService"
contract="ICalcService"
name="BasicHttpBinding_ICalcService" />
<endpoint address="net.tcp://localhost:9002/CalcService"
binding="netTcpBinding"
bindingConfiguration="NetTcpBinding_ICalcService"
contract="ICalcService"
name="NetTcpBinding_ICalcService">
<identity>
<servicePrincipalName value="host/PRAVEEN-WIN7" />
</identity>
</endpoint>
</client>
The client section contains a list of endpoints a client uses to connect to a service.
Note: Identity might be different on your machine.
Calling the service, using the Proxy
Open programs.cs file of the client project and write code to use the service, using the generated proxy class, as shown below:
Listing of Client Program (Program.cs):
using System;
using System.Text;
namespace CalcClient
{
class Program
{
static void Main(string[] args)
{
double dblX = 2000.0;
double dblY = 100.0;
double dblResult = 0;
try
{
Console.WriteLine("Using TCP Binding", dblX, dblY, dblResult);
CalcServiceClient objCalcClient1 = new CalcServiceClient
("NetTcpBinding_ICalcService");
dblResult = objCalcClient1.Add(dblX, dblY);
Console.WriteLine("Calling Add >> X : {0:F2} Y : {1:F2}
Result : {2:F2}", dblX, dblY, dblResult);
dblResult = objCalcClient1.Subtract(dblX, dblY);
Console.WriteLine("Calling Sub >> X : {0:F2} Y : {1:F2}
Result : {2:F2}", dblX, dblY, dblResult);
dblResult = objCalcClient1.Multiply(dblX, dblY);
Console.WriteLine("Calling Mul >> X : {0:F2} Y : {1:F2}
Result : {2:F2}", dblX, dblY, dblResult);
dblResult = objCalcClient1.Divide(dblX, dblY);
Console.WriteLine("Calling Sub >> X : {0:F2} Y : {1:F2}
Result : {2:F2}", dblX, dblY, dblResult);
Console.WriteLine("Using Basic HTTP Binding", dblX, dblY, dblResult);
CalcServiceClient objCalcClient2 = new CalcServiceClient
("BasicHttpBinding_ICalcService");
dblResult = objCalcClient2.Add(dblX, dblY);
Console.WriteLine("Calling Add >> X : {0:F2} Y : {1:F2}
Result : {2:F2}", dblX, dblY, dblResult);
dblResult = objCalcClient2.Subtract(dblX, dblY);
Console.WriteLine("Calling Sub >> X : {0:F2} Y : {1:F2}
Result : {2:F2}", dblX, dblY, dblResult);
dblResult = objCalcClient2.Multiply(dblX, dblY);
Console.WriteLine("Calling Mul >> X : {0:F2} Y : {1:F2}
Result : {2:F2}", dblX, dblY, dblResult);
dblResult = objCalcClient2.Divide(dblX, dblY);
Console.WriteLine("Calling Sub >> X : {0:F2} Y : {1:F2}
Result : {2:F2}", dblX, dblY, dblResult);
}
catch (Exception eX)
{
Console.WriteLine("There was an error while calling Service
[" + eX.Message + "]");
}
}
}
}
Explanation
It simply does two things:
Creates the proxy object for the TCP binding
CalcServiceClient objCalcClient1 =
new CalcServiceClient("NetTcpBinding_ICalcService");
Call the service, and print the result:
dblResult = objCalcClient1.Add(dblX, dblY);
Console.WriteLine("Calling Add >> X : {0:F2}
Y : {1:F2} Result : {2:F2}", dblX, dblY, dblResult);
dblResult = objCalcClient1.Subtract(dblX, dblY);
Console.WriteLine("Calling Sub >> X : {0:F2}
Y : {1:F2} Result : {2:F2}", dblX, dblY, dblResult);
dblResult = objCalcClient1.Multiply(dblX, dblY);
Console.WriteLine("Calling Mul >> X : {0:F2}
Y : {1:F2} Result : {2:F2}", dblX, dblY, dblResult);
dblResult = objCalcClient1.Divide(dblX, dblY);
Console.WriteLine("Calling Sub >> X : {0:F2}
Y : {1:F2} Result : {2:F2}", dblX, dblY, dblResult);
Creates the proxy object for the HTTP binding
CalcServiceClient objCalcClient2 =
new CalcServiceClient("BasicHttpBinding_ICalcService");
Call the service, and print the result:
dblResult = objCalcClient2.Add(dblX, dblY);
Console.WriteLine("Calling Add >>
X : {0:F2} Y : {1:F2} Result : {2:F2}", dblX, dblY, dblResult);
dblResult = objCalcClient2.Subtract(dblX, dblY);
Console.WriteLine("Calling Sub >>
X : {0:F2} Y : {1:F2} Result : {2:F2}", dblX, dblY, dblResult);
dblResult = objCalcClient2.Multiply(dblX, dblY);
Console.WriteLine("Calling Mul >>
X : {0:F2} Y : {1:F2} Result : {2:F2}", dblX, dblY, dblResult);
dblResult = objCalcClient2.Divide(dblX, dblY);
Console.WriteLine("Calling Sub >>
X : {0:F2} Y : {1:F2} Result : {2:F2}", dblX, dblY, dblResult);
Build and execute and here is the output:
Fourth Module: Client Web page, which can use this service
Create a new Web Site, open the default page (default.aspx), put some controls, and set the properties as described.
- asp:Label ID="Label1" Text ="Value 1 :", runat="server"
- asp:Label ID="Label2" Text ="Value 2 :", runat="server"
- asp:TextBox ID="txtVal1" runat="server"
- asp:TextBox ID="txtVal2" runat="server"
- asp:Button = ID="btnCalc", onclick="btnCalc_Click", Text="Call Service" , runat="server"
Below is the listing of default.aspx:
<%@ Page Language="C#" AutoEventWireup="true"
CodeFile="Default.aspx.cs" Inherits="_Default" %><span style="
font-size: 9pt;"><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"></span><pre><html
xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label ID="Label1" runat="server"
Text="Value 1 : "></asp:Label>
<asp:TextBox ID="txtVal1" runat="server"></asp:TextBox>
<br />
<asp:Label ID="Label2" runat="server"
Text="Value 2 : "></asp:Label>
<asp:TextBox ID="txtVal2" runat="server"></asp:TextBox>
<br />
<asp:Button ID="btnCalc" runat="server"
onclick="btnCalc_Click" Text="Button"
Width="91px" />
<br />
<asp:Label ID="lblOutput" runat="server"
BorderStyle="None" Height="152px"
Width="606px"></asp:Label>
</div>
</form>
</body>
</html>
<span style="font-size: 16px; font-weight: bold;">Generating the Service Proxy </span>
Generating the Service Proxy
Right click on the website project node, select the option Add service reference. type any mex end point address of the service in the Address : field:
http://localhost:9001/CalcService/mex
or:
net.tcp://localhost:9002/CalcService/mex
In the name give any name of your choice, it can be the name of the client's namespace, let's say for instance name it CalcServiceReference
.
Click OK as shown below:
It will add a new App_WebReferences folder to your web site and will modify your web.config file, let's analyze the changes, it made to the web.config. It made a new node in the last of the existing web.config named <system.ServiceModel>
as shown below:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_ICalcService"/>
</basicHttpBinding>
<netTcpBinding>
<binding name="NetTcpBinding_ICalcService"/>
</netTcpBinding>
</bindings>
<client>
<endpoint address="http://localhost:9001/CalcService"
binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_ICalcService"
contract="CalcServiceReference.ICalcService"
name="BasicHttpBinding_ICalcService"/>
<endpoint address="net.tcp://localhost:9002/CalcService"
binding="netTcpBinding"
bindingConfiguration="NetTcpBinding_ICalcService"
contract="CalcServiceReference.ICalcService"
name="NetTcpBinding_ICalcService">
<identity>
<servicePrincipalName value="host/PRAVEEN-WIN7"/>
</identity>
</endpoint>
</client>
</system.serviceModel><span style="font-size: 9pt;"> </span>
If you analyze it carefully, you will see two sections bindings
and client
, binding
sections simply defines the default binding names supported by this service, and client
section defines the endpoints available for this service. Each endpoint has been named, so that we know, which endpoint we are using to call the service.
Calling the Service
Call the service, I have called the service on the click event of Call Service button. Code for the event handler is shown below:
protected void btnCalc_Click(object sender, EventArgs e)
{
double dblX = 0, dblY = 0 ;
bool b1 = double.TryParse(txtVal1.Text, out dblX);
bool b2 = double.TryParse(txtVal2.Text, out dblY);
if ((b1) && (b2))
{
StringBuilder sbTmp = new StringBuilder( "<font size=3 color=#000080>");
sbTmp.Append("<p>Value 1 : " + dblX.ToString("F2"));
sbTmp.Append("<br>Value 2 : " + dblY.ToString("F2"));
sbTmp.Append ("<p>Using TCP Binding");
CalcServiceReference.CalcServiceClient calcProxy1 = new CalcServiceReference.CalcServiceClient("NetTcpBinding_ICalcService");
double dblResult = calcProxy1.Add(dblX, dblY);
sbTmp.Append("<br>Calling Add >> X : " + dblX.ToString("F2") + " Y : " + dblY.ToString("F2") + " Result : " + dblResult.ToString("F2"));
dblResult = calcProxy1.Subtract(dblX, dblY);
sbTmp.Append("<br>Calling Add >> X : " + dblX.ToString("F2") + " Y : " + dblY.ToString("F2") + " Result : " + dblResult.ToString("F2"));
dblResult = calcProxy1.Multiply(dblX, dblY);
sbTmp.Append("<br>Calling Add >> X : " + dblX.ToString("F2") + " Y : " + dblY.ToString("F2") + " Result : " + dblResult.ToString("F2"));
dblResult = calcProxy1.Divide(dblX, dblY);
sbTmp.Append("<br>Calling Add >> X : " + dblX.ToString("F2") + " Y : " + dblY.ToString("F2") + " Result : " + dblResult.ToString("F2"));
sbTmp.Append("<p>Using Basic HTTP Binding");
CalcServiceReference.CalcServiceClient calcProxy2 = new CalcServiceReference.CalcServiceClient("BasicHttpBinding_ICalcService");
dblResult = calcProxy2.Add(dblX, dblY);
sbTmp.Append("<br>Calling Add >> X : " + dblX.ToString("F2") + " Y : " + dblY.ToString("F2") + " Result : " + dblResult.ToString("F2"));
dblResult = calcProxy2.Subtract(dblX, dblY);
sbTmp.Append("<br>Calling Add >> X : " + dblX.ToString("F2") + " Y : " + dblY.ToString("F2") + " Result : " + dblResult.ToString("F2"));
dblResult = calcProxy2.Multiply(dblX, dblY);
sbTmp.Append("<br>Calling Add >> X : " + dblX.ToString("F2") + " Y : " + dblY.ToString("F2") + " Result : " + dblResult.ToString("F2"));
dblResult = calcProxy2.Divide(dblX, dblY);
sbTmp.Append("<br>Calling Add >> X : " + dblX.ToString("F2") + " Y : " + dblY.ToString("F2") + " Result : " + dblResult.ToString("F2"));
sbTmp.Append ( "<font>");
lblOutput.Text = sbTmp.ToString();
}
else
{
lblOutput.Text = "<font size=4 COLOR=#ff0000> Invalid Input </font>";
}
}
Output
And here is the output:
Points of Interest
- There was no configuration file on the Hosting side (neither with Windows service, nor with WCF library). at the hosting side (in Windows service). End points are generated on the fly, this way, the port value can be made configurable.
History