我的WCF之旅(5):面向服务架构(SOA)和面向对象编程(OOP)的结合——如何实现Service Contract的重载(Overloading)

对于.NET重载(Overloading)——定义不同参数列表的同名方法(顺便提一下,我们但可以在参数列表上重载方法,我们甚至可以在返回类型层面来重载我们需要的方法——页就是说,我们可以定义两个具有相同参数列表但不同返回值类型的两个同名的方法。不过这种广义的Overloading不被我们主流的.NET 语言所支持的——C#, VB.NET, 但是对于IL来说,这这种基于返回值类型的Overloading是支持的)。相信大家听得耳朵都要起老茧了。我想大家也清楚在编写传统的XML Web Service的时候,Overloading是不被支持的。

原因很简单,当我们用某种支持.NET的高级语言写成的程序被相应的编译器编译成Assembly的过程中,不单单是我们的Source Code会被变成IL Code,在Assembly中还会生成相应的原数据Metadata——这些Metadata 可以被看看是一张张的Table。这些Table存储了定义了主要3个方面的信息——构成这个Assembly文件的信息;在Assembly中定义的Type及其相关成员的信息;本引用的Assembly 及Type的信息。这些完备的Metadata成就了Assembly的自描述性(Self-Describing),也只是有了这些Metadata,使.NET可以很容易地根据方法参数的列表甚至是返回值得类型来判断调用的究竟了那个方法。

而对于XML Web Service,它的标准实际上是基于XML的,近一步说,一个XML Web Service是通过一个一段XML来描述的,而这个描述XML Web Service的XML,我们称之为WSDL(Web Service Description Language)。在WSDL中,Web Service的一个方法(Method)对应的是一个操作(Operation),Web Service 所有的Operation定义在WSDL中的portType Section。我们可以参照下面一段XML,它是从一个完整的WSDL中截取下来的。我们可以看到,portType包含了Web Service定义的所有Operation,每个Operation由一个operation XML Element表示。看过我前面Blog的读者应该知道,从消息交换(Message Exchange)的层面上讲,一个Operation实际上体现的是一种消息交换的模式(Message Exchange Pattern——MEP)。所以我们完全可以通过一定消息交换的输入消息(Input Message)和输出(Output Message )定义一个Operation。而WSDL也是这样做的。(这里顺便提一下,Output Message部仅仅对应一个方法的Return Value,还包括表明ref 和out的Parameter)。除了定义进行消息交互的Message的格式(一般通过XSD)之外,每个Operation还应该具有一个能够为一标识该Operation的ID,这个ID通过name XML Attribute来定义。通常的情况下,Operation的Name使用Web Service的方法名——这就是在传统XML Web Service不可以使用Overloading的原因。

<wsdl:portType name="ICalculator">
  <wsdl:operation name="AddWithTwoOperands">
    <wsdl:input wsaw:Action="http://tempuri.org/ICalculator/AddWithTwoOperands" message="tns:ICalculator_AddWithTwoOperands_InputMessage" />
    <wsdl:output wsaw:Action="http://tempuri.org/ICalculator/AddWithTwoOperandsResponse" message="tns:ICalculator_AddWithTwoOperands_OutputMessage" />
  </wsdl:operation>
  <wsdl:operation name="AddWithThreeOperands">
    <wsdl:input wsaw:Action="http://tempuri.org/ICalculator/AddWithThreeOperands" message="tns:ICalculator_AddWithThreeOperands_InputMessage" />
    <wsdl:output wsaw:Action="http://tempuri.org/ICalculator/AddWithThreeOperandsResponse" message="tns:ICalculator_AddWithThreeOperands_OutputMessage" />
  </wsdl:operation>
</wsdl:portType>

和XML Web Service,WCF也面临一样的问题——我觉得我们可以把WCF看成.NET平台下新一代的Web Service。虽然现有XML Web Service现在具有广泛的使用——尤其在构建跨平台性的分布是应用和进行系统集成上面,但是从Microsoft已经明确提出WSE 3.0将是最后一个Version的WSE,所以,现有的Web Service将会全面的过渡到WCF。WCF到底是什么东西,我在前面的文章中不断地提出这个问题,在这里我们从 另外一个方面来看待WCF。我们知道W3C定义了一系列关于WS的规范Specification,成为WS-* Specification。这一系列的Specification定义了建立在XML和SOAP标准之上的基于如何将一个可互操作系统(Interoperable System)的各个方面的标准,比如WS-Messaging,WS-Security,WS-Transaction等等。而WCF则可以看成是这一整套Specification的实现。但是这种实现最终还是落实到我们.NET编程上。我们可以把WS-Specification和我们的基于.NET语言的编程看成是两种截然不同的编程模型(Programming Model)。WCF的功能则是把这两种不同的编程模型统一起来,实现他们之间的一个Mapping——可以把WCF看成一个Adapter。

回到我们的Overloading上面来,Overloading是.NET Framework原生支持的。通过Overloading,我们可以使用同名的方法来定义不同的操作,从而使我们的Code显得更加优雅(Elegant)。要是Overloading在WCF中可以使用,WCF必须提供这样的一个Mapping——是被重载的具有相同方法的的方法Mapping到不同的Operation上。而提供着一个功能的就是ServiceContract。下面我们来结合一个Sample来看如何在WCF中使用Overloading。

沿用我们的Calculator的应用,现在我们做一个加法器,它具有两个Operation——两书相加和三数相加。这两个方法都用一个名称Add。

1.下面是Solution的结构。不像前面的结构,这这里我们没有把Service Contract单独提取出来,供Client和Service供用。因为我们现在模拟的是,Service完全由一个外部的第三方提供,Service 已经确定,不能根据Client的具体要求来修改Service。Source Code从这里下载。

2.Service端的Code:

Service Contract: Artech.OverloadableContract.Service ICalculator.cs.

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;

namespace Artech.OverloadableContract.Service
{
    [ServiceContract]
   public interface ICalculator
    {
        [OperationContract(Name = "AddWithTwoOperands")]
       double Add(double x, double y);

       [OperationContract(Name = "AddWithThreeOperands")]
       double Add(double x, double y, double z);
    }
}

这个Service Contract定义了Overloading的两个Add方法,为了把这两个方法映射到两个不同的Operation,我们通过System.ServiceModel.OperationAttribute 的Name属性为Operation指定一个Name——AddWithTwoOperands 和AddWithThreeOperands。

下面是Service的Code,简单地实现了Service Conract,无须赘言。

using System;
using System.Collections.Generic;
using System.Text;

namespace Artech.OverloadableContract.Service
{
    public class CalculatorService:ICalculator
    {
        ICalculator Members#region ICalculator Members

        public double Add(double x, double y)
        {
            return x + y;
        }

        public double Add(double x, double y, double z)
        {
            return x + y + z;
        }

        #endregion
    }
}

3.Hosting Service

App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
      
        <behaviors>
            <serviceBehaviors>
                <behavior name="calculatorServiceBehavior">
                    <serviceMetadata httpGetEnabled="true" />
                </behavior>
            </serviceBehaviors>
        </behaviors>
      
        <services>
            <service behaviorConfiguration="calculatorServiceBehavior" name="Artech.OverloadableContract.Service.CalculatorService">
                <endpoint binding="basicHttpBinding" contract="Artech.OverloadableContract.Service.ICalculator" />
                <host>
                    <baseAddresses>
                        <add baseAddress="http://localhost:1234/calcuator" />
                    </baseAddresses>
                </host>
            </service>
        </services>
    </system.serviceModel>
</configuration>

Program.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using Artech.OverloadableContract.Service;

namespace Artech.OverloadableContract.Hosting
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
            {
                host.Open();
                Console.WriteLine("Calculator service has begun to listen ");
                Console.Read();
            }
        }
    }
}

相关的已经在前面的文章中说过,代码很简单,没有什么好说的。

现在我们来启动这个Host,在IE中通过键入这个地址http://localhost:1234/calcuator?wsdl看看生成的WSDL是什么样子。

通过截图我们可以看到,在WSDL的portType Section,两个Operation的Name已经成功地变成了我们在OperationContract Attrbute中指定的那样。

4.接下来我们为Client端添加一个Server Reference。就像在使用XML Web Service中添加Web Reference一样,添加Server Reference会为Client添加相应的客户端代码——倒入的Service Contract,继承自ClientBase<T>的Proxy Class, 和相应的Confugration。下面我们来分析这些通过添加Service Reference而生成的Code。

Imported Service Contract:

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
    [System.ServiceModel.ServiceContractAttribute(ConfigurationName="Artech.OverloadableContract.Client.CalculatorService.ICalculator")]
    public interface ICalculator
    {
        
        [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/ICalculator/AddWithTwoOperands", ReplyAction="http://tempuri.org/ICalculator/AddWithTwoOperandsResponse")]
        double AddWithTwoOperands(double x, double y);
        
        [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/ICalculator/AddWithThreeOperands", ReplyAction="http://tempuri.org/ICalculator/AddWithThreeOperandsResponse")]
        double AddWithThreeOperands(double x, double y, double z);
    }
    
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
    public interface ICalculatorChannel : Artech.OverloadableContract.Client.CalculatorService.ICalculator, System.ServiceModel.IClientChannel
    {
}

我们可以看到这个Service Contract已经不是Service端的Contract了,Overloading方法已经被换成了与Oper阿tion Name相匹配的方法了。我们再看看Proxy Class:

[System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
    public partial class CalculatorClient : System.ServiceModel.ClientBase<Artech.OverloadableContract.Client.CalculatorService.ICalculator>, Artech.OverloadableContract.Client.CalculatorService.ICalculator
    {
        
        public CalculatorClient()
        {
        }
        
        public CalculatorClient(string endpointConfigurationName) : 
                base(endpointConfigurationName)
        {
        }
        
        public CalculatorClient(string endpointConfigurationName, string remoteAddress) : 
                base(endpointConfigurationName, remoteAddress)
        {
        }
        
        public CalculatorClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : 
                base(endpointConfigurationName, remoteAddress)
        {
        }
        
        public CalculatorClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : 
                base(binding, remoteAddress)
        {
        }
        
        public double AddWithTwoOperands(double x, double y)
        {
            return base.Channel.AddWithTwoOperands(x, y);
        }
        
        public double AddWithThreeOperands(double x, double y, double z)
        {
            return base.Channel.AddWithThreeOperands(x, y, z);
        }
}

实现了我们倒入的Service Contract并提供了相应的Constract,相关的也在前面的Blog提及,这里不用再多说什么了。现在我们毫无疑问,可以直接调用非重载的方法AddWithTwoOperands和AddWithThreeOperands来调用Calculator Service。但是我们需要的不是这样,我们需要的Overloading,在Service 我们实现以Overlaoding的方式提供Service,在Client端我们也希望以相同的方式来调用这个Service。下面我们来看怎么做:

在Client端,重写Service Contract,当然是一Overloading的方式,同时像在Service端一样,通过OperatonContract的Name属性为Operation 制定一个和Service完全匹配的Operation Name。

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;

namespace Artech.OverloadableContract.Client
{
    [ServiceContract(Name = "ICalculator")]
   public interface IMyCalculator
    {
        [OperationContract(Name = "AddWithTwoOperands")]
       double Add(double x, double y);

       [OperationContract(Name = "AddWithThreeOperands")]
       double Add(double x, double y, double z);
    }
}

重写Proxy Class

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;

namespace Artech.OverloadableContract.Client
{
    class MyCalculatorClient:ClientBase<IMyCalculator>,IMyCalculator
    {
        IMyCalculator Members#region IMyCalculator Members

        public double Add(double x, double y)
        {
            return this.Channel.Add(x, y);
        }

        public double Add(double x, double y, double z)
        {
            return this.Channel.Add(x, y,z);
        }

        #endregion
    }
}

现在我们有两个Proxy Class,我们同时使用,看看他们会不会返回一样的结果:

using System;
using System.Collections.Generic;
using System.Text;
using Artech.OverloadableContract.Client.CalculatorService;

namespace Artech.OverloadableContract.Client
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Begin to invocate generated proxy");
            InvocateGeneratedProxy();
            Console.WriteLine("\nBegin to invocate revised proxy");
            InvocateGeneratedProxy();
Console.Read();
        }

        static void InvocateGeneratedProxy()
        {
            using (CalculatorClient calculator = new CalculatorClient())
            { 
                Console.WriteLine("x + y = {2} where x = {0}and y = {1} ",1,2,calculator.AddWithTwoOperands(1,2));
                Console.WriteLine("x + y + z = {3} where x = {0}and y = {1} and z = {2}", 1, 2, 3,calculator.AddWithThreeOperands(1, 2,3));
            }
        }

        static void InvocateRevisedProxy()
        {
            using (MyCalculatorClient calculator = new MyCalculatorClient())
            {
                Console.WriteLine("x + y = {2} where x = {0}and y = {1} ", 1, 2, calculator.Add(1, 2));
                Console.WriteLine("x + y + z = {3} where x = {0}and y = {1} and z = {2}", 1, 2, 3, calculator.Add(1, 2, 3));
            }
        }
    }
}

同时在加入下面简单的Configuration:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <client>
            <endpoint address="http://localhost:1234/calcuator" binding="basicHttpBinding"                 contract="Artech.OverloadableContract.Client.IMyCalculator" />
                  </client>
    </system.serviceModel>
</configuration>

运行Client,下面是Screen Shot,可见两个Proxy是等效的。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员的SOD蜜

DataSet的灵活,实体类的方便,DTO的效率:SOD框架的数据容器,打造最适合DDD的ORM框架

引言:DDD的困惑 最近,我看到园子里面有位朋友的一篇博客 《领域驱动设计系列(一):为何要领域驱动设计? 》文章中有下面一段话,对DDD使用产生的疑问: •没...

38690
来自专栏Java成神之路

Java微信公众平台开发_05_微信网页授权

登录微信公众平台后台, 开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息 - 修改,

1.5K30
来自专栏Java进阶架构师

03:SpringBoot整合SpringDataJPA实现数据库的访问(二)

首先回忆一下,前面我们创建studentRepo类继承JpaRepository<T,ID>接口,即可实现最基本的crud。如下:

9920
来自专栏大内老A

WCF中关于可靠会话的BUG!!

对WCF的可靠会话编程有一定了解的人应该知道,我们可以使用 DeliveryRequirementsAttribute 可以指示WCF确认绑定提供服务或客户端实...

198100
来自专栏大内老A

让我们的ASP.NET MVC应用可以单独维护验证消息

在项目开发中,我们会使用到很多的描述性文字,比如验证消息、错误消息和确认消息等,让这些文本消息具有可维护性具有重要的意义。虽然我们可以将它们存储于资源文件中,并...

20670
来自专栏GreenLeaves

C# 文件读写系列二

读取文件原则上非常简单,但它不是通过FileInfo和DirectoryInfo来完成的,关于FileInfo和DirectoryInfo请参考C# 文件操作系...

35090
来自专栏跟着阿笨一起玩NET

C# 中参数验证方式的演变

一般在写方法的时候,第一步就是进行参数验证,这也体现了编码者的细心和缜密,但是在很多时候这个过程很枯燥和乏味,比如在拿到一个API设计文档的时候,通常会规定类型...

14520
来自专栏.NET技术

.net平台的MongoDB使用

  最近花了点时间玩了下MongoDB.Driver,进行封装了工具库,平常也会经常用到MongoDB,因此写一篇文章梳理知识同时把自己的成果分享给大家。

14820
来自专栏大内老A

像TransactionScope一样使用DbTransaction

System.Transactions.TransactionScope为了提供一种非常方便的实现分布式事务的方式,但是在某些情况下为了阻止本地事务向分布式事务...

52970
来自专栏更流畅、简洁的软件开发方式

我的数据访问类(第二版)—— for .net2.0 (一)

asp.net2.0已经出来好久了,由于许多的原因一直没有使用,一个月前才开始使用VS2005写东西。 这一个月里又重新学习了一下基础知识,比如多态、接口了什么...

21690

扫码关注云+社区

领取腾讯云代金券