初用WCF的朋友可能會遇到這樣的問題,就是在使用svcutil.exe生成proxy和config的時候,或者利用add service reference添加引用的時候,部署的WCF服務到底它的metadata是什麼。或者換句話說,svcutil的URL參數,以及添加服務引用時候的那個Address,到底應該填什麼。
在這裡我用兩個最常用的Binding方式,WSHttpBinding和NetTcpBinding,分別以實際的例子來進行說明。
建立服務契約
在這裡就從MSDN上抄一個四則運算的服務來作為我們的素材。建立一個控制台程序,添加System.ServiceModel引用,然後添加下面兩個文件:
ICalculatorService.cs :
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace Nocturne.Learning.WcfAddressDemo { [ServiceContract] public interface ICalculatorService { [OperationContract] double Add(double n1, double n2); [OperationContract] double Subtract(double n1, double n2); [OperationContract] double Multiply(double n1, double n2); [OperationContract] double Divide(double n1, double n2); } }
CalculatorService.cs:
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace Nocturne.Learning.WcfAddressDemo { public class CalculatorService : ICalculatorService { public double Add(double n1, double n2) { double result = n1 + n2; Console.WriteLine("Received Add({0},{1})", n1, n2); Console.WriteLine("Return: {0}", result); return result; } public double Subtract(double n1, double n2) { double result = n1 - n2; Console.WriteLine("Received Subtract({0},{1})", n1, n2); Console.WriteLine("Return: {0}", result); return result; } public double Multiply(double n1, double n2) { double result = n1 * n2; Console.WriteLine("Received Multiply({0},{1})", n1, n2); Console.WriteLine("Return: {0}", result); return result; } public double Divide(double n1, double n2) { double result = n1 / n2; Console.WriteLine("Received Divide({0},{1})", n1, n2); Console.WriteLine("Return: {0}", result); return result; } } }
下面我們就分別來看看怎樣HOST這個服務,怎樣獲取它的元數據。
WSHttpBind方式
這種方式下,元數據可以直接從http地址中獲得。先看看下面這段啟動服務的代碼(注意,我只使用code方式啟動,如果存在app.config,請將其刪除)。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Description; namespace Nocturne.Learning.WcfAddressDemo { class Program { static void Main(string[] args) { Uri baseAddress=new Uri("http://localhost:8009/MyService"); ServiceHost host = new ServiceHost(typeof(CalculatorService), baseAddress); ServiceMetadataBehavior smb = new ServiceMetadataBehavior() { HttpGetEnabled = true }; host.Description.Behaviors.Add(smb); host.AddServiceEndpoint(typeof(ICalculatorService), new WSHttpBinding(), baseAddress); host.Open(); Console.WriteLine("The service is ready."); Console.WriteLine("Press <ENTER> to terminate service."); Console.WriteLine(); Console.ReadLine(); } } }
啟動該工程,這時候就可以訪問這個服務了。注意這裡的baseAddress,決定了該host是采用基地址訪問,而這個“訪問”,僅僅是針對endpoint的位置來說的,理解這個概念非常重要。如果我們在將host.AddServiceEndpoint()的最後一個參數改為一個string,比如"CalculatorService“,影響的只是該服務的endpoint地址,對元數據沒有影響。
這個時候,我們就可以通過svcutil來生成輔助文件了,命令如下:
D:\Program\temp>svcutil http://localhost:8009/MyService /language:cs /out:Proxy.cs /config:app.config
Microsoft (R) Service Model Metadata Tool
[Microsoft (R) Windows (R) Communication Foundation, Version 3.0.4506.2152]
Copyright (c) Microsoft Corporation. All rights reserved.
Attempting to download metadata from 'http://localhost:8009/MyService' using WS-Metadata Exchange or DISCO.
Generating files...
D:\Program\temp\Proxy.cs
D:\Program\temp\app.config
其中的URL參數,就是代碼中寫到的baseAddress,在後面添加endpoint的時候不管最後那個參數寫的是啥,這個命令都這麼寫,因為metadata是屬於一個host的,並不屬於一個endpoint。
如果是通過給工程添加Service Reference,也是在Address裡填入這個baseAddress。如圖所示:
之後該怎麼玩就悉聽尊便了。
NetTcpBinding方式
由於在這種方式下,服務本身是通過NetTcp方式來與客戶端應答的,元數據就得另開一個mex的endpoint,來專門提供。看下面的代碼:
using System; using System.ServiceModel; using System.ServiceModel.Description; namespace Nocturne.Learning.WcfAddressDemo { class Program { static void Main(string[] args) { Uri baseAddress = new Uri("net.tcp://localhost:8009/MyService"); ServiceHost host = new ServiceHost(typeof(CalculatorService), baseAddress); ServiceMetadataBehavior smb = new ServiceMetadataBehavior(); host.Description.Behaviors.Add(smb); host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexTcpBinding(), "mex"); host.AddServiceEndpoint(typeof(ICalculatorService), new NetTcpBinding(), baseAddress); host.Open(); Console.WriteLine("The service is ready."); Console.WriteLine("Press <ENTER> to terminate service."); Console.WriteLine(); Console.ReadLine(); } } }
與前面建立WSHttpBinding的代碼有兩個不同的地方。首先是smb裡取消了HttpGetEnabled=true的屬性設置,這是由於我們的基地址不是HTTP地址,會引發異常,錯誤信息是:The HttpGetEnabled property of ServiceMetadataBehavior is set to true and the HttpGetUrl property is a relative address, but there is no http base address. Either supply an http base address or set HttpGetUrl to an absolute address。第二點就是最關鍵的元數據地址,添加了一個MexTcpBinding綁定類型的endpoint,它就承擔為整個service對外提供元數據的任務。元數據的地址就是在基址後面加了“mex”的形式,在這裡就是net.tcp://localhost:8009/MyService/mex。在實際應用中,這個“mex”可以省略,即只用net.tcp://localhost:8009/MyService,系統會自動去尋找mex,而如果在前面建立endpoint的時候,用的是其它的名稱(比如mex1),那就不能省略了。建議使用完整路徑。
D:\Program\temp>svcutil net.tcp://localhost:8009/MyService/mex /language:cs /out:Proxy.cs /config:app.config
Microsoft (R) Service Model Metadata Tool
[Microsoft (R) Windows (R) Communication Foundation, Version 3.0.4506.2152]
Copyright (c) Microsoft Corporation. All rights reserved.
Attempting to download metadata from 'net.tcp://localhost:8009/MyService' using WS-Metadata Exchange. This URL does not support DISCO.
Generating files...
D:\Program\temp\Proxy.cs
D:\Program\temp\app.config
Add Service Reference的操作類同。
小結
Address和元數據,是部署WCF服務時最最基本的概念,有必要非常熟練地掌握,筆者也只是對常用的兩種綁定方式做了一個討論,更多的內容還有待發掘。