Http Processors in the WCF Web Programming Model

The code drop recently released as part of wcf.codeplex.com introduced a new feature for injecting cross-cutting concerns through a pipeline into any existing service. The idea is simple, when you move to the http world, there are some aspects that might want to move out of your operation implementation. A typical example is content negotiation, in which the http messages are serialized or deserialized into specific entity or object graph expected by the service based on the “content-type” or “accept” http headers.  You don’t want to have all that logic spread across all the service implementations or tie your operation to an specific format as it happened with previous model with the WebInvoke and WebGet attributes. For that kind of logic, it’s really useful to have that concern implemented in a specific class that you can test individually and inject into your service to support new message formats.

From a high level, if you look at how the WCF service model works, this new pipeline runs just after the operation has been selected and right after the operation has been executed, meaning that you have two pipeline instances, one for request messages and another one for the responses.

The image above illustrates the service model architecture, and the pipelines are implemented in the message formatting stage.

processors_architecture

The pipeline is a collection a processors or handlers that perform a transformation task. A simple processor receives a set of input arguments and returns some output arguments. The nice thing about how the processors and pipeline are implemented in this new model is that you can chain the output results from a processor into another processor as input arguments.

The following image shows how the pipeline works at first glance.

processors_pipeline

The first processor in the pipeline injects some output values into the context (A and B), and that context is then passed to the successive processors until the service operation is finally executed. The arguments that the operation is expecting  are also be resolved from the available values in the pipeline context.

The UriTemplateProcessor is a built-in processor implementation that WCF always inject by default in the request pipeline to transform the values passed as uri templates to the http resource (or service) as output arguments that other processors can reuse as input. For example, if you have an UriTemplate with a variable argument “culture” as part of your operation definition like this one,

[WebGet(UriTemplate="{culture}")]

And you pass a value for that template in the URL,

http://localhost/helloworld/en-us (being en-us the {culture} template in the url)

The UriTemplateProcessor will catch the “en-us” value and add it to the pipeline context as “culture”.

For other processors that you can implement on your own, you can either derive your implementation from the base class “System.ServiceModel.Dispatcher.Processor” or one of the existing definitions that use generic templates for the input and output arguments.

public abstract class Processor<T, TOutput> : Processor
public abstract class Processor<T1, T2, TOutput> : Processor
public abstract class Processor<T1, T2, T3, TOutput> : Processor
......
public abstract class Processor<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TOutput> : Processor

The main difference with these class definitions is that you can specify a type for the input or output arguments that the processor can handle. When you implement a processor, in addition to specify the types the processor might expect for the input or output arguments, you also need to give names to those arguments so the pipeline knows how to map the values for those arguments from the existing context.

In the given example with the “culture” argument, and assuming that UriTemplateProcessor has already run and added the value for that argument into the pipeline context, a pipeline processor can get access to that value by naming an input argument with the same name.

public class CultureProcessor : Processor<string, CultureInfo>
{
    public CultureProcessor()
        : base()
    {
        this.InArguments[0].Name = "culture";
        this.OutArguments[0].Name = "culture";
    }

The example above receives an input argument culture as string, and returns an output argument with the same name as an instance of the CultureInfo. Either the service or any other processor in the chain will able to receive now an instance of CultureInfo if they expect an argument with a name “culture”.

A processor can also receive an instance of the object representing the request (HttpRequestMessage) or response messages (HttpResponseMessage), which are the two new message primitive classes added in this release for giving better access to developers to the http context. You can receive either of these messaging classes by naming the arguments HttpPipelineFormatter.ArgumentHttpRequestMessage (HttpRequestMessage) or HttpPipelineFormatter.ArgumentHttpResponseMessage (HttpResponseMessage) respectively.

As we have discussed so far, a processor runs late in the WCF pipeline and there much after the Http transport channels have done their job. For that reason, it would not make much sense to implement Http transport concerns like security in a processor.

The framework also ships with a processor implementation for handling content negotiation “Microsoft.ServiceModel.Http.MediaTypeProcessor”, which provides several abstraction methods that you can override for participating in the process. 

public abstract class MediaTypeProcessor : Processor
{
    public abstract IEnumerable<string> SupportedMediaTypes { get; }
 
    public abstract void WriteToStream(object instance, Stream stream, HttpRequestMessage request);
 
    public abstract object ReadFromStream(Stream stream, HttpRequestMessage request);
}

The names for these method pretty much say everything. You have a method “SupportedMediaTypes” for specifying the supported content types that your implementation can handle (for example text/xml or text/html), and two methods for serializing and deserializing an object instance representing the request body or response body into one of the supported media types. Some implementations come with the framework the handling the most common media types like XmlProcessor, HtmlProcessor, JsonProcessor, PlainTextProcessor or FormUrlEncodedProcessor.

You are also free to implement your own media type processors to support custom media types that makes sense for your services. A typical example is when you want to support media types “+xml” or “+json” in your services like “application/x-order-process+xml” or “ application/x-order-process+json” for example or support a versioning schema based on custom media types as it is discussed here, http://barelyenough.org/blog/2008/05/versioning-rest-web-services/ 

Finally, the processors are injected into any existing service using a concrete implementation of “Microsoft.ServiceModel.Http.HostConfiguration” class, which is usually wired up with host initialization.

public class ContactManagerConfiguration : HostConfiguration
{
    public override void RegisterRequestProcessorsForOperation(HttpOperationDescription operation, IList<Processor> processors, MediaTypeProcessorMode mode)
    {
        if(operation.Name == "Get" && operation.DeclaringContract.Name == "ContactResource")
            processors.Add(new CultureProcessor());
 
        processors.Add(new JsonProcessor(operation, mode));
        processors.Add(new FormUrlEncodedProcessor(operation, mode));
    }
 
    public override void RegisterResponseProcessorsForOperation(HttpOperationDescription operation, IList<Processor> processors, MediaTypeProcessorMode mode)
    {
        processors.Add(new JsonProcessor(operation, mode));
        processors.Add(new PngProcessor(operation, mode));
    }
}

Two abstract methods are provided with that class, one for registering the processors for the request pipeline, and another one for the response pipeline respectively. The example below passes this custom configuration as part of the routing configuration for a service hosted in ASP.NET,

public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        var configuration = new ContactManagerConfiguration();
        RouteTable.Routes.AddServiceRoute<ContactResource>("contact/", configuration);
        RouteTable.Routes.AddServiceRoute<ContactsResource>("contacts", configuration);
        RouteTable.Routes.AddServiceRoute<HelloWorldResource>("hello", configuration);
    }
}
Published Monday, November 15, 2010 9:34 AM by cibrax
Filed under: , ,

Comments

No Comments