In the first part of this series, I gave you a little introduction to REST/POX in contrast to SOAP and also explained some of the differences in how incoming requests are dispatched. Now I’ll start digging into how we can teach Indigo a RESTish dispatch mechanism that dispatches based on the HTTP method and by matching on the URI against a suffix pattern.
The idea here is that we have a service implementation that takes care of a certain resource namespace. To stick with the example from Part 1, we assume that the resources managed within this (URI) namespace are customers and data related to customers. Mind that this might not be all data of a respective customer, but that some data may very well be residing in completely different namespaces (and on different servers).
As a reminder: When I write “namespaces” I specifically mean that we’re creating hierarchical scopes for data. All customer representations managed by our imaginary service are subordinates of the namespace http://www.example.com/customers, the representation of the customer identified by customer-id 00212332 occupies the namespace http://www.example.com/customers/00212332, all communication (phone) numbers of that customer are subordinates of http://www.example.com/customers/00212332/comm and the home phone number might be identified by http://www.example.com/customers/00212332/comm/home-phone. However, all orders made by that respective customer might be found somewhere completely different; maybe here: http://www.tempuri.org/ordersystem/customer-orders/00212332. The data representation of the customer would contain that link, but the customer service would not manage those resources (the orders), at all.
Purists might (and do) argue that plain HTTP handlers (or “servlets”, in Java terms) representing exactly one resource type are the best way to implement the processing logic for this URI/HTTP centric model, but since I am much more a pragmatist than a purist, I prefer using a infrastructure that maps incoming requests to a programming model that’s easy enough for most programmers to deal with. It turns out that a class with methods that deal with related stuff (a customer and his addresses and phone numbers) is something that most programmers can handle pretty well by now and there’s nothing wrong with co-locating related handlers for data from a given data source on one flat interface, even if the outside representation of that data suggests that the data and its “endpoints” are ordered hierarchically. In the end, the namespace organization is just a smokescreen that we put in front of our implementation. Just to make Mark happy, I’ll show a very HTTP and service-per-object aligned contract model and, later in the next part, also a more readable model for the rest of us to explain how the dispatch works. I’ll start with the model for idealists:
[ServiceContract, HttpMethodOperationSelector] interface ICustomerResource { [OperationContract, HttpMethod("GET", UriSuffix = "/customers/?")] Message Get(Message msg); [OperationContract, HttpMethod("PUT", UriSuffix = "/customers/?")] Message Put(Message msg); [OperationContract, HttpMethod("POST", UriSuffix = "/customers/?")] Message Post(Message msg); [OperationContract, HttpMethod("DELETE", UriSuffix = "/customers/?")] Message Delete(Message msg); }
|
[ServiceContract, HttpMethodOperationSelector] interface ICommunicationResource { [OperationContract, HttpMethod("GET", UriSuffix = "/customers/?/comm/?")] Message Get(Message msg); [OperationContract, HttpMethod("PUT", UriSuffix = "/customers/?/comm/?")] Message Put(Message msg); [OperationContract, HttpMethod("POST", UriSuffix = "/customers/?/comm/?")] Message Post(Message msg); [OperationContract, HttpMethod("DELETE", UriSuffix = "/customers/?/comm/?")] Message Delete(Message msg); }
|
We have two different contracts here, one for the “customers” namespace and one for the “comm” sub-namespace, and the implementation of these two contracts could be sitting on the same implementation class or on two different classes whereby they could be co-located at the exact same root address or sitting on different machines. All of that doesn’t really matter, since the filtering/dispatch logic we’ll use here will figure out the right thing to do, meaning the right handler method to dispatch to. Also mind that there’s a difference to the examples I show in Part 1 in that I am now using messages.
The UriSuffix of the HttpMethodAttribute serves three purposes. First, it is used to construct a regular expression that is used to match incoming messages to the right endpoint using a custom endpoint Filter. Second, the same regular expression is used to figure out which method the message shall be dispatched on at that endpoint using a custom implementation of IDispatchOperation. Third, the regular expression is also used to isolate the URI-embedded parameters and make them easily accessible on a message property. So for the ICommunicationResource.Get() operation above, the handler implementation would start out as follows and would make the values occurring at the two “?” of the suffix available in the UriArgumentsMessageProperty.InUrlArgs collection that is shown further below:
Message ICommunicationResource.Get(Message msg) { UriArgumentsMessageProperty uriArgs = UriArgumentsMessageProperty.FromOperationContext(); string customerid = uriArgs.InUrlArgs[0]; string commid = uriArgs.InUrlArgs[1]; ...
|
The expressions for UriSuffix support two different wildcards. The “*”wildcard will match any character and the “?” wildcard will match any character except the forward slash. If you would, for instance, want to build an operation that behaves like a web server and might serve up data from a directory and its subdirectories, you’d use something as global as “/*” and any URI would match the respective endpoint/method. If you want to match/extract segments of a namespace path as we do here, you use the “?”.
And so here is the complete and rather straightforward implementation of the HttpMethodAttribute:
using System; using System.Collections.Generic; using System.Text; using System.ServiceModel; using System.Text.RegularExpressions;
namespace newtelligence.ServiceModelExtensions { /// <summary> /// The HttpMethodAttribute is used to declare the HTTP method and /// an optional suffix for the REST/POX extensions to dispatch on. In absence of /// this attribute, the dispatch mechanism will attempt to dispatch on the /// name of the operation and try matching by name it to the HTTP method used. /// </summary> [AttributeUsage(AttributeTargets.Method)] public class HttpMethodAttribute : Attribute, IOperationBehavior { /// <summary> /// Initializes a new instance of the <see cref="T:HttpMethodAttribute"/> class. /// </summary> /// <param name="method">The method.</param> public HttpMethodAttribute(string method) { _Method = method; }
private string _Method; /// <summary> /// Gets the HTTP method. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html /// </summary> /// <value>The method.</value> public string Method { get { return _Method; } }
private string _UriSuffix; /// <summary> /// Sets or gets the Uri suffix. The Uri suffix is an expression used /// to match an incoming request against the endpoint and this method. /// The UriSuffix may contain two different wildcards: '*' matches the /// shortest sequence of arbitrary character up to the next concrete character /// in the suffix string. The '?' behaves similarly, but excludes the '/' /// character from matching. /// </summary> /// <value>The URI suffix.</value> public string UriSuffix { set { _UriSuffix = value; _UriSuffixRegex = new Regex( Regex.Escape(_uriSuffix).Replace("\\*", "(.*?)") .Replace("\\?", "([^/]*?)") + "$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled); } get { return _UriSuffix; } }
private Regex _UriSuffixRegex; /// <summary> /// Gets the regular match expression constructed from /// the UriSuffix. /// </summary> /// <value>The URI suffix regex.</value> public Regex UriSuffixRegex { get { return _UriSuffixRegex; } }
private int _Priority; /// <summary> /// Gets or sets the priority. The priority is used to control the /// order in which the suffix expressions are processed when matching. /// A higher priority causes the expression to be matched earlier. /// </summary> /// <value>The priority.</value> public int Priority { get { return _Priority; } set { _Priority = value; } }
/// <summary> /// Applies the behavior. /// </summary> /// <param name="description">Description.</param> /// <param name="proxy">Proxy.</param> /// <param name="parameters">Parameters.</param> public void ApplyBehavior(OperationDescription description, ProxyOperation proxy, BindingParameterCollection parameters) { // do nothing proxy-side }
/// <summary> /// Applies the behavior. /// </summary> /// <param name="description">Description.</param> /// <param name="dispatch">Dispatch.</param> /// <param name="parameters">Parameters.</param> public void ApplyBehavior(OperationDescription description, DispatchOperation dispatch, BindingParameterCollection parameters) { // We're adding a parameter inspector that parses the Uri parameters into // an UriArgumentsMessageProperty dispatch.ParameterInspectors.Add( new HttpMethodParameterInspector(this)); } } }
|
Of course this attribute is a bit special. You might have noticed that it implements the IOperationBehavior interface with its two method overloads of ApplyBehavior whereby one is for the proxy side of a channel (which we don’t care about in this case) and the other for the dispatcher (service-) side of an implementation. The presence of this interface causes the Indigo runtime to instantiate the attribute and add it to the contract metadata whenever the contract is built using reflection. You could also instantiate the attribute yourself and add it to any existing in-memory Indigo contract’s operation description if you liked. This is a convenient way to get the additional metadata into the contract description because we need it at several places later.
At the dispatch-side we’re also adding an implementation of IParameterInspector, whose job it is to extract the URI-embedded arguments and also parse “key=value” pairs of an optional query string, if one is present.
Parameter inspectors are called immediately before and after a method has been invoked and, as their name implies, are meant to be used to inspect a method’s parameters and/or output. However, their use is not restricted to that. Because the operation context is also available when the inspectors are called, you can also inspect headers, properties or any other context information at this point.
Even though this is largely a convenience feature not central to the dispatcher, I’ll show the class here, because I mentioned the UriArgumentsMessageProperty above. This is where it gets populated and set:
using System; using System.Collections.Generic; using System.Text; using System.ServiceModel; using System.ServiceModel.Channels; using System.Text.RegularExpressions; using System.Collections.Specialized;
namespace newtelligence.ServiceModelExtensions { class HttpMethodParameterInspector : IParameterInspector { HttpMethodAttribute _metadata; /// <summary> /// Creates a new instance of HttpMethodParameterInspector /// </summary> public HttpMethodParameterInspector(HttpMethodAttribute metadata) { _metadata = metadata; } #region IParameterInspector Members
public object BeforeCall(string operationName, object[] inputs) { OperationContext opCtx = OperationContext.Current; NameValueCollection queryArguments; StringCollection inUrlArgs = new StringCollection(); Uri toHeader = opCtx.IncomingMessageHeaders.To;
// Parse query strings if (opCtx.IncomingMessageProperties.ContainsKey(HttpRequestMessageProperty.Name)) { HttpRequestMessageProperty rqMsgProp = opCtx.IncomingMessageProperties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty; queryArguments = UriHelper.ParseQueryString(rqMsgProp.QueryString); } else { queryArguments = UriHelper.ParseQueryString(toHeader.Query); }
// Get the in URL arguments from the regex captures Match match = _metadata.UriSuffixRegex.Match(toHeader.AbsolutePath); if (match.Success && match.Groups.Count > 0) { // if we have more than 1 capture group (the first is always the // full match expression), we store the next groups in the // irUrlArgument collection in order of occurrence. for (int i = 1; i < match.Groups.Count; i++) { inUrlArgs.Add(match.Groups[i].Value); } }
opCtx.IncomingMessageProperties.Add( UriArgumentsMessageProperty.Name, new UriArgumentsMessageProperty(queryArguments,inUrlArgs) );
return null; }
public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState) { // do nothing }
#endregion } }
|
The query string is parsed using a helper class which splits the query string at each ‘&’ first and then splits the segments at ‘=’ and throws the resulting pairs into a NameValueCollection. That is no big deal and I'll omit the helper here. The UriArgumentsMessageProperty that gets put into the mesage properties is just a class that holds the query arguments and the string collection with the regex matches. It is quite trivial:
using System; using System.Collections.Specialized; using System.Collections.Generic; using System.Text; using System.ServiceModel;
namespace newtelligence.ServiceModelExtensions {
/// <summary> /// Message property class holding arguments fro the request URI. /// </summary> public class UriArgumentsMessageProperty { NameValueCollection _queryArguments; StringCollection _inUrlArgs;
/// <summary> /// Creates a new instance of UrlArgumentsMessageProperty /// </summary> internal UriArgumentsMessageProperty(NameValueCollection queryArguments, StringCollection inUrlArgs) { _queryArguments = queryArguments; _inUrlArgs = inUrlArgs; }
/// <summary> /// Gets the query arguments. /// </summary> /// <value>The query arguments.</value> public NameValueCollection QueryArguments { get { return _queryArguments; } }
/// <summary> /// Gets the arguments in the URL. /// </summary> /// <value>The in URL args.</value> public StringCollection InUrlArgs { get { return _inUrlArgs; } }
/// <summary> /// Gets the name of the property. /// </summary> /// <value>The name.</value> public static string Name { get { return "urlArguments"; } }
/// <summary> /// Retrieves the message property from the current operation context. /// </summary> /// <returns></returns> static public UriArgumentsMessageProperty FromOperationContext() { return FromOperationContext(OperationContext.Current); }
/// <summary> /// Retrieves the message property from the operation context. /// </summary> /// <param name="operationContext">operation context</param> /// <returns></returns> static public UriArgumentsMessageProperty FromOperationContext(OperationContext operationContext) { return operationContext.IncomingMessageProperties[Name] as UriArgumentsMessageProperty; } } }
|
Ok, fine, and how is that now really used for dispatching? You’ll see that when I explain the SuffixFilter and the HttpMethodOperationSelectorBehavior in the next parts.
(There will also be a downloadable version of the code later, but I am still tweaking some little things and don’t want to keep updating)
Go to Part 3