程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 我的WCF之旅(1):創建一個簡單的WCF程序

我的WCF之旅(1):創建一個簡單的WCF程序

編輯:關於.NET

寫在前面

在Microsoft提出.NET戰略以來, 先後推出了一系列產品和技術, 這些產品和技術為我們在.NET平台下建立企業級的分布式應用提供了很大的 便利。這些技術和產品包括:.NET Remoting,XML WebSerivce,WSE(2.0,3.0),Enterprise Service, MSMQ ......

我們知道,和一個相對獨立的應用不同,我們開發一個分布式應用, 尤其是開發一個企業級的分布式應用, 我們需要考慮較多的東西。比如我們要考慮數據在不同的應用之間傳遞時采取什麼樣的機制, 這種數據傳遞是否是安全的,可靠的;如何在分布式的環境下進行異常處理;如何把分別在 不同應用中執行的操作納入同一個事務……

對於我們上面提到的這些 問題, 這些都是開發分布式應用考慮的典型的問題。值得慶幸的是,Microsoft開發的分布式的產品能夠部分的解決這些問題。.NET Remoting 為我們在.NET平台下提供了非常好的解決方案(我個人認為,.NET Remoting是.NET平台下最為成熟的分布式技術。比如相較於另一個使用更為廣泛的技術XML Web Service,它具有一些自己獨特的特性:可以使用不同的傳輸層協議進行通信——Http & TCP;可以使用不同的消息編碼方式——Bianry & Text (XML);可以寄宿到IIS和任何一種托管的應用下 ——Console Application 、WinForm Application、 Windows Service……;Server端可以通過雙向通信回調(Callback)客戶端的操作;……)XMLWeb Service為使我們實現跨平台的系統能夠集成顯得如此簡單。隨著技術的不斷發展,相關的技術規范(WS-* Specification)不斷完善, XML Web Service現在已經成為使用最為廣泛的分布式技術了。XML Web Service能夠得到如此廣泛的推廣,這得得益於Microsoft先後兩次推出的Web Service Enhancement (WSE 2.0 、WSE 3.0)。如果沒有WSE, 單純的asmx下的如此的擔保和不可靠。WSE為Web Service解決了幾大難題:Security、Reliable Messaging、transaction Handling以及大數據的有效傳輸。 MSMQ作為一種簡單而有效的機制為不同應用之間數據的傳遞提供了保障。

其實,通過合理利用上面這些分布式的技術完全可以為我們建立的一套適合不同層次需要的分布式構架。但這裡面仍然存在一些問題,那就是上面這些技術和產品只能解決某一方面的問題; 比如.NET Remoting雖然在.NET平台下是一個很好的依靠, 但是考慮到他不能提供不同平台之間的互操作性。另外,這些技術適合用了完全不同的編程方式,使得我們很難從容地從其中一種轉移到另一種上來。基於這些原因, 我們需要一套全新的技術整合以上都這些技術, 於是我們有了今天的WCF——Windows Communication Foundation。WCF建立一套框架,是我們通過一致的編程模式,使用不同的技術構建我們的分布式應用。

雖然很早開始接觸WCF,但所學的總是零零碎碎。現在開始系統地研究WCF,希望與大家一同分享我的一些所得, 同時希望能通過這樣的一個機會與大家一些探討WCF,不對的地方希望大家指正。

一開始我們先建立一個簡單程序看WCF如何工作:

1 建立整個應用的簡單構架

整個構架如圖所示,這個Solution由5個Project組成:Artech.WCFService.Contract;  Artech.WCFService.Service;Artech.WCFService.Hosting;Artech.WCFService.Client;http://localhost/WCFService。

Artech.WCFService.Contract: Class Library Project,用來保存Contract(Service Contact、Message Contract、Data Contract), 之所以把Contract獨立出來的原因是考慮到他同時被Server端——Service本身和Service Hosting和Client端使用。(現在很多的參考書,包括MSDN都使用ServiceModel Metadata Utility Tool (Svcutil.exe)這樣的一個工具來訪問Service的Metadata Endpoint來生成我們的客戶段代碼,這些代碼就包括Service Contract(一般是一個Interface),實現了這個Contract的Proxy Class(一個集成自System.ServiceModel.CientBase的一個Class)和相應的Configuration。 這個工具確實給我提供了很大的方便。但我不推薦使用這樣的方法(我天生不傾向對於這些代碼生成器),因為我覺得, 在Contract可得的情況下-比如Service和Client都是自己開發,讓Service和Client實現的Contract是同一個Contract能夠保證一致性。這個Project引用System.ServiceModel DLL。

Artech.WCFService.Service:Class Library Project,Service的業務邏輯, 這個Project引用Artech.WCFService.Contract Project和System.ServiceModel DLL。

Artech.WCFService.Hosting:Console Application, 用於以Self-Hosting的方式Host Service。這個Project引用Artech.WCFService.Contract和Artech. Project WCFService.Service。Project和System.ServiceModel DLL。

Artech.WCFService.Client:Console Application, 用以模擬現實中的調用Service的Clinet。這個Project引用Artech.WCFService.Contract Project 和System.ServiceModel DLL。

http://localhost/WCFService: Web Site Project, 用於模擬如何把Service Host到IIS中。這個Project引用Artech.WCFService.Contract、Artech.WCFService.Service和System.ServiceModel DLL。

2 創建Service Contract

在這個例子中我們建立一個簡單的案例,做一個計算器, 假設我們只要求他做簡單的加法運算就可以了。在Artech.WCFService.Contract添加一個interface,名稱叫做ICalculator。

using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
namespace Artech.WCFService.Contract
{
  [ServiceContract]
  public interface ICalculator
  {
    [OperationContract]
    double Add(double x, double y);
  }
}

使一個Interface成為Service Contract的方法很簡單,就是把ServiceContractAttribute應用到這個interface上,並在代表單個Operation的方法上應用OperationContractAttribute。這個使用Custom Attribute的編程模式被稱為聲明式的編程(Declarative)方式, 他在.NET Framework 1.1以前用到的地方還不是太多,在.NET Framework 2.0, 尤其是NET Framework 3.0中,這種方式已經變得隨處可見了。

我們可以把Contract定義成一個Interface,也可以把它定義到一個Class中——這個Class中既包涵Service本身又作為一個Contract而存在。但我推薦使用第一種方法——Serive和Contract相分離。

在WCF中,Contract的功能實際上就定義一個Service包含哪些可用的Operation, 以及的每個Opertaion的方法簽名。從消息交換(Message Exchange)的角度講,Contract定義了調用相應的Serive采取的消息交換的模式(Message Exchange Pattern - MEP),我們經常使用的MEP包括三種:Oneway, Request/Response,和Duplex。因為調用Service的過程實際就是消息交換的過程, 以常見的Request/Response為例。Client調用某個方面遠程訪問Service,所有的輸入參數被封裝到Request Soap Message並被發送到Service端, Service端監聽到這個Soap Request,創建相應的Service Object並調用相關的操作,最後將Result(這可以是Return Value,Reference Parameter和Output Parameter)封裝成Soap Message發送回Client端。這裡需要注意,如果采用的是Request/Response的模式,即使相應的操作沒有Return Value,Reference Parameter和Output Parameter(它被標記為void),Service仍然會返回一個空的Soap Message給Client端。

3 創建Service

前面我們已經創建了我的Artech.WCFService.Contract。其實我們從Contract這個單詞上講, 它就是一種契約,一種承諾。 他表明在上面簽了字你就的履行Contract上義務。Service就是這樣一個需要履行Contract義務的人。在這個例子中, Contract以Interface的方式定義的一些Operation。作為Service, 在Contract上簽字的方式就是實現這樣的一個Interface。 下面的Service得到code, 很簡單。

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using Artech.WCFService.Contract;

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

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

    #endregion
  }
}

4.Hosting Service

就像Remoting一樣,我們繼承自System.MarshalByRefObject 的對象必須Host到某一個運行的進程中, 他才開始監聽來自Client端的請求,當Client才能通過Proxy遠程的調用,Remoting Infrastructure監聽到來自Client端的請求,他會激活相應的remote Object(我們只考慮Server Activate Object——SAO)。實際上對於WCF Service也需要一個Host環境才有其發揮作用的舞台。就像Remoting一樣,你可以使用任何一種Managed Application——Console Application、WinForm Application、ASP.NET Application——作為它的Host環境。 你甚至可以用它Host到Windows Service中和IIS中(後面我將會講到如何做)。

我們知道WCF中,Client端和Service端是通過Endpoint來通信的,Endpoint有包含3個部分,經典地稱為ABC.

A代表Address,它包含一個URI,它指明Service存在於網絡的某個地方,也就是說它為Client斷指明在什麼地方去找到這個Service。很多人認識Address僅僅只是一個具有Identity的URI,實際上不然, Address不止於此, 它還包含一些Header,這些信息在某些情況下對於如何尋址有很大的意義(比如在client的最終Service之間還有一些Intermediary節點的情況下)。 在.NET中, Address用System.ServiceModel.EndpointAddress 來表示。

B代表Binding,Binding封裝了所有Client和Service段消息交換的通信細節。比如他定義了通信應該采用的Transport-比如我們是因該采用Http, TCP,Named Pipe或者是MSMQ;通信的內容應該采取怎樣的編碼——比如是Text/XML,Binary還是MTOM。以上這些都得一個Binding所必須定義的內容, 此外,Binding 還可以定義一些其他的有關通信的內容, 比如Security,Reliable Messaging, Session, Transaction等等。正因為Binding對於通信的重要性,只有Service端的Binding和Client的Binding相互匹配的時候,他們之間在可以相互通信。如何使Client Binding 匹配Service Binding呢?最簡單也是最直接的方法就是使用相同的Binding。WCF為我們定義了一系列的System Defined Binding,這些Binding在Transport,Interoperability,Security,Session Support,以及Transaction Support方面各有側重。基本上WCF為我們定義的這些Binding 已經夠我們使用的了,如果,實在滿足不了要求, 你還可以建立自己的Custom Binding。

C 代表Contract這在上面已經提及,這裡不再累贅。

Host的本質就是把一個Service 置於一個運行中的進程中,並以Endpoint的形式暴露出來,並開始監聽來自Client端的請求。這裡值得注意的是,同一個Service可以注冊若干不同的Endpoint,這樣不同的Client就可以以不同的方式來訪問同一個Service.比如,同一個Intranet的Client可以以TCP的方式訪問Service,另一個存在已Internet中的Client則只能以Http的方式訪問。

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using Artech.WCFService.Contract;
using Artech.WCFService.Service;
using System.ServiceModel.Description;

namespace Artech.WCFService.Hosting
{
  class Program
  {
    static void Main(string[] args)
    {
      HostingServiceViaCode();
    }

    static void HostingServiceViaCode()
    {
      //Specify the base Address
      Uri baseUri = new Uri("http://localhost:8080/calculatorService");
       //create a new ServiceHost object and specify the corresponding Service and base Address
      //It is recommended to apply the using pattern to make sure the sevice host can be closed properly.
      using (ServiceHost calculatorServiceHost = new ServiceHost(typeof(CalculatorService), baseUri))
      {
        //Create a Binding for Endpoint.
        BasicHttpBinding Binding = new BasicHttpBinding();
        //Create a Service Endpoint by specify the Address(it is absolute or relative path based on the base Address, the empty string indicates the Address equals base Address),
        //Binding(the basicHttpBinding created) and Contrace(it is now the type info of the contract interface)
        calculatorServiceHost.AddServiceEndpoint(typeof(ICalculator), Binding, string.Empty);

        //Such a segment of code snip shows how to make the metadata exposed to the outer world by setting the Service metadata behavior
        //Find the Service metadata behavior if exists, otherwize return null.
        ServiceMetadataBehavior behavior = calculatorServiceHost.Description.Behaviors.Find<ServiceMetadataBehavior>();

        //If the Service metadata behavior has not to added to the Service. we will create a new one and evaluate the HttpGetEnabled&HttpGetUrl to make outer world can retrieve to metadata.
        if (behavior == null)
        {
          behavior = new ServiceMetadataBehavior();
          behavior.HttpGetEnabled = true;
          //HttpGetUrl is absolute or relative based on base Address
          behavior.HttpGetUrl = baseUri;
          //We must add the new behavior created to the behavior collection, otherwize it will never take effect.
          calculatorServiceHost.Description.Behaviors.Add(behavior);
        }
        //if the metadata behavior exists in the behavior collection, we just need to evaluate the HttpGetEnabled&HttpGetUrl
        else
        {
          behavior.HttpGetEnabled = true;
          behavior.HttpGetUrl = baseUri;
        }

        //Add the opened event handler to make a friendly message displayed after opening the Service host indicating the Service begin to listen to request from Clients.
        calculatorServiceHost.Opened += delegate { Console.WriteLine("Calculator Service begin to listen via the Address:{0}", calculatorServiceHost.BaseAddresses[0].ToString()); };
        //Open the Service host make it begin to listen to the Clients.
        calculatorServiceHost.Open();

        Console.Read();
      }
    }
  }
}

我們現在可以單獨運行Hosting Projet,以下是運行後的截圖。

當Hosting啟動之後,由於我們為它開啟了Http Enabled,Hosting為專門創建一個Metadata Endpoint,通過訪問這個Endpoint,我們可獲取Service相關的Metadata。像ServiceModel Metadata Utility Tool (Svcutil.exe) 這得工具就是通過獲取Metadata來幫我們生成相應的客戶端代碼和配置文件。當Hosting被啟動之後,我們可以在IE中輸入Metadata Endpoint的Address來測試Service的可訪問性。如下圖。

以上我們完全以代碼的方式Host一個已經創建的Service。在這個例子中,Endpoint的所有信息都在代碼中指定。實際上真正的項目開發之中,我們基本上不采用這樣的方法。這是因為,Service的Addresss以及Binding 信息在開發階段和部署階段往往是不一樣的。所以我們通常的做法是把這些信息放在Config文件中,這樣我們可以根據實際的需求改變存儲於Config文件中的信息,比如我們把Service移植上另外的位置,我們只要改變Endpoint的Address配置信息就可以了; 再比如,我們把原來存在Intranet的Service放到Internet上,原來可能基於TCP的Binding必須改成基於Http的Binding,在這種情況下,我們依然可修改配置文件就可以了,這樣的改動通常是很小的,並且修改了配置文件之後我們不需要對現有的代碼進行重編譯和重部署,它們可以其實生效。下面我們來看看如何創建基於Configuration的Hosting。

首先,在Artech.WCFService.Hosting中創建App.config,並編寫如下結構的配置信息。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.ServiceModel>
  <Services>

   <Service name="Artech.WCFService.Service.CalculatorService" behaviorConfiguration=" calculatorBehavior">
    <host>
     <baseAddresses>
      <add baseAddress="http://localhost:8888/GeneralCalculator"/>
      </baseAddresses>
    </host>
    <Endpoint Address="" Binding ="basicHttpBinding" contract="Artech.WCFService.Contract.ICalculator"></Endpoint>
   </Service>
  </Services>

  <behaviors>
   <ServiceBehaviors>
    <behavior name="calculatorBehavior">
     <ServiceMetadata httpGetEnabled="true" httpGetUrl=""/>
   </ServiceBehaviors>
  </behaviors>
</system.ServiceModel>
</configuration>

然後我們相應地改變我們的Hosting代碼,現在由於我們的Endpoint的信息都放在我們的配置文件中,所以我們可以較大的簡化我們的Hosting 代碼。

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using Artech.WCFService.Contract;
using Artech.WCFService.Service;
using System.ServiceModel.Description;

namespace Artech.WCFService.Hosting
{
  class Program
  {
    static void Main(string[] args)
    {
      using (ServiceHost calculatorServiceHost = new ServiceHost(typeof(CalculatorService)))
      {
        calculatorServiceHost.Opened += delegate { Console.WriteLine("Calculator Service begin to listen via the Address:{0}", calculatorServiceHost.BaseAddresses[0].ToString()); };
        calculatorServiceHost.Open();
        Console.Read();
      }
    }
  }
}

當calculatorServiceHost.Open();被調用之後,系統會查看calculatorServiceHost對應的Service的類型,結果發現它的類型是Artech.WCFService.Service. CalculatorService(在ServiceHost被創建時最為第一個傳入參數);然後會在config文件中Services部分中找name屬性和這個Service type相匹配,找到之後,把它host進來,然後添加它在配置文件中定義的所有的Endpoint,並設置在配置文件中定義的所有Binding 以及 Behavior。

現在我們運行Hosting,將會得到同上面一樣的結果。同樣,在IE中輸入Metadata Endpoint的Address,也會看到上解圖一樣的顯示。

5.創建Client

到現在為止,Service端的工作已經完成,當你啟動Hosting的時候,一個可用的Service就已經存在了。現在所做的事情是如何創建我們的客戶段程序去使用這個Service。幾乎所有的WCF的書,其中包括MSDN都是叫你如何使用Service Utility這樣的一個工具來幫你生成客戶端代碼和配置信息。為了讓我們能夠清晰的Client的整體內容, 我們現在選擇手工的方式來編寫這樣的部分代碼。

幾乎所有分布式的調用都有這樣的一個概念,調用的具體實現被封裝在Server端,Client不可能也不需要了解這個具體實現,它所關心的就是我如何去調用,也就是說Cient需要的不是Service的實現,而是一個interface。WCF也是一樣,Client不需要了解Service的具體實現,它只需要獲得Service 的Contract,已經如何與Service通信就足夠了。說到Contract和通信,我們很自然地會想到Endpoint,不錯,Endpoint恰恰給我們提供這兩個方面的內容。所以到現在我們可以這樣說,這樣在Client建立和Serivce端相匹配的Endpoint,Client就可以調用它所希望的Service。前面提到Endpoint包括三個部分, Address, Binding,Contract那我們現在來看看Client如何獲得這3要素的信息。

在System。Service。Model 命名空間裡,定義了一個類abstract class ClientBase<TChannel>,給我們調用Service提供極大的便利。我們只要是我們的Client繼承這樣一個類,並為它指定Endpoint的三要素就一切OK了,下面我們來看看我們可以以那些方式來指定這些內容

1. Conract:我們看到了ClientBase是一個Generic的類,我們在創建一個繼承自這個類的時候必須給它指定特定的TChannel.我們可以把Contract對應的類型作為Client的generic類型。

2. Binding和Address:和Service端的Endpoint一樣,我們可以把相關的信息放在我們的Client端代碼裡面,也可以放在Client的Config裡面。那個這些數據如何應用要我們創建的派生自ClientBase的類的對象上呢。其實很簡單,ClientBase給我們定義了若干重載的構造函數,我們只要定義我們相應的構造函數應簡單地調用基類的構造函數。下面列出了ClientBase定義的全部的構造函數

protected ClientBase():這個構造函數沒有任何的參數,它用於Endpoint的信息全部存放於Config

protected ClientBase(InstanceContext callbackInstance):指定一個Callback instance用於Service回調Client代碼,這用Deplex Communication。

protected ClientBase(string EndpointConfigurationName):指定一個ID,它標識configuration 文件中定義的某一個Endpoint。這個方法在使用不同的Endpoint調用同一個Service的情況下用到的比較多。

ClientBase(Binding Binding, EndpointAddress remoteAddress);顯示的指定Binding 和Address

ClientBase(InstanceContext callbackInstance, string EndpointConfigurationName)
ClientBase(string EndpointConfigurationName, EndpointAddress remoteAddress)
ClientBase(string EndpointConfigurationName, string remoteAddress)
ClientBase(InstanceContext callbackInstance, Binding Binding, EndpointAddress remoteAddress)
ClientBase(InstanceContext callbackInstance, string EndpointConfigurationName, EndpointAddress remoteAddress)
ClientBase(InstanceContext callbackInstance, string EndpointConfigurationName, string remoteAddress)

介紹完ClientBase後, 我們來創建我們自己的CalculatorClient。下面的相應的Code

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Artech.WCFService.Contract;

namespace Artech.WCFService.Client
{
  class CalculatorClient:ClientBase<ICalculator>,ICalculator

  {
    internal CalculatorClient()
      : base()
    { }

    #region ICalculator Members

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

    #endregion
  }
}

上面的例子中我們僅僅定義了一個無參的構造函數,因為我們會把所有的Endpoint信息放在Config文件裡面:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.ServiceModel>
  <Client>
      <Endpoint Address="http://localhost:8080/WCFService/CalculatorService " Binding="basicHttpBinding" contract="Artech.WCFService.Contract.ICalculator" />
      </Client>
</system.ServiceModel>
</configuration>

然後我們再Client Project中的Program class裡面通過這樣的方式調用CalculatorService;

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Artech.WCFService.Contract;

namespace Artech.WCFService.Client
{
  class Program
  {
    static void Main(string[] args)
    {
      try
      {
         using (CalculatorClient caluclator = new CalculatorClient())
          {
                Console.WriteLine("Begin to invocate the calculator Service");
                Console.WriteLine("x + y = {2} where x = {0} and y = {1}", 1, 2, caluclator.Add(1, 2));

                Console.Read();
           }
        }
      }

      catch (Exception ex)
      {
        Console.WriteLine("StackTrace:{0}", ex.StackTrace);
        Console.WriteLine("Message:{0}", ex.Message);
        Console.Read();
      }
    }
  }
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved