翔如菲菲

其实天很蓝,阴云总会散;其实海不宽,此岸连彼岸.

导航

Building RESTful Services with WCF

Designing RESTful services properly is probably more challenging than actually implementing them once you know exactly what you’re trying to accomplish. However, the key to a successful and smooth implementation is choosing a programming framework designed to simplify working with HTTP.

Today, Microsoft offers exceptional support for HTTP across a variety of programming frameworks. First, .NET comes with the System.Web and System.Net assemblies, which contain the foundational classes for building HTTP clients and servers. ASP.NET builds on this foundation and provides a higher-level HTTP framework that simplifies the process of building Web applications for human consumption.

Although ASP.NET could be used to build RESTful services, the framework wasn’t designed with that goal in mind. Instead, Microsoft’s service-oriented investments have gone into WCF, the unified programming model for connecting applications on the .NET platform. Although WCF began as a SOAP framework, it has quickly evolved into a first-class framework for both SOAP and REST-based services. Now, WCF is the default choice for building services regardless of which approach you wish to use.

Using WCF 3.5 to build RESTful services offers communication and hosting flexibility, a simple model for mapping URI templates to methods, and simplified support for numerous representations including XML, JSON, RSS and Atom. In addition to this core support, Microsoft is now shipping the WCF REST Starter Kit, which provides additional APIs, extension methods, and various Visual Studio project templates to simplify REST development. The WCF REST Resource Kit is expected to evolve through CodePlex, and some of its features may make their way into future versions of the .NET framework.

The ADO.NET team was able to leverage the WCF REST support when they built ADO.NET Data Services, a higher-level REST framework that almost fully automates the process of exposing RESTful services around underlying data/object entities using AtomPub. ADO.NET Data Services is a great example of what’s possible when using WCF as your underlying REST communication framework.

Throughout this section, we’ll take a closer look at the built-in REST support found in WCF 3.5, the REST Starter Kit, and ADO.NET Data Services. But first, let’s look at how you’d have to do it without WCF.

The Motivation for a REST Framework

If you were going to implement our RESTful bookmark service using an IHttpHandler-derived class, there are several things that you’d have to manage yourself. IHttpHandler provides only a single entry point – ProcessRequest – for processing all incoming HTTP requests. In order to implement the RESTful interface we’ve designed, your implementation of ProcessRequest will have to perform the following tasks:

  1. Inspect the incoming URI and figure out which resource it identifies.
  2. Extract any variables found within the URI and map them to variables.
  3. Authenticate and authorize the user responsible for the request.
  4. Determine the HTTP method used in the request and whether it’s allowed for the resource.
  5. Read the resource representation found in the entity body (if any).
  6. Use all of this information to perform the underlying service logic.
  7. Generate an appropriate HTTP response, including the proper status code, description, and outgoing resource representation in the response entity body (if any).

Check out Figure 7 to get a feel for what this code might look like. I’ve provided a few methods that abstract away a lot of details like Matches and ExtractVariables but there is still a lot of tedious work going on around the actual service logic (e.g., dealing with user accounts and bookmarks).

Figure 7: A sample IHttpHandler implementation

 

public class BookmarkService : IHttpHandler
{
    
public bool IsReusable { get { return true;  } }
    
public void ProcessRequest(HttpContext context)
    {
        Uri uri 
= context.Request.Url;
        
// compare URI to resource templates and find match
        if (Matches(uri, "{username}?tag={tag}"))
        {
            
// extract variables from URI
            Dictionary<stringstring> vars =
                ExtractVariables(uri, 
"{username}?tag={tag} ");
            
string username = vars["username"];
            
string tag = vars["tag"];
            
// figure out which HTTP method is being used
            switch (context.Request.HttpMethod)
            {
                
// dispatch to internal methods based on URI and HTTP method
                
// and write the correct response status & entity body
                case "GET":
                    List
<Bookmark> bookmarks = GetBookmarks(username, tag);
                    WriteBookmarksToResponse(context.Response, bookmarks);
                    SetResponseStatus(context.Response, 
"200""OK");
                    
break;
                
case "POST":
                    Bookmark newBookmark 
= ReadBookmarkFromRequest(context.Request);
                    
string id = CreateNewBookmark(username, newBookmark);
                    WriteLocationHeader(id);
                    SetResponseStatus(context.Response, 
"201""Created");
                    
break;
                
default:
                    SetResponseStatus(context.Response, 
"405""Method Not Allowed");
            }
        }
        
if (Matches(uri, "users/{username}/bookmarks/{id}"))
        {
            
// dispatch to internal methods based on URI and HTTP method
            
// and write the correct response status & entity body
            ...
        }
        ... 
// match addition URI templates here
    }

 

CF 3.5 provides a programming model that shields you from the tedious aspects of this code – it shields you from most HTTP protocol details, URIs, and the resource representations transmitted on the wire.  It accomplishes this by providing a built-in URI template programming model that makes it easy to match URIs and extract variables. It also provides a new set of attributes for mapping HTTP method + URI template combinations to method signatures, and some serialization improvements for supporting different types of resource representations. And, of course, it provides the underlying runtime components that know how to bring these new RESTful programming constructs to life.

Introducing the WCF “Web” Programming Model

WCF 3.5 shipped with a new assembly called System.ServiceModel.Web.dll, which contains a variety of new classes that provide an easy-to-use “Web-based” programming framework for building RESTful services. To begin using this new “Web” programming model, simply add a reference to System.ServiceModel.Web.dll, a using statement to System.ServiceModel.Web, and you’re ready to go.

The first thing to realize is that the WCF “Web” model is still based on mapping a service interface to a set of methods. The only difference for a RESTful service is what the interface looks like. Instead of exposing a set of RPC-based operation names to the world, we’re going to define the service interface in terms of HTTP’s uniform interface and a set of URI templates. We’ll accomplish this by first defining a set of logical operations for performing the resource logic, and then we can apply the new “Web” attributes to define the mapping between the HTTP methods, our URI design, and the corresponding methods.

Modeling Resource Representations in WCF

WCF supports a variety of different mechanisms for working with the resource representations that will be transmitted in the HTTP request/response messages. You can always work directly with the raw request/response messages, if you want, by defining your method signatures in terms of System.ServiceModel.Channels.Message. If you take this route, you’re free to use your favorite XML or JSON API to process the messages; however, most developers prefer using a serialization engine that automatically moves between messages and .NET objects that are easier to consume.

WCF supports several different serializers out-of-the-box including the DataContractSerializer (the default), the DataContractJsonSerializer, and even the XmlSerializer from ASP.NET Web services. These serializers all perform essentially the same task, but they each do it a little bit differently, and each comes with its pros and cons. For example, the DataContractSerializer is very efficient and streamlined but supports only a small subset of XML Schema. XmlSerializer, on the other hand, allows you to build more advanced structures not supported by DataContractSerializer. WCF allows you to choose the serializer you want to use on a per-method basis when defining your service contracts.

For our bookmarking service, the DataContractSerializer should be sufficient for our needs. So we’ll define a few classes that will work with DataContractSerializer to represent our resources (see Figure 8).

Figure 8: User Account and Bookmark Resource Classes
public class User
{
    
public Uri Id { getset; }
    
public string Username { getset; }
    
public string Name { getset; }
    
public string Email { getset; }
    
public Uri Bookmarks { getset; }
}
public class UserProfile
{
    
public Uri Id { getset; }
    
public string Name { getset; }
    
public Uri Bookmarks { getset; }
}
public class Bookmark
{
    
public Uri Id { getset; }
    
public string Title { getset; }
    
public Uri Url { getset; }
    
public string User { getset; }
    
public Uri UserLink { getset; }
    
public string Tags { getset; }
    
public bool Public { getset; }
    
public DateTime LastModified { getset; }
}
[CollectionDataContract]
public class Bookmarks : List<Bookmark>
{
    
public Bookmarks() { }
    
public Bookmarks(List<Bookmark> bookmarks) : base(bookmarks) {}
}

 

As of .NET Framework 3.5 SP1, DataContractSerializer now supports serializing plain-old CLR objects (POCO, for short) without any serializer attributes as shown in the example above. Before SP1, we would have had to annotate the User and Bookmark classes with [DataContract] and [DataMember]; now you don’t have to. If you want more control over naming, default values, and ordering, you can always add these attributes back to the class definition but for this example, we’ll just accept the default mapping. In this case I still had to annotate the collection class with [CollectionDataContract] in order to make the name of the root element <Bookmarks> instead of the default <ArrayOfBookmark>.

DataContractSerializer treats all of the fields found on User and Bookmark as optional by default, so these classes can handle the input/output representations for each resource. We’ve also defined a couple of custom collection types for modeling lists of users and bookmarks. These classes all conform to the resource representations we defined earlier in the previous section.

Defining the Logical HTTP Methods

The next step is to model the logical HTTP methods we need to support (as outlined in Figure 4 and Figure 5 and) with method signatures that use the resource classes we just defined. Figure 9 shows the definition of a BookmarkService class that contains a method for each resource operations.

Figure 9: Modeling the logical HTTP methods

 

 

public class BookmarkService
{
    Bookmarks GetPublicBookmarks(
string tag) {...}
    Bookmarks GetUserPublicBookmarks(
string username, string tag) {...}
    Bookmarks GetUserBookmarks(
string username) {...}
    UserProfile GetUserProfile(
string username) {...}
    User GetUser(
string username) {...}
    
void PutUser(string username, User user) {...}
    
void DeleteUser(string username) {...}
    Bookmark GetBookmark(
string username, string id) {...}
    
void PostBookmark(string username, Bookmark newValue) {...}
    
void PutBookmark(string username, string id, Bookmark bm) {...}
    
void DeleteBookmark(string username, string id) {...}
}

You’ll notice that most of these methods operate on User and Bookmark objects or their respective collection classes. Some of them also require extra parameters like username, id, and tag, which we’ll harvest from the incoming URI according to the URI template variables.

Implementing the URI Design with UriTemplate

The next thing we need to figure out is how to model the various URI templates we defined in Figure 4 and Figure 5 so we can use them in conjunction with the methods we just defined. The .NET Framework comes with the System.Uri class for modeling URIs, but it doesn’t contain the variable or matching logic. Hence, WCF provides a few additional classes for specifically dealing with URI templates, variables, and matching. These classes include UriTemplate, UriTemplateMatch, and UriTemplateTable. 

When you construct a UriTemplate object, you supply a URI template string like the ones we used in Figure 4 and Figure 5. These templates may contain variables within curly braces (“{username}?tag={tag}”) and even an asterisk (“*”), which acts as a wildcard, when you want to match anything from that point on in the path. You can also specify default variable values within the template, making it possible to omit that part of the path. For example, a template of “{username}/{tag=all}” means that the variable “tag” will have the default value of “all” when that path segment is omitted.

Once you have a UriTemplate object, you can call the Match method, passing in a candidate Uri to see if it matches the template. If it does, it returns a UriTemplateMatch object containing the bound variables; otherwise, it simply returns null. You can also go the other direction – you can call BindByPosition or BindByName to generate a new URI from the template, supplying the required variable values. The following example illustrates how to use Match and BindByPosition to move in both directions:

Uri baseUri = new Uri("http://contoso.com/bookmarkservice");
UriTemplate uriTemplate 
= new UriTemplate(
   
"users/{username}/bookmarks/{id}");
// generate a new bookmark URI
Uri newBookmarkUri = uriTemplate.BindByPosition(baseUri, "skonnard""123");
// match an existing bookmark URI
UriTemplateMatch match = uriTemplate.Match(baseUri, newBookmarkUri);
System.Diagnostics.Debug.Assert(match 
!= null);
Console.WriteLine(match.BoundVariables[
"username"]);
Console.WriteLine(match.BoundVariables[
"id"]);
 

The UriTemplateTable class provides a mechanism for managing a collection of UriTemplate objects. This makes it easy to call Match on the table to find all templates that match the supplied Uri. Alternatively, you can call MatchSingle to ensure it matches only a single UriTemplate in the table.

The WCF “Web” programming model makes it easy to map UriTemplate objects to your method signatures through the new [WebGet] and [WebInvoke] attributes. Once you have these attributes enabled, WCF will perform its internal method dispatching based on UriTemplate matching logic.

Defining the HTTP Interface: [WebGet] and [WebInvoke]

Now that we have an understanding of UriTemplate, we can use a few different WCF attributes to define the HTTP interface that our service will support. First, it’s important to know that all WCF service contracts must be annotated with [ServiceContract] and [OperationContract] regardless of whether you’re planning to use SOAP or REST. These attributes control what operations are ultimately exposed through the service. So we’ll first need to add these attributes to our class.

Once we have those attributes in place, we can add the new [WebGet] and [WebInvoke] attributes to our method signatures to define the specific mapping to the HTTP uniform interface. The reason they provided two attributes is because GET requests are fundamentally different from all the others, in that they are safe, idempotent, and highly cacheable. If you want to map an HTTP GET request to one of your service methods, you use [WebGet], and for all other HTTP methods, you use [WebInvoke].

The main thing you specify when using [WebGet] is the UriTemplate that the method is designed to handle. You can map the various UriTemplate variables to the method parameters by simply using the same name in both places. Figure 10 shows how we can map the various GET requests from our URI design (in Figure 4 and Figure 5 and) to our new BookmarkService class.

Figure 10: Applying [WebGet] to BookmarkService

[ServiceContract]
public partial class BookmarkService
{
    [WebGet(UriTemplate 
= "?tag={tag}")]
    [OperationContract]
    Bookmarks GetPublicBookmarks(
string tag) {...}
    [WebGet(UriTemplate 
= "{username}?tag={tag}")]
    [OperationContract]
    Bookmarks GetUserPublicBookmarks(
string username, string tag) {...}
    [WebGet(UriTemplate 
= "users/{username}/bookmarks?tag={tag}")]
    [OperationContract]
    Bookmarks GetUserBookmarks(
string username, string tag) {...}
    [WebGet(UriTemplate 
= "users/{username}/profile")]
    [OperationContract]
    UserProfile GetUserProfile(
string username) {...}
    [WebGet(UriTemplate 
= "users/{username}")]
    [OperationContract]
    User GetUser(
string username) {...}
    [WebGet(UriTemplate 
= "users/{username}/bookmarks/{bookmark_id}")]
    [OperationContract]
    Bookmark GetBookmark(
string username, string bookmark_id) {...}
    ...
}

We’ll handle the remaining HTTP methods with the [WebInvoke] attribute. It works a lot like [WebGet], but with a couple of key differences. Since it can be used with any HTTP method, you must specify which HTTP method it will handle via the Method property. And for PUT and POST methods, where the client will be supplying an entity body, you’ll need to add one more parameter to the method signature capable of holding the deserialized entity body. It should come after all of the UriTemplate parameters. Figure 11 shows how to complete our HTTP mapping for the bookmark service using [WebInvoke].

Figure 11: Applying [WebInvoke] to BookmarkService 

[ServiceContract]
public partial class BookmarkService
{
    [WebInvoke(Method 
= "PUT", UriTemplate = "users/{username}")]
    [OperationContract]
    
void PutUserAccount(string username, User user) {...}
    [WebInvoke(Method 
= "DELETE", UriTemplate = "users/{username}")]
    [OperationContract]
    
void DeleteUserAccount(string username) {...}
    [WebInvoke(Method 
= "POST", UriTemplate = "users/{username}/bookmarks")]
    [OperationContract]
    
void PostBookmark(string username, Bookmark newValue) {...}
    [WebInvoke(Method 
= "PUT", UriTemplate = "users/{username}/bookmarks/{id")]
    [OperationContract]
    
void PutBookmark(string username, string id, Bookmark bm) {...}
    [WebInvoke(Method 
= "DELETE", UriTemplate = "users/{username}/bookmarks/{id}")]
    [OperationContract]
    
void DeleteBookmark(string username, string id) {...}
    ...

Notice how each of these methods takes either a User or Bookmark object as the final parameter in the method signature – WCF will deserialize the request body and pass it to us through this parameter.

Implementing the Uniform Interface Methods

If you compare this implementation to the one I showed back in Figure 7, you should appreciate how much simpler WCF makes things on the HTTP front. Now, when we implement the methods, we can focus primarily on the business logic around managing the user account and bookmark resources. The WCF programming model has effectively shielded us from most HTTP programming details.

However, there are still some aspects of the HTTP programming model that we do need to manage from within your method implementations. For example, in most of our methods, we’ll need to inject response headers, set the response status code and description, and generate outbound links. So how do we get access to the underlying HTTP methods within our WCF method implementations?

This is where the WebOperationContext class comes into play. WebOperationContext provides properties for accessing the IncomingRequest and OutgoingResponse messages, which you can use to inspect the HTTP request or to manipulate the HTTP response before it’s sent. For example, you can simply call WebOperationContext.Current.OutgoingResponse.SetStatusAsNotFound() to return a 404 (“Not Found”) response to the client. WebOperationContext is your primary interface to HTTP.

You simply need to implement the logical HTTP operations for each of the resources exposed by your service. To help you get a feel for the type of code you’ll be writing within each method body, I’ve provided a few complete method implementations for you to inspect in Figure 12. Notice how they set different HTTP response codes, headers, and create outbound links through helper methods. The GetUserLink help generates an outbound link based on the same UriTemplates used by the service.

Figure 12: Sample method implementations for user account resources

[ServiceContract]

 

public partial class BookmarkService
{
    
// in-memory resource collections
    Dictionary<string, User> users = new Dictionary<string, User>();
    Dictionary
<string, Bookmark> bookmarks = new Dictionary<string, Bookmark>();
    [WebGet(UriTemplate 
= "users/{username}")]
    [OperationContract]
    User GetUserAccount(
string username)
    {
        
if (!IsUserAuthorized(username))
        {
            WebOperationContext.Current.OutgoingResponse.StatusCode 
=
                HttpStatusCode.Unauthorized;
            
return;
        }
        User user 
= FindUser(username);
        
if (user == null)
        {
            WebOperationContext.Current.OutgoingResponse.SetStatusAsNotFound();
            
return null;
        }
        
return user;
    }
    [WebInvoke(Method 
= "PUT", UriTemplate = "users/{username}")]
    [OperationContract]
    
void PutUserAccount(string username, User newValue)
    {
        User user 
= FindUser(username);
        
if (user == null)
        {
            
// set status to created and include new URI in Location header
            WebOperationContext.Current.OutgoingResponse.SetStatusAsCreated(
                GetUserLink(username));
            ... 
// process new user backend logic
        }
        
else if (!IsUserAuthorized(username))
        {
            WebOperationContext.Current.OutgoingResponse.StatusCode 
=
               HttpStatusCode.Unauthorized;
            
return;
        }
        
// create or update new user, but don't let user set Id/Bookmarks
        newValue.Id = GetUserLink(username);
        newValue.Bookmarks 
= GetUserBookmarksLink(username);
        users[username] 
= newValue;
    }
    ... 
// remaining methods ommitted
}
 

Hosting and Configuring WCF “Web” Services

WCF provides a great deal of flexibility around hosting services. Thanks to this flexibility, you can host your RESTful services over HTTP in any application of your choosing, or you can choose to host them within IIS/ASP.NET. The latter is probably the best choice if you’re building large-scale “Web” services.

When hosting your RESTful WCF services, there are two key components that you need to configure in order to enable the new “Web” behavior within the runtime. First, you need to expose an endpoint that uses the new binding for RESTful services – WebHttpBinding. Then, you need to configure the “Web” endpoint with the WebHttpBehavior. The new binding instructs WCF to not use SOAP anymore, but rather plain XML messages while the new behavior injects custom dispatching logic based on the [WebGet] and [WebInvoke] attributes and their corresponding UriTemplates.

Figure 13 illustrates how to accomplish this in your application configuration file.

Figure 13: Configuring WCF "Web" Services

<configuration>
   
<system.serviceModel>
     
<services>
        
<service name="BookmarkService">
            
<endpoint binding="webHttpBinding" contract="BookmarkService"
                      behaviorConfiguration
="webHttp"/>
        
</service>
     
</services>
     
<behaviors>
        
<endpointBehaviors>
            
<behavior name="webHttp">
                
<webHttp/>
            
</behavior>
        
</endpointBehaviors>
     
</behaviors>
  
</system.serviceModel>

<configuration>

With this configuration in place, you can simply create a ServiceHost instance based on BookmarkService and open it to get your RESTful service up and running:

ServiceHost host = new ServiceHost(typeof(BookmarkService),

    new Uri("http://localhost:8080/bookmarkservice"));

host.Open();

... // keep the host open until you want to shut it down

In this example, we specified the base URI for the service when constructing the ServiceHost, and that same address will be used for the endpoint, since we didn’t specify an address on the endpoint itself.

If the hosting experience I just described feels a little tedious to you, worry not, WCF has made things even easier through a custom ServiceHost-derived class called WebServiceHost. When you use WebServiceHost instead of ServiceHost, it will automatically create a Web endpoint for you using the base HTTP address and configure the injected endpoint with the WebHttpBehavior. So this example is equivalent to the prior example, only this time we don’t need a configuration file at all:

WebServiceHost host = new WebServiceHost(typeof(BookmarkService),

    new Uri("http://localhost:8080/bookmarkservice"));

host.Open();

... // keep the host open until you want to shut it down

This little gem also greatly simplifies the process of hosting WCF “Web” services within IIS/ASP.NET through .svc files. By specifying the mapping from the .svc file to the service class name, we can take advantage of the Factory attribute to specify the WebServiceHostFactory as shown here:

<%@ ServiceHost Service="BookmarkService"

    Factory="System.ServiceModel.Activation.WebServiceHostFactory"%>

This custom ServiceHostFactory intercepts the ServiceHost creation process at run time and generates WebServiceHost instances instead. In this case, the base URI of the service will simply be the URI of the .svc file and no further configuration is necessary, unless you need to configure additional behaviors.

If you’re hosting your WCF “Web” services in IIS/ASP.NET, it’s also a good idea to enable the ASP.NET compatibility mode within the WCF runtime. Doing so makes it possible for you to access the HttpContext object managed by ASP.NET from within your WCF methods. You enable ASP.NET compatibility by adding the following global flag to your Web.config file:

<configuration>

   <system.serviceModel>

      <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>

   </system.serviceModel>

</configuration>

Then, you’ll also want to declare on your service class whether you allow, require, or don’t allow ASP.NET compatibility mode as illustrated here:

[AspNetCompatibilityRequirements(RequirementsMode =

    AspNetCompatibilityRequirementsMode.Allowed)]

[ServiceContract]

public partial class BookmarkService

{

   ...

You will most likely end up hosting your WCF “Web” services in IIS/ASP.NET using .svc files like I’ve just shown you. One issue with using this technique is what the final URIs will look like. If the .svc file is located at http://localhost/bookmarkservice.svc, the final service URIs end up like this:

http://localhost/bookmarkservice.svc/skonnard?tag=wcf

http://localhost/bookmarkservice.svc/users/skonnard/bookmarks/13

...

The .svc portion of the URI is really a .NET implementation detail and not something that you probably want to have in the URI. The easiest way to work around this issue today is to leverage the Microsoft URL Rewrite Module for IIS 7.0, which makes it possible to remove the “.svc” from the path segment. For a complete example on how to do this, see Rob Bagby’s blog post on Controlling the URI. Once you’ve applied the URL Rewrite Module to remove “.svc”, your URIs will look like this:

http://localhost/bookmarkservice/skonnard?tag=wcf

http://localhost/bookmarkservice/users/skonnard/bookmarks/13

...

Once you have your WCF service implemented, configured, and hosted, you can begin testing your RESTful service by simply browsing to it (see  

Figure 14). You can also use any HTTP client utility  to test the non-GET operations for creating, updating, and deleting resources at this point.

Figure 14: Browsing to your WCF RESTful service

WCF Support for Ajax & JSON

Now that we’ve seen how to develop, host, and configure WCF “Web” services, we’re ready to explore supporting additional representation formats besides our custom XML vocabulary. We decided during our design phase that we wanted to support JSON message formats to simplify things for Ajax-based client applications. WCF makes this trivial to accomplish.

Both [WebGet] and [WebInvoke] expose two properties called RequestFormat and ResponseFormat. These properties are of type WebMessageFormat, an enum that contains two values, Xml and Json. The default value for these properties is WebMessageFormat.Xml, which is why our service currently returns XML. If you want a specific operation to support JSON, you simply need to set RequestFormat or ResponseFormat to WebMessageFormat.Json.

Since we want to support both message formats on our service, we’ll need to add another complete set of method signatures and new UriTemplates that are JSON-specific.  Both method versions (XML and JSON) can call the same internal method to perform the operation logic, so we won’t be duplicating any code. WCF will simply perform the message-to-object translation differently based on the target URI.

Figure 15 shows how to begin defining the JSON-specific interface to our bookmarking service. Notice how we’ve added “?format=json” to the end of the UriTemplate strings we’ve been using for the equivalent XML operations (whose attributes have remained unchanged, by the way). We’ve also added the RequestFormat/ResponseFormat properties, specifying WebMessageFormat.Json, where applicable.

Figure 15: Defining a JSON-specific interface

[ServiceContract]
public partial class BookmarkService
{
    ...
    [WebInvoke(Method = "POST", RequestFormat=WebMessageFormat.Json,
        UriTemplate = "users/{username}/bookmarks?format=json")]
    [OperationContract]
    void PostBookmarkAsJson(string username, Bookmark newValue)
    {
        HandlePostBookmark(username, newValue);
    }
    [WebGet(ResponseFormat= WebMessageFormat.Json,
        UriTemplate = "users/{username}/bookmarks/{id}?format=json")]
    [OperationContract]
    Bookmark GetBookmarkAsJson(string username, string id)
    {
        HandleGetBookmark(username, id);
    }
    ...
}

 

Notice how these methods are also dispatching to internal methods called HandlePostBookmark and HandleGetBookmark, which are format independent. Both the XML and JSON methods can call these same internal methods to perform the business logic, reducing code duplication.

If you navigate to one of the JSON URIs in a browser, you’ll be prompted to save the file to disk. Go ahead and save the file as a .txt file and you can inspect the JSON response. With JSON enabled, Ajax clients can more easily communicate with our RESTful service and consume the data we send back.

WCF even goes one step further on the Ajax front by providing a special behavior that can automatically expose a WCF service as an Ajax-friendly JSON service. This is made possible by EnableWebServiceBehavior, which is wired-up when you use the new WebScriptServiceHost to host your services. This behavior not only exposes your WCF service, it also adds behavior to automatically generate Javascript proxies for Web browsers upon request. If you browse to your service’s base address and add “/js” to the end, you’ll get the auto-generated Javascript proxy. This makes it really simple for Javascript code running in a browser to call your service.

This technique is only useful for simple Ajax-based services that are primarily serving up data to Web pages. You cannot use UriTemplates in conjunction with this type of Ajax-based service because the auto-generated JavaScript proxies aren’t capable of dealing with them properly today.

WCF Support for Atom Feeds and AtomPub

In addition to the JSON support, WCF 3.5 also comes with built-in support for feeds, including both RSS 2.0 and Atom feed formats. They’ve provided a common API for building logical feeds regardless of the format you intend to use for the feed. The API consists of various classes found in System.ServiceModel.Syndication, including SyndicationFeed, SyndicationItem, SyndicationContent, etc.

Once you have a SyndicationFeed object, you can format it using either the Atom10FeedFormatter or the RSS20FeedFormatterClass. This gives you the ability to support multiple feed formats when you need to accommodate different client scenarios and make your feeds as widely consumable as possible. Figure 16 illustrates how to build a SyndicationFeed instance containing all of the public bookmarks. It allows the client to specify what type of feed to retrieve in the URI.

Figure 16: Building a SyndicationFeed containing the public bookmarks

[WebGet(UriTemplate ="feed?tag={tag}&format=atom")]
[ServiceKnownType(typeof(Rss20FeedFormatter))]
[ServiceKnownType(typeof(Atom10FeedFormatter))]
[OperationContract]
SyndicationFeedFormatter GetPublicBookmarksFeed(string tag, string format)
{
    Bookmarks publicBookmarks = HandleGetPublicBookmarks(tag);
    WebOperationContext ctx = WebOperationContext.Current;
    List<SyndicationItem> items = new List<SyndicationItem>();
    foreach (Bookmark bm in publicBookmarks)
    {
        SyndicationItem item = new SyndicationItem(bm.Title, "", bm.Url,
            bm.Id.ToString(), new DateTimeOffset(bm.LastModified));
        foreach (string c in bm.Tags.Split(','))
            item.Categories.Add(new SyndicationCategory(c));
        item.Authors.Add(new SyndicationPerson("", bm.User, ""));
        items.Add(item);
    }
    SyndicationFeed feed = new SyndicationFeed()
    {
        Title = new TextSyndicationContent("Public Bookmarks"),
        Id = ctx.IncomingRequest.UriTemplateMatch.RequestUri.ToString(),
        LastUpdatedTime = DateTime.Now,
        Items = items
    };
    if (format.Equals("atom"))
        return new Atom10FeedFormatter(feed);
    else
        return new Rss20FeedFormatter(feed);
}

Notice how in Figure 16 the method is defined to return a SyndicationFeedFormatter type but the implementation actually returns an instance of either Rss20FeedFormatter or Atom10FeedFormater. To make this work, we had to annotate the method with two [ServiceKnownType] attributes, one specifying each of the derived SyndicationFeedFormatter types we needed to return.

This type of feed can be consumed and viewed by any RSS/Atom reader, which includes most of today’s modern Web browsers (see Figure 6), and it can be easily syndicated by other feed-savvy sites. There is a great deal of RSS/Atom infrastructure available on the Web today, so exposing your data this way opens up numerous possibilities around how the data can be harvested.

WCF also provides built-in classes for AtomPub, a standard API for interacting with Atom-based collections using a RESTful interface. You can think of AtomPub as a standard application of the HTTP uniform interface but applied specifically to Atom feeds, which are modeled as collections of entries.

The WCF AtomPub support is found across several classes including ServiceDocument, ServiceDocumentFormatter, AtomPub10ServiceDocumentFormatter, and Workspace all found in the System.ServiceModel.Syndication namespace. Figure 17 illustrates how to use these classes to generate an AtomPub service document describing the feeds provided by our service.

Figure 17: Generating an AtomPub service document describing our feeds

ServiceDocument HandleGetServiceDocument()
{
    List<ResourceCollectionInfo> collections = new List<ResourceCollectionInfo>();
    collections.Add(new ResourceCollectionInfo(
        new TextSyndicationContent("Public Bookmarks"),
        GetPublicBookmarksFeedLink()));
    foreach (string user in users.Keys)
        collections.Add(
            new ResourceCollectionInfo(
                new TextSyndicationContent(
                    string.Format("Public Bookmarks by {0}", user)),
                    GetUserBookmarksFeedLink(user)));
    List<Workspace> workspaces = new List<Workspace>();
    workspaces.Add(new Workspace(
        new TextSyndicationContent("Contoso Bookmark Service"), collections));
    return new ServiceDocument(workspaces);
}

 

 

To fully support the AtomPub protocol, we’d need to redesign our service quite a bit to do everything in terms of Atom feeds and entries. In other words, we’d no longer use our current representations for user accounts and bookmarks. Instead, we’d figure out a way to represent user accounts and bookmarks within the AtomPub format and implement the appropriate HTTP methods (according to the AtomPub specification) using the [WebGet] and [WebInvoke] attributes.

Implementing Authentication and Authorization

Now we need to implement the HMAC authentication scheme I described earlier. The easiest way to accomplish this is to simply add a method called AuthenticateUser to your service implementation and call it from each service operation that we need to restrict access to. Figure 18 shows how to implement HMAC authentication by simply signing the URL with the secret key.

Figure 18: Implementing HMAC authentication

private bool AuthenticateUser(string user)
{
    WebOperationContext ctx = WebOperationContext.Current;
    string requestUri = ctx.IncomingRequest.UriTemplateMatch.RequestUri.ToString();
    string authHeader = ctx.IncomingRequest.Headers[HttpRequestHeader.Authorization];
    // if supplied hash is valid, user is authenticated
    if (IsValidUserKey(authHeader, requestUri))
        return true;
    return false;
}
public bool IsValidUserKey(string key, string uri)
{
    string[] authParts = key.Split(':');
    if (authParts.Length == 2)
    {
        string userid = authParts[0];
        string hash = authParts[1];
        if (ValidateHash(userid, uri, hash))
            return true;
    }
    return false;
}
bool ValidateHash(string userid, string uri, string hash)
{
    if (!UserKeys.ContainsKey(userid))
        return false;
    string userkey = UserKeys[userid];
    byte[] secretBytes = ASCIIEncoding.ASCII.GetBytes(userkey);
    HMACMD5 hmac = new HMACMD5(secretBytes);
    byte[] dataBytes = ASCIIEncoding.ASCII.GetBytes(uri);
    byte[] computedHash = hmac.ComputeHash(dataBytes);
    string computedHashString = Convert.ToBase64String(computedHash);
    return computedHashString.Equals(hash);
}

You can simply call this method from each [WebGet] and [WebInvoke] operation that requires authentication as illustrated here:

if (!AuthenticateUser(username))

{

    WebOperationContext.Current.OutgoingResponse.StatusCode =

        HttpStatusCode.Unauthorized;

    return;

}

Once we’ve authenticated a user, we can save the user’s identity somewhere (e.g., as a user principal). In this example, I’m storing in the message properties so that we’ll be able to authorize the user within operations that need to further control access to certain resources. You could make this implementation more elegant by integrating with [PrincipalPermission] or by providing your own security attributes.

We could have also implemented this logic as a custom WCF channel, thereby removing the need to make calls to AuthenticateUser altogether and simplifying reuse, but building a custom channel is not an easy proposition. The WCF REST Starter Kit simplifies this by introducing a new request interception model specifically for situations like this when using the WebHttpBinding.

WCF REST Starter Kit

Up to this point, we’ve walked through all of the WCF 3.5 framework support for building RESTful services. WCF 3.5 makes building RESTful services quite easy today. Nevertheless, the WCF team wants to make building RESTful services even easier through a suite of new helper classes and Visual Studio project templates that they’ve packaged up and called the WCF REST Starter Kit.

The WCF REST Starter Kit has been commissioned as a CodePlex project, which is where you can download the code today.  Microsoft plans to continue investing in the WCF REST Starter Kit through iterative releases that continue to introduce new features and capabilities to further ease the REST implementation challenges you might face with today’s support. The WCF team will eventually take some of these features and roll them into the next version of the .NET framework when appropriate. If you’re serious about building RESTful services with WCF, keep your eyes on this CodePlex project.

Microsoft.ServiceModel.Web Extensions

Once you start building RESTful services, you’ll begin running into some common pain points that feel tedious or cumbersome, even when using a modern framework like WCF (for example, writing the various HTTP status codes and descriptions to the response message). Hence, the WCF REST Starter Kit provides a new suite of APIs designed to address some of those common pain points in an effort to make building RESTful services even easier with WCF moving forward. These new classes and extension methods are found in the new Microsoft.ServiceModel.Web assembly and namespace.

As an example, the WCF REST Starter Kit introduces a new WebProtocolException class that you can throw in your service logic to specify an HTTP status code, which feels much more natural to a typical .NET developer. In addition to this, the WCF REST Starter Kit also comes with a new WebServiceHost2 class and the corresponding WebServiceHost2Factory class, which provides a zero-config experience tailored for a RESTful service. This custom service host introduces some new extensions and behaviors including support for an automatic help page that describes your RESTful service.

This new help page is a huge step forward for RESTful service development. By default, you navigate to it at “/help”. And you can annotate your “Web” operations with [WebHelp] to provide human-readable descriptions for each operation found on your service. This help pages makes it easy for consumers to figure out the service’s URI design, retrieve resource schemas, and view examples of them. The obvious next step is to provide a REST-savvy client experience on top of this complete with code generation.

The WCF REST Starter Kit also provides a simpler model for controlling caching through the [WebCache] attribute that you can declaratively apply to your service operations. And it adds numerous extension methods to the WebOperationContext class that address some common REST-oriented tasks.

And finally, the WCF REST Starter Kit introduces a new request interception model (based on RequestInterceptor) that you can use for different types of HTTP interception tasks such as authentication. This model makes it much easier to introduce your own request interception logic without having to deal with writing a custom WCF message inspector and an associated behavior. (see Figure 19)

Figure 19: Automatic help page for RESTFul services

Project Templates for RESTful Services

There are certain types of RESTful services that require a lot of boilerplate code (for example, an Atom feed service or an AtomPub service) that you shouldn’t necessarily have to write by hand. Hence, the WCF REST Starter Kit comes with some valuable project templates that provide the necessary boiler plate code for certain types of services, which should greatly reduce the code you have to write. Once you’ve installed the WCF REST Starter Kit, you’ll see a suite of new project templates in the Visual Studio New Project dialog (see Figure 20). You simply need to choose one, enter the remaining project details, and press OK. Then you’ll end up with a skeleton REST project to start building on.

See The REST Singleton Service produces a service that exposes the full HTTP interface (GET, POST, PUT, and DELETE) around a singleton resource, and it automatically provides both XML and JSON representations for the underlying resource. The REST Collection Service does the same thing only it exposes a collection of underlying resources, as opposed to a singleton resource. This is probably where you want to start.

The HTTP Plain XML Service is different in that the generated service doesn’t support the full HTTP interface. Instead, it provides simple GET and POST operations for those who don’t care about fully conforming to the RESTful design principles we’ve discussed and would rather build a REST/RPC hybrid service by overloading GET and POST. I personally wouldn’t recommend this approach.

The last few Atom-related project templates add value when working with feeds. The Atom Feed Service template provides a sample implementation that shows how to programmatically generate and return a SyndicationFeed instance. You simply need to change the implementation to fill in your business data as appropriate. And finally, the Atom Publishing Protocol Service template provides a complete skeleton implementation for a fully compliant AtomPub service, greatly reducing the amount of code you have to write for this scenario, and allowing you to focus primarily on how to map your resources to Atom.

Figure 21 for a description of each project template. When you use one of these project templates, your remaining tasks include modifying the resource class definitions, propagating those changes throughout the implementation (you can use Visual Studio refactoring here), refining the URI design when needed, and implementing the method stubs for each HTTP operation.

Figure 20: The WCF REST Starter Kit project templates

The REST Singleton Service produces a service that exposes the full HTTP interface (GET, POST, PUT, and DELETE) around a singleton resource, and it automatically provides both XML and JSON representations for the underlying resource. The REST Collection Service does the same thing only it exposes a collection of underlying resources, as opposed to a singleton resource. This is probably where you want to start.

The HTTP Plain XML Service is different in that the generated service doesn’t support the full HTTP interface. Instead, it provides simple GET and POST operations for those who don’t care about fully conforming to the RESTful design principles we’ve discussed and would rather build a REST/RPC hybrid service by overloading GET and POST. I personally wouldn’t recommend this approach.

The last few Atom-related project templates add value when working with feeds. The Atom Feed Service template provides a sample implementation that shows how to programmatically generate and return a SyndicationFeed instance. You simply need to change the implementation to fill in your business data as appropriate. And finally, the Atom Publishing Protocol Service template provides a complete skeleton implementation for a fully compliant AtomPub service, greatly reducing the amount of code you have to write for this scenario, and allowing you to focus primarily on how to map your resources to Atom.

Figure 21: WCF REST Starter Kit project template descriptions

Project TemplateDescription

HTTP Plain XML Service

Produces a service with simple GET and POST methods that you can build on for plain-old XML (POX) services that don’t fully conform to RESTful design principles, but instead rely only on GET and POST operations.

REST Singleton Service

Produces a service that defines a sample singleton resource (SampleItem) and the full HTTP interface for interacting with the singleton (GET, POST, PUT, and DELETE) with support for both XML and JSON representations.

REST Collection Service

Similar to the REST Singleton Service only it also provides support for managing a collection of SampleItem resources.

Atom Feed Service

Produces a service that produces a sample Atom feed with dummy data.

Atom Publishing Protocol Service

Produces a full-fledged AtomPub service capable of managing collections of resources as well as media entries.

Let’s walk through a few examples using the WCF REST Starter Kit to help illustrate how it can simplify the process of building these types of RESTful services.

Using the “REST Collection Service” Project Template

Let’s start by creating a bookmark collection service similar to what we built manually earlier in this whitepaper.  First, we’ll create a new project by selecting the REST Collection Service template. Then, we’d end up with a WCF project containing a resource class named SampleItem along with a service class that implements a RESTful HTTP interface. From this point, we need to make only a few changes.

The first thing we need to change is the name of the resource class – we’ll change it from “SampleItem” to “Bookmark” and I’ll take advantage of Visual Studio refactoring to propagate the change throughout the project. Now I can fill in the Bookmark class with the fields I need for representing a bookmark resource. I’ll make this Bookmark class look like the one we’ve been using thus far:

public class Bookmark

{

    
public Uri Url { getset; }

    
public string User { getset; }

    
public string Title { getset; }

    
public string Tags { getset; }

    
public bool Public { getset; }

    
public DateTime LastModified { getset; }

}

The template also provides a class called SampleItemInfo, which contains an individual Bookmark and the URL that links to it directly. I’ll change the name to BookmarkItemInfo and use refactoring to propagate the change. I’m also going to change the name of the “Item” field to “Bookmark”. Here’s what the BookmarkItemInfo class looks like for this example:

 public class BookmarkItemInfo


{

    public Bookmark Bookmark { getset; }

    
public Uri Link { getset; }

}

 

With these changes in place, my new bookmark collection service is ready to test. Simply press F5 in Visual Studio and it will load the service into the ASP.NET Development Server, and once it does, you should be able to access the service. When the browser first comes up to test the service, you’ll see an empty <ArrayOfBookmarkItemInfo> element because the collection is currently empty.

At this point you can either use an HTTP client utility to add some bookmarks to the collection (via POST) or you can pre-populate the Bookmark items collection in the service constructor. Once you’ve populated the Bookmark collection, you’ll get some <BookmarkItemInfo> elements back when you browse to the service’s root address (e.g., “http://localhost:59256/Service.svc”), which essentially requests the entire collection of bookmark resources. See Figure 22 for some sample results.

You can then browse to an individual bookmark resource by following one of the <Link> elements returned in the <BookmarkItemInfo> element. For example, you can retrieve the Bookmark with an Id of “3” by browsing to “http://localhost:59256/Service.svc/3” in this particular example (see Figure 23).

At this point you can also use POST, PUT, and DELETE operations without any additional coding – the generated project template already contains a default implementation for each one. You can POST new <Bookmark> elements to the service’s root address and it will generate a new Bookmark resource and assign it a new Id, and return a 201 Created response with the corresponding Location header. You can also PUT <Bookmark> elements to individual bookmark URIs to perform updates. And you can send DELETE requests to individual bookmark resources to remove them from the collection.

As you can see, for this particular type of RESTful service (a collection-oriented service), the WCF REST Starter Kit made it possible to get our implementation up and running with very little coding on our part. And it provides mostly the same functionality that I built myself in our BookmarkService example. It wouldn’t be hard to build on this starting point to add user accounts and the remaining functionality.

Figure 22: Browsing to the Bookmark collection service

Figure 23: Browsing to an individual bookmark resource

Using the “Atom Feed Service” Project Template

When you need to generate a simple Atom feed service, create a new project of type “Atom Feed Service” from the WCF REST Starter Kit. It will generate a WCF service with a sample feed implementation like the one shown in Figure 24. This service will run as-is and produce a sample Atom feed. You simply need to modify the code to insert your data into the SyndicationFeed instance.

Figure 24: Default implementation for an Atom Feed Service

[ServiceBehavior(IncludeExceptionDetailInFaults = true,
    InstanceContextMode = InstanceContextMode.Single,
    ConcurrencyMode = ConcurrencyMode.Single)]
[AspNetCompatibilityRequirements(
    RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceContract]
public partial class FeedService
{
    [WebGet(UriTemplate = "?numItems={i}")]
    [OperationContract]
    public Atom10FeedFormatter GetFeed(int i)
    {
        SyndicationFeed feed;
        // TODO: Change the sample content feed creation logic here
        if (i == 0) i = 1;
        // Create the list of syndication items. These correspond to Atom entries
        List<SyndicationItem> items = new List<SyndicationItem>();
        for (int j = 1; j <= i; ++j)
        {
            items.Add(new SyndicationItem()
            {
                // Every entry must have a stable unique URI id
                Id = String.Format(
                    CultureInfo.InvariantCulture, "http://tmpuri.org/Id{0}", j),
                Title = new TextSyndicationContent(
                    String.Format("Sample item '{0}'", j)),
                // Every entry should include the last time it was updated
                LastUpdatedTime = new DateTime(
                    2008, 7, 1, 0, 0, 0, DateTimeKind.Utc),
                // The Atom spec requires an author for every entry.
                Authors =
                {
                    new SyndicationPerson()
                    {
                        Name = "Sample Author"
                    }
                },
                // The content of an Atom entry can be text, xml, a link or
                // arbitrary content. In this sample text content is used.
                Content = new TextSyndicationContent("Sample content"),
            });
        }
        // create the feed containing the syndication items.
        feed = new SyndicationFeed()
        {
            // The feed must have a unique stable URI id
            Id = "http://tmpuri.org/FeedId",
            Title = new TextSyndicationContent("Sample feed"),
            Items = items,
            Links =
            {
                // A feed should have a link to the service that produced the feed.
                GetSelfLink(),
            }
        };
        WebOperationContext.Current.OutgoingResponse.ContentType = AtomContentType;
        return feed.GetAtom10Formatter();
    }
    ...
}

Using the “Atom Publishing Protocol Service” Project Template

When you want to implement a service that conforms to the Atom Publishing Protocol, you should use the “Atom Publishing Protocol Service” project template that comes with the WCF REST Starter Kit. This template generates a complete AtomPub service that exposes a single sample collection.

You can test the service immediately by browsing to the service.svc file and the service will return an AtomPub service document describing the collections it supports (see Figure 28). As you can see from Figure 28, this service exposes a collection called “Sample Collection” that you can access by adding “collection1” to the end of the service’s root URL. When you access the collection, the service returns an Atom feed representing the sample collection (see Figure 29). The service also supports adding, updating, and deleting Atom entries through the standard AtomPub HTTP interface.

Figure 25: Browsing to the AtomPub service

Figure 26: Browsing to the sample collection exposed by the AtomPub service

When you build AtomPub services using the WCF REST Starter Kit, your job is to focus on the logical collections you want to expose. You’ll need to define a mapping between your business entity collections and AtomPub collections exposed by the service, which essentially boils down to defining a mapping between your custom business entity classes and the WCF SyndicationFeed/Item classes.

ADO.NET Data Services

When building data-oriented services (that focus primarily on CRUD operations), there’s an even easier solution than the WCF REST Starter Kit. .NET Framework 3.5 SP1 introduced a new technology called ADO.NET Data Services, which almost automates the process of exposing data entities as fully functional RESTful services using the Atom Publishing Protocol. Interestingly, ADO.NET Data Services builds on the WCF REST infrastructure we just discussed. With ADO.NET Data Services, you define the entities you want to expose and the infrastructure takes care of everything else. Let’s walk through a few examples.

Using the ADO.NET Entity Data Model

If you’re building your service on top of a database resource, the easiest way to build an ADO.NET Data Service is to first add an ADO.NET Entity Data Model (EDM) to your project. This launches a wizard that walks you through the process of connecting to a database and defining a new entity mapping. Once you’re done, the wizard generates an EDM definition within your project. The class that sits behind the EDM definition derives from ObjectContext, and as a result, it’s usable with ADO.NET Data Services.

Assume that I’ve walked through the wizard and called the new model “BookmarksEntities”. Next, you add an ADO.NET Data Service item to your site as illustrated in Figure 27.

Figure 27: Creating a new ADO.NET Data Service

Doing this adds a new WCF BookmarksEDM.svc endpoint to your site and a code-behind file containing a single class deriving from DataService<T>, which looks like this:

public class BookmarksEDM : DataService< /* TODO: your data source class name */ >

{

    
// This method is called only once to initialize service-wide policies.

    
public static void InitializeService(IDataServiceConfiguration config)

    {

        
// TODO: set rules to indicate which entity sets are visible, updatable, etc.

        
// Examples:

        
// config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead);

        
// config.SetServiceOperationAccessRule("MyServiceOperation",

        
//     ServiceOperationRights.All);

    }

}
Since we already have a data source class (the one I generated using the EDM wizard), we can simply modify this to use my BookmarksEntities class. We’ll also need to uncomment the call to SetEntitySetAccessRule to allow access to the “Bookmarks” entity. Here’s what the final class looks like:
public class BookmarksEDM : DataService<BookmarksModel.BookmarksEntities>

{

    
public static void InitializeService(IDataServiceConfiguration config)

    {

        config.SetEntitySetAccessRule(
"Bookmarks", EntitySetRights.All);

    }

}

We now have a fully functional RESTful service built around the Bookmarks entity model. If you navigate to the BookmarksEDM.svc file at this point, you should get an AtomPub service document back that looks like the one show in Figure 28, listing the collections exposed by the service.

Figure 28: The AtomPub service document returned by ADO.NET Data Services

Now, if you browse to the Bookmarks collection by simply adding “Bookmarks” to the end of the URL, you should get an Atom feed containing the list of Bookmarks found in the database (see Figure 29).

Notice that the Bookmark class properties have been serialized within the Atom <content> element. You can further filter your GET queries using the standard URI syntax defined by ADO.NET Data Services, which includes a variety of operators and functions for performing logical data comparisons. For example, the following URL retrieves the bookmarks only for the User named “skonnard":

http://localhost:55555/BookmarksEDM.svc/Bookmarks?$filter=User%20eq%20'skonnard'

The service is also capable of handling POST, PUT, and DELETE requests according to the AtomPub protocol. We now have a fully functional AtomPub service and we hardly wrote any code. The ADO.NET Data Services Framework was able to hide nearly all of the WCF/REST implementation details.

Figure 29: The Atom feed returned for the Bookmarks collection

Using an In-Memory Entity Model

You can also use ADO.NET Data Services to expose collections of in-memory objects. The process is similar to what I just described, only you’re not going to use an EDM definition this time. Instead, you’ll have to write an in-memory data source class to replace the EDM definition. Figure 30 provides a complete implementation showing how to expose an in-memory collection of Bookmark objects.

Figure 30: Implementing an ADO.NET Data Service over an in-memory collection

[DataServiceKey("Id")]
public class Bookmark
{
    public string Id { get; set; }
    public string Url { get; set; }
    public string User { get; set; }
    public string Title { get; set; }
    public string Tags { get; set; }
    public bool Public { get; set; }
    public DateTime LastModified { get; set; }
}
// this class replaces the EDM definition - it provides an in-memory data source
public class BookmarkService
{
    static List<Bookmark> bookmarks = new List<Bookmark>();
    static BookmarkService()
    {
        ... // initialize 'bookmarks' collection with a bunch of objects
    }
    public IQueryable<Bookmark> Bookmarks
    {
        get { return bookmarks.AsQueryable<Bookmark>(); }
    }
}
public class BookmarkDataService : DataService<BookmarkService>
{
    public static void InitializeService(IDataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("Bookmarks", EntitySetRights.All);
    }
}

At this point you end up with an AtomPub service capable responding to GET requests for your in-memory resources. If you want the service to support POST, PUT, and DELETE requests, you must also implement IUpdateable (which was handled for us when using the EDM approach).

ADO.NET Data Services is a great example of an automated REST framework built on the WCF REST framework. For more information, see the ADO.NET Data Services learning center on MSDN.

Consuming RESTful Services

The easiest way to consume a RESTful service is with a Web browser –just browse to the URIs supported by the service and view the results – but this obviously works only for GET operations. You’ll need to use an HTTP client utility/library to test the non-GET methods. In general, the process for programmatically consuming RESTful services is a bit different than the process for consuming SOAP services, because RESTful services typically don’t come with a WSDL definition. However, since all RESTful services implement the same uniform interface, consumers inherently know the basic mechanics for interacting with the service through a traditional HTTP library. On the Microsoft platform, you can use MSXML2.XMLHTTP, System.Net, or WCF. Let’s look at a few client-side examples.

Consuming RESTful Services with JavaScript

Figure 31 provides the complete code for a simple HTTP command-line client utility written in JavaScript. It uses the MSXML2.XMLHTTP component that you’d use from scripting running within a Web browser (Ajax) application. The nice thing about this particular utility is that you can use it from a command prompt to quickly test your non-GET operations. Simply copy and paste this text into a text file and name it httputility.js. Then you can run it from a command window to issue HTTP requests.

Consuming RESTful Services with System.Net

If you’re writing .NET code, you can take advantage of the System.Net classes to programmatically issue HTTP requests and process the responses. The following code illustrates how easy this can be by using the HttpWebRequest and HttpWebResponse classes:

static void GetPublicBookmarks()

{

    string uri = "http://localhost:55555/bookmarkservice.svc/";

    HttpWebRequest req = WebRequest.Create(uri) as HttpWebRequest;

    HttpWebResponse resp = req.GetResponse() as HttpWebResponse;

    Bookmarks bookmarks = DeserializeBookmarks(resp.GetResponseStream());

    foreach (Bookmark bm in bookmarks)

        Console.WriteLine("{0}\r\n{1}\r\n", bm.Title, bm.Url);

}

It’s not hard to imagine how you could build a REST client library around your services (that uses code like this) to make them even easier for .NET clients to consume them. It’s also not hard to imagine how you could write a generic REST client on top of these classes that can be used with all your services.

Consuming RESTful Services with WCF

WCF 3.5 provides an abstraction over HTTP for consuming RESTful services the “WCF” way. To use this technique, you need a client-side interface definition containing [WebGet] & [WebInvoke] signatures for each operation you want to consume. You can use the same UriTemplate variable techniques in the client-side method definition. Here’s an example of how to get started:

[ServiceContract]

public interface IBookmarkService

{

    [WebGet(UriTemplate = "?tag={tag}")]

    [OperationContract]

    Bookmarks GetPublicBookmarks(string tag);

    ...

}

Then you can create a WCF channel based on this interface definition using the new WebChannelFactory class that ships with WCF 3.5. This factory knows how to create channels that are aware of the [WebGet] & [WebInvoke] annotations and know how to map the method calls to the uniform HTTP interface. Here’s an example of how you can retrieve the public bookmarks tagged with “wcf”:

WebChannelFactory<IBookmarkService> cf = new WebChannelFactory<IBookmarkService>(
    new Uri("http://localhost:55555/BookmarkService.svc"));
IBookmarkService channel = cf.CreateChannel();
Bookmarks bms = channel.GetPublicBookmarks("WCF");
foreach (Bookmark bm in bms)
    Console.WriteLine("{0}\r\n{1}", bm.Title, bm.Url);

If you happened to define your server-side service contract using an interface definition, you can simply share that same interface definition with your WCF client applications. But unfortunately, there’s no way to automatically generate the client side RESTful contract from a service description.

Consuming ADO.NET Data Services

Because ADO.NET Data Services uses a standard AtomPub representation, it was also able to provide a simple service description language known as the Conceptual Schema Definition Language (CSDL), originally defined for the ADO.NET Entity Data Model. You can browse to the metadata for a particular ADO.NET Data Service by adding “$metadata” to the service’s base URL (see Figure 32).

This description contains enough information to generate client-side proxies. ADO.NET Data Services comes with a client utility called DataServiceUtil.exe for automatically generating the client-side classes. You can also perform an “Add Service Reference” command within Visual Studio to do the same thing. Once you’ve generated the client-side classes, you can write code like this to query your service:

BookmarkService bookmarkService = new BookmarkService(
    new Uri("http://localhost:55555/Bookmarks.svc/"));
// this generates the following URL:
// http://localhost:55555/Bookmarks.svc/Bookmarks()?$filter=substringof('WCF',Tags)&
// $orderby=LastModified
var bookmarks = from b in bookmarkService.Bookmarks
                where b.Tags.Contains("WCF")
                orderby b.LastModified
                select b;
foreach (Bookmark bm in bookmarks)
    Console.WriteLine("{0}\r\n{1}", bm.Title, bm.Url);

This illustrates the potential of code generation combined with a truly RESTful design. The client is interacting with a resource-centric model, yet all of the HTTP details have been abstracted away.

Figure 32: Browsing to ADO.NET Data Service metadata

 

 

posted on 2011-08-04 14:47  翔如飞飞  阅读(1071)  评论(0编辑  收藏  举报