Dan Rigsby – Coding Up Style

Developer.Speaker.Blogger

REST Services and Metadata Endpoints in WCF

Posted by Dan Rigsby on May 29th, 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();
 
The implementation of this is pretty straight forward.  We basically just create a new StreamWriter, write the contents of our HTML to it, set content type of the outgoing response to "text/html", and return the stream:
 
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;
}
 
When a user navigates to "http://localhost:8080/ContactService/help", the following HTML page is displayed which documents how to use the REST interface:

restdoc

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> &mdash; Returns a feed of all contacts as "rss" or "atom"</li>
        </ul>
        <h2>Available Endpoints</h2>
        <ul>
            <li><b>GET /help</b> &mdash; Displays the service documentation</li>
            <li><b>GET /contacts</b> &mdash; Returns all contacts</li>
            <li><b>GET /contact/{contactId}</b> &mdash; Returns a contact by contactId</li>
            <li><b>GET /contact/name/{name}</b> &mdash; Returns all contacts with the specified name</li>
            <li><b>GET /contact/name/startswith/{startswith}</b> &mdash; Returns all contacts whose name starts with the specified string</li>
            <li><b>GET /contact/name/endsWith/{endsWith}</b> &mdash; Returns all contacts whose name ends with the specified string</li>
            <li><b>GET /contact/name/contains/{contains}</b> &mdash; Returns all contacts whose name contains the specified string</li>

            <li><b>POST /contact/{name}</b> &mdash; Adds a new contact with the specified name (ignores POST data)</li>
            <li><b>POST /contact</b> &mdash; Adds a new contact with the POST data</li>
            <li><b>DELETE /contact/{contactId}</b> &mdash; Deletes a contact by contactId</li>
            <li><b>PUT /contact/{contactId}</b> &mdash; 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

kick it on DotNetKicks.com

14 Responses to “REST Services and Metadata Endpoints in WCF”

  1. Matt Brewer Says:

    Great idea, Dan!  I have been struggling with how to do this very thing in a uniform, repeatable way.  Thanks for the kickstart.  Heck… I am even working on services that deal with contacts, so the example directly applies.
    Keep up these awesome WCF postings!

  2. Dew Drop - May 30, 2008 | Alvin Ashcraft's Morning Dew Says:

    [...] REST Services and Metadata Endpoints in WCF and Extending WCF InstanceContext to Store Custom State (Dan Rigsby) [...]

  3. chris Says:

    hi – great article. One question – you mention “This isn’t really metadata that can be consumed to generate a proxy class”.I didn’t think you could auto-generate proxies for REST services. Or at least I’ve never seen it done.is it EVER possible to autogenerate proxies for REST services?Is the best way for a client to access the service by using WebRequest object or similar?

  4. REST Services and Metadata EndPoints in WCF - David Strommer Says:

    [...] http://www.danrigsby.com/blog/index.php/2008/05/29/rest-services-and-metadata-endpoints-in-wcf/ [...]

  5. Dan Rigsby Says:

    Hi Chris,

    It is not possible to generate a proxy for REST services.  You can generate wsdl for the service though since the contract still uses ServiceContract and OperationContract attributes.  However you cannot use this metadata to generate a proxy to a REST endpoint.  You can still use it to talk to any other type of endpoint though.  You could have your service exposed via REST and wsHttp.  You could also use this metadata as a means to understand the POX returned from a REST service.  REST just returns the pure XML representation of the objects (unless you specify a different format such as JSON).

  6. chris Says:

    OK – thanks for the reply. could you use it to return an XSD which describes the format of the xml that the various webmethods (for want of a better word) return?

  7. Wöchentliche Rundablage: Silverlight 2, WPF, ASP.NET MVC, jQuery… | Code-Inside Blog Says:

    [...] REST Services and Metadata Endpoints in WCF [...]

  8. Weekly Links: Silverlight 2, WPF, ASP.NET MVC, jQuery… | Code-Inside Blog International Says:

    [...] REST Services and Metadata Endpoints in WCF [...]

  9. Dan Rigsby » REST and Max URL Size Says:

    [...] This just makes things confusing and less discoverable.  Especially since REST has no way to publish metadata about the operations.   You could follow standards such as [...]

  10. Iwan Says:

    Hi Dan,
    i tried to download your application and test it. It works fine for GET. But when I tried to test the POST/contact, by calling this url = “http://localhost:8080/ContactService/contact” and POST xml data below

    10ABC555-0001

    … this POST request never hit the “expected” endpoint. Could you give me enlightenment? Is there any special thing with XML format to adhere?

  11. Iwan Says:

    re-post XML data for comment no.10

    “<Contact> <ContactId>10</ContactId> <Name>ABC S</Name> <PhoneNumber>555-0001</PhoneNumber></Contact>”

  12. Richard Ponton Says:

    Hi Dan. This post was recently of great use to me. I used it for something slightly different. Being a strong advocate of the “HTTP GET should never be destructive” philosophy, I like the fact that WCF will return “method not allowed” if you try and access a WebInvoke with GET. However, some of my colleagues (especially those implementing REST services in perl) are in the habit if testing their services by typing URLs in their web browser and claim that requiring POST is too inconvenient. To get the best of both worlds, I say you can have the GET return a form that does the POST. Here’s how to do it with WCF using your technique.

    The service contract:

    [OperationContract]
    [WebGet(UriTemplate = "WipeOut/user={userId}")]
    System.IO.Stream StartWipeout(string userId);

    // both the Get and the Invoke share the same UriTemplate
    [OperationContract]
    [WebInvoke(UriTemplate = "WipeOut/user={userId}")]
    XElement DoWipeout(string addressBookId);

    The implementation:

    public Stream StartWipeout(string userId)
    {
    MemoryStream ms = new MemoryStream();
    StreamWriter sw = new StreamWriter(ms, Encoding.UTF8);
    sw.Write("This will clear all data for " + HttpUtility.HtmlEncode(userId) + "");
    sw.Write("");
    if (null != WebOperationContext.Current)
    WebOperationContext.Current.OutgoingResponse.ContentType = "text/html";
    sw.Flush();

    ms.Position = 0;
    return ms;
    }
    public XElement DoWipeout(string userId)
    {
    // actually do the WipeOut
    return new XElement("WipedOut", userId);
    }

  13. Richard Ponton Says:

    Gah. Forgot to escape my < and > in my example. Sorry. I wish there was a preview post button.

    The service contract:

    [OperationContract]
    [WebGet(UriTemplate = "{addressBookId}?action=WipeOut")]
    System.IO.Stream StartWipeout(string addressBookId);

    // both the Get and the Invoke share the same UriTemplate
    [OperationContract]
    [WebInvoke(UriTemplate = "{addressBookId}?action=WipeOut")]
    XElement DoWipeout(string addressBookId);

    The implementation:

    public Stream StartWipeout(string addressBookId)
    {
    MemoryStream ms = new MemoryStream();
    StreamWriter sw = new StreamWriter(ms, Encoding.UTF8);
    sw.Write("<h1>This will clear PDS and AddressBook data for " + HttpUtility.HtmlEncode(addressBookId) + "</h1>");
    sw.Write("<form method='POST'><input type='submit' value='Do wipeout' /></form>");
    if (null != WebOperationContext.Current)
    WebOperationContext.Current.OutgoingResponse.ContentType = "text/html";
    sw.Flush();

    ms.Position = 0;
    return ms;
    }
    public XElement DoWipeout(string addressBookId)
    {
    WipeoutAddressBook(addressBookId);
    WipeoutPdsPrefs(addressBookId);
    return new XElement("WipedOut", addressBookId);
    }

  14. Ivory tower protocol wars - Greg Beech's Tech Blog - Greg Beech's Website Says:

    [...] compliant XML reader/writer, and you still have to construct and interpret the payloads. You lose the ability to self-document and thus the ability to automatically create proxies which mean you can't simply generate and [...]

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>