C# WCF服务(二)

目录

 

WCF学习

WCF概念

第一个WCF程序

场景

让WCF程序寄宿

创建客户端

WCF通信过程

WCF中的 "A","B","C"

Endpoint(终结点)

应用程序间通信

实例

WCF宿主

WCF服务应用程序与WCF服务库

概述

案例

IIS宿主

Winform应用程序宿主

Winform依赖配置文件

WAS宿主


WCF学习

WCF概念

在基于.NET的应用程序开发中,我们由客户机的浏览器访问应用程序服务器,然后通过应用程序服务器中的数据库连接去连接数据库服务器,读取或是操作数据,有时候可能会多一个文件服务器。大家可以观察到,基本上所有的应用都放在了一台服务器上,但对于一个,由于业务上的需要(如:与外部系统交互),一台服务器很难支持所有的应用。我们再看下面的图

客户机使用浏览器访问服务器A,服务器A为了业务需要与其他各种应用部署在服务器B、C、D....再通过WCF技术互相通信,相互访问...然而面向服务的好处不仅仅在此,他还提供了不同语言不同操作系统的可交互性

第一个WCF程序

  1. 新建立空白解决方案,并在解决方案中新建项目,项目类型为:WCF服务应用程序。建立完成后如下图所示

  1. 删除系统生成的两个文件IService1.cs与Service1.svc。

  2. 添加自定义的WCF【服务文件】CalcService.svc,此时VS2019会自动生成WCF接口文件ICalcService.cs,我们在ICalcService中定义WCF方法Add、Mul、Max、Div,在CalcService.svc.cs对该接口的方法进行实现。

    ICalcService.cs

       [ServiceContract]
        public interface ICalcService
        {
            [OperationContract]。
            double Add(double a,double b);
            [OperationContract]
            double Mul(double a,double b);
            [OperationContract]
            double Max(double a,double b);
            [OperationContract]
            double Div(double a,double b);
        }

    CalcService.svc.cs

      
      public class CalcService : ICalcService
        {
            public double Add(double a, double b)
            {
                return a + b;
            }
    ​
            public double Div(double a, double b)
            {
                return a - b;
            }
    ​
            public double Max(double a, double b)
            {
                return a * b;
            }
    ​
            public double Mul(double a, double b)
            {
                return a / b;
            }
        }

    大家可以看到,在WCF中的接口与普通接口的区别只在于两个上下文,其他的和我们正常学习的接口一样。

    定义这个上下文要添加System.ServiceModel的引用。

    [ServiceContract]:来说明接口是一个WCF的接口,如果不加的话,将不能被外部调用。

    [OperationContract]:来说明该方法是一个WCF接口的方法,不加的话同上。

    此时我们的第一个WCF服务程序就建立好了,将CalcService.svc“设为起始页”,然后F5运行一下试试,如下图所示,VS2019自动调用了WCF的客户端测试工具以便我们测试程序:

场景

在生产中经常应用的场景,把WCF程序寄宿在IIS之上。

IIS:指的是微软在所有的windows操作系统上提供的一个服务器管理器,能够将这个windows当做服务器来使用

此时宿主指的就是IIS程序

假设场景如下:A服务器和B服务器。我们把我们刚刚建立的WCF程序“部署”在B服务器上(A,B服务器都放是一台机器),我们的目标是在A服务器的应用程序来访问B服务器的WCF程序,实现服务器端的应用程序通讯

让WCF程序寄宿

  1. 首先我们将WCF应用程序发布一下,然后部署在B服务器的IIS之上。

  2. 如果不发布WCF应用程序也可以,但是要保证每次开启服务要在VS中右键在浏览器中运行CalcService.svc这个文件。所以比较麻烦。

创建客户端

我们这里以Winform应用程序为例,建立地物理地址为本机,但是大家可以想像成B服务器是远程计算机,localhost为一个其他的IP地址。

新建解决方案,并且创建Winform应用程序的项目。命名为:WCFClient,如下图所示:

设计好Winform的UI界面

现在目标调用http://localhost:54544/CalcService.svc?wsdl远程服务中提供的运算功能

  1. 在客户端程序的"引用"目录上“右键->添加服务引用”

  2. 在地址中填写我们的目标服务地址,点“转到”,就能获知这个服务现在是否开启

  3. 命名空间中给服务重新命名,点“确定”

  4. 在计算按钮的事件中直接调用服务对象,使用计算功能

    在添加的引用的命名空间中有一个类“CalcServiceClient”,实例化这个类,就等于实例化了WCF服务中的类CalcService.svc.cs

           
     private void button1_Click(object sender, EventArgs e)
            {
                double a = double.Parse(textBox1.Text);
                double b = double.Parse(textBox2.Text);
                ServiceCalc.CalcServiceClient client = new ServiceCalc.CalcServiceClient();
                switch (comboBox1.SelectedItem)
                {
                    case "+":
                        label1.Text = client.Add(a,b).ToString();
                        break;
                    case "-":
                        label1.Text = client.Mul(a, b).ToString();
                        break;
                    case "×":
                        label1.Text = client.Max(a, b).ToString();
                        break;
                    case "÷":
                        label1.Text = client.Div(a, b).ToString();
                        break;
                    default:
                        break;
                }
            }

     

WCF通信过程

WCF能够建立一个跨平台的安全、可信赖、事务性的解决方案,是一个WebService,.Net Remoting,Enterprise Service,WSE,MSMQ的并集,有一副很经典的对比图如下:

WCF中的 "A","B","C"

生活中的例子,某一天,公司的领导让你去送一份合同文件,送文件的过程你可以选择的交通方式为“打车”、“公交”、“地铁”,当然费用是根据发票来报销的,到了对方公司后你要找到某经理,并且要一份收到合同文件的回执和相关文件。

要完成这项工作任务我们执行以下几个主要的步骤:

  1. 首先要知道对方公司的地址,引出WCF中的"A"。

    A(Address):英文理解为"地址",在计算机中是通过一个URL唯一地址标识,通过这个地址我们可以找到我们要调用的WCF服务。

    A解决了:Where to locate the WCF Service?

  2. 选择我们的交通方式,每种交通方式达到的结果不一样。如:打车费用较贵、但是过程舒服些,时间上视道路情况而定。公交最便宜,并且可选择多条线路。地铁最方便,但是偶尔会很挤,一般都没座等等,引出WCF中的"B".

    B(Binding):英文理解为"捆绑,绑定", Binding实现在Client和Service通信的所有底层细节。如:我们在客户端与服务端传输的时候采用的是什么样的编码,XML?Text?二进制?...采用哪种传输协议进行传输,TCP?Http?以及采用什么样的机制解决安全问题,SSL?加密?...

    B解决了:How to communicate with service?

  3. 到了对方公司之后我们能做哪些事?I.送合同,II.拿回执。我们不能要求对方公司给我们其他的东西,引出WCF中的"C"。

    C(Contract):英文理解为"合同",合同是什么?告诉我们哪些事能做,哪些事不能做。 Contract的主要的作用是暴露某个WCF Service所提供的所有有效的方法。Contract实际上是把每个方法的转化成为相对应的消息。

    C解决了:What functionalities do the Service provide?

Endpoint(终结点)

WCF实现了网络系统的各个应用程序的通信。各个应用程序的通信是以“终结点(Endpoint)”的来实现的。我们在上面讲的实际例子中的A、B、C即是Endpoint 的组成部分,他是服务器间通信调用的入口。

应用程序间通信

我们在第二和第三项中讲了A、B、C与Endpoint,现在正式进入应用程序间的通信。我们还是以刚才送合同的过程为例

员工A手里有一张便签,标记着:地址、绑定、合同.....而合作方手里也有一张便签,标记着同样的内容,并且一直得在等待员工A的出现。只有当便签上的内容一样时,合作方A才会签署合同回执。

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

如上图所示:只有EndpointA中的A、B、C与EndPointB中的A、B、C完全匹配时才能通信。EndPointE与EndpointD也是一样的。

实例

之前我们主要使用了IIS做为宿主,客户端调用WCF服务的是Winform应用程序。今天主要以介绍WCF中传输的配置为主,以体现出"Endpoint"与"A、B、C"。

由于之前的案例没有手写任何配置的代码,Client端的App.config与Service端的Web.config都是自动生成的,当我们添加服务引用时,IDE自动将客户端的配置文件中Endpoint与引用的服务的Endpoint匹配了。如下代码所示:

客户端App.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_ICalcService" />
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:54544/CalcService.svc" binding="basicHttpBinding"              bindingConfiguration="BasicHttpBinding_ICalcService" contract="ServiceCalc.ICalcService"
                name="BasicHttpBinding_ICalcService" />
        </client>
    </system.serviceModel>
</configuration>

服务端Web.config代码:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.6.1" />
    <httpRuntime targetFramework="4.6.1"/>
  </system.web>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_ICalcService" />
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:54544/CalcService.svc" binding="basicHttpBinding"              bindingConfiguration="BasicHttpBinding_ICalcService" contract="ServiceCalc.ICalcService"
                name="BasicHttpBinding_ICalcService" />
        </client>
    </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <!--若要在调试过程中浏览 Web 应用程序根目录,请将下面的值设置为 True。在部署之前将该值设置为 False 可避免泄露 Web 应用程序文件夹信息。-->
    <directoryBrowse enabled="true"/>
  </system.webServer>
</configuration>

 由上面的两个配置文件我们发现,客户端system.serviceModel节点有我们刚才讲的endpoint,而服务端为什么没有?这不是和我们刚才讲的有违背吗?好吧,我们承认IDE生成的Web.config中endpoint很隐晦。那么我们看下面手工修改后[把对看起来很复杂并且对当前的学习无用的配置节删掉]的配置文件的代码(我将服务端和客户端放在一块了):

修改之后的服务端Web.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.6.1" />
  </system.web>
  <system.serviceModel>
    <services>
      <service name="WCFService">
        <endpoint address="http://localhost:54544/CalcService.svc" binding="basicHttpBinding"              
         contract="ServiceCalc.ICalcService"
         />
      </service>
    </services>
  </system.serviceModel>
</configuration>

发现WCF服务依然执行成功!这次的配置文件够明显了吧,不论是在服务端还是在客户端,endpoint中的A、B、C都是完全一样的。也就是终结点完全匹配!

那么第一次的配置文件为什么能执行呢?答案是我们把WCF寄宿在IIS上,而IIS默认监听的就是Http协议[B确定了]并且地址也是相对于IIS上的文件地址[A确定了],合同更不用说了,找到User.svc什么都有了[C确定了],所以在服务端就没有必要显示的写出system.serviceModel,不信你试试,把服务端的配置文件中system.serviceModel节删除,程序一样可以运行!服务器端的endpoint确定了,客户端的endpoint自然要和服务端去对应,所以IDE在生成客户端的配置文件里endpoint写的很详细的,而服务端却没有endpoint。

WCF宿主

WCF服务应用程序与WCF服务库

在平时开发的过程中常用的项目类型有“WCF 服务应用程序”和“WCF服务库”。

WCF服务应用程序,是一个可以执行的程序,它有独立的进程,WCF服务类契约的定义,可以直接看到运行的效果。此项目模板基于IIS托管的程序,第一个案例的开发基于IIS托管的WCF服务程序时,比较多见,自学的时候也可以使用这种类型,简单易懂。

WCF服务库,可以认为是一个包含WCF服务以及契约定义的类库。不能直接运行,你可以在其他项目里引用,在宿主里启用托管这个库,有点类似于我们在Web项目中应用的类库。考虑WCF服务设计的时候,服务类的定义为单独的库,可以为其它项目使用。提高代码的复用性。

也可以修改这些代码,比如把WCF服务程序里的类,移到一个单独的类库里,或是把类库里的类移到WCF服务程序中。

概述

通过前面的介绍我们知道,WCF在运行时必寄宿在“宿主程序”之上,WCF本身不能够独自运行(每个WCF服务必须宿主在一个Windows进程中)。.Net 提供了多种宿主(控制台、Winform、WPF、WebForm......)供WCF运行,WCF还是非常灵活的。

WCF的宿主可以是 Windows 服务、COM+应用程序、WAS(Windows Activation Services,Windows进程激活服务)或IIS、Windows应用程序,或简单的控制台应用程序及任何.net程序。

案例

重新建立WCF类库项目为例做示例,名称为:WCFLibrary,并删除自动生成的两个文件(IFeed1.cs、Feed1.cs)。如下图所示:

鼠标右键查看项目属性。我们发现,其实“WCF类库项目”与我们平时建立的“类库项目”都是类库,只不过多了WCF的类库项目在新建时多了两个dll的引用(System.ServiceModel.dll、System.Runtime.Serialization.dll)和一个自动生成的配置文件(该配置文件只用于调试时使用,在WCF寄宿以后会应用宿主的配置文件与其他应用程序通信)。这更说明了我们在做分式程序开发的时候与我们平时开发的应用程序没有多大的区别,只要我们在应用程序间通信时“符合WCF的约定”即可。

服务端我们还和第一个案例一样(ICalcService接口与CalcService实现),只建立一个方法做为我们调用的示例代码如下:

  
  [ServiceContract]
    public interface ICalcService
    {
        [OperationContract]
        double Add(double a,double b);
    }
   
 public class CalcService : ICalcService
    {
        public double Add(double a, double b)
        {
            return a + b;
        }
    }
由于原来的契约为IFeed,现在
的为ICalcService,所以配置文件会自动添加一个节点service
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <compilation debug="true" />
  </system.web>
  <system.serviceModel>
    <services>
      <service name="WCFLibrary.Feed1">
        <endpoint address="Feed1" behaviorConfiguration="WCFLibrary.Feed1Behavior"
          binding="wsHttpBinding" contract="WCFLibrary.IFeed1" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8733/Design_Time_Addresses/WCFLibrary/" />
          </baseAddresses>
        </host>
   <--自动添加的配置信息
      </service>
      <service name="WCFLibrary.CalcService">
        <endpoint address="" binding="basicHttpBinding" contract="WCFLibrary.ICalcService">
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8733/Design_Time_Addresses/WCFLibrary/CalcService/" />
          </baseAddresses>
        </host>
      </service>
     -->
    </services>
    <behaviors>
      <endpointBehaviors>
        <behavior name="WCFLibrary.Feed1Behavior">
          <webHttp />
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

IIS宿主

把WCF寄宿在IIS之上,在IIS中宿主一个服务的主要优点是在发生客户端请求时宿主进程会被自动启动,并且你可以依靠IIS来管理宿主进程的生命周期。在开发和使用的过程与Web Service非常相似。

Winform应用程序宿主

建立宿主

  1. 在解决方案下新建Winform项目“WCFIIS”

  2. 添加 System.ServiceModel.dll 的引用

  3. 添加 WCF 服务类库(WCFLibrary)的项目引用

  4. 创建宿主程序

            public WCFService()
            {
                InitializeComponent();
                toolStripStatusLabel1.Text = "WCF服务未开启!";
            }
            ServiceHost host = null;
            private void StartBtn_Click(object sender, EventArgs e)
            {  
                //创建宿主的基地址
                Uri uri = new Uri("http://192.168.0.102:4455/CalcService");
                //创建宿主
                using (host=new ServiceHost(typeof(CalcService),uri))
                {
                    //向宿主中添加终结点
                    host.AddServiceEndpoint(typeof(ICalcService),new WSHttpBinding(),"");
                    //将HTTPGetEnable属性设置为true
                    ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();
                    behavior.HttpGetEnabled = true;
                    //添加行为到宿主的Behaviors里面
                    host.Description.Behaviors.Add(behavior);
                    //打开宿主
                    host.Open();
                    toolStripStatusLabel1.Text = "WCF服务启动成功!";
                }
            }
    ​
            private void CloseBtn_Click(object sender, EventArgs e)
            {
                if (host==null)
                {
                    return;
                }
                host.Close();
                host = null;
                toolStripStatusLabel1.Text = "WCF服务已关闭!";
            }
        }

     

  5. 开启时必须使用管理特权来运行承载 WCF 服务的进程,如果从 Visual Studio 2019内运行服务,则必须以管理员身份运行 Visual Studio 2019。

    说明宿主建立成功。在上例中用到"ServiceHost"类,这里只是简单的应用,具体请查看"MSDN ServiceHost"

建立客户端

  1. 重新建立解决方案-->Winform应用程序项目。

  2. 添加对服务的引用(在引用上右键-->输入我们定义的服务宿主的基地址(此处为:http://192.168.0.102:4455/CalcService)-->前往-->确定),命名空间改成“CalcService”

  3. 按照之前的流程编写客户端代码

        public FrmClient()
            {
                InitializeComponent();
            }
    ​
            private void button1_Click(object sender, EventArgs e)
            {
                CalcService.CalcServiceClient client = new CalcService.CalcServiceClient();
                double a = double.Parse(textBox1.Text);
                double b = double.Parse(textBox2.Text);
                label2.Text = client.Add(a,b).ToString();
            }

     

  4. 在这个示例中我们把Endpoint中的ABC,基地址,Behaviors等都直接写在了代码里,但实际应用过程中都是去依赖配置文件,为了对比说配置明我们下面的例子中会使用配置文件。

Winform依赖配置文件

建立宿主

  1. 在解决方案下新建Winform应用程序项目 WCFIIS。

  2. 添加 System.ServiceModel.dll 的引用。

  3. 添加 WCF 服务类库(WCFLibrary)的项目引用。

  4. 修改应用程序配置文件App.config

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <startup> 
            <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
        </startup>
      <system.serviceModel>
        <services>
          <service name="WCFLibrary.CalcService">
            <host>
              <baseAddresses>
                <add baseAddress="http://192.168.0.102:6789/CalcService"/>
              </baseAddresses>
            </host>
            <endpoint address="" binding="basicHttpBinding" contract="WCFLibrary.ICalcService"></endpoint>
          </service>
        </services>
        <behaviors>
          <serviceBehaviors>
            <behavior>
              <serviceMetadata httpGetEnabled="true"/>
              <serviceDebug includeExceptionDetailInFaults="false"/>
            </behavior>
          </serviceBehaviors>
        </behaviors>
      </system.serviceModel>
    </configuration>

     

  5. 在程序中编写开启服务

    private void StartBtn_Click(object sender, EventArgs e)
    {
        host = new ServiceHost(typeof(CalcService));
        host.Open();
        toolStripStatusLabel1.Text = "WCF服务启动成功!";
    }

     

建立客户端

同之前的程序一样,这里要引用的地址为:addbaseAddress="http://192.168.0.102:6789/CalcService"/

这个例子中与上一个应用程序不同的是,在Winform应用程序中配置是直接写在程序中的,而在本例中应用的是配置文件,区别在于如果写在配置文件中程序运行时直接到配置文件里取出相关的配置节去创建ServiceHost类。

WAS宿主

IIS7允许通过HTTP外的协议进行激活和网络通信。此环境适合开发可通过WCF支持的任何网络协议(包括http、net.tcp、net.pipe、net.msmq)进行通信的WCF服务。部署简单、管理方便,这些网络协议在部署时可像Http一样,直接丢到IIS7上即可,我们在下面的例子中以net.tcp为协议为例。IIS7以下的版本只能支持Http的通信。

确保已安装IIS7的激活组件

在应用WAS宿主时,必须确保IIS7的激活组件安装好。打开“控制面板”-->“打开或关闭Windows功能”-->“功能”,我的机器上已经安装过,如下图所示(WCF激活与非WCF激活)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章