Udostępnij za pośrednictwem


REST in WCF - Part IX - Controlling the URI

(click here for an index of the complete series, as well as supporting screencasts)

(click here for a screencast illustrating "Controlling the URI")

Resources in REST

Arguably, the most fundamental concept in REST is that of a resource.  It is best to think of it as a conceptual representation of an entity or entities.  Blah, blah, blah, conceptual, blah blah blah, representation, blah, blah, blah entity.  What does all of that mean anyway?  Think of it this way, if you can create a link to it, it is a resource.  Now what does conceptual representation mean?  Essentially, it means that the resource is an abstraction.  It is not the underlying entity itself, rather a mapping to that entity... and that mapping may change.  Whoa, the mapping may change?  How is that?  Consider the example that you wanted to expose the top 10 best selling books at your online store.  The resource would be those top 10 best selling books.  It is clear that those would change over time. 

The need for the abstraction should be clear.  In our previous example, we don't really care what the back end was  that stored the top 10 best selling books.  It was, more than likely, a database system.  It could have been Microsoft Sql Server, or it could have been Oracle or Informix or something altogether different.  We don't really care because we are not interested in the entity itself, rather the representation.  If we were to switch back ends out after a period of time, it would be meaningless to us.  The representation would be unaffected (although, as we argued earlier the representation might have changed).  Further, imagine the complexity of the system where we were not mapping to the concept, but to the actual underlying entities. 

URIs in REST

The way in which you identify a resource is through a URI.  Section 6.2.1 of Fielding's dissertation points out that "The definition of resource in REST is based on a simple premise: identifiers should change as infrequently as possible."  He further states that "authors need an identifier that closely matches the semantics they intend by a hypermedia reference..."  This points out the importance of the structure of the URI.  Many folks try to create what I refer to as "Hackable" URIs.  These are URIs that are easily remembered and whose structure and semantics are clear enough to manipulate.  Take for instance the following URI: 'https://www.someuri.com/Product/Flange'.  It is pretty clear the the structure is: the http scheme and the hostname ('https://www.someuri.com') followed by 2 segments: Product and ProductName.  A template for this might look like the following:  'https://www.someuri.com/Product/{ProductName}' where {ProductName} represents a variable.

You might argue that this is bad design, in that if the name of the product changes, so must the URI and that clearly violates the premise that a resource is based on.  Another approach would be to use the ProductID as the final segment.  I'll leave that up to you.  What I want to discuss in this post is what your options are for controlling the URI, assuming you have already designed how the URI should look.

The approach of this Post

If you have ever seen 'What About Bob' with Bill Murray, you will understand the following reference (if you haven't, close your notebook, go to Blockbuster, rent 'What About Bob', come home, put it in the DVD player, open a beer - if that is your thang - and watch it).  We are going to take baby steps to controlling the URI.  I am going to start by outlining the default URI behavior when using the webHttpBinding along with the webHttp endpoint behavior.  I will then show you what your options were in manipulating the URI structure via the UriTemplate.  I'll then move on to some additional functionality available in SP1.  Lastly, I'll illustrate how you can take complete control over the structure of the URI using IIS7 and the Url Rewrite Module.

The Sample

Below you will see the service contract and operation contract of our sample service.  As you can see, the operation is named GetWine and takes in a string of wineID.  You will also note that I have decorated the operation contract with a WebGetAttribute, making this callable via an HTTP GET.  I have omitted the implementation, as well as the configuration, as they adds no value to this discussion.

[ServiceContract]
public interface IRESTWineService
{
    [OperationContract]
    [WebGet(ResponseFormat = WebMessageFormat.Xml)]
    WineData GetWine(string wineID);
}

One other point to make is that address to this web is https://localhost/RESTControllingUri.

The Default Behavior

It may not be clear, but this operation does have a unique URI associated with it.  The default URI scheme is the path to the *.svc file, followed by a segment with the operation name, followed by querystring parameters for each operation argument.  The following represents the default structure of the URI:

[protocol]://[hostname][:port][/path][/svc filename][?argument1=value1[&argument2=value2 ...]]

In the case of our example, it looks like this:

image

The UriTemplate

When we released the webHttpBinding and the webHttp endpoint behavior in WCF 3.5, we provided the ability to define a UriTemplate.  Aptly named, the UriTemplate allows you to define a template for a set of URIs that follow a similar structure or pattern.  The UriTemplate has 2 parts: the path and the query.  The path is a series of segments.  The segments can contain literals, parameters or variables and wildcards.  Here are some examples:

  • literal:  /products
  • literal:  /products/sportinggoods
  • literal and variable:  /product/{productId}  (Where productId is a parameter that is mapped to an operation argument)
  • wildcard:  /products/*

The query contains name-value pairs that represent the querystring parameters collection.  Here are some examples: 

  • literal in query:  /products?type=json
  • variable in query:  /products?index={index}&pageSize={pageSize}

You can set a UriTemplate through code or you can pass it as a parameter to a WebGetAttribute or WebInvokeAttribute.  Here is an example of the latter:

[OperationContract]
[WebGet(UriTemplate="/products?index={index}&pageSize=
    {pageSize}")]
WineData GetProducts(int index, int pageSize);

* One important thing to note.  In the current release (SP1), if your variables are part of the query, such as above, we will do simple type conversions for you.  So, in the previous example, note that GetProducts takes in integers, but clearly the querystring parameters are strings.  We will take care of this simple conversion for you.  However, if the variables are in the path, we will not.  There is no reason for this, other than it was done for the query, not the path.  It is my understanding that support for type conversions in the path is on the list for a future release.  Just thought you might like to know.

As you can see, the UriTemplate is useful in controlling the URI.  However, there are limitations.  As my might have noticed, the template is defined for the path and querystring after the path to the svc file.  It does not provide any help for manipulating that portion of the URI.

(see this article on MSDN for more information on UriTemplates)

Given this information, we could define a UriTemplate to clean up the URI for our GetWine operation.  Perhaps we want the path to be something like this:  wine/17 (where 17 is an actual wine id).  We could simply change our code to look like this:

[OperationContract]
[WebGet(UriTemplate="wine/{wineID}",
        ResponseFormat = WebMessageFormat.Xml)]
WineData GetWine(string wineID);

The call would look like this:

image

UriTemplate Enhancements in WCF 3.5 SP1

The WCF team added some enhancements to UriTemplates when Visual Studio 2008 SP1 (and Fx 3.5 SP1) was released.  These included support for default values for parameters in paths and compound template segments.  It is simple to define a default value.  Simply set the variable to the default value with an '='.  Remember that you cannot assign defaults to variables in the query portion of the template.  Here is what our example would look like, setting the default value of wineID = 22:

[OperationContract]
[WebGet(UriTemplate="wine/{wineID=22}",
        ResponseFormat = WebMessageFormat.Xml)]
WineData GetWine(string wineID);

You can now omit the id segment of the URI like this:

image

Compound template segments allow you to combine both variables and literals within one segment.  If you are familiar with ADO.NET Data Services, you will note that the ID for entities uses compound template segments.  The IDs look like this:  Entity(id).  ADO.NET Data Services runs on WCF and the webHttpBinding, so you might rightly assume that this functionality was added for this purpose.  However, you can take advantage as well.  If I wanted to implement a similar ID scheme, it would look like this:

[OperationContract]
[WebGet(UriTemplate="wine({wineID})", ResponseFormat =
        WebMessageFormat.Xml)]
WineData GetWine(string wineID);

Here is what it looks like:

image

Completely Controlling the URI

All of the examples we have seen so far only allow us to template the URI after the path to the svc file.  If you want to take full control of the URI, you have to do some kind of URL Rewriting.  There are a variety of means to accomplish this.  ScottGu's blog post Tip/Trick: Url Rewriting with ASP.NET illustrates 4 approaches, including implementing HttpModules, using Request.PathInfo and taking advantage of 3rd party ISAPI filters.  John Flanders' blog post Using WCF WebHttpBinding and WebGet with nicer Urls illustrates how you can build an HttpModule with IIS7 for URL Rewriting.  In this post, I will illustrate how you can take advantage of the new URL Rewrite Module for IIS 7.0 CTP1.  This module allows you to simply add rules that define pattern matches (either Regular Expressions or WildCards) and rewrite to a specified URL pattern.  Actually the module does more than that.  To see the full functionality, check out Bill Staples' blog post on the subject.

For our purposes, we simply want to define a pattern for the incoming URL using Regular Expressions.  This pattern will represent what we want the "Hackable" URI to look like.  We will then take this URI and rewrite it to the actual path that we defined earlier ([protocol]://[hostname][:port][/path][/svc filename]).  An example will probably best illustrate our goal and how we can accomplish it.  Taking the last example we had, the URI looked like this:

image

As my annotations clearly point out, we may not want the ".svc" as part of our URI.  That is really a technical implementation detail and does not really have a place in the "hackable" URI.  So, for this simple example I will illustrate how we can use the Url Rewrite Module to remove the svc extension.  Here is the process:

  1. Make sure you have installed Vista SP1
  2. Make sure you have installed the CTP of the Rewrite Module
  3. Open IIS 7
  4. Navigate to your application and dbl-click on the URL Rewrite Module Icon
    image
  5. Click on add rule in the upper right hand corner
  6. Fill out the form with the following data (we will describe the patterns in detail in a moment):
    1. Name:                       Remove Svc Extension (or any name you want)
    2. Requested URL:         Matches the pattern
    3. Using:                       Regular Expressions
    4. Pattern:                    ^([0-9a-zA-Z\-]+)/([0-9a-zA-Z\-\.\/\(\)]+)
    5. Ignore Case:             Checked
    6. Action Type:              Rewrite
    7. Rewrite URL:             {R:1}.svc/{R:2}
    8. Append QueryString   Checked
  7. Click Apply
    image

You are ready to go.  But before we test it, I want to dissect the pattern we used for matching.  The match pattern was:

^([0-9a-zA-Z\-]+)/([0-9a-zA-Z\-\.\/\(\)]+)

Here is the breakdown of the pattern:

  • The "^" indicates the beginning of the line or string
  • The parenthesis "()" allow you to define a numbered capture group, meaning that you can reference everything inside the parens later by index.  In our example, we have 2 numbered capture groups 1 and 2.  You refer back to these later with the syntax: {R:1} and {R:2}
  • The pattern [0-9a-zA-Z\-]+ indicates any character in 0-9, lowercase a-z, uppercase A-Z or "-", one or more repetitions.
  • The / is a straight match to a slash "/".  In other words, the first named capture group will capture 0-9,a-z, A-Z and "-" prior to the first "/" and you will be able to reference them with {R:1}
  • Then we have another named capture group
  • The pattern [0-9a-zA-Z\-\.\/\(\)] matches any character in 0-9, lowercase a-z, uppercase A-Z or "-", ".", "/", "(", ")", one or more repetitions.

The rewrite URL is simply everything in the first named capture group (everything prior to the first /) which should be the svc file, without the svc extension.  We then append an svc extension and a slash.  We then append everything in the named capture group. 

Let's see it in action.  Here is our new svc-less request:

image

Eliminating the ".svc" extension is just the tip of the iceburg.  Hopefully, you can see how you can take full control of the URI.

Comments

  • Anonymous
    August 25, 2008
    There's a new screencast series on building services using the WCF part of .NET. The first few are already

  • Anonymous
    December 23, 2008
    One of my interesting is "URL Rewriting". The first time I have implemented it through implementing

  • Anonymous
    December 23, 2008
    One of my interesting thing is "URL Rewriting". The first time I have implemented it through