上一节《
WCF分布式框架基础概念》我们介绍了WCF服务的概念和通信框架模型,并给出了基于自定义托管服务的WCF程序的实现代码。考虑到WCF分布式开发项目中关于托管宿主服务配置和客户端添加引用。两个环节最容易出错。对于大部分想学习WCF分布式开发的人来说,成功开发、配置、部署第一个自己的WCF服务困难重重。很多资料都介绍了WCF的基本概念。但是对于实际的项目开发过程介绍粗略,给入门者带来诸多不便。今天我们就来补充一节WCF分布式开发一个完整解决方案的开发和配置过程。本节基本结构是:首先介绍【1】WCF服务解决方案的项目组成【2】WCF服务的开发和配置过程,【3】自定义宿主的开发和配置过程【4】客户端的服务引用和配置过程。【总结】算是为各位WCF分布式技术开发的爱好者,提供的一个开发参考。
【1】WCF服务解决方案的项目组成:
1.1】WCF服务:
通常来说,WCF服务由三个部分构成:
-
服务类:包含服务契约、操作契约和数据契约的定义和实现;
-
宿主:一种应用程序域和进程,服务将在该环境中运行;
-
终结点:由客户端用于访问服务。
我们这里的解决方案包括服务类项目、托管宿主、和简单的客户端程序,结构如图:
1.2】客户端应用程序:
上一节我们介绍了WCF的基本概念,WCF基本通信机制是基于SOAP消息,SOAP消息基于XML语言,因此WCF应用程序可与运行于各种上下文环境的其他进程进行通信,当然也支持跨系统、跨平台的应用程序之间的数据交互。基于WCF构建的分布式应用程序可与下列所有程序进行交互:
-
同一 Windows 计算机上不同进程中的WCF应用程序。
-
另一 Windows 计算机上的WCF应用程序。
-
基于其他技术构建的应用程序,如基于 Java 2 企业版 (J2EE) 构建的、支持标准 Web 服务的应用程序服务器。这些应用程序可以运行在 Windows 计算机上,也可以运行在其他操作系统(如 Sun Solaris、IBM 的 z/OS 或 Linux)上。
因此可以作为客户端的应用程序,并不限制其类型,只要可以解析基于XML的SOAP消息,都可以与WCF的服务端进行通信。.NET平台上我们可以创建控制台应用程序、WinForm、Windows服务、ASP.NET应用程序等,来访问和调用WCF服务。下面我们就来具体介绍自定义托管服务,WCF解决方案的开发配置的详细过程。
【2】WCF服务类的开发过程:
要创建WCF服务解决方案,首先应该定义服务类,并编写服务类的相关的代码。我们这里把服务类创建类单独的类库项目,托管宿主程序项目引用服务类的程序集。
2.0】创建WCF服务类库项目:
WCF服务类库项目的创建非常简单,Visual Studio 2008为我们提供了便捷的方式,选择新建项目,选择--WCF服务类库项目项目,输入项目名称,选择保存位置就可以。如图:
当然我们也可以建立空项目,但是程序集引用等操作要手动完整,过程相对复杂。这里我们选择的使用创建向导方式。
System.ServiceModel和System.Runtime.Serialization程序集对WCF服务至关重要,因为后续服务契约、操作契约和数据契约等特性定义都在这两个程序集中。我们在新建的项目引用中可以看到:
使用WCF契约相关的属性,必须显示引用这两个命名空间.语句如下:
using System.ServiceModel;
using System.Runtime.Serialization;
using System.Runtime.Serialization;
如果不添加命名空间引用程序集,编写的代码会出现错误,无法通过。
2.1】服务契约和操作契约:
WCF服务类库创建完毕后,我们可以来进行代码的实际编写。我们知道,每个WCF服务类均需实现一些方法,以供其客户端使用。服务类的创建者通过将这些方法包含在某个服务契约中,来决定将哪些方法公开为客户可调用的操作。服务契约就是显示指定的服务必须实现的用户可以使用的操作。
ServiceContract 属性以及 WCF使用的所有其他属性均在 System.ServiceModel 命名空间中定义,类声明使用 using 语句来引用该命名空间。服务类中可被客户端调用的每个方法都必须使用名为 OperationContract 的另一个属性加以标记。服务类中带有前置 OperationContract 属性的所有方法都将自动被WCF公开为 SOAP 可调用操作。
WCF中最基本的属性是 ServiceContract。实际上,WCF服务类本身就是标记有 ServiceContract 属性的类或者是实现了标记有该属性的接口的类。我们使用的还是上次的代码:
首先是服务契约
IWCFService,定义了连个操作,添加[OperationContract]属性标记:
//
1.服务契约
[ServiceContract(Namespace = " http://www.cnblogs.com/frank_xl/ " )]
public interface IWCFService
{
// 操作契约
[OperationContract]
string SayHello( string name);
// 操作契约
[OperationContract]
string SayHelloToUser(User user);
}
[ServiceContract(Namespace = " http://www.cnblogs.com/frank_xl/ " )]
public interface IWCFService
{
// 操作契约
[OperationContract]
string SayHello( string name);
// 操作契约
[OperationContract]
string SayHelloToUser(User user);
}
其次定义WCF服务类,继承服务契约,实现服务契约中声明的操作,具体代码如下:
//
2.服务类,继承接口。实现服务契约定义的操作
public class WCFService : IWCFService
{
// 实现接口定义的方法
public string SayHello( string name)
{
Console.WriteLine( " Hello! {0},Using string " , name);
return " Hello! " + name;
}
// 实现接口定义的方法
public string SayHelloToUser(User user)
{
Console.WriteLine( " Hello! {0} {1},Using DataContract " , user.FirstName, user.LastName);
return " Hello! " + user.FirstName + " " + user.LastName;
}
}
public class WCFService : IWCFService
{
// 实现接口定义的方法
public string SayHello( string name)
{
Console.WriteLine( " Hello! {0},Using string " , name);
return " Hello! " + name;
}
// 实现接口定义的方法
public string SayHelloToUser(User user)
{
Console.WriteLine( " Hello! {0} {1},Using DataContract " , user.FirstName, user.LastName);
return " Hello! " + user.FirstName + " " + user.LastName;
}
}
服务类里给出了服务契约里声明的方法,也就是操作,这里都给出了具体的实现。SayHello()和
SayHelloToUser(User user)方法都将自动被WCF公开为 SOAP 可调用操作。客户端可以调用相应的操作。
2.2】数据契约:
数据契约定义类型如何转换为适合标准信息格式,即“序列化”过程。实际上,数据契约是控制数据如何序列化的一种机制。 在 WCF 服务类中,数据契约使用 DataContract 属性来定义。标记有 DataContract 的数据类、结构或其他类型都可以拥有一个或多个带有前置 DataMember 属性的成员,指示该成员必须被包含在此类型的序列化值中。不显示指定的成员不被包含在序列化数据中。这里我们定义的数据类,包含三个成员,代码如下:
//
3.数据契约
//
序列化为XML,作为元数据封装到服务里
[DataContract]
public struct User
{
[DataMember]
public string FirstName;
public string MiddleName; // 不会被传递
[DataMember]
public string LastName;
}
[DataContract]
public struct User
{
[DataMember]
public string FirstName;
public string MiddleName; // 不会被传递
[DataMember]
public string LastName;
}
由于是类库项目,所以主要涉及的内容是契约的定义和服务类的实现过程。配置的关键部分也是契约属性的标记。
不涉及配置文件的使用和定制,如果是应用程序,需要配置对应的Config文件。
【3】自定义宿主的开发和配置过程:
我们这里使用的控制台程序为托管宿主,我们这里讲解托管宿主的代码编写和配置文件的设置过程。
3.1】托管宿主程序的创建:
使用VS2008新建控制台应用程序,非常简单,选择新建项目-控制台程序,即可。
3.2】托管宿主代码编写:
这里要添加对WCF服务类库项目的引用,另外要引用ServiceModel程序集。
ServiceHost类位于ServiceModel命名空间下。这里比较重要的步骤就是,定义一个
ServiceHost实例,定义地址,定义终结点使用ABC地址、契约、绑定。
代码如下:
//
反射方式创建服务实例,
// Using方式生命实例,可以在对象生命周期结束时候,释放非托管资源
using (ServiceHost host = new ServiceHost( typeof (WCFService.WCFService)))
{
// 相同的服务注册多个基地址
// 添加服务和URI,用户资源标识符
Uri tcpAddress = new Uri( " net.tcp://localhost:8001/WCFService " );
Uri httpAddress = new Uri( " http://localhost:8002/WCFService " );
Uri pipeAddress = new Uri( " net.pipe://localhost:8002/WCFService " );
host.AddServiceEndpoint( typeof (WCFService.IWCFService), new NetTcpBinding() , tcpAddress);
host.AddServiceEndpoint( typeof (WCFService.IWCFService), new WSHttpBinding(), httpAddress);
host.AddServiceEndpoint( typeof (WCFService.IWCFService), new NetNamedPipeBinding(), pipeAddress);
// 判断是否以及打开连接,如果尚未打开,就打开侦听端口
if (host.State != CommunicationState.Opening)
host.Open();
// 显示运行状态
Console.WriteLine( " Host is runing! and state is {0} " ,host.State);
// 等待输入即停止服务
Console.Read();
}
// Using方式生命实例,可以在对象生命周期结束时候,释放非托管资源
using (ServiceHost host = new ServiceHost( typeof (WCFService.WCFService)))
{
// 相同的服务注册多个基地址
// 添加服务和URI,用户资源标识符
Uri tcpAddress = new Uri( " net.tcp://localhost:8001/WCFService " );
Uri httpAddress = new Uri( " http://localhost:8002/WCFService " );
Uri pipeAddress = new Uri( " net.pipe://localhost:8002/WCFService " );
host.AddServiceEndpoint( typeof (WCFService.IWCFService), new NetTcpBinding() , tcpAddress);
host.AddServiceEndpoint( typeof (WCFService.IWCFService), new WSHttpBinding(), httpAddress);
host.AddServiceEndpoint( typeof (WCFService.IWCFService), new NetNamedPipeBinding(), pipeAddress);
// 判断是否以及打开连接,如果尚未打开,就打开侦听端口
if (host.State != CommunicationState.Opening)
host.Open();
// 显示运行状态
Console.WriteLine( " Host is runing! and state is {0} " ,host.State);
// 等待输入即停止服务
Console.Read();
}
值得注意的是
定义终结点的代码可以由配置文件的定制来实现。 if (host.State !=CommunicationState.Opening)语句是为了判断是否以及打开连接,如果尚未打开,host.Open(); 就打开侦听端口。
Console.Read()语句是阻塞进程,使得宿主程序可以一直运行下去。直到用户输入数据。
Console.Read()语句是阻塞进程,使得宿主程序可以一直运行下去。直到用户输入数据。
3.3】配置文件的定制:
要使WCF宿主程序能够正确运行,还需要编辑配置文件信息。所有的WCF服务相关的配置信息都处于app.config文件的
<system.serviceModel>节点内。下面我们就来详细介绍一下详细的配置过程。
3.3.1【服务结点配置】
<service behaviorConfiguration="WCFService.WCFServiceBehavior" name="WCFService.WCFService">
指定公布服务的类型和行为。服务的行为要在配置文件中给出,我们下面会给出详细的说明。这里的WCF服务就是我们实现服务契约的WCF服务的类名WCFService。
服务的终结点包含ABC,地址、绑定(通信协议,其实很拗口)、契约三个部分。地址包括:通信协议、机器地址、端口、服务名。契约就是服务契约。我们这里配置了连个终结点,分别使用HTTP协议和TCP协议。端口分别是8001和8002。服务地址必须不同,所以设置了两个不同的端口。具体的配置代码如下:
<
endpoint
address = " http://localhost:8001/WCFService "
binding = " wsHttpBinding "
contract = " WCFService.IWCFService " >
</ endpoint >
< endpoint adress = " net.tcp://localhost:8002/WCFService "
binding = " netTcpBinding "
contract = " WCFService.IWCFService " >
</ endpoint >
address = " http://localhost:8001/WCFService "
binding = " wsHttpBinding "
contract = " WCFService.IWCFService " >
</ endpoint >
< endpoint adress = " net.tcp://localhost:8002/WCFService "
binding = " netTcpBinding "
contract = " WCFService.IWCFService " >
</ endpoint >
如果终结点里不设置地址,我们必须配置文件里给出相应的基地址配置,基地址包括:通信协议(绑定)、机器名或IP、端口。这样WCF服务的托管宿主启动后,状态才能成功打开。配置信息如下:
<
host
>
< baseAddresses >
< add baseAddress = " http://localhost:8001/ " />
< add baseAddress = " net.tcp://localhost:8002/ " />
</ baseAddresses >
</ host >
< baseAddresses >
< add baseAddress = " http://localhost:8001/ " />
< add baseAddress = " net.tcp://localhost:8002/ " />
</ baseAddresses >
</ host >
3.3.2【原数据终结点配置】
如果我们希望WCF可以被客户端查找和引用,我们就要设置相应的元素据交换节点,来约束WCF服务的元数据交换行为。绑定(通信协议)和我们上面设置的服务终结点对应,这样客户端可以以不同的方式获得元素据,数据交换契约为
IMetadataExchange,具体代码如下:
<
endpoint address
=
"
mex
"
binding
=
"
mexHttpBinding
"
contract
=
"
IMetadataExchange
"
/>
< endpoint address = " mex " binding = " mexTcpBinding " contract = " IMetadataExchange " />
< endpoint address = " mex " binding = " mexTcpBinding " contract = " IMetadataExchange " />
这样客户端可以通过服务地址获得我们公布的WCF服务的元素据信息,反序列化创建本地的对应的数据类、服务等类。
3.3.3【行为结点配置】
另外我们可以在这里配置服务的行文,在配置文件里的<
serviceBehaviors>节点下。定义行为名称,方便服务引用。是否可以通过
httpGet方式获取服务元素据,是否显示服务异常的详细信息,在这里都可以进行设置。
<
behaviors
>
< serviceBehaviors >
< behavior name = " WCFService.WCFServiceBehavior " >
< serviceMetadata httpGetEnabled = " true " />
< serviceDebug includeExceptionDetailInFaults = " false " />
</ behavior >
</ serviceBehaviors >
</ behaviors >
< serviceBehaviors >
< behavior name = " WCFService.WCFServiceBehavior " >
< serviceMetadata httpGetEnabled = " true " />
< serviceDebug includeExceptionDetailInFaults = " false " />
</ behavior >
</ serviceBehaviors >
</ behaviors >
这里定义的服务行为就是在上面被服务节点引用的,记住名称一定要匹配。否则会出现找不到服务行为的异常,程序将无法运行。以上的方式可以通过编程方式实现,但是相对来说配置文件使用简单,编程方式复杂,需要代码,但是功能强大,我们可以编程动态控制服务运行的状态。
配置完全后就可以编译,运行托管服务宿主程序。托管宿主启动正常:
【4】客户端的服务引用、配置和开发过程:
服务类和服务宿主已经配置完毕,下面我们来讲述客户端添加WCF服务的引用、配置和服务调用过程。
首先要运行宿主程序,这样才能在客户端添加服务引用,从元数据获取服务类的相关信息,生成本地类。
4.1】添加WCF服务引用:
服务浏览器,单击客户端项目,添加Services Reference.在弹出的窗口地址里输入服务的基地址,首先查找TCP服务。
保持地址和配置文件里服务的基地址相同,:查找成功后的窗口如下:
我们可以看到WCF服务类公布的操作,输入命名空间的名字为
ServiceReferenceTcp。同样的方式添加对HTTP服务的引用。添加成功后我们可以查看所有文件,在客户端项目的服务引用的窗口看到所有的服务引用的文件信息如图:
证明我们添加WCF服务成功。客户端app.config文件里会生成相应的服务代理的相关信息,包括客户端终结点的信息:
<
client
>
< endpoint address = " http://localhost:8001/WCFService " binding = " wsHttpBinding "
bindingConfiguration = " WSHttpBinding_IWCFService " contract = " ServiceReferenceTcp.IWCFService "
name = " WSHttpBinding_IWCFService " >
< identity >
< userPrincipalName value = " FRANK\Administrator " />
</ identity >
</ endpoint >
< endpoint address = " net.tcp://localhost:8002/WCFService " binding = " netTcpBinding "
bindingConfiguration = " NetTcpBinding_IWCFService " contract = " ServiceReferenceTcp.IWCFService "
name = " NetTcpBinding_IWCFService " >
< identity >
< userPrincipalName value = " FRANK\Administrator " />
</ identity >
</ endpoint >
< endpoint address = " http://localhost:8001/WCFService " binding = " wsHttpBinding "
bindingConfiguration = " WSHttpBinding_IWCFService1 " contract = " ServiceReferenceHttp.IWCFService "
name = " WSHttpBinding_IWCFService1 " >
< identity >
< userPrincipalName value = " FRANK\Administrator " />
</ identity >
</ endpoint >
< endpoint address = " net.tcp://localhost:8002/WCFService " binding = " netTcpBinding "
bindingConfiguration = " NetTcpBinding_IWCFService1 " contract = " ServiceReferenceHttp.IWCFService "
name = " NetTcpBinding_IWCFService1 " >
< identity >
< userPrincipalName value = " FRANK\Administrator " />
</ identity >
</ endpoint >
</ client >
< endpoint address = " http://localhost:8001/WCFService " binding = " wsHttpBinding "
bindingConfiguration = " WSHttpBinding_IWCFService " contract = " ServiceReferenceTcp.IWCFService "
name = " WSHttpBinding_IWCFService " >
< identity >
< userPrincipalName value = " FRANK\Administrator " />
</ identity >
</ endpoint >
< endpoint address = " net.tcp://localhost:8002/WCFService " binding = " netTcpBinding "
bindingConfiguration = " NetTcpBinding_IWCFService " contract = " ServiceReferenceTcp.IWCFService "
name = " NetTcpBinding_IWCFService " >
< identity >
< userPrincipalName value = " FRANK\Administrator " />
</ identity >
</ endpoint >
< endpoint address = " http://localhost:8001/WCFService " binding = " wsHttpBinding "
bindingConfiguration = " WSHttpBinding_IWCFService1 " contract = " ServiceReferenceHttp.IWCFService "
name = " WSHttpBinding_IWCFService1 " >
< identity >
< userPrincipalName value = " FRANK\Administrator " />
</ identity >
</ endpoint >
< endpoint address = " net.tcp://localhost:8002/WCFService " binding = " netTcpBinding "
bindingConfiguration = " NetTcpBinding_IWCFService1 " contract = " ServiceReferenceHttp.IWCFService "
name = " NetTcpBinding_IWCFService1 " >
< identity >
< userPrincipalName value = " FRANK\Administrator " />
</ identity >
</ endpoint >
</ client >
客户端的配置方式和宿主托管方式非常类似,同样包括地址、绑定、契约等信息。
4.2】调用服务:
要调用相应的服务,需要实例化服务代理类的实例,首先添加命名空间的引用
using ServiceReferenceHttp;
using ServiceReferenceTcp;这样可以使用本地反序列化生成的类和其他配置的信息。我们分别实例化HTTP和TCP代理的类,非别调用服务的不同操作,USER的实例也分别为不同的命名空间中的类型,需要分别指定命名空间。
using ServiceReferenceTcp;这样可以使用本地反序列化生成的类和其他配置的信息。我们分别实例化HTTP和TCP代理的类,非别调用服务的不同操作,USER的实例也分别为不同的命名空间中的类型,需要分别指定命名空间。
具体测试代码如下:
//
HTTP WSHttpBinding_IWCFService1
ServiceReferenceHttp.WCFServiceClient wcfServiceProxyHttp = new ServiceReferenceHttp.WCFServiceClient( " WSHttpBinding_IWCFService1 " );
// 通过代理调用SayHello服务
Console.WriteLine(wcfServiceProxyHttp.SayHello( " Frank Xu Lei WSHttpBinding " ));
/// /通过代理调用调用SayHelloToUser,传递对象
ServiceReferenceHttp.User user = new ServiceReferenceHttp.User();
user.FirstName = " WSHttpBinding " ;
user.LastName = " Frank " ;
Console.WriteLine(wcfServiceProxyHttp.SayHelloToUser(user));
// TCP NetTcpBinding_IWCFService
ServiceReferenceTcp.WCFServiceClient wcfServiceProxyTcp = new ServiceReferenceTcp.WCFServiceClient( " NetTcpBinding_IWCFService " );
// 通过代理调用SayHello服务
Console.WriteLine(wcfServiceProxyTcp.SayHello( " Frank Xu Lei NetTcpBinding " ));
/// /通过代理调用调用SayHelloToUser,传递对象
ServiceReferenceTcp.User userTcp = new ServiceReferenceTcp.User();
userTcp.FirstName = " NetTcpBinding " ;
userTcp.LastName = " Frank " ;
Console.WriteLine(wcfServiceProxyTcp.SayHelloToUser(userTcp));
ServiceReferenceHttp.WCFServiceClient wcfServiceProxyHttp = new ServiceReferenceHttp.WCFServiceClient( " WSHttpBinding_IWCFService1 " );
// 通过代理调用SayHello服务
Console.WriteLine(wcfServiceProxyHttp.SayHello( " Frank Xu Lei WSHttpBinding " ));
/// /通过代理调用调用SayHelloToUser,传递对象
ServiceReferenceHttp.User user = new ServiceReferenceHttp.User();
user.FirstName = " WSHttpBinding " ;
user.LastName = " Frank " ;
Console.WriteLine(wcfServiceProxyHttp.SayHelloToUser(user));
// TCP NetTcpBinding_IWCFService
ServiceReferenceTcp.WCFServiceClient wcfServiceProxyTcp = new ServiceReferenceTcp.WCFServiceClient( " NetTcpBinding_IWCFService " );
// 通过代理调用SayHello服务
Console.WriteLine(wcfServiceProxyTcp.SayHello( " Frank Xu Lei NetTcpBinding " ));
/// /通过代理调用调用SayHelloToUser,传递对象
ServiceReferenceTcp.User userTcp = new ServiceReferenceTcp.User();
userTcp.FirstName = " NetTcpBinding " ;
userTcp.LastName = " Frank " ;
Console.WriteLine(wcfServiceProxyTcp.SayHelloToUser(userTcp));
运行结果如图:
两者不同的协议服务调用都成功执行,并且返回正确的结果。
【总结】:
以上就是本节关于自定义托管宿主WCF服务解决方案开发与配置的详细过程,包括服务代码的编写、宿主程序的开发与配置、客户端服务的引用和调用。我们这里托管宿主服务使用了配置文件,来配置WCF服务的信息,这里也可以编码实现。
另外客户端要想通过原数据交换来反序列换生成本地的WCF服务类等相关代码,就需要在托管宿主里配置可以使用的原数据交换节点,这里缺少设置,就会出现获取服务元数据的异常,导致客户端添加服务出错。(最近论文答辩有点忙,所以这个文章更新的比较慢,不好意思)另外给出本文的参考代码:
/Files/frank_xl/WCFServiceConfigFrankXuLei.rar
希望本篇文章能给大家WCF分布式开发项目的配置带来一些帮助。~
本文转自 frankxulei 51CTO博客,原文链接:http://blog.51cto.com/frankxulei/320404,如需转载请自行联系原作者