REST Services and Metadata Endpoints in WCF
Posted by Dan Rigsby on 29th May 2008
As mentioned in a previous article, there is no defined a way to get metadata about REST based services using webHttpBinding. Since REST doesn’t use SOAP, there is no WSDL that defines the service and no built in way to generate metadata about it. REST operations return back POX or JSON or something else.
You can still create a mexHttpBinding endpoint which can be used to represent the structure of the data that the REST service understands. You just cant really use this metadata to build a proxy client based on the REST interface. You don’t need any addition endpoints to expose the WSDL, having just a webHttpBinding endpoint is enough. You just need to enable httpGet on the serviceMetadata (see here for more detailed information on this):
<serviceBehaviors> <behavior name="contactServiceBehavior"> <serviceMetadata httpGetEnabled="true"/> </behavior> </serviceBehaviors>
One common tactic to give the REST service some kind of documentation, is to have a "Documentation" or "Help" method on your service that returns a an HTML page as a Stream which describes how to work with the REST service (as seen in pictureservices). This isn’t really metadata that can be consumed to generate a proxy class, but it is informative to other developers and consumers of your REST service.
Example
Lets look at an example of the "Documentation" method on a REST service. This example is based on a simple contact service that holds a collection of contacts. Some of the methods are AddContact, GetContact, UpdateContact, etc. The complete interface for the service is:
using System.Collections.Generic;
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Syndication;
using System.ServiceModel.Web;
namespace Rigsby.RestDocumentation
{
[ServiceContract]
public interface IContactService
{
[OperationContract]
[WebGet(UriTemplate = "help")]
Stream GetDocumentation();
[OperationContract]
[WebGet(UriTemplate = "contacts")]
List<Contact> GetAllContacts();
[OperationContract]
[WebGet(UriTemplate = "contacts/feed?format={format}",
BodyStyle = WebMessageBodyStyle.Bare)]
[ServiceKnownType(typeof(Atom10FeedFormatter))]
[ServiceKnownType(typeof(Rss20FeedFormatter))]
SyndicationFeedFormatter GetContactsFeed(string format);
[OperationContract]
[WebGet(UriTemplate = "contact/{contactId}")]
Contact GetContact(string contactId);
[OperationContract]
[WebGet(UriTemplate = "contact/name/{name}")]
List<Contact> GetContactByName(string name);
[OperationContract]
[WebGet(UriTemplate="contact/name/startswith/{startsWith}")]
List<Contact> GetContactsWhereNameStartsWith(string startsWith);
[OperationContract]
[WebGet(UriTemplate = "contact/name/endsWith/{endsWith}")]
List<Contact> GetContactsWhereNameEndsWith(string endsWith);
[OperationContract]
[WebGet(UriTemplate = "contact/name/contains/{contains}")]
List<Contact> GetContactsWhereNameContains(string contains);
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "contact/{name}")]
void AddContact(string name);
[OperationContract(Name = "AddFullContact")]
[WebInvoke(Method = "POST", UriTemplate = "contact")]
void AddContact(Contact contact);
[OperationContract]
[WebInvoke(Method="DELETE", UriTemplate = "contact/{contactId}")]
int DeleteContact(string contactId);
[OperationContract]
[WebInvoke(Method = "PUT", UriTemplate = "contact/{contactId}")]
void UpdateContact(string contactId, Contact contact);
}
}
The operation to look at is "GetDocumentation". This method returns a stream which will be the HTML of the documentation for the service. The UriTemplate we are using is just "help", so the full path would just be something like "http://localhost:8080/ContactService/help".
[OperationContract]
[WebGet(UriTemplate = "help")]
Stream GetDocumentation();
public Stream GetDocumentation() { MemoryStream stream = new MemoryStream(); StreamWriter writer = new StreamWriter(stream, Encoding.UTF8); writer.Write(Properties.Resources.Documentation); writer.Flush(); stream.Position = 0; WebOperationContext.Current.OutgoingResponse.ContentType = "text/html"; return stream; }

The HTML for this was manually hacked together, but you could write some process that looks at some custom attributes on the service to build this help file:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US"> <head> <title>Contact Service - Documentation</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <style type="text/css"> body {margin: 0px;padding: 0px;font-family: Calibri, Tahoma, Arial;} .banner {padding: 5px 5px 5px 15px;background-color: Gray;color: White;} .content {margin: 15px;} </style> </head> <body> <div class="banner"> <h1>Contact Service</h1> </div> <div class="content"> <h2>Available Feeds</h2> <ul> <li><b>GET /contacts/feed?format={format}</b> — Returns a feed of all contacts as "rss" or "atom"</li> </ul> <h2>Available Endpoints</h2> <ul> <li><b>GET /help</b> — Displays the service documentation</li> <li><b>GET /contacts</b> — Returns all contacts</li> <li><b>GET /contact/{contactId}</b> — Returns a contact by contactId</li> <li><b>GET /contact/name/{name}</b> — Returns all contacts with the specified name</li> <li><b>GET /contact/name/startswith/{startswith}</b> — Returns all contacts whose name starts with the specified string</li> <li><b>GET /contact/name/endsWith/{endsWith}</b> — Returns all contacts whose name ends with the specified string</li> <li><b>GET /contact/name/contains/{contains}</b> — Returns all contacts whose name contains the specified string</li> <li><b>POST /contact/{name}</b> — Adds a new contact with the specified name (ignores POST data)</li> <li><b>POST /contact</b> — Adds a new contact with the POST data</li> <li><b>DELETE /contact/{contactId}</b> — Deletes a contact by contactId</li> <li><b>PUT /contact/{contactId}</b> — Updates a contact by contactId</li> </ul> </div> </body> </html>
You can download a complete sample of the code discussed in this article here: http://www.danrigsby.com/Files/Rigsby.RestDocumentation.zip
Posted in REST, Wcf | 9 Comments »










The CSS format makes sense. What time is represented by the clock to the right? With no numbers visible, your eyes will probably start at the top (in the 12 position) and move around in clockwise motion to interpret each number. Our minds have been trained to look at the top and work around clockwise. There are exceptions to this, but it is a pattern that makes sense, and one that the CSS guys though was important enough to use in the order of sides.