Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

WCF Hosting with Windows Service

4.85/5 (38 votes)
30 Sep 2013CPOL8 min read 224.2K   23.7K  
This article explains how to host a WCF library in a Windows Service.

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

C#
[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

C#
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.

C#
private ServiceHost m_svcHost = null ;  

Implement the OnStart () and OnStop () handler for the service.

Creating end points when Service is started

C#
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.

C#
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.

C#
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.

C#
ServiceMetadataBehavior mBehave = new ServiceMetadataBehavior();
m_svcHost.Description.Behaviors.Add(mBehave); 
Adding service endpoints to the hosted service
Add Basic TCP end point
C#
NetTcpBinding tcpb = new NetTcpBinding();
m_svcHost.AddServiceEndpoint(typeof(WCFCalcLib.ICalcService), tcpb, strAdrTCP);  
Add Basic HTTP end point
C#
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.

C#
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.

C#
m_svcHost.Open(); 

Disposing, when Service is Stopped

C#
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.

Image 1

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.

C#
public CalcServiceInstaller()
{
          // InitializeComponent();
           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>

Image 2

Starting the Service

You can start the service using command prompt using the following command:

Command Prompt > sc start WinSvcHostedCalcService <return> 

Image 3

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> 

Image 4

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:

XML
<?xml version="1.0" encoding="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.

XML
<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.

XML
<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):

C#
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
C#
CalcServiceClient objCalcClient1 = 
new CalcServiceClient("NetTcpBinding_ICalcService"); 

Call the service, and print the result:

C#
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

C#
CalcServiceClient objCalcClient2 = 
new CalcServiceClient("BasicHttpBinding_ICalcService"); 

Call the service, and print the result:

C#
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:

Image 5

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:

ASP.NET
<%@ 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:

Image 6

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:

XML
<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:

ASP.NET
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:

Image 7

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

  • Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)