Teaching Indigo to do REST/POX: Part 8

13 minutes read

Part 1, Part 2, Part 3, Part 4, Part 5, Part 6, Part 7

We’ve got all the moving pieces and what’s left is a way to configure the PoxEncoder into bindings and use those to hook a service up to the network and run it.

Bindings? Well, all Indigo (WCF) services need to know their ABC to function. ABC? I’ll quote from my WCF Introduction on MSDN:

“ABC” is the WCF mantra. “ABC” is the key to understanding how a WCF service endpoint is composed. Think Ernie, Bert, Cookie Monster or Big Bird. Remember "ABC".

·         "A" stands for Address: Where is the service?

·         "B" stands for Binding: How do I talk to the service?

·         "C" stands for Contract: What can the service do for me?

Web services zealots who read Web Service Description Language (WSDL) descriptions at the breakfast table will easily recognize these three concepts as the three levels of abstraction expressed in WSDL. So if you live in a world full of angle brackets, you can look at it this way:

·         "A" stands for Address—as expressed in the wsdl:service section and links wsdl:binding to a concrete service endpoint address.

·         "B" stands for Binding—as expressed in the wsdl:binding section and binds a wsdl:portType contract description to a concrete transport, an envelope format and associated policies.

·         "C" stands for Contract—as expressed in the wsdl:portType, wsdl:message and wsdl:type sections and describes types, messages, message exchange patterns and operations.

"ABC" means that writing (and configuring) a WCF service is always a three-step process:

·         You define a contract and implement it on a service

·         You choose or define a service binding that selects a transport along with quality of service, security and other options

·         You deploy an endpoint for the contract by binding it (using the binding definition, hence the name) to a network address.

A binding is a layered combination of a transport, an encoder and of any additional protocol channels (reliable session, transaction flow, etc.) that you’d like to assemble into a transport stack for exposing a service implementation on a specific endpoint address.

For exposing a HTTP-based RESTish service we need:

A.    The HTTP address to host the service at.

B.     Some binding configuration that tells Indigo’s HTTP transport to use our PoxEncoder.

C.     An implementation of a service-contract that’s configured (using the HttpMethodOperationSelectorSection config extension) or marked-up (with the HttpMethodOperationSelectorAttribute) to use our HttpMethodOperationSelectorBehavior for endpoint address filtering and selecting methods.

I’ve shown you quite a few contract variants in the first parts of this series and therefore I don’t really have to explain the C in too much detail anymore; except: While the A is just a plain HTTP address such as http://www.example.com/service, it’s interesting insofar as this address is, unlike as with SOAP services, really just the common address prefix for the dispatch URIs of the particular service and that there is a split between what is A and what is C.

As I’ve explained, the philosophy behind the contract design of my extensions is around the namespaces that are the basis for forming URIs. Because REST services aren’t simply using HTTP as a transport tunnel as SOAP services do, but rather leverage HTTP as the application protocol it is, the URI is a lot more than just a drop-off point for messages. With REST services, the URI is an expression that has both, addressing (transport) and contract (dispatch) aspects to it and we need to separate those out. A clear distinction between global and local namespaces allows us to do that (And I am going into a bit more detail than I usually would, to further address an objection of Mark Baker, 1st comment, on my choice of the programming model):

There is a global namespace that’s managed by the global DNS system of which anyone can reserve a chunk for themselves by registering a domain-name. The domain-name provides a self-manageable namespace root of which sub-namespaces (subdomains) can be derived and allocated to specific hosts/services or groups of hosts/services by the domain owners. On the particular host, you can put an application behind a specific port, which might either the default port of your particular application protocol or – diverging from the protocol standard – some other port of your choosing. Each Internet application deployment has therefore at least one unique mapping into this global namespace system.

Any further segmentation of the namespace except the host-name and the port-number are private matters of the application listing to that endpoint. With Indigo self-hosted HTTP services, the listening application is Windows (!) – more precisely it’s the HTTP.SYS kernel listener. For IIS/WAS hosted services, the listening application is IIS (for IIS 5.1 and below) or, again, Windows – through that very listener. At the HTTP.SYS listener, handler processes can register their interest in requests sent to certain sub-namespaces of the global namespace mapping (host/port), which are identified by relative URIs. To be clear: The HTTP.SYS API indeed requires the caller to provide an absolute URI like http://hostname:port/service, but the two main parts (scheme/host/port and path) of that URI are used for different purposes:

·         Global mapping: The hostname and port are used to establish a new listener on that particular port (if there is already a listener it is shared) and to populate the hostname-filter table that’s used to disambiguate requests by the Host header in case the IP address is mapped to multiple DNS host entries.

·         Local mapping: The path information of the URI (the remaining relative URI with scheme, hostname and port stripped) is used as a prefix-matching expression to figure out which handler process shall receive the request and, inside that handler process, to further identify and invoke the appropriate endpoint and handler that deals with the resource that the complete URI path represents.

As indicated, mapping URIs to an endpoint needs to distinguish between how we segment and map a local namespace and how we hook that into the global namespace. Hence, the root for any absolute URIs that’s establishing the mapping into the global namespace shall always be separate from the code and reside in configuration for reasons of flexibility as Mark was rightfully pointing out in his objection, while the shape and mapping of the local namespace is typically very application and use-case specific and might well be partially or entirely hardwired.

·         The Indigo A(ddress) of a REST service implemented with my particular programming model is used to hook a given service (or resource representation manager, if you like) into the global namespace: http://myservice.example.com/. Only to be pragmatic and to allow multiple such services to locally share a particular hostname and port and indeed only as an alternative and workaround to creating a separate DNS entry for each service, that mapping might include a path prefix allowing the local low-level infrastructure to demultiplex requests sent to the same global namespace mapping: http://myservices.example.com/serviceA and http://myservices.example.com/serviceB.

·         The Indigo C(ontract) of a REST service implemented with my particular programming model is used to define the shape of the local namespace that the service owns and which is used to provide access to the representations of the resource-types the service is responsible for.

The following configuration snippet for a simple web-server based on my extensions is illustrating that split:

<services>
   <
service type="LittleIndigoWebServer.MyWebServer">
      <
endpoint contract="LittleIndigoWebServer.IMyWebServer"
                address="http://localhost:8020/"
                
binding="customBinding"
                
bindingConfiguration="poxBinding"/>
   </
service>
</
services>

The address http://localhost:8020/ is how I map the service into the global addressing namespace. The local namespace shape for that particular service is a defined by the layout of the file-system directory from which the service grabs files and returns them. What? You can’t see the directory structure and the resulting URLs from the above mapping? Of course not. It’s a private matter of the service implementation what that the local namespace structure is and it’s up to me what parts I am exposing. If I am nice enough I will give you something on a GET/HEAD request on the root of my local namespace (= global address without any suffix), and if I am not nice you get a 404 and will just have to know what to ask for. The “will have to know” part is contract. It’s an assurance that if you come looking at a particular place in my namespace you will have access to a particular thing. My [HttpMethod] attributes manifest that assurance on Indigo contracts.

That leaves B. Before I got carried away by A and C, I wrote [now a bit annotated] “A binding is a layered combination of a transport, an encoder and of any additional protocol channels (reliable session, transaction flow, etc.) that you’d like to assemble into a transport stack for exposing a service implementation [C] on a specific endpoint address [A].”

Putting together such a binding is not much more work than putting a little text between angle brackets and quotation marks in config as shown in the following snippet:

<customBinding>
   <
binding name="poxBinding">
      <
poxEncoder/>
      <
httpTransport mapAddressingHeadersToHttpHeaders="true"
           
maxMessageSize="2048000" maxBufferSize="2048000" manualAddressing="true"
          
authenticationScheme="Anonymous" transferMode="StreamedResponse"  />
   </
binding>
</
customBinding>

I am building a custom binding that’s combining the HTTP transport with a custom binding element config extension I built for the PoxEncoder. It’s that simple. And adding the binding element extension does not require black magic, either. It’s just another XML snippet that maps the extension class to an element name (“poxEncoder”) as you can see in the extensions section of the complete config file:

 <?xml version=1.0 encoding=utf-8 ?>
<
configuration>
   <
system.serviceModel>
      <
extensions>
         <
bindingElementExtensions>
            <
add name="poxEncoder" type="newtelligence.ServiceModelExtensions.PoxEncoderBindingExtension, newtelligence.ServiceModelExtensions"/>
         </
bindingElementExtensions>
      </
extensions>
      <
bindings>
         <
customBinding>
        <
binding name="poxBinding">
               <
poxEncoder/>
               <
httpTransport mapAddressingHeadersToHttpHeaders="true"
                           
maxMessageSize="2048000" maxBufferSize="2048000" manualAddressing="true"
                           
authenticationScheme="Anonymous" transferMode="Streamed"  />
            </
binding>
         </
customBinding>
      </
bindings>
      <
services>
         <
service type="LittleIndigoWebServer.MyWebServer">
            <
endpoint contract="LittleIndigoWebServer.IMyWebServer"
                    address="http://localhost:8020/"
                    
binding="customBinding"
                    
bindingConfiguration="poxBinding"/>
         </
service>
      </
services>
  </
system.serviceModel>
</
configuration>

The PoxEncoderBindingExtension is a class that is based on System.ServiceModel.Configuration.BindingElementExtensionSection. Whenever the configuration is processed by Indigo, the presence of the “poxEncoder” element in a binding triggers the creation of an instance of the class and if we’d require any configuration attributes (which we don’t), those would be stuffed into the Properties collection.    

using System;
using System.ServiceModel.Configuration;
using System.ServiceModel;
using System.Configuration;

namespace newtelligence.ServiceModelExtensions
{
   public class PoxEncoderBindingExtension : BindingElementExtensionSection
   {
        /// <summary>
        /// Initializes a new instance of the <see cref="T:PoxEncoderBindingExtension"/> class.
        /// </summary>
      public PoxEncoderBindingExtension()
      {
      }

        /// <summary>
        /// Creates the binding element.
        /// </summary>
        /// <returns></returns>
      protected override BindingElement CreateBindingElement()
      {
         PoxEncoderBindingElement pcc = new PoxEncoderBindingElement();
         return pcc;
      }

        /// <summary>
        /// Gets the type of the binding element.
        /// </summary>
        /// <value>The type of the binding element.</value>
      public override Type BindingElementType
      {
         get
         {
            return typeof(PoxEncoderBindingElement);
         }
      }

        /// <summary>
        /// Gets the name of the configured section.
        /// </summary>
        /// <value>The name of the configured section.</value>
      public override string ConfiguredSectionName
      {
         get
         {
            return "poxEncoder";
         }
      }

      private ConfigurationPropertyCollection properties;
        /// <summary>
        /// Gets the collection of properties.
        /// </summary>
        /// <value></value>
        /// <returns>The <see cref="T:System.Configuration.ConfigurationPropertyCollection"></see> collection of properties for the element.</returns>
      protected override ConfigurationPropertyCollection Properties
      {
         get
         {
            if (this.properties == null)
            {
               ConfigurationPropertyCollection configProperties = new ConfigurationPropertyCollection();
               this.properties = configProperties;
            }
            return this.properties;
         }
      }
   }
}

Once the configuration information has been read, the extension is asked to create a BindingElement from the acquired information. So these extensions are really just factories for binding elements. The binding element, which can also be used to compose such a binding in code by explicitly adding it to a System.ServiceModel.CustomBinding is shown below:

 using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Design;
using System.ServiceModel.Channels;

namespace newtelligence.ServiceModelExtensions
{
    public class PoxEncoderBindingElement : BindingElement, IMessageEncodingBindingElement
   {
        /// <summary>
        /// Clones this instance.
        /// </summary>
        /// <returns></returns>
      public override BindingElement Clone()
      {
         return new PoxEncoderBindingElement();
      }

        /// <summary>
        /// Creates the message encoder factory.
        /// </summary>
        /// <returns></returns>
      public MessageEncoderFactory CreateMessageEncoderFactory()
      {
         return new PoxEncoderFactory();
      }

        /// <summary>
        /// Gets the addressing version.
        /// </summary>
        /// <value>The addressing version.</value>
      public AddressingVersion AddressingVersion
      {
         get
         {
            return AddressingVersion.Addressing1;
         }
      }

        /// <summary>
        /// Gets the protection requirements.
        /// </summary>
        /// <returns></returns>
      public override System.ServiceModel.Security.Protocols.ChannelProtectionRequirements GetProtectionRequirements()
      {
         return null;
      }

        /// <summary>
        /// Builds the channel factory.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <returns></returns>
      public override IChannelFactory BuildChannelFactory(ChannelBuildContext context)
      {
         if (context == null)
            throw new ArgumentNullException("context");

         context.UnhandledBindingElements.Add(this);
         return context.BuildInnerChannelFactory();
      }

        /// <summary>
        /// Builds the channel listener.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <returns></returns>
        public override IChannelListener<TChannel> BuildChannelListener<TChannel>(ChannelBuildContext context)
        {
            if (context == null)
            throw new ArgumentNullException("context");

         context.UnhandledBindingElements.Add(this);
         return context.BuildInnerChannelListener<TChannel>();
      }
   }
}

Binding elements are typically used to put together client-side (channel factory) or service-side (channel listener) transport stacks. At the bottom is the transport and layered on top of it are security, reliable sessions, transaction flow and all other protocol features you need. Each protocol or feature on the channel/listener level has its own binding element and using those you configure yourself a binding combining the features you need and in the order that they should be applied.

The binding elements for message encoders are a bit different, because they are not contributing their own channel factories or channel listeners into the stack, but rather “only” supply the message encoder for the configured transport.

Whenever a binding is instantiated, Indigo creates a ChannelBuildContext which contains the sequence of the binding elements that shall be stacked onto each other into a channel or listener stack and starts stacking them from top to bottom by invoking the topmost binding element’s BuildChannelListener or BuildChannelFactory method. Once the binding element is done creating its channel factory or channel listener, it invokes BuildInnerChannel[Listener/Factory] on the context to have the binding element underneath do its work. (The context is also used to validate whether combination of the elements yields a functional binding stack, but I won’t go into that here).

Our binding element, however, won’t create a channel factory or listener, but rather put itself into the UnhandledBindingElements collection on the build context and will then just have the context complete the construction work. With putting itself into that collection, the binding element makes itself and its most irresistible feature (you’d also think that if you were an Indigo transport) – the IMessageEncodingBindingElement implementation – visible to the transport and waves its hand that it wants to be used. The transport’s binding element, which is at the bottom of the stack and therefore asked to build its channel factory/listener after our binding element has been invoked, will go look in the UnhandledBindingElements  collection whether a message encoding binding element is advertising itself for use. And if that’s so it will forget all of its defaults and happily embrace and use an encoder created by the factory returned by IMessageEncodingBindingElement.CreateMessageEncoderFactory, which is, in our case, this rather simple class:

 using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel.Channels;
using System.ServiceModel;

namespace newtelligence.ServiceModelExtensions
{
    /// <summary>
    ///
    /// </summary>
   public class PoxEncoderFactory : MessageEncoderFactory
   {
      MessageEncoder encoder;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:PoxEncoderFactory"/> class.
        /// </summary>
      public PoxEncoderFactory()
      {
          encoder = new PoxEncoder();

      }

         /// <summary>
        /// Gets the encoder.
        /// </summary>
        /// <value>The encoder.</value>
        public override MessageEncoder Encoder
      {
         get
         {
            return encoder;
         }
      }

        /// <summary>
        /// Gets the message version.
        /// </summary>
        /// <value>The message version.</value>
      public override MessageVersion MessageVersion
      {
         get
         {
            return encoder.MessageVersion;
         }
      }
   }
}

Soooooooo….!

If you had actually copied all those classes from Parts 1-8 down into local files and compiled them into an assembly, you’d have all my REST/POX plumbing code by now (except, admittedly, an application-level utility class that helps putting messages together).

But wait … don’t do that. In the next part(s) I’ll give you the code all packed up and ready to compile along with the little web server that we’ve configured here and will also share some code snippets from my TV app … maybe the RSS and ASX pieces?

Updated:

Leave a Comment