HTTP/POX Programming Basics

One of the goals we have for WCF in Orcas is to extend our feature set to better meet the needs of the "plain old XML over HTTP programmer". In other words, we're all about making the experience of building these types of apps better on our platform. There's a good chunk of this story already in the March CTP bits, and there will be more coming in Orcas Beta 2.

The price of admission for POX

Making WCF speak POX involves three things:

  1. Washing the SOAP off the binding.
  2. Dispatching HTTP requests -> server operations
  3. Programming model support at the Service Model layer for controlling the details of (2)

In WCF v1, we gave you rudimentary ability to do (1) via a CustomBinding and MessageVersion.None, but that's about it. The only programming model support we had was with HttpRequestMessageProperty/HttpResponseMessageProperty, which wasn't exactly the easiest thing to get to. V1 didn't attempt to address anything relating to (2); instead we punt and make you write Message/Message/* contracts.

All this gets much better in Orcas.

Washing the SOAP off the Binding with the WebHttpBinding

We're adding a new system-provided binding (System.ServiceModel.WebHttpBinding) specifically written for non-SOAP scenarios. It's essentially the BasicHttpBinding that's been pre-wired with MessageVersion.None support. There's more to it than that (it supports JSON as well) but that's enough to get started.

Writing POX Contracts with Orcas

In V1, if you wanted to write a WCF operation that responded to HTTP GET you had to write that contract in a loosely typed way (the "Message/Message/*" pattern we talk so much about). That meant if you wanted to write a GetStockQuote service that actually used the HTTP GET verb, you pretty much had to do everything yourself. In Orcas, you can write service contracts in the way you'd expect:

    [ServiceContract(Namespace="")]

    interface IStockQuoteService

    {

        [OperationContract]

        string GetStockQuote(string symbol);

    }

In the simple case, you don't need to mark up your service contract with any special metadata to support HTTP GET. It just works.

Adding a POX endpoint

The code for setting up an HTTP/POX endpoint looks like the following:

    WebHttpBinding webBinding = new WebHttpBinding();

 

    ServiceHost host = new ServiceHost(typeof(StockQuoteService), new Uri("http://localhost:8080/"));

    ServiceEndpoint ep = host.AddServiceEndpoint(typeof(IStockQuoteService), webBinding, "stocks");

 

    ep.Behaviors.Add(new HttpTransferEndpointBehavior());

The last line there is critical. System.ServiceModel.Description.HttpTransferEndpointBehavior is the secrect sauce that adds support for POX dispatching to a particular endpoint. Under the hood, this plugs in an implementation of the IDispatchOperationSelector and IDispatchOperationFormatter extensibility interfaces to binding HTTP requests to service operations and extract operation parameters from the URI.

The POX Dispatching Model

Abstractly, our dispatching model associates each operation on your service's contract with a unique combination of URI Path Suffix + HTTP Method. By default, we use the OperationName as the Path suffix and "GET" as the HTTP Method.

Concretely, this means that the default URI for accessing this service is the following:

    http://localhost:8080/stocks/GetStockQuote?symbol=msft

The http://localhost:8080/stocks comes from the endpoint address, the /GetStockQuote?symbol=msft part comes from the operation ("Path Suffix" in our vocabulary means everything after

Of course, you can control the path/method for each operation by using the [HttpTransferContract] programming model:

    [ServiceContract(Namespace="")]

    interface IStockQuoteService

    {

        [OperationContract]
        [HttpTransferContract(Path="quote")]

        string GetStockQuote(string symbol);

    }

 

Once you've done that, the URI for the GetStockQuote operation becomes

    http://localhost:8080/stocks/quote?symbol=msft

which is a little easier on the eyes. Maybe not the best URL design in the world but hey, this is a sample :)

[HttpTransferContract] also has a Method parameter which controls the HTTP Method (GET, POST, PUT, DELETE, etc) associated with the operation. Since each operation binds to the combination of path + method, you can bind different operations to different verbs on the same path, which comes in handy for more advanced scenarios.

In Beta 1, we support a fairly literal algorithm for matching Path values -- we consider all the URI segments after the endpoint address to be the Path, and then we do a case-insenstive match against each operation's Path component to find out which ones might match. If we find an operation that matches both the request's path and the request's method, we dispatch to the operation. If we matched the path but not the method (e.g. you tried to GET something that only supports PUT), we return HTTP 405 (Method Not Allowed). If we didn't match the path at all, we return HTTP 404.

In Beta 2, we plan on supporting more advanced template-based Path matching, which will give you better control over what your URI's look like. This will also allow URI segments to be bound to method parameters. I'll save the details on that for another post.

Next time I'll talk about parameter passing and serialization. In the mean time, here's a simple little console app for you to play around with (don't forget to add a reference to System.ServiceModel.Web.dll before you try to compile this):

using System;

using System.Collections.Generic;

using System.Text;

 

using System.ServiceModel;

using System.ServiceModel.Description;

 

namespace HelloHTTP

{

   [ServiceContract(Namespace = "")]

   interface IHelloService

   {

        [OperationContract]

        [HttpTransferContract(Path="SayHello", Method="GET")]

        string SayHello(string name);

   }

 

   public class HelloService : IHelloService

   {

        public string SayHello(string name)

        {

            return String.Format("Hello, {0}!", name);

        }

   }

 

   class Program

   {

        static void Main(string[] args)

        {

            WebHttpBinding webBinding = new WebHttpBinding();

 

            ServiceHost host = new ServiceHost(typeof(HelloService), new Uri("http://localhost:8080/"));

            ServiceEndpoint ep = host.AddServiceEndpoint(typeof(IHelloService), webBinding, "pox");

            ep.Behaviors.Add(new HttpTransferEndpointBehavior());

 

            host.Open();

            Console.WriteLine("Open!");

 

            //URL's for the SayHello operation will look like:

            //http://localhost:8080/pox/SayHello?name=Foo
            //Try hitting that in a browser now...

  

            Console.ReadLine();

        }

   }

}

#1 Dave on 3.05.2007 at 4:29 PM

Will you guys have a good story around a programming model forassociating HTTP status codes with CLR exceptions? Or are we stuck with the existing fault model?

#2 Daniel Crowley-Wilson on 6.05.2007 at 5:57 AM

Is there any trick to getting POST operations to work with the WebHttpBinding? I've made a sample very similiar to your example in this post, except my contract has an operation contract with [HttpTransferContract(Method="POST")]. When I rig up a test html form and submit to my service, Fiddler reports:HTTP/1.1 415 Cannot process the message because the content type 'application/x-www-form-urlencoded' was not the expected type 'text/xml; charset=utf-8'.