REST und POX
Dieses Beispiel veranschaulicht die Verwendung des HTTP-Transports in indigo1 zum Senden und Empfangen standardmäßiger POX-Nachrichten, d. h. von Nachrichten, die nur aus XML-Nutzlasten ohne einschließenden SOAP-Umschlag bestehen. POX-Nachrichten können von vielen Clienttypen gesendet und empfangen werden, einschließlich Webbrowsern, die keine systemeigene Unterstützung für SOAP-basierte Protokolle bieten. POX ist eine passende Wahl für Dienste, die Daten über HTTP austauschen und keine Verwendung für die erweiterten Protokollfunktionen von SOAP und WS-* haben, z. B. Nicht-HTTP-Transporte, andere Nachrichtenaustauschmuster als Anforderung/Antwort sowie Nachrichtensicherheit, Zuverlässigkeit und Transaktionen.
Tipp
Die Unterstützung für REST- und POX-Dienste erfolgt jetzt direkt durch .NET Framework, Version 3.5. Weitere Informationen finden Sie unter dem Thema Web Programming Model in der .NET Framework 3.5-Dokumentation.
Implementieren eines POX-Diensts
Der Dienst in diesem Beispiel implementiert eine der grundlegenden Kundendatenbanken. Aus der Sicht eines Vertrags macht er den Vorgang ProcessMessage
verfügbar, der eine Message
als Eingabe verwendet und eine Message
ausgibt.
[ServiceContract]
public interface IUniversalContract
{
[OperationContract(Action = "*", ReplyAction = "*")]
Message ProcessMessage(Message input);
}
Dieser Vertrag ist jedoch nicht sehr funktional. Es soll eine grundlegende Adressierungskonvention implementiert werden, um mithilfe von HTTP auf den Inhalt dieser Auflistung zugreifen zu können:
- Die Auflistung ist unter verfügbar. HTTP GET-Anforderungen, die an diesen URI gesendet werden, geben den Inhalt der Auflistung als Liste von URIs zurück, die auf einzelne Einträge verweisen.
- Jeder Eintrag in der Auflistung hat einen eindeutigen URI, der durch das Anfügen der Kunden-ID an den URI der Auflistung gebildet wird. Beispielsweise identifiziert https://localhost:8100/customers/1 den Kunden mit ID 1.
- Anhand eines Eintrags-URI kann eine XML-Darstellung des Kunden abgerufen werden, indem eine HTTP GET-Anforderung an den Eintrags-URI ausgegeben wird.
- Ein Eintrag kann geändert werden, indem mit PUT eine neue Darstellung auf einen vorhandenen Eintrags-URI angewendet wird.
- Ein Eintrag wird hinzugefügt, indem der Inhalt des neuen Eintrags mithilfe von HTTP-POST an den Auflistungs-URI gesendet wird. Der URI für den neuen Eintrag wird vom HTTP-Speicherortheader in der Antwort des Servers zurückgegeben.
- Ein Eintrag kann entfernt werden, indem eine DELETE-Anforderung an den URI des Eintrags gesendet wird.
Dieser Architekturtyp wird als REST (Representational State Transfer) bezeichnet. Dies ist eine Möglichkeit für Anwendungen, mit HTTP- und POX-Nachrichten zu kommunizieren.
Um all das zu erreichen, muss zuerst ein Dienst erstellt werden, der den Vertrag implementiert, der verfügbar gemacht werden soll.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
AddressFilterMode = AddressFilterMode.Prefix)]
class CustomerService : IUniversalContract
{
Dictionary<string, Customer> customerList;
public Message ProcessMessage(Message request) { ... }
}
Die lokale customerList
-Variable speichert den Inhalt der Datenbank. Es soll sichergestellt werden, dass der Inhalt dieser Variable bei alle Anforderungen beibehalten wird. Daher wird das Attribut ServiceBehavior zum Festlegen von InstanceContextMode.Single verwendet, mit dem WCF angewiesen wird, bei allen Anforderungen die gleiche physische Dienstinstanz zu verwenden. Es wird auch AddressFilterMode.Prefix festgelegt. Damit wird die hierarchische Adressierungsstruktur unterstützt, die in dem Dienst implementiert werden soll. Durch AddressFilterMode.Prefix überwacht der Dienst alle URIs, die mit seiner Endpunktadresse beginnen, und nicht nur die genau mit dieser Adresse übereinstimmenden URIs.
Der Dienst wird mithilfe der folgenden Konfiguration konfiguriert.
<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>
Diese Konfiguration richtet einen einzelnen Dienst (unter https://localhost:8100) mit einem einzelnen Endpunkt (unter ) ein. Dieser Endpunkt kommuniziert mit einer benutzerdefinierten Bindung, die den HTTP-Transport und den Textencoder enthält. Der Encoder verwendet MessageVersion.None und kann dadurch Nachrichten ohne SOAP-Umschlag annehmen und den SOAP-Umschlag beim Schreiben von Antwortnachrichten unterdrücken. In diesem Beispiel wird die Kommunikation über einen ungesicherten Transport aus Gründen der Einfachheit und Übersichtlichkeit veranschaulicht. Wenn Sicherheit erforderlich ist, sollten POX-Anwendungen eine Bindung verwenden, die HTTP-Transportsicherheit (HTTPS) integriert.
Implementieren von "ProcessMessage()"
Die Implementierung von ProcessMessage()
soll abhängig von der HTTP-Methode in der eingehenden Anforderung unterschiedliche Aktionen vornehmen. Um das zu erreichen, muss auf einige HTTP-Protokollinformationen zugegriffen werden, die nicht direkt in Message verfügbar sind. Der Zugriff auf die HTTP-Methode (und andere nützliche Protokollelemente wie die Headerauflistung der Anforderung) ist jedoch mithilfe der Klasse HttpRequestMessageProperty möglich.
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];
…
}
Sobald HttpRequestMessageProperty verfügbar ist, ist eine Weiterleitung zu anderen internen Implementierungsmethoden möglich.
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);
}
Obwohl es sich bei GET und POST um die am häufigsten verwendeten HTTP-Methoden handelt (PUT und DELETE werden weniger häufig verwendet), definiert die HTTP-Spezifikation mehrere andere Verben (z. B. HEAD und OPTIONS), die nicht in dem Beispieldienst unterstützt werden sollen. Die HTTP-Spezifikation definiert jedoch auch einen Statuscode (405: Unzulässige Methode) für diesen bestimmten Zweck. Daher ist in ProcessMessage
des Diensts die folgende Logik enthalten.
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 ist HttpRequestMessageProperty sehr ähnlich, enthält jedoch antwortspezifische Eigenschaften wie Statuscode und Statusbeschreibung. Nachrichten enthalten diese Eigenschaft nicht standardmäßig, wenn sie erstellt werden. Daher muss sie ausdrücklich zur Liste Properties hinzugefügt werden, bevor die Antwortnachricht zurückgegeben wird.
Die übrigen Dienstimplementierungsmethoden (GetCustomer, AddCustomer usw.) machen ähnlichen Gebrauch von den HttpRequest/HttpResponse-Nachrichteneigenschaften und sind sehr einfach.
Implementieren eines POX-Clients
Aufgrund der REST-Architektur unseres Beispieldiensts handelt es sich bei dem Client in diesem Beispiel um einen grundlegenden HTTP-Client. Er wird zur Veranschaulichung auf dem WCF-HTTP-Transportstapel implementiert, es kann jedoch auch HttpWebRequest (wenn beispielsweise WCF nicht auf dem Client verfügbar ist) ebenso wie die XmlHttpRequest-Unterstützung verwendet werden, die in den meisten modernen Webbrowsern enthalten ist.
Das Senden unformatierter HTTP-Anforderungen mit WCF schließt das Erstellen von Nachrichten, das Einrichten der entsprechenden Werte für HttpRequestMessageProperty und das optionale Auffüllen des Entitätskörpers mit Daten ein, die an den Server gesendet werden sollen. Um diesen Prozess ein wenig zu erleichtern, kann eine grundlegende HTTP-Clientklasse erstellt werden.
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
}
So wie die Clientklassen, die von WSDL-Metadaten vom ServiceModel Metadata Utility Tool (Svcutil.exe) generiert wurden, erbt die HttpClient
-Klasse von ClientBase<TChannel>. Auch wenn der Client als sehr allgemeiner Vertrag implementiert wurde (IRequestChannel), bietet ClientBase`1<TChannel> dennoch zahlreiche nützliche Funktionen. Beispielsweise erstellt es automatisch einen ChannelFactory<IRequestChannel> und verwaltet seine Lebensdauer automatisch.
Ähnlich zur auf dem Dienst verwendeten Bindung kommuniziert der HTTP-Client mit einer benutzerdefinierten Bindung.
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 } );
}
Die große Ergänzung der Clientbindung besteht im zusätzlichen Schritt des Festlegens von ManualAddressing auf true auf dem HttpTransportBindingElement. Wenn ManualAddressing als false festgelegt wird, werden durch den Transport gesendete Nachrichten normalerweise an den angegebenen URI adressiert, wenn die ChannelFactory des Transports erstellt wird. Die manuelle Adressierung ermöglicht, dass einzelne durch den Transport gesendete Nachrichten von Anforderung zu Anforderung unterschiedliche URIs haben können, solange diese URIs das gleiche Präfix wie der URI der ChannelFactory besitzen. Die URIs, an die Nachrichten auf Anwendungsebene gesendet werden sollen, werden ausgewählt. Daher ist es sinnvoll, ManualAddressing auf true festzulegen.
Die wichtigste Methode bei HttpClient ist Request, bei der eine Nachricht an einen bestimmten URI gesendet und die Antwort des Servers zurückgegeben wird. Für HTTP-Methoden wie GET und DELETE, bei denen keine Daten in der HTTP-Nachricht transportiert werden können, wird eine Überladung von Request() definiert, die SuppressEntityBody auf true festlegt
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 );
}
Um Verben wie POST und PUT zu unterstützen, die das Transportieren von Daten im Text der Anforderung ermöglichen, wird auch eine Überladung von Request() definiert, die ein Objekt akzeptiert, das den gesamten Text darstellt.
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 );
}
Da das Objekt entityBody
direkt an Message.CreateMessage()
übergeben wird, wird diese Objektinstanz standardmäßig von WCF mithilfe von DataContractSerializer in XML konvertiert. Wenn ein anderes Verhalten gewünscht wird, kann dieses Objekt in einer Implementierung von BodyWriter umschlossen werden, bevor es an Request() übergeben wird.
Um die Implementierung von HttpClient
abzuschließen, werden einige Dienstprogrammmethoden hinzugefügt, die ein Programmiermodell um die Verben erstellen, die unterstützt werden sollen.
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" );
}
Nachdem diese grundlegende HTTP-Hilfsklasse zur Verfügung steht, kann sie mithilfe von Code wie dem folgenden zum Durchführen von HTTP-Anforderungen an den Dienst verwendet werden.
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>();
Ausführen des Beispiels
Um das Beispiel auszuführen, starten Sie zuerst das Serverprojekt. Dieses Projekt startet einen selbst gehosteten Dienst in einer Konsolenanwendung. Der Server teilt den Status aller Anforderungen mit, die er im Konsolenfenster empfängt.
Sobald das Dienstprojekt ausgeführt wird und auf Nachrichten wartet, können Sie das Clientprojekt starten. Der Client gibt eine Reihe von HTTP-Anforderungen an den Server heraus, um ein Beispiel dafür zu zeigen, wie HTTP- und POX-Nachrichten zum Ändern des Serverzustands verwendet werden. Im Einzelnen führt der Client die folgenden Aktionen aus:
- Ruft Kunde 1 ab und zeigt die Daten an
- Ändert den Namen von Kunde 1 von "Bob" in "Robert"
- Ruft Kunde 1 erneut ab, um anzuzeigen, dass der Zustand des Servers geändert wurde
- Erstellt zwei neue Kunden namens "Alice" und "Charlie"
- Löscht Kunde 1 vom Server
- Ruft wieder Kunde 1 vom Server ab und erhält die Antwort "Es wurde kein Endpunkt gefunden".
- Erhält den aktuellen Inhalt der Auflistung, bei dem es sich um eine Liste von Links handelt
- Ruft jedes Element in der Auflistung durch das Ausgeben einer GET-Anforderung für jeden Link in der Auflistung ab
Die Ausgabe jeder Anforderung und Antwort wird im Konsolenfenster des Clients angezeigt.
So richten Sie das Beispiel ein, erstellen es und führen es aus
Vergewissern Sie sich, dass Sie die Beispiele zum einmaligen Setupverfahren für Windows Communication Foundation ausgeführt haben.
Zum Erstellen der C#- oder Visual Basic .NET-Version der Projektmappe befolgen Sie die unter Erstellen der Windows Communication Foundation-Beispiele aufgeführten Anweisungen.
Wenn Sie das Beispiel in einer Konfiguration mit einem einzigen Computer oder computerübergreifend ausführen möchten, befolgen Sie die unter Durchführen der Windows Communication Foundation-Beispiele aufgeführten Anweisungen.
Siehe auch
Weitere Ressourcen
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.