1、WCF概述
WCF全称为Windows Communication Foundation,在.Net 3.0 中引入,用于客户端与服务端通信,替换了之前的一些技术,如.Net Remoting 及 WSE。
WCF 相比ASP.NET Web API 复杂,但提供了更多的功能,如
(1)、可靠性
(2)、事务
(3)、Web服务安全
如果不需要这些先进的通信功能,ASP.NET Web API是更好的选择。
2、WCF 主要功能
(1)、存储组件和服务,可以将WCF服务存放在ASP.NET 运行库、Windows服务、COM+进程或WPF应用程序中,进行对等计算
(2)、声明行为,不需要继承基类,可以使用属性定义服务。
(3)、通信信道,WCF提供了用HTTP、TCP、IPC信道进行通信的多条信道,支持自定义信道。
(4)、安全结构,为实现独立于平台的WEB服务,必须使用标准化的安全环境,标准用WSE3.0实现。
(5)、可扩展性,支持将功能注入客户端和服务端的消息流。
(6)总结
!最终目标:通过进程或不同系统,通过本地网络或Internet收发客户端和服务之间的消息,如果需要以独立于平台的方式尽快收发消息,就应该使用WCF。
!远距离视图
服务提供了一个端点,由协定、绑定、地址描述。
a:协定,定义了服务提供的操作
b:绑定,给出了协议和编码信息
c:地址,是服务的位置,客户端需要一个兼容的端点来进行访问。
3、WCF 组件及步骤解读
步骤解读:
(1)、客户端,客户端调用代理的一个方法
(2)、代理,代理将方法调用转化为一条消息,并把该消息传输到信道上。
(3)、信道,包含客户端部分及服务端部分,他们通过网络协议进行通信,在信道上,把消息传递给调度程序。
(4)、调度程序,将消息转换为服务调用的方法调用。
4、WCF 其他重要技术
(1)、SOAP协议
服务从客户端接收SOAP消息,并返回一条SOAP响应消息,SOAP消息包含信封,信封包含标题和正文。
(2)、WSDL
WSDL文档描述了服务的操作和消息,WSDL定义了服务的元数据,这些元数据用于为客户端应用程序创建代理。
WSDL包含如下信息:
a:消息的类型——用XML架构描述
b:从服务中收发的信息——消息的各部分用XML架构定义的类型
c:端口类型——映射服务协定,列出用服务协定定义的操作,操作包含信息,如与请求和响应序列一起使用的输入和输出消息
d:绑定信息——包含用端口类型列出的操作,并定义使用的SOAP变体
e:服务信息——把端口类型映射到端点地址
5、WCF实战——预定会议室
5.1 前言
业务场景,存储会议室预订信息,应用mysql数据库中的roomreservations表存储预订信息,
a:主要实施步骤包括
(1)创建服务和数据协定
(2)使用 自定义类库 访问数据库
(3)实现服务
(4)使用WCF服务宿主(Service Host)和WCF测试客户端(Test Client)
(5)创建定制的服务宿主
(6)使用元数据创建客户应用程序
(7)使用共享的协定创建客户应用程序
(8)配置诊断设置
b:主要包括:数据访问类、协定(服务协定、数据协定)、自定义宿主程序、客户端、服务实现。
类说明:
(1)Net.BCloudSoft.Core.DataAccess 数据访问底层代码。
RoomReservationData 调用数据访问提供的ExecuteNonQuery方法,实现数据的写入。
(2)RoomReservationContracts ,定义数据协定及服务协定,其中数据协定是数据实体映射,采用DataContract 及 DataMemeber 分别标记类及属性。服务协定,是一个接口类,包括服务协定及操作协定,接口类中声明方法,如ReserveRoom等,在类上添加ServiceContract,在方法上标记OperationContract。
(3)RoomReservationHost,自定义服务宿主,控制台程序,由StartService、StopService组成,Open方法会启动服务的监听器信道,Close方法会停止信道,主要用到的核心类是ServiceHost,ServiceHost类实例化时,可以将服务及服务地址进行处理。
(4)RoomReservationService 服务实现,实现服务协定中定义的接口方法。
(5)RoomReservationClient ,WPF作为客户端,右键添加服务引用,选取Service服务,此时会为RoomReservationService服务生成代理,代理类中会自动生成异步方法,使用async及await关键字调用ReserveRoomAsync() 异步方法,使订阅功能支持异步。
5.2 具体实现
(1)创建数据协定和服务协定
整体结构:
1.1 创建数据协定
创建RoomReservationContracts类库,新建RoomReservation类,该类主要包含数据协定,具体属性字段与数据库中表对应。
using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.CompilerServices; using System.Runtime.Serialization; namespace RoomReservationContracts {/// <summary>/// 数据协定/// </summary>[DataContract]public class RoomReservation : INotifyPropertyChanged{private int _id;private string _roomName;private DateTime _startTime;private DateTime _endTime;private string _contact;private string _text;/// <summary>///要通过WCF服务发送数据, 引入DataContract 和 DataMember 特性对该类进行注解/// </summary>[DataMember]public int Id{get { return _id; }set { SetProperty(ref _id, value); }}[DataMember][StringLength(45)]public string RoomName{get { return _roomName; }set { SetProperty(ref _roomName, value); }}[DataMember]public DateTime StartTime{get { return _startTime; }set { SetProperty(ref _startTime, value); }}[DataMember]public DateTime EndTime{get { return _endTime; }set { SetProperty(ref _endTime, value); }}[DataMember][StringLength(45)]public string Contact{get { return _contact; }set { SetProperty(ref _contact, value); }}[DataMember][StringLength(45)]public string Text{get { return _text; }set { SetProperty(ref _text, value); }}/// <summary>/// 接口需实现的事件方法/// </summary>public event PropertyChangedEventHandler PropertyChanged;/// <summary>/// 得知属性发生变更时(定义为protected virtual 允许子类覆写本方法)/// </summary>/// <param name="propertyName">属性字段</param>protected virtual void OnNotifyPropertyChanged(string propertyName){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}/// <summary>/// 设置属性/// </summary>/// <typeparam name="T">传入的类型参数</typeparam>/// <param name="item">字段名</param>/// <param name="value">字段值</param>/// <param name="propertyName"></param>protected virtual void SetProperty<T>(ref T item,T value,[CallerMemberName] string propertyName = null){//如果两个类型的对象T 是否不等if (!EqualityComparer<T>.Default.Equals(item, value)){item = value;OnNotifyPropertyChanged(propertyName);}}} }
1.2 创建服务协定
服务提供的操作可由接口定义,服务协定用ServiceContract特性定义,操作由OperationContract特性定义
using System; using System.ServiceModel; namespace RoomReservationContracts {/// <summary>/// 服务协定/// </summary>[ServiceContract(Namespace ="http://www.myfirstwcfservice.com/RoomReservation/2017")]public interface IRoomService{[OperationContract]bool ReserveRoom(RoomReservation roomReservation);[OperationContract]RoomReservation[] GetRoomReservations(DateTime fromTime, DateTime dateTime);} }
(2)、创建数据访问接口及实现类
本实例中使用自定义的数据访问类,基于反射实现的数据访问接口。采用该类添加RoomReservationData 类库,实现具体操作协定。
using System; using RoomReservationContracts; using Net.BCloudSoft.Core.DataAccess; using System.Data; using System.Collections.Generic;namespace RoomReservationData {/// <summary>/// 实现服务类/// </summary>public class RoomReservationRepository{/// <summary>/// 预订房间/// </summary>/// <param name="roomReservation"></param>public bool ReserveRoom(RoomReservation roomReservation){try{IDataAccess dataAccess = DataAccessFactory.Instance().GetDataAccess("server=127.0.0.1;database=wcfstudy_db;uid=root;pwd=admin;");string insertSql = string.Format("INSERT INTO wcfstudy_db.roomreservations VALUES" +"({0},'{1}','{2}','{3}','{4}','{5}')",roomReservation.Id,roomReservation.RoomName,roomReservation.StartTime,roomReservation.EndTime,roomReservation.Contact,roomReservation.Text);dataAccess.ExecuteNonQuery(insertSql);return true;}catch (Exception ex){throw ex;}}/// <summary>/// 获取所有预订信息/// </summary>/// <param name="fromTime"></param>/// <param name="toTime"></param>/// <returns></returns>public RoomReservation[] GetReservations(DateTime fromTime,DateTime toTime){try{IDataAccess dataAccess = DataAccessFactory.Instance().GetDataAccess("MySqlDataAccess");DataTable dt = dataAccess.ExecuteDataTable(string.Format("SELECT * FROM [wcfstudy_db].[roomreservations] WHERE [StartTime] > '{0}' AND [EndTime] <'{1}'", fromTime, toTime));List<RoomReservation> list = new List<RoomReservation>();foreach (DataRow row in dt.Rows){RoomReservation roomReservation = new RoomReservation();roomReservation.Id = int.Parse(row["Id"].ToString());roomReservation.RoomName = row["RoomName"].ToString();roomReservation.StartTime = Convert.ToDateTime(row["StartTime"]);roomReservation.EndTime = Convert.ToDateTime(row["EndTime"]);roomReservation.Contact = row["Contact"].ToString();roomReservation.Text = row["Text"].ToString();list.Add(roomReservation);}return list.ToArray();}catch (Exception ex){throw ex;}}} }
(3)、服务的实现
3.1 基于 WCF 服务库直接生成的模型,默认包含服务协定和服务实现。
a:根据模板创建的服务实现
b:根据模板创建的服务协定
3.2 如果客户应用程序只使用 元数据 信息来创建访问服务的代理,则使用visio studio 创建基于 WCF 服务库的模板是可行的。
但是,如果客户端直接使用协定类型,则最好把协定放在一个独立的程序集中,如本例所示。
3.3 本案例中服务的实现
using System; using RoomReservationContracts; using RoomReservationData; using System.ServiceModel;namespace RoomReservationService {[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]public class RoomReservationService : IRoomService{/// <summary>/// 获取所有预订信息/// </summary>/// <param name="fromTime">开始时间</param>/// <param name="dateTime">结束时间</param>/// <returns></returns>public RoomReservation[] GetRoomReservations(DateTime fromTime, DateTime dateTime){RoomReservationRepository repository = new RoomReservationRepository();return repository.GetReservations(fromTime, dateTime);}/// <summary>/// 预订房间/// </summary>/// <param name="roomReservation"></param>/// <returns></returns>public bool ReserveRoom(RoomReservation roomReservation){RoomReservationRepository repository = new RoomReservationRepository();return repository.ReserveRoom(roomReservation);}} }
3.4 配置AppConfig
在创建WCF 服务库时会创建AppConfig 文件,主要用于配置服务实现及服务协定,具体配置方法如下所示
<?xml version="1.0" encoding="utf-8" ?> <configuration><appSettings><add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" /></appSettings><system.web><compilation debug="true" /></system.web><!-- 部署服务库项目时,必须将配置文件的内容添加到主机的 app.config 文件中。System.Configuration 不支持库的配置文件。 --><system.serviceModel><services><!--service 节点中 配置 实现类--><service name="RoomReservationService.RoomReservationService"><!--contract 节点中配置接口类--><endpoint address="" binding="basicHttpBinding" contract="RoomReservationContracts.IRoomService"><identity><dns value="localhost" /></identity></endpoint><endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /><host><baseAddresses><add baseAddress="http://localhost:8733/Design_Time_Addresses/RoomReservationService/Service1/" /></baseAddresses></host></service></services><behaviors><serviceBehaviors><behavior><!-- 为避免泄漏元数据信息,请在部署前将以下值设置为 false --><serviceMetadata httpGetEnabled="True" httpsGetEnabled="True"/><!-- 要接收故障异常详细信息以进行调试,请将以下值设置为 true。在部署前设置为 false 以避免泄漏异常信息 --><serviceDebug includeExceptionDetailInFaults="False" /></behavior></serviceBehaviors></behaviors></system.serviceModel></configuration>
(4)使用WCF服务宿主和WCF测试客户端
将RoomReservationService 类设置为启动项目,启动项目,WCF服务主机会启动WCF测试客户端,该测试客户端可用于测试应用程序,在输入参数后,点击‘调用’,会执行操作,输入测试数据可查看到响应结果。如下图:
查看数据库,可知数据已经完成写入操作
(5)创建定制的服务宿主
使用WCF可以在任何宿主上运行服务,可以为对等服务创建一个WPF应用程序,可以创建一个Windows 服务,使用WAS 或 IIS 存放该服务,控制台程序也可以演示简单的自定义宿主。创建自定义宿主必须引入 System.ServiceModel和RoomReservationService。具体实现思路是,调用Open方法启动服务的监听器信道,该服务准备用于监听请求。Close方法会停止信道。代码如下:
using System; using System.ServiceModel; using System.ServiceModel.Description; using RoomReservationService; using static System.Console;/// <summary> /// 自定义服务宿主 /// </summary> namespace RoomReservationHost {public class Program{internal static ServiceHost _serviceHost = null;/// <summary>/// 开启服务监听/// </summary>internal static void StartService(){try{//ServiceHost的构造函数第二个参数定义了服务的基地址,可以设置默认绑定,HTTP//的默认值是basicHttpBinding_serviceHost = new ServiceHost(typeof(RoomReservationService.RoomReservationService),new Uri("http://localhost:9000/RoomReservation"));_serviceHost.Description.Behaviors.Add(new ServiceMetadataBehavior{//获取设置一个值,指示是否发布服务元数据以使用Http/Get请求进行检索。HttpGetEnabled = true});}catch (AddressAccessDeniedException){WriteLine("地址禁止访问,使用netsh.exe 注册监听端口");throw;}}/// <summary>/// 停止服务监听/// </summary>internal static void StopService(){if(_serviceHost != null && _serviceHost.State== CommunicationState.Opened){_serviceHost.Close();}}static void Main(){StartService();WriteLine("服务正在运行,请退出");ReadLine();StopService();}} }
除以上使用编码方式实现WCF配置外,还可以通过,右键RoomReservationServcie 的App.config文件,点击 编辑 WCF 配置,如下图所示
(6)使用元数据创建客户应用程序
此业务场景下,创建一个包含控件的WPF应用程序,命名为RoomReservationClient。如下图所示
添加服务引用,右键选取RoomReservationClient类库,添加 服务引用,点击发现,查找当前解决方案下的服务,将名称空间设置为RoomReservationService,这将为生成的代理类定义名称。如下图所示:
根据数据协定把RoomReservation 引入到RoomReservationClient 中,RoomServiceClient 是RoomReservationService的客户端代理类(RoomServiceClient 是由上图执行后生成的),该客户端包含由操作协定定义的方法,使用这个客户端,可以将会议室预定信息发送给正在运行的服务。
在代码MainWindow.xaml.cs中,通过click 事件调用的OnReserveRoom方法,通过代理类调用ReserveRoomAsync。_roomReservation 为操作的数据源。代码如下
using System; using System.Windows; using RoomReservationClient.RoomReservationService;namespace RoomReservationClient {/// <summary>/// MainWindow.xaml 的交互逻辑/// </summary>public partial class MainWindow : Window{private RoomReservationService.RoomReservation _roomReservation;public MainWindow(){InitializeComponent();_roomReservation.StartTime = DateTime.Now;_roomReservation.EndTime = DateTime.Now.AddHours(1);this.DataContext = _roomReservation;}private async void onReserveRoom(object sender, RoutedEventArgs e){//RoomServiceClient 是客户端的代理,该客户端包含由操作协定定义的方法,使用这个客户端,//可以将会议室预定信息发送给正在运行的服务。var client = new RoomServiceClient();bool reserved = await client.ReserveRoomAsync(_roomReservation);client.Close();if (reserved){MessageBox.Show("实体保存成功!");}}} }
6、最后
参考的案例还有诊断及与客户端共享协定程序集两部分内容,两部分内容的具体实现并没有跟着书本完成,有兴趣的同学可以自行查看,参考的源代码路径为http://www.wrox.com/go/professionalcsharp6