REST 和 POX
這個範例會示範如何在 Windows Communication Foundation (WCF) 中使用 HTTP 傳輸,以傳送及接收 “plain old XML” (POX) 訊息,這就是僅包含 XML 內容但沒有封入 SOAP 信封的訊息。許多用戶端類型都可傳送及接收 POX 訊息,包括沒有 SOAP 通訊協定之原生支援的 Web 瀏覽器這類用戶端。POX 對於透過 HTTP 交換資料,而且不需要使用 SOAP 和 WS-* 之進階通訊協定功能 (例如,非 HTTP 傳輸、要求/回應以外的訊息交換模式以及訊息安全性、可靠性與交易等) 的服務來說,是很適當的選擇。.
注意: |
---|
現在 .NET Framework version 3.5 直接支援 REST 和 POX 樣式服務。如需詳細資訊,請參閱 .NET Framework 3.5 文件中的 Web Programming Model主題。 |
實作 POX 服務
這個範例中的服務會實作很基本的客戶資料庫。從合約的角度來看,會公開名稱為 ProcessMessage
的作業,而這個作業會使用 Message
做為輸入並傳回 Message
。
[ServiceContract]
public interface IUniversalContract
{
[OperationContract(Action = "*", ReplyAction = "*")]
Message ProcessMessage(Message input);
}
不過,這個合約不是很實用。我們希望實作基本位址轉換,以透過可使用 HTTP 的方式來存取此集合的內容:
- 該集合的位置為:https://localhost:8100/customers。傳送至此 URI 的 HTTP GET 要求會傳回集合內容,做為指向個別項目的 URI 清單。
- 集合中的每個項目都有唯一的 URI,而形成此 URI 的方式為將客戶識別碼附加至集合 URI。.例如,https://localhost:8100/customers/1 會識別識別碼為 1 的客戶。
- 提供項目 URI,透過對項目 URI 發出 HTTP GET 要求可擷取客戶的 XML 表示。
- 使用 PUT 將新表示套用至現有的項目 URI,即可修改項目。
- 而使用 HTTP POST 將新項目的內容傳送至集合 URI,即可新增項目。伺服器回應中的 HTTP 位置標頭會傳回新項目的 URI。
- 將 DELETE 要求傳送至項目 URI,即可移除項目。
此結構樣式也稱為 REST (代表性狀態傳輸),而這是使用 HTTP 和 POX 訊息進行通訊之應用程式的一種設計方式。
若要完成上述動作,必須先建立會實作要公開之合約的服務。
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
AddressFilterMode = AddressFilterMode.Prefix)]
class CustomerService : IUniversalContract
{
Dictionary<string, Customer> customerList;
public Message ProcessMessage(Message request) { ... }
}
本機 customerList
變數會存放資料庫的內容。需要確定此變數的內容會在要求之間保留,因此可使用 ServiceBehavior 屬性來指定 InstanceContextMode.Single,而這會通知 WCF 在各要求之間使用同樣的實體服務執行個體。也會設定 AddressFilterMode.Prefix,而這會支援您希望服務來實作的階層式位址結構。AddressFilterMode.Prefix 會使服務接聽以其端點位址為開頭的所有 URI,而不僅是完全符合位址的 URI 而已。
您可以使用下列組態來設定服務。
<system.serviceModel>
<bindings>
<customBinding>
<binding name="poxBinding">
<textMessageEncoding messageVersion="None" />
<httpTransport />
</binding>
</customBinding>
</bindings>
<services>
<service name="Microsoft.ServiceModel.Samples.CustomerService">
<host>
<baseAddresses>
<add baseAddress="https://localhost:8100/" />
</baseAddresses>
</host>
<endpoint address="customers"
binding="customBinding"
bindingConfiguration="poxBinding"
contract="Microsoft.ServiceModel.Samples.IUniversalContract" />
</service>
</services>
</system.serviceModel>
這個組態會使用單一端點 (位置為 https://localhost:8100/customers) 來設定單一服務 (位置為 https://localhost:8100)。這個端點會使用具有 HTTP 傳輸和文字編碼器的自訂繫結來進行通訊。會將編碼器設定為使用 MessageVersion.None,可讓編碼器接受讀取時沒有 SOAP 信封的訊息,並導致寫出回應訊息時隱藏 SOAP 信封。為了達到簡化和避免困擾的目的,這個範例會示範透過不安全傳輸進行的通訊。如果需要安全性,POX 應用程式應該使用合併了 HTTP 傳輸安全性 (HTTPS) 的繫結。
實作 ProcessMessage()
您想要讓 ProcessMessage()
實作根據傳入要求中顯示的 HTTP 方法,而採取不同的動作。若要完成此動作,必須存取不會直接在 Message 上公開的某些 HTTP 通訊協定資訊。不過,您可以透過 HttpRequestMessageProperty 類別來存取 HTTP 方法 (以及其他常見的通訊協定項目,例如要求的標頭集合)。
public Message ProcessMessage(Message request)
{
Message response = null;
//The HTTP Method (for example, GET) from the incoming HTTP request
//can be found on the HttpRequestMessageProperty. The MessageProperty
//is added by the HTTP Transport when the message is received.
HttpRequestMessageProperty requestProperties =
(HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
…
}
一旦擁有 HttpRequestMessageProperty,就可以用它來分派不同的內部實作方法。
if (String.Equals("GET", requestProperties.Method,
StringComparison.OrdinalIgnoreCase))
{
response = GetCustomer(request);
}
else if (String.Equals("PUT", requestProperties.Method,
StringComparison.OrdinalIgnoreCase))
{
response = UpdateCustomer(request);
}
else if (String.Equals("POST", requestProperties.Method,
StringComparison.OrdinalIgnoreCase))
{
response = AddCustomer(request);
}
else if (String.Equals("DELETE", requestProperties.Method,
StringComparison.OrdinalIgnoreCase))
{
response = DeleteCustomer(request);
}
雖然 GET 和 POST 是最常見的 HTTP 方法 (PUT 和 DELETE 則較少使用),HTTP 規格仍會定義其他動詞,例如在範例服務中沒有要支援的 HEAD 和 OPTIONS。所幸 HTTP 規格也會對此特定用途定義狀態碼 (405 表示不允許使用方法)。因此,在服務的 ProcessMessage
中便存在下列邏輯。
else
{
//This service does not implement handlers for other HTTP verbs (such as HEAD), so we
//construct a response message and use the HttpResponseMessageProperty to
//set the HTTP status code to 405 (Method Not Allowed) which indicates the client
//used an HTTP verb not supported by the server.
response = Message.CreateMessage(MessageVersion.None, String.Empty, String.Empty);
HttpResponseMessageProperty responseProperty = new HttpResponseMessageProperty();
responseProperty.StatusCode = HttpStatusCode.MethodNotAllowed;
response.Properties.Add( HttpResponseMessageProperty.Name, responseProperty );
}
HttpResponseMessageProperty 和 HttpRequestMessageProperty 非常相似,不過前者帶有回應特定的屬性,例如「狀態碼」和「狀態描述」。建立訊息時,訊息中預設沒有此屬性,這也是為何在傳回回應訊息之前,必須明確將此屬性新增至 Properties 集合的原因。
其他服務實作方法 (GetCustomer、AddCustomer 等) 也會以類似方法明確使用 HttpRequest/HttpResponse 訊息屬性。
實作 POX 用戶端
由範例服務的 REST 結構可見,此範例的用戶端為基本 HTTP 用戶端。為了說明用途會在 WCF HTTP 傳輸堆疊上實作該用戶端,但也可以透過 HttpWebRequest 來實作 (例如,如果用戶端上無法使用 WCF),或甚至使用最新 Web 瀏覽器所提供的 XmlHttpRequest 支援來實作。
使用 WCF 傳送未經處理的 HTTP 要求則牽涉到建立訊息、在 HttpRequestMessageProperty 上設定適當的值,並選擇性用資料填入實體內容以傳送至伺服器。若要更輕鬆進行此處理序,可以撰寫基本 HTTP 用戶端類別。
public class HttpClient : ClientBase<IRequestChannel>
{
public HttpClient( Uri baseUri, bool keepAliveEnabled ) :
this( baseUri.ToString(), keepAliveEnabled )
{
}
public HttpClient( string baseUri, bool keepAliveEnabled ) :
base( HttpClient.CreatePoxBinding( keepAliveEnabled ),
new EndpointAddress( baseUri ) )
{
}
//Other members elided for clarity
}
就和使用 ServiceModel Metadata Utility Tool (Svcutil.exe) 從 WSDL 中繼資料產生的用戶端類別一樣,HttpClient
類別繼承自 ClientBase<TChannel>。即使是以極為一般的合約 (IRequestChannel) 來實作用戶端,ClientBase`1<TChannel> 仍提供了許多好用的功能。例如,會自動建立 ChannelFactory<IRequestChannel> 並自動管理其存留時間。
類似於服務上使用的繫結,HTTP 用戶端會使用自訂繫結進行通訊。
private static Binding CreatePoxBinding( bool keepAliveEnabled )
{
TextMessageEncodingBindingElement encoder =
new TextMessageEncodingBindingElement( MessageVersion.None, Encoding.UTF8 );
HttpTransportBindingElement transport = new HttpTransportBindingElement();
transport.ManualAddressing = true;
transport.KeepAliveEnabled = keepAliveEnabled;
return new CustomBinding( new BindingElement[] { encoder, transport } );
}
用於用戶端繫結的主要新增項目為在 HttpTransportBindingElement 上,將 ManualAddressing 設定為 true 的額外步驟。一般來說,在 ManualAddressing 設定為 false 的情況下,透過傳輸傳送的訊息會指向建立傳輸的 ChannelFactory 時所提供的 URI。以手動方式定址時,透過傳輸傳送的個別訊息在要求之間會有各種不同的 URI,而前提為這些 URI 與 ChannelFactory 的 URI 擁有相同的前置詞即可。將會在應用程式層選取要將訊息傳送至的 URI,因此將 ManualAddressing 設定為 true 就可說是使用上的正確作法。
HttpClient 上最重要的方法為 Request,這個方法會將訊息傳送至特定 URI,並傳回伺服器的回應。針對和 GET 與 DELETE 一樣,不允許在 HTTP 訊息中夾帶資料的 HTTP 方法,您可定義 Request() 多載,並將 SuppressEntityBody 設定為 true。
public Message Request( Uri requestUri, string httpMethod )
{
Message request = Message.CreateMessage( MessageVersion.None, String.Empty );
request.Headers.To = requestUri;
HttpRequestMessageProperty property = new HttpRequestMessageProperty();
property.Method = httpMethod;
property.SuppressEntityBody = true;
request.Properties.Add( HttpRequestMessageProperty.Name, property );
return this.Channel.Request( request );
}
若要支援 POST 和 PUT 這類允許在要求內容中夾帶資料的動詞,也可定義 Request(),而此項會採用表示實體內容的物件。
public Message Request( Uri requestUri, string httpMethod, object entityBody )
{
Message request = Message.CreateMessage( MessageVersion.None, String.Empty, entityBody );
request.Headers.To = requestUri;
HttpRequestMessageProperty property = new HttpRequestMessageProperty();
property.Method = httpMethod;
request.Properties.Add( HttpRequestMessageProperty.Name, property );
return this.Channel.Request( request );
}
由於 entityBody
物件已直接傳遞至 Message.CreateMessage()
,預設 WCF 行為便是透過 DataContractSerializer,將此物件執行個體轉換為 XML。如果想要使用其他行為,可以在將物件傳遞至 Request() 之前,先在 BodyWriter 的實作中包裝此物件。
若要完成 HttpClient
實作,將會新增一些公用程式方法,而這些方法會對要支援的動詞建立程式設計模型。
public Message Get( Uri requestUri )
{
return Request( requestUri, "GET" );
}
public Message Post( Uri requestUri, object body )
{
return Request( requestUri, "POST", body );
}
public Message Put( Uri requestUri, object body )
{
return Request( requestUri, "PUT", body );
}
public Message Delete( Uri requestUri )
{
return Request( requestUri, "DELETE" );
}
現在您已具備此基本 HTTP Helper 類別,接著便可撰寫如下的程式碼,用此類別對服務提出 HTTP 要求。
HttpClient client = new HttpClient( collectionUri );
//Get Customer 1 by doing an HTTP GET to the customer's URI
requestUri = new Uri( collectionUri, "1" );
Message response = client.Get( requestUri );
string statusCode = client.GetStatusCode( response );
string statusDescription = client.GetStatusDescription( response );
Customer aCustomer = response.GetBody<Customer>();
執行範例
若要執行範例,請先啟動伺服器專案。這個專案會在主控台應用程式中,啟動自我裝載的服務。此伺服器會在主控台視窗中,報告所接收之任何要求的狀態。
一旦服務專案正在執行和等待訊息,就可以啟動用戶端專案。用戶端會對伺服器發出一連串 HTTP 要求,表示使用 HTTP 和 POX 訊息來修改伺服器的狀態。用戶端會明確執行下列動作:
- 擷取 Customer 1 並顯示資料。
- 將 Customer 1 的名稱從 “Bob” 變更為 “Robert”。
- 再次擷取 Customer 1,顯示已修改伺服器的狀態。
- 建立兩個新客戶名稱,Alice 和 Charlie。
- 從伺服器中刪除 Customer 1。
- 再次從伺服器擷取 Customer 1,並得到「找不到端點」回應。
- 取得集合的現有內容,這也是連結清單。
- 在集合的每個連結上發出 GET 要求,以擷取集合中的每個項目。
在用戶端的主控台視窗上,顯示每個要求和回應的輸出。
若要設定、建置及執行範例
若要建置方案的 C# 或 Visual Basic .NET 版本,請遵循建置 Windows Communication Foundation 範例中的指示。
若要在單一或跨電腦的組態中執行本範例,請遵循執行 Windows Communication Foundation 範例中的指示。
請參閱
其他資源
How To: Create a Basic Self-Hosted Service
How To: Create a Basic IIS-Hosted Service
Send comments about this topic to Microsoft.
© 2007 Microsoft Corporation. All rights reserved.