zoukankan      html  css  js  c++  java
  • c#进阶(5)—— WCF 实现简单预订功能

    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

  • 相关阅读:
    设计模式学习总结:(7)工厂方法模式和抽象工厂模式
    设计模式学习总结:(6)桥模式
    设计模式学习总结:(5)装饰模式
    设计模式学习总结:(4)观察者模式
    设计模式学习总结:(3)策略模式
    设计模式学习总结:(2)模板方法模式
    [算法总结]DFS(深度优先搜索)
    [总结]拓扑排序
    [总结]树与图的遍历
    [算法总结]康托展开Cantor Expansion
  • 原文地址:https://www.cnblogs.com/cklovefan/p/7956449.html
Copyright © 2011-2022 走看看