WCF

初入职场,开始接触C#,开始接触WCF,那么从头开始学习吧,边学边补充吧。学习WCF之前,有必要先来了解下SOA

SOA

Service-Oriented Architecture,面向服务架构,粗粒度、开放式、松耦合的服务结构,将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来。接口采用中立的方式定义,独立于实现服务的硬件平台、操作系统和编程语言(跨平台)。

SOA既是一种编程方式,也是软件开发的一种架构方法,服务层是SOA的基础,核心是“服务”,本质就是将服务组合起来并对外提供接口。SOA架构的技术基础是SOAP(Simple Object Access Protocol,简易对象访问协议)标准,SOAP用XML语言定义一个服务操作方法所发送和接收消息的内容。

实现SOA思想的技术

  • Web Service:
  • WCF:

参考对SOA的一点理解漫谈SOA

有关 web service 的学习,参见:Web Service - sqh

关于 Web Service 和 WCF,异同点作简单的说明

  • 数据交换/通信方式:Web 服务在HTTP上通过SOAP与端点通信,WCF 服务可以选择使用不同的协议进行通信
  • 运行方式:Web 服务总是运行在 IIS 上,WCF 服务可以选择合适宿主

关于WCF与Web Service的区别参见:http://www.cnblogs.com/xiurui12345/archive/2012/03/30/2425445.html。下面主要研究 WCF。

WCF

Windows Communication Foundation(WCF)是由微软开发的一系列支持弹性数据通信的应用程序框架,Windows通讯开发平台。支持和集成Web Service,兼容和具备微软早期技术的特性,整合了原有windows通讯的 .Net Remoting、WebService、Socket机制,融合有HTTP和FTP相关技术,是建立在Web Service架构上的一个全新通信平台。

WCF可以理解为Web Service的升级版。

微软官方解释:WCF是完全使用托管代码建立和运行面向服务应用程序的统一框架,使开发者能建立一个跨平台的安全、可信赖、事务性的解决方案,且能与已有系统兼容协作

  • Microsoft为构建 面向服务 的应用提供的 分布式通信框架
  • WCF是.NET Framework 3.5的重要组成部分
  • Web服务和远程技术相结合的通用基础结构,提供创建远程服务并与其通信的框架
  • 微软分布式应用程序开发的集大成者,整合了.Net平台与分布式系统的有关技术,是Win平台上开发分布式应用最佳的实践方式

WCF是一套框架,用来创建各种服务,且能够创建兼容Web服务的服务,也就是说可以创建能够与Web服务互联互通的服务。WCF最基本的通信机制是SOAP,保证系统之间的互操作性。WCF技术允许创建服务,可以跨进程、计算机和网络从其他应用程序访问这些服务。利用这些服务,可以在多个应用程序中共享功能,提供数据源或抽象复杂过程。

优点

  • 统一性、兼容性,支持跨平台交流
  • 具有互操作性的Web服务,支持.Net与.Net通信、分布式事务、消息队列(Message Queue)
  • 安全可靠的消息通信,灵活配置支持单工、半双工、全双工
  • 支持HTTP或TCP通信,寄宿在web服务器、windows服务或自我寄宿

其余相关信息可参考:WCF .vs. Web Service

WCF做啥

WCF 是面向服务的,跨平台的安全、可信赖、事务性的解决方案,作为 WebService,.Net Remoting,Enterprise Service,WSE,MSMQ 的并集,以面向服务为思想提供了包含通讯、事务、并发、队列、安全等一系列的整套的分布式开发方案。

  • WCF最终目标是通过进程或不同的系统、通过本地网络或是通过Internet收发客户端服务器之间的消息
  • WCF专门用于面向服务(Service-Oriented)开发

WCF 是 .NET 提供的一种服务,可以将写的完成特定功能的程序(如从数据库中读取数据操作等)封装成服务,然后发布到服务器上会生成一个网址,客户端编程时可以引用这个服务,使用服务中提供的功能。关于 WCF 的其他信息参考:http://blog.csdn.net/fynjy/article/details/46874597

WCF架构

WCF Service通过 终结点(Endpoint) 发布服务来实现网络系统各个应用程序间的通信,一个WCF Service由一个Endpoint集合组成。Endpoint是WCF实现通信的核心要素,是服务器间通信调用的入口,客户端和服务端通过Endpoint交换信息。

当我们寄宿 WCF 服务时,必须定义一个或多个终结点,然后 Serivce 端通过监听这些终结点来处理 Client 发来的请求。由于应用程序之间靠 Endpoint 通信,那么 Client 端也必须定义终结点,只有 Client 与 Service 的终结点完全匹配时才能通信。

namespace System.ServiceModel.Desc
{
	// 表示允许服务的客户端查找并与服务通信的服务的终结点
	public class ServiceEndpoint
	{
		public string Name { get; set; } // 服务终结点的名称
		public EndpointAddress Address { get; set; }  // 服务终结点的终结点地址
		public ContractDescription Contract { get; set; } // 服务终结点的协定
		public Binding Binding { get; set; } // 服务终结点的绑定
	}
}

Endpoint由三个主要部分组成:

A:地址 - Address,服务的位置,Where to locate the WCF Service?:唯一定位服务,提供额外的寻址信息和认证信息

右键 SqhService.svc ,选择在浏览器中查看,获取该服务的唯一地址标识URI(WCF Service Address):http://localhost:18583/SqhService.svc

其中,URI(Uniform Resource Identifier)统一资源标识,格式:[Schema传输协议]://[主机名|域名|IP地址]:[端口号]/[资源路经] 

namespace System.ServiceModel
{
    // 提供客户端用来与服务终结点进行通信的唯一网络地址。
    public class EndpointAddress
    {
		public Uri Uri { get; } // 终结点的 URI,服务的唯一标识
		public EndpointIdentity Identity { get; } // 用于验证终结点的标识
		public AddressHeaderCollection Headers { get; } // 获取生成器可以创建的终结点的地址标头的集合
	}
}

对于终结点地址,是区别于服务地址的:终结点地址是服务地址的子地址,或相对地址;服务地址是终结点地址的父地址,或基地址,示例

服务地址:江陵路xx小区2号楼,楼房地址,只有一个,唯一标识服务

终结点地址:江陵路xx小区2号楼601室,门牌号地址,一个服务可以包含多个终结点

下面给出网上的一段示例代码,有助于理解服务地址和终结点地址的关系

// 服务地址
Uri baseAddress = new Uri("http://localhost:8000/MyService"); 
 
// 服务宿主
using(ServiceHost host = new ServiceHost(typeof(HelloWCFService), baseAddress))
{
	// 添加终结点(地址、绑定、服务协定)
	host.AddServiceEndpoint(typeof(IHelloWCFService), new WSHttpBinding(), "HelloWCFService");  

	// 元数据交换结点,用于元数据交换,并指定启用元数据交换行为
	ServiceMetadataBehavior smb = new ServiceMetadataBehavior();  
	smb.HttpGetEnabled = true;  
	host.Description.Behaviors.Add(smb);  

	// 启动服务
	host.Open();  
	// doSomething(...)
	host.Close(); 
}

其实,以上信息均可以在配置文件中实现,当配置发生改变时可以不用重新编译程序集。

B:绑定 - Binding,服务的通信方式,How to communicate with service?,实现Client和Service通信的所有底层细节,解决WCF服务的通信问题;

在这给出支持双工通信的两种协议:

  • wsDualHttpBinding:基于HTTP传输协议HTTP协议是基于请求-回复的传输协议,基于HTTP的通道本质上是单向的
  • netTcpBinding:完全基于支持双工通信的TCP协议
wsDualHttpBinding 实际上创建了两个通道,一个用于客户端向服务端的通信,而另一个则用于服务端到客户端的通信,从而间接地提供了双工通信的实现。

C:契约 - Contract,服务的内容,What functionalities do the Service provide?,见下;

除此之外,还有一个 Behavior,用于定制Endpoint在运行时的一些必要的动作等。

WCF架构体系是基于拦截机制的,一个完整的 WCF 解决方案包括如下四个部分:

  1. 契约(Contracts):主要定义了实现那些服务,如何访问服务
  2. 服务(Services):实现契约定义的方法
  3. 宿主程序(Hosting):提供低层传输功能的支持
  4. 客户端(Client): 根据契约访问服务

Metadata(元数据)

元数据交换(Metadata Exchange):服务端要提供服务的接口描述(或类型描述)、操作的方法签名,相关的数据描述等给客户端

配置文件

这里给出配置文件的完整结构代码,其中:

  • services:[必须],配置服务、接口和终结点
  • bindings:[可有可无],配置绑定协议
  • behaviors:[可有可无],配置行为、认证等
<?xml version="1.0" encoding="utf-8"?>
 <configuration>
   <system.serviceModel>
 
     <!--配置服务和终结点-->
     <services>
       <service name="服务命名空间.服务协定" behaviorConfiguration="myBehavior">
	     // 宿主服务地址
		 <host>
           <baseAddresses>
             <add baseAddress="wcfURI"/>
           </baseAddresses>
         </host>
         // 服务终结点
		 <endpoint address="xxx" binding="wsHttpBinding" contract="服务命名空间.服务协定接口" bindingConfiguration="myBinding"></endpoint>
	     // 元数据交换结点
		 <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
	   </service>
     </services>
 
     <!--配置绑定-->
     <bindings>
       <netTcpBinding>
         <binding name="myBinding"></binding>
       </netTcpBinding>
     </bindings>
 
     <!--配置行为-->
     <behaviors>
       <serviceBehaviors>
         <behavior name="myBehavior"></behavior>
       </serviceBehaviors>
     </behaviors>
 
   </system.serviceModel>
 </configuration>

下面给出一个简单的 WCF 程序的资源结构图

  

注意,其中的 .svc 文件就是服务托管文件

3.1 契约

WCF的基本概念是以 契约(Contract)  来定义双方通信的协议,合约必须要以接口的方式来体现,而实际的服务代码必须要由这些合约接口派生并实现。契约与平台无关、以XML格式发布,是消息参与者之间的约定,提供服务通信所必需的元数据。首先,了解下代码中的几种契约属性的形式

// 数据协定
// 指定该类型要定义或实现一个数据协定,并可由序列化程序(如 System.Runtime.Serialization.DataContractSerializer)进行序列化
// 若要使其类型可序列化,类型作者必须为其类型定义数据协定
[DataContract]
// 数据成员协定
// 当应用于类型的成员时,指定该成员是数据协定的一部分并可由 System.Runtime.Serialization.DataContractSerializer 进行序列化
[DataMember]

// 服务协定
// 指示接口或类在 应用程序中定义服务协定
[ServiceContract]
// 操作协定
// 指示方法定义一个操作,该操作是 应用程序中服务协定的一部分
[OperationContract]

下面通过代码示例,具体研究各个契约的用法

  • 服务契约(Service Contract):定义整个服务(接口
  • 操作契约(Operation Contract):定义服务提供的方法
using System.ServiceModel;
using System.Runtime.Serialization;
using SqhWcfFunction;
namespace SqhWcfService
{
    [ServiceContract]  // 服务契约
    public interface ISqhService{
        [OperationContract]   // 操作契约
        QueryOut QueryPersonInfoService(QueryIn inParam);
    }
}

:可以在特性中使用Namespace和Name属性,实现对命名空间和协定方法名称的加密。具体信息在Service References:SqhService下的SqhService.wsdl文件中查看。其中,WSDL(网络服务描述语言,Web Services Description Language)是基于XML的语言,用于描述Web Services、服务元数据以及如何对它们进行访问。=> WSDL 教程 | W3School

下面的服务协定接口的实现

namespace SqhWcfService
{
    public class SqhService : ISqhService
    {
        public QueryOut QueryPersonInfoService(QueryIn inParam)
        {
            QueryOut outParam = new QueryOut();
            try
            {
                outParam = SqhServiceFunction.QueryPersonInfo(inParam);
            }
            catch (Exception e)
            {
                outParam.ErrMsg = "查询person信息时发生异常," + e.Message;
                outParam.isSuccessed = false;
                return outParam;
            }
            return outParam;   
        }
    }
}

其中,SqhWcfFunction.SqhServiceFunction 是专门定义的数据处理函数类。

  • 数据契约(Data Contract):定义双方通信时的数据格式,类型可序列化,用于数据传输。使用数据协定可以灵活控制哪些成员可以被客户端识别。
using System.ServiceModel;
using System.Runtime.Serialization;
namespace SqhWcfFunction
{
    [DataContract]  // 数据契约
    public class QueryIn{
        [DataMember]   // 数据成员契约,标识类型可序列化
        public string personID;
        [DataMember]
        public string personName;
    }

    [DataContract]
    public class QueryOut{
        [DataMember]
        public string personResInfo;
        [DataMember]
        public bool isSuccessed;
    }
}

关于数据协定的详细使用,可参见:WCF - 数据协定

  • 消息契约(Message Contract):定义在通信期间改写消息内容的规范和信息格式化方式

更好地控制SOAP头和SOAP体,支持序列化期间的安全机制。所有的消息契约必须实现一个public的无参构造函数。

给出一个 WCF 的简单 Demo,参见:http://www.cnblogs.com/iamlilinfeng/p/4083827.html

3.2 宿主程序

WCF本身不能够独自运行,必须寄宿在一个宿主进程(Host Process)中。服务寄宿的目的就是开启一个进程,为WCF服务提供一个运行的环境。单个宿主进程可以托管多个服务,相同的服务也可以托管在多个宿主进程中。

  • Windows进程激活服务:WAS,支持所有的绑定协议
  • IIS:客户端第一次请求时宿主进程自动启动,管理宿主进程的生命周期
  • 自驻留(自托管):Self-Hosting,控制台程序、窗体程序等,由开发者提供和管理宿主进程生命周期,且适应任意WCF传输协议,但稳定性差

关于通过控制台承载寄宿服务,相关资料可参见:http://blog.csdn.net/songyefei/article/details/7363296

关于在 IIS 中寄宿 WCF 服务

在 IIS 中宿主服务的主要优点:发生客户端请求时宿主进程会被自动启动,并且可以依靠 IIS 管理宿主进程的生命周期

相关资料可参见:

首先,一个服务要部署到 IIS 上,必须要生成待部署的文件包,具体操作:

[1]. 右键项目,生成部署包
[2]. ...objDebugPackagePackageTmp 目录下的文件就是待部署的文件
[3]. PackageTmp 下的文件全部拷贝到 IIS 下的相关文件夹中即可

下面对 IIS 寄宿 WCF 服务中遇到的几个问题进行总结:

问题1:
“/SQHWCFSERVICE”应用程序中的服务器错误。
配置错误
 说明: 在处理向该请求提供服务所需的配置文件时出错。请检查下面的特定错误详细信息并适当地修改配置文件。
 分析器错误消息: 无法识别的属性“targetFramework”。请注意属性名称区分大小写。
源错误:
     <compilation debug="true" targetFramework="4.0"/>
版本信息: Microsoft .NET Framework 版本:2.0.50727.4961; ASP.NET 版本:2.0.50727.4955 

原因:.NET Framework 版本不匹配,IIS 配置 和 程序配置的版本不一致

解决方法:修改.NET Framework 版本为相应版本,将 2.0 改为 4.0 版本

具体参见:http://blog.csdn.net/muchlin/article/details/6800863

问题2:
应用程序“NET/CRM”中的服务器错误  Internet Information Services 7.5
错误摘要:HTTP 错误 404.2 - Not Found
由于 Web 服务器上的“ISAPI 和 CGI 限制”列表设置,无法提供您请求的页面。

原因:ISAPI和CGI限制没有允许需要的版本

解决方法:具体可参见上面的链接。

注意,如果ISAPI和CGI限制列表中不包含需要的版本,需要手动添加 C:WindowsMicrosoft.NETFrameworkv4.0.30319aspnet_isapi.dll

问题3:
通过IIS直接浏览页面时报错:HTTP 错误 404.3 - Not Found 由于扩展配置问题而无法提供您请求的页面。
如果该页面是脚本,请添加处理程序。如果应下载文件,请添加 MIME 映射。

解决方法:为 IIS 重新注册 .net framework 4.0,具体参见:http://blog.csdn.net/g200407331/article/details/9078219

  • 确保 控制面板->打开或关闭windows功能->Internet信息服务->万维网服务->应用程序开发功能 勾选上 “.net扩展性”和“ASP.NET”,保存后,重启 IIS
  • Visual Studio 命令提示工具中,输入命令:  aspnet_regiis -i,注册 .net framework 4.0

注意,要以管理员权限运行Visual Studio 命令提示工具,该工具在电脑左下角的开始中可以找到。

为了保险起见,在 IIS 根目录下,双击 MIME 类型,添加如下信息

文件扩展名:.json
MIME 类型:application/json

3.3 服务

Service:服务定义,注重逻辑

using System.ServiceModel;
using System.Runtime.Serialization;
using SqhWcfFunction;
namespace SqhWcfService{
    public class SqhService : ISqhService
    {
        public QueryOut QueryPersonInfoService(QueryIn inParam)
        {
            QueryOut outParam = new QueryOut();
            try{
                outParam = SqhServiceFunction.QueryPersonInfo(inParam);
            }
            catch (Exception e){
                outParam.ErrMsg = "查询person信息时发生异常," + e.Message;
                outParam.isSuccessed = false;
                return outParam;
            }
            return outParam;   
        }
    }
}

Function:服务函数,具体实现

namespace SqhWcfFunction{
    public class SqhServiceFunction
    {
        public static QueryOut QueryPersonInfo(QueryIn inParam)
        {
            QueryOut outParam = new QueryOut();
            try{
                // 动态SQL、访问数据库等其他各种操作
                outParam.isSuccessed = true;
                outParam.personResInfo = string.Format("My WCF Service:{0}-{1}", inParam.personID, inParam.personName);
            }
            catch (Exception e){
                outParam.ErrMsg = "查询失败" + e.Message;
                outParam.isSuccessed = false;
                return outParam;
            }
            return outParam;
        }
    }
}

3.4 客户端

客户端应用程序通过代理类与WCF服务进行通信,代理类为WCF服务实现了服务契约接口,对这个接口的操作方法的所有调用都重定向到WCF服务上。

using System.ServiceModel;
using System.Runtime.Serialization;
using SqhWcfClient.SqhService;
namespace SqhWcfClient
{
    public class Program{
        static void Main(string[] args){
            SqhServiceClient SqhClient = new SqhServiceClient();  // 客户端
            QueryIn inParam = new QueryIn(){ personID = "001", personName = "sqh" };
            QueryOut outParam = SqhClient.QueryPersonInfoService(inParam);
            if(outParam.isSuccessed)
                Console.WriteLine(outParam.personResInfo);
            SqhClient.Close();   // 关闭客户端
        }
    }
} 

关于双工消息通信模式(异步)的实现

WCF 支持多种消息格式:单向、请求/回复、双工,但必须绑定对应的协议。

  • 请求/回复:basicHttpBinding,客户端同步等待服务端处理结果
  • 单向:OperationContract 操作协定必须标明 IsOneWay = true 属性,相应操作方法不得声明输出参数out、引用参数或返回值
  • 双工:wsDualHttpBinding,ServiceContract 服务协定必须标明 CallbackContract = typeof(服务回调协定接口) 属性,回调协定接口里的方法标明 IsOneWay = true 属性

三种方式的基本流程参见:WCF 通信模式

该部分通过一个简单的加法方法来了解 WCF 的双工通信方式:

[1]. 服务端

首先给出服务协定接口的代码,注意服务接口和服务回调接口的命名方式

using System.ServiceModel;
namespace sqhWcfService
{
    [ServiceContract(SessionMode = SessionMode.Required,
        CallbackContract = typeof(IServiceAsyncCallback))]
    public interface IServiceAsync
    {
        [OperationContract(IsOneWay = true)]
        void AddTwoInteger(int a, int b);
    }

    [ServiceContract]
    public interface IServiceAsyncCallback
    {
        [OperationContract(IsOneWay = true)]
        void ReturnResult(int c);
    }
}

在服务端,需要实现服务协定接口的方法,服务协定回调接口的方法在客户端实现。服务协定接口必须指明 SessionMode.Required 和 回调协定属性。注意接口命名格式,推荐: 服务协定回调接口 = 服务协定接口 + Callback 

using System.ServiceModel;
namespace sqhWcfService
{
    public class ServiceAsync : IServiceAsync
    {
        public void AddTwoInteger(int a, int b)
        {
            int result = a + b;
            Console.WriteLine("AddTwoInteger:{0}", result);
            IServiceAsyncCallback callback 
                = OperationContext.Current.GetCallbackChannel<IServiceAsyncCallback>();
            callback.ReturnResult(result);          
        }
    }  
}

注意,在 Web.config 文件 <system.serviceModel> 结点下添加如下结点

<services>
  <service name="sqhWcfService.ServiceAsync">       
    <endpoint address="ServiceAsync" binding="wsDualHttpBinding" contract="sqhWcfService.IServiceAsync" />
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
  </service>
</services>

注意,双工通信必须是 wsDualHttpBinding 绑定,该属性会建立两条绑定实现互相调用,也可以是 NetTcpBinding。

至此,服务端配置完毕,记录下服务的地址备用。

[2]. 客户端

首先添加服务引用,命名为:sqhWcfServiceReference,然后实现服务回调接口的方法

using sqhWcfClient.sqhWcfServiceReference;
namespace sqhWcfClient
{
    public class ServicAsyncCallback : IServiceAsyncCallback
    {
        public void ReturnResult(int c)
        {
            int result = c;
            Console.WriteLine("ReturnResult:{0}", result);
        }
    }
}

下面是实例化客户端、调用服务的代码

using sqhWcfClient.sqhWcfServiceReference;
namespace sqhWcfClient
{
    public class Program
    {
        public static void Main(string[] args)
        {
            // 服务回调对象
            ServicAsyncCallback callbackObj = new ServicAsyncCallback();
            // 服务回调上下文,维护一个服务回调对象
            InstanceContext callbackInstance = new InstanceContext(callbackObj);

            // 利用上下文实例对象初始化代理客户端对象
            ServiceAsyncClient client
                = new ServiceAsyncClient(callbackInstance);

            client.AddTwoInteger(1, 2);
        }
    }
}

至此,客户端代码配置完毕。运行客户端,即可调用服务方法完成加法实现。为了查看异步调用的效果,可以再客户端代码和服务端代码中添加延时。

详细实现信息参见:消息通信模式(下)- 双工在 WCF 中实现双工通信深入理解 - Artech

关于利用 WCF服务库 提供服务

上面介绍的 WCF 服务均是通过 VS新建项目 -> WCF服务应用程序 的方式提供服务,此处介绍通过 VS新建项目 -> WCF服务库 的方式提供服务并部署到 IIS 上。首先了解下 WCF服务应用程序WCF服务库 的区别:

  • WCF服务应用程序 有独立的进程、是可以执行的程序,该项目模板已经基于 IIS 托管
  • WCF服务库 包含WCF服务和契约定义的类库、不能直接运行,需要宿主托管执行

其他区别的详细信息可以参见:WCF Service Application -VS- WCF Service LibraryWCF服务应用程序 - WCF服务库

在项目工作中,推荐使用 WCF服务库,某个服务的类定义为一个单独可以编译的类库,低耦合,可以为其他项目使用、提高代码的复用性

下面的 WCF服务,包含2个 WCF 服务库(具体的独立的服务,CalculateService 和 CertificateService)和1个 ASP.NET 空 Web 应用程序(包含2个WCF服务类库,对外提供服务)

[1]. 创建WCF服务库

首先,分别创建2个独立的WCF服务库:CalculateService(计算服务) 和 CertificateService(注册服务),下面直接给出代码:

(1)CalculateService

 1 namespace RVC.WCF.Calculate
 2 {
 3     [ServiceContract]
 4     public interface ICalculateService
 5     {
 6         [OperationContract]
 7         double Add(double a, double b);
 8         [OperationContract]
 9         double Minus(double a, double b);
10         [OperationContract]
11         double Multiple(double a, double b);
12         [OperationContract]
13         double Divide(double a, double b);
14     }
15 }
ICalculateService.cs
 1 namespace RVC.WCF.Calculate
 2 {
 3     public class CalculateService : ICalculateService
 4     {
 5         public double Add(double a, double b) {
 6             return a + b;
 7         }
 8         
 9         public double Minus(double a, double b) {
10             return a - b;
11         }
12 
13         public double Multiple(double a, double b) {
14             return a * b;
15         }
16 
17         public double Divide(double a, double b) {
18             if ((int)b == 0){
19                 return double.MaxValue;
20             }
21             return a * 1.0 / b;
22         }
23     }
24 }
CalculateService.svc.cs

(2)CertificateService

 1 namespace RVC.WCF.Certificate
 2 {
 3     [ServiceContract]
 4     public interface ICertificateService
 5     {
 6         [OperationContract]
 7         string CertificateUser(string userName, string passWord);
 8         [OperationContract]
 9         UserInfo GetUserInfo(string userID);
10         [OperationContract]
11         bool CancelUser(string userID);
12     }
13 }
ICertificateService.cs
namespace RVC.WCF.Certificate
{
    public class CertificateService : ICertificateService
    {
        private static Dictionary<string, UserInfo> UserDic = new Dictionary<string, UserInfo>();

        public string CertificateUser(string userName, string passWord)
        {
            string userID = DateTime.Now.ToShortTimeString() + passWord;
            UserInfo user = new UserInfo() {
                UserID = userID,
                UserName = userName,
                Password = passWord,
                CertTime = DateTime.Now
            };

            if (UserDic.ContainsKey(user.UserID)) {
                throw new Exception("该用户已存在");
            }
            UserDic.Add(user.UserID, user);

            return userID;
        }

        public UserInfo GetUserInfo(string userID)
        {
            UserInfo ret = new UserInfo();

            if (!UserDic.ContainsKey(userID)){
                throw new Exception("该用户不存在");
            }
            ret = UserDic[userID];

            return ret;
        }

        public bool CancelUser(string userID)
        {
            if (!UserDic.ContainsKey(userID)){
                throw new Exception("该用户不存在");
            }
            UserDic.Remove(userID);
            return true;
        }
    }
}
CertificateService.svc.cs
 1 namespace RVC.WCF.Certificate
 2 {
 3     [DataContract] 
 4     public class UserInfo
 5     {
 6         [DataMember] 
 7         public string UserID;
 8         [DataMember] 
 9         public string UserName;
10         [DataMember] 
11         public string Password;
12         [DataMember] 
13         public DateTime CertTime;
14     }
15 }
UserInfo

创建的 WCF服务库,仅需新增 .cs接口文件 和 .svc.cs接口实现文件,配置文件 App.config 暂时无需改动。注意代码仅供参考,不要过于讲究细节。

[2]. 包装WCF服务库

创建好的 WCF服务库不能直接运行,需要宿主托管才能执行。通过  VS新建项目 -> ASP.NET 空 Web 应用程序,作为该 WCF服务类库的 Host 将上述 WCF服务类库包含进来。

每一个 ASP.NET Web服务 都具有一个 .asmx 文件,客户端通过访问 .asmx 文件实现对相应 web 服务的调用。类似,每个 WCF 服务也具有一个对应的文件,即 .svc 文件。基于 IIS 的服务寄宿要求相应的 WCF 服务具有相应的 .svc 文件,.svc 文件部署于 IIS 站点中,对 WCF 服务的调用体现在对 .svc 文件的访问上。

(1)右键项目,添加一个文件夹,命名为 Services

(2)右键 Services 文件夹,添加 WCF服务 文件,分别命名为 CalculateService.svc 和 CertificateService.svc,同时将 .cs接口文件 和 .svc.cs接口实现文件删除,只留下 .svc服务托管文件

(3)依次修改 .svc 文件为如下形式,分别指向对应的服务实现文件

// CalculatorService
<%@ ServiceHost Language="C#" Debug="true" Service="RVC.WCF.Calculator.CalculatorService" CodeBehind="CalculateService.svc.cs" %>

// CertificateService
<%@ ServiceHost Language="C#" Debug="true" Service="RVC.WCF.Certificate.CertificateService" CodeBehind="CertificateService.svc.cs" %>

其中 Service = "命名空间.服务名称" 表示提供的服务,CodeBehind = "服务接口实现文件" 表示服务实现类。

除此之外,Web.config 暂不改动。至此,服务相关文件全部创建完毕,下面给出资源结构图

其中,右图是为了调试方便,将2个WCF服务类库项目添加到了该Web应用程序中。

为了验证服务是否能运行,右键 RVC.WCF.Service,选择 在浏览器中查看 并进入 Services 文件夹

点击即可查看服务运行情况。

[3]. 在 IIS 上发布 WCF 服务

同样是右键 RVC.WCF.Service,选择 生成部署包,生成文件路径:...RVC.WCF.ServiceobjDebugPackagePackageTmp 

将该文件夹下的文件复制到 IIS 的相关路径下,在 IIS -> 网站 -> 添加网站 或 IIS -> 网站 -> Default Web Site -> 添加应用程序 并指向该路径即可。

至此,将该 WCF 服务成功发布到 IIS 上。

推荐文章:WCF4.0新特性体验(7):IIS无SVC文件托管WCF服务(IIS hosting without an SVC file )

在 WCF 学习中遇到的一些问题

[1]. 如何读取配置文件中 endpoint 结点的地址?

// 方法1
SqhServiceClient SqhClient = new SqhServiceClient();
var endpoint = SqhClient.Endpoint;

// 方法2
string sectionPath = "system.serviceModel/client";
var clientNode = (ClientSection)ConfigurationManager.GetSection(sectionPath);
var endpoints = clientNode.Endpoints;

其中,方法1只能读取某个终结点的信息,而方法2可以读取该服务下所有终结点的信息。

关于方法2的具体解决方法参见:http://www.cnblogs.com/huangxincheng/p/4396284.html

原文地址:https://www.cnblogs.com/wjcx-sqh/p/5929903.html