对于.NET重载(Overloading)——定义不同参数列表的同名方法(顺便提一下,我们但可以在参数列表上重载方法,我们甚至可以在返回类型层面来重载我们需要的方法——页就是说,我们可以定义两个具有相同参数列表但不同返回值类型的两个同名的方法。不过这种广义的Overloading不被我们主流的.NET 语言所支持的——C#,VB.NET,但是对于IL来说,这这种基于返回值类型的Overloading是支持的)。相信大家听得耳朵都要起老茧了。我想大家也清楚在编写传统的XMLWeb Service的时候,Overloading是不被支持的。
原因很简单,当我们用某种支持.NET的高级语言写成的程序被相应的编译器编译成Assembly的过程中,不单单是我们的Source Code会被变成ILCode,在Assembly中还会生成相应的原数据Metadata——这些Metadata可以被看看是一张张的Table。这些Table存储了定义了主要3个方面的信息——构成这个Assembly文件的信息;在Assembly中定义的Type及其相关成员的信息;本引用的Assembly及Type的信息。这些完备的Metadata成就了Assembly的自描述性(Self-Describing),也只是有了这些Metadata,使.NET可以很容易地根据方法参数的列表甚至是返回值得类型来判断调用的究竟了那个方法。
而对于XML WebService,它的标准实际上是基于XML的,近一步说,一个XML Web Service是通过一个一段XML来描述的,而这个描述XMLWeb Service的XML,我们称之为WSDL(Web Service Description Language)。在WSDL中,WebService的一个方法(Method)对应的是一个操作(Operation),Web Service所有的Operation定义在WSDL中的portTypeSection。我们可以参照下面一段XML,它是从一个完整的WSDL中截取下来的。我们可以看到,portType包含了WebService定义的所有Operation,每个Operation由一个operation XMLElement表示。看过我前面Blog的读者应该知道,从消息交换(MessageExchange)的层面上讲,一个Operation实际上体现的是一种消息交换的模式(Message ExchangePattern——MEP)。所以我们完全可以通过一定消息交换的输入消息(Input Message)和输出(Output Message)定义一个Operation。而WSDL也是这样做的。(这里顺便提一下,Output Message部仅仅对应一个方法的ReturnValue,还包括表明ref和out的Parameter)。除了定义进行消息交互的Message的格式(一般通过XSD)之外,每个Operation还应该具有一个能够为一标识该Operation的ID,这个ID通过name XML Attribute来定义。通常的情况下,Operation的Name使用WebService的方法名——这就是在传统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 WebService,WCF也面临一样的问题——我觉得我们可以把WCF看成.NET平台下新一代的Web Service。虽然现有XML WebService现在具有广泛的使用——尤其在构建跨平台性的分布是应用和进行系统集成上面,但是从Microsoft已经明确提出WSE3.0将是最后一个Version的WSE,所以,现有的WebService将会全面的过渡到WCF。WCF到底是什么东西,我在前面的文章中不断地提出这个问题,在这里我们从另外一个方面来看待WCF。我们知道W3C定义了一系列关于WS的规范Specification,成为WS-*Specification。这一系列的Specification定义了建立在XML和SOAP标准之上的基于如何将一个可互操作系统(InteroperableSystem)的各个方面的标准,比如WS-Messaging,WS-Security,WS-Transaction等等。而WCF则可以看成是这一整套Specification的实现。但是这种实现最终还是落实到我们.NET编程上。我们可以把WS-Specification和我们的基于.NET语言的编程看成是两种截然不同的编程模型(ProgrammingModel)。WCF的功能则是把这两种不同的编程模型统一起来,实现他们之间的一个Mapping——可以把WCF看成一个Adapter。
回到我们的Overloading上面来,Overloading是.NETFramework原生支持的。通过Overloading,我们可以使用同名的方法来定义不同的操作,从而使我们的Code显得更加优雅(Elegant)。要是Overloading在WCF中可以使用,WCF必须提供这样的一个Mapping——是被重载的具有相同方法的的方法Mapping到不同的Operation上。而提供着一个功能的就是ServiceContract。下面我们来结合一个Sample来看如何在WCF中使用Overloading。
沿用我们的Calculator的应用,现在我们做一个加法器,它具有两个Operation——两书相加和三数相加。这两个方法都用一个名称Add。
1.下面是Solution的结构。不像前面的结构,这这里我们没有把ServiceContract单独提取出来,供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);
}
}
这个ServiceContract定义了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中添加WebReference一样,添加Server Reference会为Client添加相应的客户端代码——倒入的ServiceContract,继承自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);
}
}
实现了我们倒入的ServiceContract并提供了相应的Constract,相关的也在前面的Blog提及,这里不用再多说什么了。现在我们毫无疑问,可以直接调用非重载的方法AddWithTwoOperands和AddWithThreeOperands来调用CalculatorService。但是我们需要的不是这样,我们需要的Overloading,在Service我们实现以Overlaoding的方式提供Service,在Client端我们也希望以相同的方式来调用这个Service。下面我们来看怎么做:
在Client端,重写ServiceContract,当然是一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是等效的。