首页 > 其他分享 >WCF学习-第一个示例

WCF学习-第一个示例

时间:2022-12-19 14:57:39浏览次数:39  
标签:服务 第一个 示例 Text System book WCF using

       最近需要用到WCF,所以对WCF进行了解。在实践中学习新知识是最快的,接下来先做了一个简单的WCF服用应用示例。

       本文的WCF服务应用功能很简单,却涵盖了一个完整WCF应用的基本结构。希望本文能对那些准备开始学习WCF的初学者提供一些帮助。

       在这个例子中,我将实现一个简单的书籍数据查询功能(BookService),即根据书籍ID去查询对应书籍的信息,并显示出来。和传统的分布式通信框架一样,WCF本质上提供一个跨进程、跨机器、跨网络的服务调用。在本例中,客户端和WCF应用服务通过运行在同一台机器上的不同进程模拟。如下图。

  WCF应用服务运行环境

       WCF应用服务不能单独存在,无法独立运行,WCF应用服务需要寄宿于一个正在运行中的进程之内。我们把承载WCF应用服务的进程称为宿主,为WCF应用服务指定宿主的过程称为服务寄宿 (Service Hosting)。在我们的WCF应用服务程序中,通过自我寄宿(Self-Hosting)的方式创建一个控制台应用作为服务的宿主 (寄宿进程为Hosting.exe)。客户端通过一个WindowsFrom应用程序(进程为WinClient.exe)去访问WCF应用服务实现数据查询功能。接下来,我们就一步一步来学习如何创建这样的一个WCF应用。

第一步:创建SCF.WcfService解决方案

       通过Visual Studio 2015创建一个名为“SCF.WcfService”空白的解决方案,添加如下五个项目。项目的类型、承载的功能和相互引用关系如下,整个项目在Visual Studio 2015中的结构如下图所示。

SCF.WcfService的项目结构

  • SCF.WcfService:一个类库项目,提供对WCF服务的实现。引用关系见下图。

 

  • Hosting:一个控制台(Console)应用,实现对定义在SCF.WcfServices项目中的服务的寄宿。引用关系见下图。

 

  • SCF.Model:一个对象实体项目,实现对数据库中的表结构进行实体对象构建。
  • WinClient:一个控制台应用模拟服务的客户端,该项目引用System.ServiceMode程序集。引用关系见下图。

 

  • SCF.Common:SCF. WcfService解决方案中公共使用的类

第二步:创建WCF服务协定

       WCF采用基于接口(MSDN上翻译为:服务协定)的交互方式实现了服务功能,以及客户端和服务端之间的松耦合。WCF包含五种类型的协定:服务协定、操作协定、消息协定、错误协定和数据协定。

       从功能上讲,服务协定将多个相关的操作联系在一起,组成单个功能单元。协定可以定义服务级设置,如服务的命名空间、对应的回调协定以及其他此类设置,以及各种操作。

      从消息交换的角度来讲,服务协定则定义了基于服务调用的消息交换过程中, 请求消息和回复消息的结构,以及采用的消息交换模式。

      从使用编程语言的角度来讲,协定是通过所选的编程语言创建一个接口,然后将 ServiceContractAttribute 属性应用于该接口。通过实现该接口,可生成实际的服务代码。

      我们通过接口的形式定义服务协定。通过下面的代码,将接口IBookService定义成服务协定。WCF广泛采用基于自定义特性(Custom Attribtue)的声明式编程模式,我们通过在接口上应用System.ServiceModel.ServiceContractAttribute特性将一个接口定义成服务协定。在应用ServiceContractAttribute特性的同时,还可以指定服务协定的名称和命名空间。

        通过应用ServiceContractAttribute特性将接口定义成服务协定之后,接口的方法成员并不能自动成为服务的操作。在此方面,WCF采用的是显式选择(Explicit Opt-in)的策略:我们须要在相应的操作方法上面显式地应用OperationContractAttribute特性。

         在SCF.WcfService项目中添加一个IBookService.cs文件,具体代码如下。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace SCF.WcfService
{
    // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的接口名“IBookService”。
    [ServiceContract]
    public interface IBookService
    {
        [OperationContract]
        string GetBook(string Id);
    }
}

第三步:实现WCF服务协定

      当服务协定成功创建时,我们需要通过实现服务协定来创建具体的WCF服务应用。在SCF.WcfServices项目中添加一个BookService.cs文件,此文件用来实现服务协定接口IBookService,实现所有的服务操作。BookService.cs中的代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Data.Entity;
using SCF.Model;
using SCF.Common;

namespace SCF.WcfService
{
    // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码、svc 和配置文件中的类名“BookService”。
    // 注意: 为了启动 WCF 测试客户端以测试此服务,请在解决方案资源管理器中选择 BookService.svc 或 BookService.svc.cs,然后开始调试。
    public class BookService : IBookService
    {
        public string GetBook(string Id)
        {
            int bookId = Convert.ToInt32(Id);
            Books book= SetBook(bookId);
            string xml=XMLHelper.ToXML<Books>(book);
            return xml;
        }
        
        public Books SetBook(int Id)
        {
            Books book = new Books();
            book.BookID = Id;
            book.AuthorID = 1;
            book.Category = "IBM";
            book.Price = 9.99M;
            book.Numberofcopies = 22;
            book.Name = "DB 2";
            book.PublishDate = new DateTime(1986, 2, 23);
            return book;
        }
    }
}

第四步:通过自我寄宿的方式寄宿服务

       WCF服务需要依存一个运行着的进程(宿主),服务寄宿就是为服务指定一个宿主的过程。WCF是一个基于消息的通信框架,采用基于终结点(Endpoint)的通信手段。

       终结点主要由地址(Address)、绑定(Binding)和协定(Contract)三要素组成,如图所示。由于三要素应为首字母分别为ABC,所以就有了易于记忆的公式:Endpoint = ABC。一个终结包含了实现通信所必需的所有信息。如下图。

终结点三要素  

  • 地址(Address):一个指示可以查找终结点的位置的地址。地址决定了服务的位置,解决了服务寻址的问题
  • 绑定(Binding):一个指定客户端如何与终结点进行通信的绑定。绑定实现了通信的所有细节,包括网络传输、消息编码,以及其他为实现某种功能(比如安全、可靠传输、事务等)对消息进行的相应处理。WCF中具有一系列的系统定义绑定,比如BasicHttpBinding、WsHttpBinding、NetTcpBinding等,
  • 协定(Contract):一个标识可用操作的协定。协定是对服务操作的抽象,也是对消息交换模式以及消息结构的定义。
  • 行为(Behavior):一组指定终结点的本地实现细节的行为。

      服务寄宿的目的就是开启一个进程,为WCF服务应用提供一个运行的环境。通过为服务添加一个或多个终结点,使之暴露给潜在的服务调用者。服务调用者最终通过相匹配的终结点对该服务进行调用。

一)代码方式实现寄宿

      我们可以通过代码的方式完成所有的服务寄宿工作。在Hosting项目中的Program.cs文件中的Main方法中,通过代码实现对 BookService的WCF服务应用的寄宿实现。具体代码如下:

using SCF.WcfService;
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Text;
using System.Threading.Tasks;

namespace Hosting
{
    class Program
    {
        // 无配置文件的启动程序
        static void Main(string[] args)
        {
            using (ServiceHost host = new ServiceHost(typeof(BookService)))
            {
                host.AddServiceEndpoint(typeof(IBookService), new WSHttpBinding(), "http://127.0.0.1:8888/BookService");
                if (host.Description.Behaviors.Find<ServiceMetadataBehavior>() == null)
                {
                    ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();
                    behavior.HttpGetEnabled = true;
                    behavior.HttpGetUrl = new Uri("http://127.0.0.1:8888/BookService/metadata");
                    host.Description.Behaviors.Add(behavior);
                }
                host.Opened += delegate
                {
                    Console.WriteLine("BookService,按任意键终止服务!");
                };
                host.Open();
                Console.Read();
            }
        }
    }
}

       WCF服务寄宿通过一个特殊的对象完成:ServiceHost。在上面的代码基本实现的功能说明,基于WCF服务应用的类型(typeof(BookService))创建了ServieHost对象,并添加了一个终结点。具体的地址为http://127.0.0.1:8888/BookService,采用了WSHttpBinding,并指定了服务协定的类型IBookService。

       松耦合是SOA的一个基本的特征,WCF服务应用中客户端和服务端的松耦合体现在客户端只需要了解WCF服务基本的描述,而无需知道具体的实现细节,就可以实现正常的WCF服务调用。WCF服务的描述通过元数据(Metadata)的形式发布出来。WCF中元数据的发布通过一个特殊的服务行为ServiceMetadataBehavior实现。在上面提供的服务寄宿代码中,我们为创建的ServiceHost添加了ServiceMetadataBehavior,并采用了基于HTTP-GET的元数据获取方式,元数据的发布地址通过ServiceMetadataBehavior的HttpGetUrl指定。在调用ServiceHost的Open方法对服务成功寄宿后,我们可以通过该地址获取服务相关的元数据。

      3) 运行已经生成的hosting.exe,然后在浏览器地址栏上键入http://127.0.0.1:8888/BookService/metadata,你将会得到以WSDL形式体现的服务元数据,如下图所示。

通过HTTP-GET的方式获取WCF服务的元数据

二)配置文件方式实现寄宿

       在实际应用中,对于WCF应用服务的寄宿,一般不会直接通过编码的方式进行终结点的添加和服务行为的定义,而是通过写配置文件的方式实现,这样可以方便修改。

      1)  现在我在Hosting项目中添加一个app.config配置文件,把下面的配置信息添加到配置文件app.config中。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="metadataBehavior">
          <serviceMetadata httpGetEnabled="true" httpGetUrl="http://127.0.0.1:8888/BookService/metadata" />
        </behavior>
      </serviceBehaviors>
    </behaviors> 
    <services>
      <service  behaviorConfiguration="metadataBehavior" name="SCF.WcfService.BookService">
        <endpoint address="http://127.0.0.1:8888/BookService" binding="wsHttpBinding" bindingConfiguration="" contract="SCF.WcfService.IBookService" />
      </service>
    </services>
  </system.serviceModel>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
  </startup>
</configuration>

      2) 增加app.config配置文件与配置信息之后,我们原来写的寄宿代码就不能使用了,需要进行服务寄宿代码的修改,而且代码会变的更简洁,只需几行代码就可以了。代码如下。

static void Main(string[] args)
{
    try
    {
        using (ServiceHost host = new ServiceHost(typeof(BookService)))
        {
            host.Opened += delegate
            {
                Console.WriteLine("BookService,使用配置文件,按任意键终止服务!");
            };
            host.Open();
            Console.Read();
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

      3) 执行hosting.exe应用程序,结果如下图。

第五步:创建客户端

      WCF应用服务被成功寄宿后,WCF服务应用便开始了服务调用请求的监听工作。此外,服务寄宿将服务描述通过元数据的形式发布出来,相应的客户端就可以获取这些元数据。接下来我们来创建客户端程序进行服务的调用。

      1) 现在请先运行服务寄宿程序(Hosting.exe)。

      2) 在Visual Studio 2015的“解决方案资源管理器”中,把WinClient项目展开,左键选中“引用”,点击鼠标右键,弹出菜单,在弹出的上下文菜单中选择“添加服务引用(Add Service References)”。如下图。

      3) 此时会弹出一个对话框,如下图所示。在对话框中的“地址”输入框中输入服务元数据发布的源地址:http://127.0.0.1:8888/BookService/metadata,并在“命名空间”输入框中输入一个命名空间,然后点击“确定”按钮(如下图)。Visual studio 2015会自动生成一系列用于服务调用的代码和配置。

添加服务引用

      4)  在Visual Studio 2015自动生成的类中,包含一个服务协定接口、一个服务代理对象和其他相关的类。被客户端直接用于服务调用的是一个继承自 ClientBase<IBookService>并实现了IBookService接口的服务代理类BookServiceClient。 如下图。

 

     5)BookServiceClient的具体实现如下图。

 

      6)  我们可以实例化BookServiceClient对象,执行相应方法调用WCF服务操作。客户端进行WCF服务调用的代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace WinClient
{
    public partial class FrmBook : Form
    {
        public FrmBook()
        {
            InitializeComponent();
        }

        private void btnGetBook_Click(object sender, EventArgs e)
        {
            BookServiceRef.Books book = new BookServiceRef.Books();
            BookServiceRef.BookServiceClient bookSvrClient = new BookServiceRef.BookServiceClient();
            textBoxMsg.Text = bookSvrClient.GetBook("3");
            book = XMLHelper.DeSerializer<BookServiceRef.Books>(textBoxMsg.Text);
            txtBookId.Text = book.BookID.ToString();
            txtAuthorID.Text = book.AuthorID.ToString();
            textBoxName.Text = book.Name;
            textBoxCategory.Text = book.Category.ToString();
            textBoxPrice.Text = book.Price.ToString();
        }
    }
}

运行后的结果,如下图。

 

第六步:客户端通过ChannelFactory<T>方式调用WCF服务

      客户端通过服务代理对象进行服务的调用,上面的例子通过创建自动生成的继承自ClientBase<T>的类型对象进行服务调用。实际上,我们还具有另外一种创建服务代理的方法,就是通过ChannelFactory<T>。此外,WCF采用基于协定的服务调用方法,从上面的例子我们也可以看到,Visual Studio 2015在进行服务引用添加的过程中,会在客户端创建一个与服务端等效的服务协定接口。在我们的例子中,由于服务端和客户端都是在同一个解决方案中,完全可以让服务端和客户端引用相同的协定。

       1)  为了实现通过ChannelFactory<T>调用WCF服务的功能,我们需要添加一个新的项目,命名为SCF.Contracts,把原来在SCF. WcfService项目中的IBookService.cs文件移到SCF.Contracts项目中,同时变更命名空间。SCF. WcfService与WinClient项目同时添加对SCF.Contracts项目的引用。最后的项目结构如下图。

 

       2)  我们将通过于这个SCF.Contracts项目中的IBookService服务协定接口,使用 ChannelFactory<IBookService>创建服务代理对象,直接进行相应的服务调用。我们先实现全部在代码中实现基于 ChannelFacotory<T>进行服务代理的创建和服务调用的方式。代码如下:

private void buttonChannelFactory_Click(object sender, EventArgs e)
{
    using (ChannelFactory<IBookService> channelFactory = new ChannelFactory<IBookService>(new WSHttpBinding(), "http://127.0.0.1:8888/BookService"))
    {
        IBookService proxy = channelFactory.CreateChannel();
        using (proxy as IDisposable)
        {
            textBoxMsg.Text = proxy.GetBook("4");
            Books book = XMLHelper.DeSerializer<Books>(textBoxMsg.Text);
            txtBookId.Text = book.BookID.ToString();
            txtAuthorID.Text = book.AuthorID.ToString();
            textBoxName.Text = book.Name;
            textBoxCategory.Text = book.Category.ToString();
            textBoxPrice.Text = book.Price.ToString();
        }
    }
}

        3)  点击“ChannelFactory方式”按钮之后,结果如下图。

 

       4)  由于终结点是WCF进行通信的唯一手段,ChannelFactory<T>本质上是通过指定的终结点创建用于进行服务调用的服务代理。在上面的代码中,在创建ChannelFactory<T>的时候再在构造函数中指定终结点的相关要素(协定通过范型类型表示,地址和绑定则通过参数指定)。上面这种直接在代码中写相应地址与绑定的方法,对于后期的维护不方便。在实际的WCF应用中,一般在配置文件中写地址与绑定,然后通过读取配置文件的方式来实现服务调用。我们在app.config配置文件中配置指定终结点的三要素,并为相应的终结点指定一个终结点配置名称(BookService)。配置信息如下:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
    </startup>
    <system.serviceModel>
        <bindings>
            <wsHttpBinding>
                <binding name="WSHttpBinding_IBookService" />
            </wsHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://127.0.0.1:8888/BookService" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IBookService"
                      contract="SCF.Contracts.IBookService" name="WSHttpBinding_IBookService">
                <identity>
                    <userPrincipalName value="DEVELOPER\Administrator" />
                </identity>
            </endpoint>
        </client>
    </system.serviceModel>
</configuration>

      5) 接下来我们通过配置信息来创建ChannelFactory<T>对象,此时无须再指定终结点的绑定和地址了,而只须制定对应的终结点配置名称。代码如下。

private void buttonChannelConfig_Click(object sender, EventArgs e)
{
    using (ChannelFactory<IBookService> channelFactory = new ChannelFactory<IBookService>("WSHttpBinding_IBookService"))
    {
        IBookService proxy = channelFactory.CreateChannel();
        using (proxy as IDisposable)
        {
            textBoxMsg.Text = proxy.GetBook("5");
            Books book = XMLHelper.DeSerializer<Books>(textBoxMsg.Text);
            txtBookId.Text = book.BookID.ToString();
            txtAuthorID.Text = book.AuthorID.ToString();
            textBoxName.Text = book.Name;
            textBoxCategory.Text = book.Category.ToString();
            textBoxPrice.Text = book.Price.ToString();
        }
    }
}

      6) 点击“ChannelFactorys配置方式”按钮之后,结果如下图。

 

注: 我在编写这个示例项目的过程中遇到了以下问题:

WCF由于目标计算机积极拒绝,无法连接错误

错误描述:新建的WCF类库项目,由WinForm程序托管,托管的时候没有错误,但是在客户端引用服务的时候,却找不到服务,而且 如果打开多个服务也不会报端口占用错误。

解决思路:

1)检查配置文件,看配置信息是否写的正确,不行

2)重启电脑,不行

3)把配置方式改成了直接代码方式,不行

4)不寄宿WCF服务的情况下引用服务,提示一样的错误。我打开监听端口仔细找,没有找到我定义的8888端口。看来WCF服务寄宿没有成功,如果寄宿成功,端口肯定是在监听状态。

解决办法:首先去掉using,然后再试,引用服务成功,调用也成功。原来当使用using时,如果using被释放,则host会被using关闭,所以服务打开之后,然后就立即被关闭了。请仔细观察下面两段代码的区别。

错误代码:

static void Main(string[] args)
{
    try
    {
        using (ServiceHost host = new ServiceHost(typeof(BookService)))
        {
            host.Opened += delegate
            {
                Console.WriteLine("BookService,使用配置文件,按任意键终止服务!");
            };
            host.Open();
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
    Console.Read();
}

正确代码:

static void Main(string[] args)
{
    try
    {
        using (ServiceHost host = new ServiceHost(typeof(BookService)))
        {
            host.Opened += delegate
            {
                Console.WriteLine("BookService,使用配置文件,按任意键终止服务!");
            };
            host.Open();
            Console.Read();
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

 

标签:服务,第一个,示例,Text,System,book,WCF,using
From: https://www.cnblogs.com/storm12/p/16992129.html

相关文章