Export (0) Print
Expand All

REST and POX

Download sample

This sample demonstrates how to use HTTP transport in Windows Communication Foundation (WCF) to send and receive “plain old XML” (POX) messages – that is, messages that consist solely of XML payloads without any enclosing SOAP envelope. POX messages can be sent and received by many types of clients, including clients such as Web browsers that do not have any native support for SOAP-based protocols. POX is a suitable choice for services that exchange data over HTTP and have no requirement to use the advanced protocol capabilities of SOAP and WS-* such as non-HTTP transports, message exchange patterns other than request/response, and message-based security, reliability, and transactions.

Aa395208.Important(en-us,VS.90).gif Note:
Support for REST and POX style services are now directly supported by the .NET Framework version 3.5. For more information, see the Web Programming Model topic in the .NET Framework 3.5 documentation.

Implementing a POX service

The service in this sample implements a very basic customer database. From a contract perspective, it exposes one operation called ProcessMessage that takes a Message as input and returns a Message.

[ServiceContract]
public interface IUniversalContract
{
    [OperationContract(Action = "*", ReplyAction = "*")]
    Message ProcessMessage(Message input);
}

However, this contract is not very functional. We want to implement a basic addressing convention for accessing the contents of this collection in a way that can use HTTP:

  • The collection lives at http://localhost:8100/customers. HTTP GET requests sent to this URI return the contents of the collection as a list of URIs that point to individual entries.

  • Each entry in the collection has a unique URI formed by appending the customer ID to the collection URI. For example, http://localhost:8100/customers/1 identifies the customer with ID 1.

  • Given an entry URI, we can retrieve an XML representation of the customer by issuing an HTTP GET request to the entry URI.

  • We can modify an entry by using PUT to apply a new representation to an existing entry URI.

  • Adding an entry is accomplished by sending the contents of the new entry to the collection URI using HTTP POST. The URI for the new entry is returned by the HTTP location header in the server’s response.

  • We can remove an entry by sending a DELETE request to the entry’s URI.

This architectural style is known as REST (Representational State Transfer), which is one way that applications that communicate using HTTP and POX messages are designed.

To accomplish all of this, we must first create a service that implements the contract we want to expose.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, 
  AddressFilterMode = AddressFilterMode.Prefix)]
class CustomerService : IUniversalContract
{
    Dictionary<string, Customer> customerList;
    public Message ProcessMessage(Message request) { ... }
}

The local customerList variable stores the contents of the database. We want to make sure that the contents of this variable are retained from request to request, so we use the ServiceBehavior attribute to specify InstanceContextMode.Single, which informs WCF to use the same physical service instance from request to request. We also set AddressFilterMode.Prefix, which supports the hierarchical addressing structure we want our service to implement. AddressFilterMode.Prefix causes our service to listen on all URIs that start with its endpoint address, not just those that match the address exactly.

The service is configured using the following configuration.

<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="http://localhost:8100/" />
            </baseAddresses>
          </host>
            <endpoint address="customers" 
                      binding="customBinding" 
                      bindingConfiguration="poxBinding"
                    contract="Microsoft.ServiceModel.Samples.IUniversalContract" />
        </service>
    </services>
 </system.serviceModel>

This configuration sets up a single service (at http://localhost:8100) with a single endpoint (at http://localhost:8100/customers). This endpoint communicates using a custom binding that has the HTTP Transport and the Text Encoder. The encoder is set up to use MessageVersion.None, which allows the encoder to accept messages that do not have SOAP envelopes on read and causes it to suppress the SOAP envelope when it writes out response messages. For simplicity and clarity, this sample demonstrates communication over an unsecured transport. If security is required, POX applications should use a binding that incorporates HTTP transport security (HTTPS).

Implementing ProcessMessage()

We want the implementation of ProcessMessage() to take different actions based on the HTTP method present in the incoming request. To accomplish that, we must access some HTTP protocol information that is not directly exposed on the Message. However, we can get access to the HTTP method (and other usefully protocol elements such as the request’s Headers collection) through the HttpRequestMessageProperty class.

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];
    …
}

Once we have the HttpRequestMessageProperty, we can use it to dispatch to different internal implementation methods.

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);
}

Although GET and POST are the most common HTTP methods (PUT and DELETE to a lesser extent), the HTTP specification defines several other verbs such as HEAD and OPTIONS that we do not intend to support in our sample service. Fortunately, the HTTP specification also defines a status code (405 Method Not Allowed) for this specific purpose. As such, we have the following logic in our service’s 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 is very similar to HttpRequestMessageProperty except it carries response-specific properties such as Status Code and Status Description. Message’s do not have this property by default when they are created, which is why we must explicitly add it to the Properties collection prior to returning the response message.

The rest of the service implementation methods (GetCustomer, AddCustomer, and so on) make similar use of the HttpRequest/HttpResponse message properties and are straightforward.

Implementing a POX client

Due to the REST architecture of our sample service, the client for this sample is a basic HTTP client. It is implemented on top of the WCF HTTP transport stack for illustrative purposes, but it can be done with HttpWebRequest (if WCF was not available on the client, for example) or even the XmlHttpRequest support offered by most modern Web browsers.

Sending raw HTTP requests using WCF involves creating messages, setting the appropriate values on HttpRequestMessageProperty, and optionally populating the entity body with data to send to the server. To make this process a little easier, we can write a basic HTTP client class.

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
}

Like the client classes generated from WSDL metadata by the ServiceModel Metadata Utility Tool (Svcutil.exe)l, our HttpClient class inherits from ClientBase<TChannel>. Even though our client is implemented in terms of a very general contract (IRequestChannel), the ClientBase`1<TChannel> still provides a lot of useful functionality. For example, it automatically creates a ChannelFactory<IRequestChannel> and automatically manages its lifetime.

Similar to the binding used on the service, our HTTP client communicates using a custom binding.

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 } );
}

The major addition to the client binding is the additional step of setting ManualAddressing to true on the HttpTransportBindingElement. Normally, when ManualAddressing is set to false, messages sent through the transport are addressed to the URI provided when the transport’s ChannelFactory is created. Manual addressing allows individual messages sent through the transport to have varying URIs from request to request so long as those URIs have the same prefix as the URI of the ChannelFactory. The URIs we want to send messages to at the application layer are selected, so setting ManualAddressing to true is a good option for the intended use.

The most important method on HttpClient is Request, which sends a message to a specific URI and returns the server’s response. For HTTP methods like GET and DELETE that do not allow any data to be carried in the HTTP message, we define an overload of Request() that sets SuppressEntityBody to 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 );
}

To support verbs like POST and PUT that allow data to be carried in the body of the request, we also define an overload of Request() that takes an object that represents the entity body.

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 );
}

Because the entityBody object is directly passed to Message.CreateMessage(), the default WCF behavior is to convert this object instance into XML by means of the DataContractSerializer. If we wanted other behavior, we could wrap this object in an implementation of BodyWriter prior to passing it to Request().

To finish the implementation of HttpClient, a few utility methods are added that create a programming model around the verbs we want to support.

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" );
}

Now that we have this basic HTTP helper class, we can use it to make HTTP requests to the service by writing code similar to the following.

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>();

Running the Sample

To run the sample, first start the Server project. This project starts a self-hosted service in a console application. The server reports the status of any requests it receives in the console window.

Once the service project is running and waiting for messages, you can start the client project. The client issues a series of HTTP requests to the server to demonstrate using HTTP and POX messages to modify state on the server. Specifically, the client performs the following actions:

  • Retrieves Customer 1 and displays the data.

  • Changes the name of Customer 1 from “Bob” to “Robert”.

  • Retrieves Customer 1 again to show the server’s state has been modified.

  • Creates two new Customer’s named Alice and Charlie.

  • Deletes Customer 1 from the server.

  • Retrieves Customer 1 again from the server and obtains an Endpoint Not Found response.

  • Obtains the current contents of the collection, which is a list of links.

  • Retrieves each element in the collection by issuing a GET request on each link in the collection.

The output of each request and response is shown in the client’s console window.

To set up, build, and run the sample

  1. Ensure that you have performed the One-Time Set Up Procedure for the Windows Communication Foundation Samples.

  2. To build the C# or Visual Basic .NET edition of the solution, follow the instructions in Building the Windows Communication Foundation Samples.

  3. To run the sample in a single- or cross-machine configuration, follow the instructions in Running the Windows Communication Foundation Samples.

See Also


© 2007 Microsoft Corporation. All rights reserved.

Community Additions

ADD
Show:
© 2014 Microsoft