REST y POX
Este ejemplo muestra cómo utilizar el transporte HTTP en Windows Communication Foundation (WCF) para enviar y recibir mensajes "antiguos XML sin formato" (POX). Es decir, mensajes que están solamente compuestos de cargas de XML sin ningún sobre SOAP adjunto. Los mensajes POX se pueden enviar y recibir por muchos tipos de clientes, incluidos clientes como exploradores web que no tienen ninguna compatibilidad nativa para los protocolos basados en SOAP. POX es una opción conveniente para los servicios que intercambian datos sobre HTTP y no tienen ningún requisito para utilizar las funciones protocolares avanzadas de SOAP y WS - *, como transportes no HTTP, modelos de intercambio de mensajes distintos a solicitud/respuesta, y seguridad, fiabilidad y transacciones basadas en mensaje.
Implementar un servicio POX
El servicio en este ejemplo implementa una base de datos de cliente muy básica. Desde una perspectiva de contrato, expone una operación llamada ProcessMessage
que toma Message
como entrada y devuelve Message
.
[ServiceContract]
public interface IUniversalContract
{
[OperationContract(Action = "*", ReplyAction = "*")]
Message ProcessMessage(Message input);
}
Sin embargo, este contrato no es muy funcional. Deseamos implementar una convención de direccionamiento básica para tener acceso al contenido de esta colección de modo que podamos utilizar HTTP:
La colección reside en https://localhost:8100/customers. Las solicitudes HTTP GET enviadas a este URI devuelven el contenido de la colección como una lista de URI que señalan a las entradas individuales.
Cada entrada en la colección tiene un URI único, formado al anexar el id. del cliente a la colección URI. Por ejemplo, https://localhost:8100/customers/1 identifica el cliente con id. 1.
Con un URI de entrada podemos recuperar una representación XML del cliente emitiendo una solicitud HTTP GET al URI de entrada.
Podemos modificar una entrada utilizando PUT para aplicar una nueva representación a un URI de entrada existente.
Agregar una entrada se logra enviando el contenido de la nueva entrada a la colección URI utilizando HTTP POST. El encabezado de ubicación de HTTP devuelve el URI para la nueva entrada en la respuesta del servidor.
Podemos quitar una entrada enviando una solicitud de DELETE al URI de entrada.
Este estilo de arquitectura se conoce como REST (Transferencia de Estado Representacional), que es una manera en la que están diseñadas las aplicaciones que comunican utilizando mensajes HTTP y POX.
Para lograr todo esto, debemos crear primero un servicio que implemente el contrato que deseamos exponer.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
AddressFilterMode = AddressFilterMode.Prefix)]
class CustomerService : IUniversalContract
{
Dictionary<string, Customer> customerList;
public Message ProcessMessage(Message request) { ... }
}
La variable local customerList
almacena el contenido de la base de datos. Deseamos asegurarnos de que el contenido de esta variable se retiene en todas las solicitudes, por lo que utilizamos el atributo ServiceBehavior para especificar InstanceContextMode.Single, que informa a WCF para utilizar la misma instancia de servicio física en todas las solicitudes. También establecemos AddressFilterMode.Prefix, que admite la estructura de direccionamiento jerárquica que deseamos que nuestro servicio implemente. AddressFilterMode.Prefix hace que nuestro servicio realice escuchas en todos los URI que se inician con su dirección de extremo, no simplemente aquellos que contengan exactamente la misma dirección .
El servicio se configura utilizando la configuración siguiente.
<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>
Esta configuración prepara un servicio único (en https://localhost:8100) con un extremo único (en https://localhost:8100/customers). Este extremo se comunica utilizando un enlace personalizado que tiene el Transporte HTTP y el Codificador de Texto. El codificador se configura para utilizar MessageVersion.None, que permite al codificador aceptar mensajes que no tienen sobres SOAP en lectura, y hace que suprima el sobre SOAP cuando escribe los mensajes de respuesta. Para una mayor simplicidad y claridad, este ejemplo muestra la comunicación sobre un transporte no seguro. Si se requiere seguridad, las aplicaciones POX deberían utilizar un enlace que incorpore seguridad en transporte HTTP (HTTPS).
Implementar ProcessMessage ()
Deseamos que la implementación de ProcessMessage()
tome medidas diferentes basadas en el método HTTP presente en la petición entrante. Para lograr eso, debemos tener acceso a alguna información del protocolo HTTP que no se expone directamente en Message. Sin embargo, podemos conseguir el acceso al método HTTP (y otros elementos protocolares útiles, como la colección de encabezados de solicitud) a través de la clase 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];
…
}
Cuando tengamos HttpRequestMessageProperty, podemos utilizarlo para enviar a métodos de implementación internos diferentes.
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);
}
Aunque GET y POST son los métodos HTTP más comunes (PUT y DELETE en menor grado), la especificación de HTTP define otros verbos como HEAD y OPTIONS que no pretendemos soportar en nuestro servicio de ejemplo. Afortunadamente, la especificación de HTTP también define un código de estado (Método 405 no Permitido) para este propósito concreto. Como tal, tenemos la lógica siguiente en ProcessMessage
de nuestro servicio.
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 es muy similar a HttpRequestMessageProperty excepto porque lleva propiedades específicas de respuesta, como Código de estado y Descripción del Estado. Los mensajes no tienen de forma predeterminada esta propiedad cuando se crean, razón por la cual debemos agregarlo explícitamente a la colección Properties antes de devolver el mensaje de respuesta.
El resto de los métodos de implementación de servicio (GetCustomer, AddCustomer, etc.) realizan un uso similar de las propiedades de mensaje de HttpRequest/HttpResponse y son sencillos.
Implementar un cliente POX
Debido a la arquitectura REST de nuestro servicio de ejemplo, el cliente para este ejemplo es un cliente HTTP básico. Se implementa sobre la pila de transporte HTTP WCF con propósito ilustrativo, pero se puede hacer con HttpWebRequest (si WCF no estuviera disponible en el cliente, por ejemplo) o incluso la compatibilidad XmlHttpRequest proporcionada por la mayoría de los exploradores web modernos.
Enviar solicitudes HTTP sin formato utilizando WCF implica crear mensajes, establecer los valores adecuados en HttpRequestMessagePropertyy rellenar opcionalmente el cuerpo de la entidad con datos para enviar al servidor. Para facilitar este proceso, podemos escribir una clase de cliente HTTP básica.
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
}
Como las clases de cliente generadas a partir de los metadatos del WSDL por ServiceModel Metadata Utility Tool (Svcutil.exe)l, nuestra clase HttpClient
hereda de ClientBase<TChannel>. Aunque nuestro cliente se implementa en un contrato muy general (IRequestChannel), el ClientBase`1<TChannel> todavía proporciona gran cantidad de funcionalidad útil. Por ejemplo, crea automáticamente un ChannelFactory<IRequestChannel> y automáticamente administra su duración.
Similar al enlace utilizado en el servicio, nuestro cliente HTTP comunica utilizando un enlace personalizado.
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 } );
}
La adición principal al enlace del cliente es el paso adicional de establecer ManualAddressing como true en HttpTransportBindingElement. Normalmente, cuando ManualAddressing está establecido como false, los mensajes enviados a través del transporte se dirigen al URI proporcionado cuando se crea ChannelFactory del transporte. El direccionamiento manual permite que los mensajes individuales enviados a través del transporte puedan tener URI diferentes en cada solicitud, siempre y cuando tengan el mismo prefijo que el URI de ChannelFactory. Los URI a los que deseamos enviar mensajes en el nivel de aplicación están seleccionados, por lo que establecer ManualAddressing a true es una opción buena para el uso previsto.
El método más importante en HttpClient es Request, que envía un mensaje a un URI concreto y devuelve la respuesta del servidor. Para métodos HTTP como GET y DELETE, que no permiten llevar ningún dato en el mensaje HTTP, definimos una sobrecarga de Request() que establece SuppressEntityBody como 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 );
}
Para soportar verbos como POST y PUT, que permiten llevar datos en el cuerpo de la solicitud, también definimos una sobrecarga de Request() que toma un objeto que representa el cuerpo de la entidad.
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 );
}
Dado que el objeto entityBody
se pasa directamente a Message.CreateMessage()
, el comportamiento WCF predeterminado es convertir esta instancia de objeto en XML por medio de DataContractSerializer. Si deseáramos otro comportamiento, podríamos ajustar este objeto en una implementación de BodyWriter antes de pasarlo a Request().
Para finalizar la implementación de HttpClient
, se agrega una pequeña cantidad de métodos de utilidad, los cuales crean un modelo de programación alrededor de los verbos que deseamos admitir.
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" );
}
Ahora que tenemos esta clase de ayudante de HTTP básico, podemos utilizarlo para realizar las solicitudes HTTP al servicio escribiendo código similar al siguiente.
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>();
Ejecutar el ejemplo
Para ejecutar el ejemplo, primero inicie el proyecto Servidor. Este proyecto inicia un servicio auto-hospedado en una aplicación de consola. El servidor informa del estado de cualquier solicitud que recibe en la ventana de la consola.
Cuando el proyecto de servicio se esté ejecutando y espere los mensajes, puede usted iniciar el proyecto de cliente. El cliente emite una serie de solicitudes HTTP al servidor para mostrar cómo utilizar mensajes HTTP y POX para modificar el estado en el servidor. Específicamente, el cliente realiza las acciones siguientes:
Recupera el cliente número 1 y muestra los datos.
Cambia el nombre de Cliente 1 de "Bob" a "Robert".
Recupera de nuevo Cliente 1 para mostrar que el estado del servidor se ha modificado.
Crea dos nuevos Clientes, llamados Alice y Charlie.
Elimina el Cliente 1 del servidor.
Recupera de nuevo el Cliente 1 del servidor y obtiene una respuesta de extremo no encontrado.
Obtiene el contenido actual de la colección, que es una lista de vínculos.
Recupera cada elemento en la colección emitiendo una solicitud GET en cada vínculo en la colección.
El resultado de cada solicitud y respuesta se muestra en la ventana de la consola del cliente.
Para configurar, generar y ejecutar el ejemplo
Asegúrese de que ha realizado Procedimiento de instalación único para ejemplos de Windows Communication Foundation.
Para generar el código C# o Visual Basic .NET Edition de la solución, siga las instrucciones de Generación de ejemplos de Windows Communication Foundation.
Para ejecutar el ejemplo en una configuración de equipos única o cruzada, siga las instrucciones de Ejecución de ejemplos de Windows Communication Foundation.
Consulte también
Otros recursos
How To: Create a Basic Self-Hosted Service
How To: Create a Basic IIS-Hosted Service
Copyright © 2007 Microsoft Corporation. Reservados todos los derechos.