REST e POX
In questo esempio viene illustrato come utilizzare il trasporto HTTP in indigo1 per inviare e ricevere messaggi POX (Plain Old XML), ovvero messaggi costituiti unicamente da payload XML senza envelope SOAP di inclusione. I messaggi semplici possono essere inviati e ricevuti da molti tipi di client, inclusi client quali browser Web che non hanno supporto nativo per i protocolli basati su SOAP. POX è una scelta adatta per servizi che scambiano dati su HTTP e non hanno necessità di utilizzare le funzionalità avanzate dei protocolli SOAP e WS - * ad esempio trasporti non HTTP, schemi di scambio messaggi diversi da richiesta/risposta e protezione, affidabilità e transazioni basate su messaggi.
Nota
Il supporto per i servizi di tipo REST e POX viene ora fornito direttamente da .NET Framework versione 3.5. Per ulteriori informazioni, vedere l'argomento Web Programming Model nella documentazione di .NET Framework 3.5.
Implementazione del servizio POX
Il servizio in questo esempio implementa un database client molto semplice. Dalla prospettiva del contratto, espone, un'operazione chiamata ProcessMessage
che prende un Message
come input e restituisce un Message
.
[ServiceContract]
public interface IUniversalContract
{
[OperationContract(Action = "*", ReplyAction = "*")]
Message ProcessMessage(Message input);
}
Tuttavia, questo contratto non è molto funzionale. Si vuole implementare una convenzione di indirizzamento di base per l'accesso al contenuto di questo insieme in modo da poter utilizzare HTTP:
- L'insieme si trova su https://localhost:8100/customers. Richieste HTTP GET inviate all'URI restituiscono il contenuto dell'insieme come un elenco di URI che puntano alle singole voci.
- Ogni voce dell'insieme ha un URI univoco formato aggiungendo l'ID del cliente all'insieme di URI. Ad esempio, https://localhost:8100/customers/1 identifica il cliente con ID 1.
- Data una voce URI, si può recuperare una rappresentazione XML del cliente inviando una richiesta HTTP GET alla voce URI.
- Si può modificare una voce utilizzando PUT per applicare una nuova rappresentazione a una voce URI esistente.
- L'aggiunta di una voce viene effettuata inviando il contenuto della nuova voce all'insieme URI utilizzando HTTP POST. L'URI della nuova voce viene restituito dall'intestazione del percorso HTTP nella risposta del server.
- Si può rimuovere una voce inviando una richiesta DELETE all'URI della voce.
Questo stile architettonico è noto come Representational State Transfer (REST) che è un modo in cui le applicazioni che comunicano utilizzando messaggi HTTP e messaggi semplici sono progettate.
Per realizzare quanto detto, prima si deve creare un servizio che implementa il contratto che si desidera esporre.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
AddressFilterMode = AddressFilterMode.Prefix)]
class CustomerService : IUniversalContract
{
Dictionary<string, Customer> customerList;
public Message ProcessMessage(Message request) { ... }
}
La variabile customerList
locale archivia il contenuto del database. Bisogna assicurarsi che il contenuto di questa variabile sia mantenuto da richiesta a richiesta, pertanto si utilizza l'attributo ServiceBehavior per specificare InstanceContextMode.Singleche informa WCF di utilizzare la stessa istanza fisica del servizio da richiesta a richiesta. Impostare anche AddressFilterMode.Prefixche supporta la struttura gerarchica di indirizzamento che il servizio deve implementare. AddressFilterMode.Prefix fa in modo che il servizio ascolti sugli URI che si avviano con l'indirizzo endpoint, non solo su quelli il cui indirizzo corrisponde precisamente.
Il servizio viene configurato come segue.
<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>
Questa configurazione imposta un solo servizio (su https://localhost:8100) con un solo endpoint (su https://localhost:8100/customers). Questo endpoint comunica utilizzando un'associazione personalizzata che ha Trasporto HTTP e Codificatore di Testo. Il codificatore è configurato per utilizzare MessageVersion.Noneche gli consente di accettare messaggi che non hanno envelope SOAP per la lettura e fa in modo che sopprima l'envelope SOAP quando scrive messaggi di risposta. Per semplicità e chiarezza, questo esempio illustra una comunicazione su un trasporto non protetto. Se è necessaria protezione, bisogna utilizzare un'associazione che incorpora la protezione del trasporto HTTP (HTTPS).
Implementazione di ProcessMessage ()
Si vuole che l'implementazione di ProcessMessage()
esegua diverse azioni sulla base del metodo HTTP presente nella richiesta in entrata. Per fare ciò è necessario accedere a alcune informazioni del protocollo HTTP che non sono esposte direttamente su Message. Tuttavia, si può ottenere accesso al metodo HTTP (e a altri utili elementi del protocollo, ad esempio l'insieme Intestazioni della richiesta) tramite la classe HttpRequestMessageProperty.
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];
…
}
Una volta ottenuta la classe HttpRequestMessageProperty, la si può utilizzare per l'invio a diversi metodi interni di implementazione.
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);
}
Anche se GET e POST sono i metodi HTTP più comuni (PUT e DELETE sono meno diffusi), la specifica HTTP definisce molti altri verbi ad esempio HEAD e OPTIONS che non si intendono supportare nel servizio di esempio. La specifica HTTP fortunatamente definisce anche un codice di stato (405 Metodo non consentito) per questo specifico scopo. Quindi, nel servizio, si ha la logica seguente 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 è molto simile a HttpRequestMessageProperty con l'eccezione che porta proprietà specifiche per la risposta, ad esempio codice di stato e descrizione dello stato. I messaggi non hanno questa proprietà per impostazione predefinita, ecco perché la si deve aggiungere in modo esplicito all'insieme Properties prima di restituire il messaggio di risposta.
Il resto dei metodi di implementazione del servizio (GetCustomer, AddCustomere così via) si avvalgono in modo simile delle proprietà del messaggio HttpRequest/HttpResponse e sono diretti.
Implementazione di client POX
A causa dell'architettura REST del servizio di esempio, il client è un client HTTP di base. È implementato su uno stack di trasporto di HTTP WCF per scopi dimostrativi, ma può essere fatto con la classe HttpWebRequest (se WCF non è disponibile sul client, ad esempio) o anche sul supporto XmlHttpRequest offerto dai più moderni browser Web.
L'invio di richieste HTTP non elaborate utilizzando WCF coinvolge la creazione di messaggi, l'impostazione di valori adeguati su HttpRequestMessagePropertye facoltativamente la popolazione del corpo dell'entità con i dati da inviare al server. Per rendere questo processo più facile si può scrivere una classe di base del client 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
}
Come le classi client generate da metadati WSDL dia ServiceModel Metadata Utility Tool (Svcutil.exe), la classe HttpClient
eredita da ClientBase<TChannel>. Anche se il client viene implementato come un contratto molto generale (IRequestChannel), ClientBase`1<TChannel> fornisce molte utili funzionalità. Ad esempio, crea automaticamente un ChannelFactory<IRequestChannel> e ne gestisce automaticamente la durata.
Similmente all'associazione utilizzata nel servizio, il client HTTP comunica utilizzando un'associazione personalizzata.
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 } );
}
L'aggiunta principale all'associazione client è il passaggio aggiuntivo di ManualAddressing su true sulla classe HttpTransportBindingElement. Di solito, quando ManualAddressing è impostato su false, i messaggi inviati tramite il trasporto sono indirizzati all'URI fornito quando viene creata la ChannelFactory del trasporto. L'indirizzamento manuale consente ai singoli messaggi inviati tramite il trasporto di avere diversi URI da richiesta a richiesta fintantoché hanno lo stesso prefisso dell'URI di ChannelFactory. Gli URI cui si desiderano inviare messaggi a livello dell'applicazione sono selezionati, quindi l'impostazione di ManualAddressing su true, è una buona opzione per l'utilizzo desiderato.
Il metodo più importante su HttpClient è Requestche invia un messaggio a un URI specifico e restituisce la risposta del server. Per metodi HTTP come GET e DELETE che non consentono che i dati siano inclusi nel messaggio HTTP, si definisce un overload di Request() che imposta SuppressEntityBody su 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 );
}
Per supportare verbi come POST e PUT che consentono che dati siano portati nel corpo della richiesta, si definisce anche un sovraccarico di Request() che prende un oggetto che rappresenta il corpo dell'entità.
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 );
}
Perché l'oggetto entityBody
è passato direttamente a Message.CreateMessage()
, il comportamento predefinito di WCF è di convertire questa istanza dell'oggetto in XML per mezzo della classe DataContractSerializer. Se si desidera un altro comportamento, è possibile eseguire il wrapping di questo oggetto in un'implementazione di BodyWriter prima di passarlo a Request().
Per finire l'implementazione di HttpClient
, vengono aggiunti alcuni metodi di utilità che creano un modello di programmazione attorno ai verbi che si desiderano supportare.
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" );
}
Ora che si ha questa classe di supporto HTTP di base, la si può utilizzare per fare richieste HTTP al servizio scrivendo codice simile negli elementi seguenti.
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>();
Esecuzione dell'esempio
Per eseguire l'esempio, prima avviare il progetto Server. Questo progetto avvia un servizio indipendente in un'applicazione console. Il server riporta lo stato di qualsiasi richiesta che riceve nella finestra della console.
Quando il progetto di servizio è in esecuzione e aspetta messaggi, è possibile avviare il progetto client. Il client rilascia una serie di richieste HTTP al server per dimostrare di utilizzare messaggi HTTP e POX per modificare lo stato del server. In particolare, il client esegue le azioni riportate di seguito.
- Recupera il Cliente 1 e mostra i dati.
- Imposta il nome del Cliente 1 da "Bob" a "Robert".
- Recupera nuovamente il Cliente 1 per mostrare che lo stato del server è stato modificato.
- Crea due nuovi clienti denominati Alice e Charlie.
- Elimina il Cliente 1 dal server.
- Recupera nuovamente il Cliente 1 dal server e ottiene una risposta Endpoint non trovato.
- Ottiene il contenuto corrente dell'insieme, che è un elenco di collegamenti.
- Recupera ogni elemento dell'insieme pubblicando una richiesta GET per ogni collegamento dell'insieme.
L'output di ogni richiesta e la risposta vengono mostrati nella finestra della console del client.
Per impostare, compilare ed eseguire l'esempio
Assicurarsi di aver eseguito la Procedura di installazione singola per gli esempi di Windows Communication Foundation.
Per compilare l'edizione in C# o Visual Basic .NET della soluzione, seguire le istruzioni in Generazione degli esempi Windows Communication Foundation.
Per eseguire l'esempio su un solo computer o tra computer diversi, seguire le istruzioni in Esecuzione degli esempi di Windows Communication Foundation.
Vedere anche
Altre risorse
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.