對於.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已經成功地變成了我們在OperationContractAttrbute中指定的那樣。
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;
重寫Proxy Class
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);
}
}
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是等效的。