使用 Windows 服务托管 WCF






4.85/5 (36投票s)
本文档解释了如何将 WCF 库托管在 Windows 服务中。
- 下载 WinSvcHostedCalcServiceExe.zip - 5.1 KB
- 下载 WCFCalcLibSrc 源代码 - 2.6 KB
- 下载 CalcWinClientSrc 源代码 - 3.7 KB
- 下载 CalcWinClientExe.zip - 3.4 KB
- 下载 CalcWebClientSrc 源代码 - 140.3 KB
- 下载 WinSvcHostedCalcServiceSrc 源代码 - 7.2 KB
引言
在本文档中,我将解释如何将 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 服务中),终结点是动态生成的,这样,端口值就可以配置。
历史
- 初始版本