September 23, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Creating RESTful Web Services with Windows Communication Foundation

  • August 21, 2007
  • By Aaron Lerch
  • Send Email »
  • More Articles »

WebInvoke provides the same properties as WebGet, but with one additional property, "Method". Note that when hosting your service in IIS using the WebScriptServiceHostFactory (discussed later), the only valid "Method" values are "GET" and "POST". I am unsure if this is a restriction due to the fact that .NET 3.5 is in Beta 2, or if the restriction will remain for the official release.

Property Name Type Possible Values Description
Method String "POST", "DELETE", "*", and so forth. The HTTP verb that must be specified for an incoming request to match the given OperationContract.

An OperationContract that retrieves a list of blog posts might be represented as follows:

[OperationContract]
[WebGet(UriTemplate = "/posts")]
PostResultSet GetPosts();

An example of an OperationContract that updates an existing blog post might be represented as follows:

[OperationContract]
[WebInvoke(Method = "PUT", UriTemplate = "/admin/post/{id}")]
void UpdatePost(string id, Post post);

You can see from this example that the string parameter "id" is bound to the UriTemplate's value for "{id}". Remaining parameters on an OperationContract method marked with the WebGet/WebInvoke attributes will attempt to deserialize the data within the HTTP request. Because these examples do not specify the RequestFormat or ResponseFormat properties on either the WebGet or WebInvoke attributes, the default serialization configured for the endpoint will be used.

System.ServiceModel.Web.WebOperationContext

The WebOperationContext is available within an OperationContract implementation via the WebOperationContext.Current property. It exposes four "contexts" as properties, each of which relate to the HTTP request or response within the WCF request/response model: IncomingRequest, IncomingResponse, OutgoingRequest, and OutgoingResponse. The two that will typically be used the most are IncomingRequest and OutgoingResponse.

The IncomingRequest property is important because it offers access to web-specific properties of the request that was mapped to the OperationContract implementation. This allows inspection of data such as ContentType, ContentLength, Method (GET, POST, PUT, and so on), UserAgent, and UriTemplateMatch. This information can be used to invoke specific processing for various conditions. For example, in a blog service, a URI of "/posts" retrieves a list of posts, unfiltered and unrestricted. However, for performance and scalability reasons, a blog that contains hundreds or thousands of posts should not return all of them by default. One possible solution is to use defaults defining a "paged" response of posts, and allowing query string parameters within the URI to override the defaults. Information about the URI that matched the UriTemplate is available in the UriTemplateMatch property. Given the following as the OperationContract for a "GET /posts" HTTP request:

[OperationContract]
[WebGet(UriTemplate = "/posts")]
PostResultSet GetPosts();

You can create the following implementation to allow a limited subset as a default response, with query string parameters that can override the defaults. Note that I've moved common processing such as query string parameter retrieval into a static helper class:

public PostResultSet GetPosts()
{
   int start;
   int maxresults;
   Utilities.GetPageInfo(out start, out maxresults, true);

   if ((start >= 0) && (start <= _posts.Count)
      && (maxresults > 0))
   {
      // Set the upper limit to the smaller of the two relevant values
      maxresults = Math.Min(maxresults, _posts.Count);

      // Select all posts that fall within the range provided,
      // ordered by the date they were modified
      IEnumerable<Post> posts = from post in _posts
                                orderby post.ModifiedOn
                                select post;

      // Return the page specified by the querystring parameters
      // (or defaults)
      posts = posts.Skip<Post>(start).Take<Post>(maxresults);

      PostResultSet postResultSet = new PostResultSet();
      postResultSet.Posts = posts.ToList<Post>();
      postResultSet.Start = start;
      postResultSet.TotalCount = _posts.Count;
      return postResultSet;
   }
   else
   {
      WebOperationContext.Current.OutgoingResponse.StatusCode =
         System.Net.HttpStatusCode.BadRequest;
      // Return an empty list
      return new PostResultSet();
   }
}

public static class Utilities
{
   public static void GetPageInfo(out int start,
                    out int maxresults,
                    bool capMaxResultsDefault)
   {
      if (!GetFromQueryString("start", out start))
      {
         start = 0;
      }

      if (!GetFromQueryString("maxresults", out maxresults))
      {
         maxresults = (capMaxResultsDefault) ? 25 : Int32.MaxValue;
      }
   }

   public static bool GetFromQueryString(string name, out int value)
   {
      value = 0;
      WebOperationContext context = WebOperationContext.Current;
      UriTemplateMatch uriMatch =
         context.IncomingRequest.UriTemplateMatch;
      string strValue = uriMatch.QueryParameters[name];

      if (!String.IsNullOrEmpty(strValue))
      {
         return Int32.TryParse(strValue, out value);
      }

      return false;
   }
}




Page 2 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel