對于.NET重載(Overloading)——定義不同參數(shù)列表的同名方法(順便提一下,我們但可以在參數(shù)列表上重載方法,我們甚至可以在返回類型層面來重載我們需要的方法——頁就是說,我們可以定義兩個具有相同參數(shù)列表但不同返回值類型的兩個同名的方法。不過這種廣義的Overloading不被我們主流的.NET 語言所支持的——C#, VB.NET, 但是對于IL來說,這這種基于返回值類型的Overloading是支持的)。相信大家聽得耳朵都要起老繭了。我想大家也清楚在編寫傳統(tǒng)的XML Web Service的時候,Overloading是不被支持的。
原因很簡單,當我們用某種支持.NET的高級語言寫成的程序被相應(yīng)的編譯器編譯成Assembly的過程中,不單單是我們的Source Code會被變成IL Code,在Assembly中還會生成相應(yīng)的原數(shù)據(jù)Metadata——這些Metadata 可以被看看是一張張的Table。這些Table存儲了定義了主要3個方面的信息——構(gòu)成這個Assembly文件的信息;在Assembly中定義的Type及其相關(guān)成員的信息;本引用的Assembly 及Type的信息。這些完備的Metadata成就了Assembly的自描述性(Self-Describing),也只是有了這些Metadata,使.NET可以很容易地根據(jù)方法參數(shù)的列表甚至是返回值得類型來判斷調(diào)用的究竟了那個方法。
而對于XML Web Service,它的標準實際上是基于XML的,近一步說,一個XML Web Service是通過一個一段XML來描述的,而這個描述XML Web Service的XML,我們稱之為WSDL(Web Service Description Language)。在WSDL中,Web Service的一個方法(Method)對應(yīng)的是一個操作(Operation),Web Service 所有的Operation定義在WSDL中的portType Section。我們可以參照下面一段XML,它是從一個完整的WSDL中截取下來的。我們可以看到,portType包含了Web Service定義的所有Operation,每個Operation由一個operation XML Element表示??催^我前面Blog的讀者應(yīng)該知道,從消息交換(Message Exchange)的層面上講,一個Operation實際上體現(xiàn)的是一種消息交換的模式(Message Exchange Pattern——MEP)。所以我們完全可以通過一定消息交換的輸入消息(Input Message)和輸出(Output Message )定義一個Operation。而WSDL也是這樣做的。(這里順便提一下,Output Message部僅僅對應(yīng)一個方法的Return Value,還包括表明ref 和out的Parameter)。除了定義進行消息交互的Message的格式(一般通過XSD)之外,每個Operation還應(yīng)該具有一個能夠為一標識該Operation的ID,這個ID通過name XML Attribute來定義。通常的情況下,Operation的Name使用Web Service的方法名——這就是在傳統(tǒng)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。雖然現(xiàn)有XML Web Service現(xiàn)在具有廣泛的使用——尤其在構(gòu)建跨平臺性的分布是應(yīng)用和進行系統(tǒng)集成上面,但是從Microsoft已經(jīng)明確提出WSE 3.0將是最后一個Version的WSE,所以,現(xiàn)有的Web Service將會全面的過渡到WCF。WCF到底是什么東西,我在前面的文章中不斷地提出這個問題,在這里我們從 另外一個方面來看待WCF。我們知道W3C定義了一系列關(guān)于WS的規(guī)范Specification,成為WS-* Specification。這一系列的Specification定義了建立在XML和SOAP標準之上的基于如何將一個可互操作系統(tǒng)(Interoperable System)的各個方面的標準,比如WS-Messaging,WS-Security,WS-Transaction等等。而WCF則可以看成是這一整套Specification的實現(xiàn)。但是這種實現(xiàn)最終還是落實到我們.NET編程上。我們可以把WS-Specification和我們的基于.NET語言的編程看成是兩種截然不同的編程模型(Programming Model)。WCF的功能則是把這兩種不同的編程模型統(tǒng)一起來,實現(xiàn)他們之間的一個Mapping——可以把WCF看成一個Adapter。
回到我們的Overloading上面來,Overloading是.NET Framework原生支持的。通過Overloading,我們可以使用同名的方法來定義不同的操作,從而使我們的Code顯得更加優(yōu)雅(Elegant)。要是Overloading在WCF中可以使用,WCF必須提供這樣的一個Mapping——是被重載的具有相同方法的的方法Mapping到不同的Operation上。而提供著一個功能的就是ServiceContract。下面我們來結(jié)合一個Sample來看如何在WCF中使用Overloading。
沿用我們的Calculator的應(yīng)用,現(xiàn)在我們做一個加法器,它具有兩個Operation——兩書相加和三數(shù)相加。這兩個方法都用一個名稱Add。
1.下面是Solution的結(jié)構(gòu)。不像前面的結(jié)構(gòu),這這里我們沒有把Service Contract單獨提取出來,供Client和Service供用。因為我們現(xiàn)在模擬的是,Service完全由一個外部的第三方提供,Service 已經(jīng)確定,不能根據(jù)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,簡單地實現(xiàn)了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();
}
}
}
}
相關(guān)的已經(jīng)在前面的文章中說過,代碼很簡單,沒有什么好說的。
現(xiàn)在我們來啟動這個Host,在IE中通過鍵入這個地址http://localhost:1234/calcuator?wsdl看看生成的WSDL是什么樣子。
通過截圖我們可以看到,在WSDL的portType Section,兩個Operation的Name已經(jīng)成功地變成了我們在OperationContract Attrbute中指定的那樣。
4.接下來我們?yōu)镃lient端添加一個Server Reference。就像在使用XML Web Service中添加Web Reference一樣,添加Server Reference會為Client添加相應(yīng)的客戶端代碼——倒入的Service Contract,繼承自ClientBase<T>的Proxy Class, 和相應(yīng)的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已經(jīng)不是Service端的Contract了,Overloading方法已經(jīng)被換成了與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);
}
}
實現(xiàn)了我們倒入的Service Contract并提供了相應(yīng)的Constract,相關(guān)的也在前面的Blog提及,這里不用再多說什么了。現(xiàn)在我們毫無疑問,可以直接調(diào)用非重載的方法AddWithTwoOperands和AddWithThreeOperands來調(diào)用Calculator Service。但是我們需要的不是這樣,我們需要的Overloading,在Service 我們實現(xiàn)以O(shè)verlaoding的方式提供Service,在Client端我們也希望以相同的方式來調(diào)用這個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
}
} 現(xiàn)在我們有兩個Proxy Class,我們同時使用,看看他們會不會返回一樣的結(jié)果:
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是等效的。