65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Windows 服务托管 WCF

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (36投票s)

2013年9月16日

CPOL

8分钟阅读

viewsIcon

226540

downloadIcon

23824

本文档解释了如何将 WCF 库托管在 Windows 服务中。

引言

在本文档中,我将解释如何将 WCF 库托管在 Windows 服务中。

背景

托管 WCF 服务库有多种方法(IIS、Windows 服务、自托管),Windows 服务是其中一种。Windows 服务托管选项适用于不在 IIS 中托管、非消息激活的长期运行的 WCF 服务。服务的生命周期由操作系统控制。

Using the Code

本文档分为 4 个模块

  • WCF 服务库 (WCFCalcLib.dll):实际的服务逻辑,它定义了一个服务契约接口,OperationContract,并实现它们,并向外界公开了一些函数供使用。
  • 用于托管 WCF 服务库的 Windows 服务 (WinSvcHostedCalcService.exe):托管 WCF 库。
  • 控制台客户端应用程序 (CalcClient.exe):将使用此服务的客户端应用程序。
  • 基于 Web 的客户端应用程序 (CalcWebClient.exe):将使用此服务的 Web 应用程序。

第一个模块:WCF 服务库 (WCFCalcLib.dll)

要创建此项目,您可以从项目向导选项中选择一个“类库”项目。让我们将其命名为“WCFCalcLib”,这是实现业务逻辑的实际服务。该项目已包含一个文件 Class1.cs,将其重命名为 CalcService.cs,再添加一个文件 (ICalcService.cs) 来定义接口,尽管您也可以在此文件中定义接口。

服务契约定义

  [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);
    }    
解释

接口仅定义此服务支持的 4 个操作。

服务契约实现

  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 属性

服务契约描述了哪些相关操作可以作为单个功能单元绑定在一起,供客户端在服务上执行。

OperationContract 属性

操作契约指定该操作由服务公开,服务定义操作的参数和返回类型。

正如您所见,这里没有什么特别之处,只是一个服务契约的定义及其实现。

第二个模块:Windows 服务托管 WCF 服务库 (WinSvcHostedCalcService.exe)

要创建此项目,您可以从项目向导选项中选择一个“Windows 服务”项目。让我们将其命名为“WinSVCHostedCalcService”。此模块是一个 Windows 服务,它将托管我们的 WCF 库(业务逻辑),并向外界提供用于使用 WCF 库的终结点。在此模块中,在服务启动时,将创建一个基础设施来公开服务,并在服务停止时,所有对象都会被释放。

一些小调整

Service1.cs 重命名为 MyCalcWinService.cs

添加一个类型为 ServiceHost 的成员变量。顾名思义,ServiceHost 为服务提供了宿主。例如,将其命名为 m_svcHost

private ServiceHost m_svcHost = null ;  

实现服务的 OnStart () OnStop () 处理程序。

服务启动时创建终结点

   protected override void OnStart(string[] args)
        {
            if (m_svcHost != null) m_svcHost.Close();
                
            string strAdrHTTP = "https://:9001/CalcService";
            string strAdrTCP = "net.tcp://: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();
        }  
解释

当服务启动时(通过服务控制面板、命令行或其他方式),将调用 OnStart() 处理程序,它执行以下操作。

定义两个终结点。

string strAdrHTTP = "https://:9001/CalcService";
string strAdrTCP = "net.tcp://:9002/CalcService";   

使用服务(WCFCalcLib.CalcService)的实例及其基本地址(adrbase)创建并初始化 ServiceHost 类的新实例。

Uri[] adrbase = { new Uri(strAdrHTTP), new Uri(strAdrTCP) };
m_svcHost = new ServiceHost(typeof(WCFCalcLib.CalcService), adrbase)  
添加服务行为

ServiceMetadataBehavior 类控制服务元数据和相关信息的发布,尽管。

ServiceMetadataBehavior mBehave = new ServiceMetadataBehavior();
m_svcHost.Description.Behaviors.Add(mBehave); 
为托管的服务添加服务终结点
添加基本的 TCP 终结点
NetTcpBinding tcpb = new NetTcpBinding();
m_svcHost.AddServiceEndpoint(typeof(WCFCalcLib.ICalcService), tcpb, strAdrTCP);  
添加基本的 HTTP 终结点
BasicHttpBinding httpb = new BasicHttpBinding();
m_svcHost.AddServiceEndpoint(typeof(WCFCalcLib.ICalcService), httpb, strAdrHTTP);

仅仅为服务添加行为不足以发布元数据,您必须为**每种支持的绑定类型**将一个 IMetadataExchange 终结点添加到您的服务中。

m_svcHost.AddServiceEndpoint(typeof(IMetadataExchange), 
MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
m_svcHost.AddServiceEndpoint(typeof(IMetadataExchange), 
MetadataExchangeBindings.CreateMexTcpBinding(), "mex");  

最后打开宿主,我们就可以开始工作了。

m_svcHost.Open(); 

服务停止时释放

protected override void OnStop()
        {
            if (m_svcHost != null)
            {
                m_svcHost.Close();
                m_svcHost = null;
            }
        }  

当服务通过服务控制台/命令提示符或其他方式停止时,将关闭服务宿主,并将对象赋值为 null

为服务添加安装程序

通过右键单击 MyWinCalcService.cs 节点,在**设计模式**下打开 MathWndowsService。**右键单击服务设计器视图中的空白区域**,然后从上下文菜单中选择**添加安装程序**。如下图所示。

添加安装程序将创建一个名为 **ProjectInstaller** 的安装程序类,该类还会创建依赖项文件 **ProjectInstaller.Designer.cs** 和 ProjectInstaller.resx。只需进行一些小调整即可让一切就绪。

选择快捷菜单中的**重命名**,将 **ProjectInstaller.cs** 重命名为 CalcServiceInstaller.cs。

现在切换到安装程序类的代码视图。并将以下代码添加到安装程序类的构造函数中,这基本上是设置服务的属性。

 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);
 }

构建 Windows 服务。

安装服务

Windows 服务需要安装并运行。您可以使用 InstallUtil.exe 实用程序来安装服务。此实用程序是 SDK 的一部分,当您通过**Visual Studio 命令提示符**运行此实用程序时,路径已设置。

通过以下方式打开 Visual Studio 命令提示符:

开始 -> 所有程序 -> Microsoft Visual Studio 2010 -> Visual Studio Tools -> Visual Studio 命令提示符

切换到构建了 WinSvcHostedCalcService.exe 的文件夹,或者最好是您要部署它的位置。

发出以下命令来安装服务:

Command Prompt > InstallUtil  WinSvcHostedCalcService.exe <return>

启动服务

您可以使用命令提示符通过以下命令启动服务:

Command Prompt > sc start WinSvcHostedCalcService <return> 

另一种启动服务的方式是:

计算机 -> 管理 -> 服务和应用程序 -> 服务

**找到**名为 WinSvcHostedCalcService 的服务,**右键单击**并选择**启动**。

并且应该运行。您可以使用 InstallUtil.exe 实用程序来安装服务。此实用程序是 SDK 的一部分,当您通过**Visual Studio** 运行时,路径已设置。

第三个模块:控制台客户端应用程序 (CalcClient.exe)

服务构建并部署后,就可以创建客户端了。为了保持简单,我选择了基于控制台的应用程序。选择“控制台应用程序”项目模板,并将其命名为 CalcClient

生成服务代理

要使用服务,我们必须生成代理。**切换到已创建客户端项目的文件夹**。打开命令提示符并发出命令来生成代理。

Command Prompt >  SvcUtil https://:9001/CalcService /out:CalcServiceProxy.cs /config:App.config <return> 

将生成两个文件 - 一个服务代理文件和一个客户端配置文件。

  • App.config
  • CalcServiceProxy.cs

将这​​两个文件添加到新生成的客户端项目中。当我们仔细检查配置文件时,会发现以下部分:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <system.serviceModel>
        <bindings>
		. . . .
        </bindings>
        <client>
		. . . .
        </client>
    </system.serviceModel>
</configuration>

binding 部分包含标准绑定和自定义绑定的集合。每个条目都是一个绑定元素,可以通过其唯一名称进行标识。

        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_ICalcService" />
            </basicHttpBinding>
            <netTcpBinding>
                <binding name="NetTcpBinding_ICalcService" />
            </netTcpBinding>
        </bindings> 

client 部分包含客户端用于连接到服务的终结点列表。

 <client>
 
            <endpoint address="https://:9001/CalcService" 
            binding="basicHttpBinding"
                bindingConfiguration="BasicHttpBinding_ICalcService" 
                contract="ICalcService"
                name="BasicHttpBinding_ICalcService" />
            <endpoint address="net.tcp://:9002/CalcService" 
            binding="netTcpBinding"
                bindingConfiguration="NetTcpBinding_ICalcService" 
                contract="ICalcService"
                name="NetTcpBinding_ICalcService">
                <identity>
                    <servicePrincipalName value="host/PRAVEEN-WIN7" />
                </identity>
            </endpoint>
        </client>     

client 部分包含客户端用于连接到服务的终结点列表。

注意:您的计算机上的 Identity 可能不同。

使用代理调用服务

打开客户端项目的 programs.cs 文件,并编写代码以使用生成的代理类来使用服务,如下所示:

客户端程序列表 (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 + "]");
            }
        }
    }
} 
解释

它简单地做了两件事:

为 TCP 绑定创建代理对象
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);

为 HTTP 绑定创建代理对象

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); 

构建并执行,输出如下:

第四个模块:可以使用此服务的客户端网页

创建一个新的网站,打开默认页面 (default.aspx),添加一些控件,并设置如下描述的属性。

  • asp:Label ID="Label1" Text ="值 1 :", runat="server"
  • asp:Label ID="Label2" Text ="值 2 :", runat="server"
  • asp:TextBox ID="txtVal1" runat="server"
  • asp:TextBox ID="txtVal2" runat="server"
  • asp:Button = ID="btnCalc", onclick="btnCalc_Click", Text="调用服务" , runat="server"

下面是 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> 

生成服务代理

右键单击网站项目节点,选择“添加服务引用”选项。在地址:字段中键入服务的任何 **mex** 终结点地址。

https://:9001/CalcService/mex

net.tcp://:9002/CalcService/mex

 

在名称中输入任何您选择的名称,它可以是客户端命名空间的名字,例如,将其命名为 CalcServiceReference

单击 OK,如下图所示。

它将在您的网站中添加一个名为 App_WebReferences 的新文件夹,并修改您的 web.config 文件。让我们分析一下它对 web.config 所做的更改。它在现有的 web.config 文件末尾添加了一个名为 <system.ServiceModel> 的新节点,如下图所示。

<system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_ICalcService"/>
      </basicHttpBinding>
      <netTcpBinding>
        <binding name="NetTcpBinding_ICalcService"/>
      </netTcpBinding>
    </bindings>
    <client>
      <endpoint address="https://:9001/CalcService" 
      binding="basicHttpBinding" 
      bindingConfiguration="BasicHttpBinding_ICalcService" 
      contract="CalcServiceReference.ICalcService" 
      name="BasicHttpBinding_ICalcService"/>
      <endpoint address="net.tcp://: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>

如果您仔细分析,您会看到两个部分:bindings client binding 部分仅定义此服务支持的默认绑定名称,而 client 部分定义了此服务的可用终结点。每个终结点都已命名,以便我们知道我们正在使用哪个终结点来调用服务。

调用服务

调用服务。我在“调用服务”按钮的**单击事件**中调用了服务。事件处理程序的代码如下所示:

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>";
        }
    }

输出

这是输出

关注点

  • 托管端(Windows 服务或 WCF 库)没有配置文件。在托管端(在 Windows 服务中),终结点是动态生成的,这样,端口值就可以配置。

历史

  • 初始版本
© . All rights reserved.