Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

REST, WCF and Streams: Getting Rid of those Names Spaces

4.17/5 (5 votes)
28 Apr 2009CPOL5 min read 54.9K   726  
This article details out the mechanism by which RESTfull web services may be created using WCF in which the developer is in control of the XML structure

Introduction

The web is governed by some simple verbs and many models. Any browser can access a resource on the web using some simple well defined verbs. It is argued that this simplicity is the cause of its success. To bring this simplicity to the world of SOAP web services, the concept of RESTfull web services has been introduced.

The .NET Framework 3.5 introduces WCF with REST initiative. In this article, we discuss the issues faced by us when developing RESTfull web services.

Audience

The audience for the discussion is expected to know how to develop RESTfull web services using WCF. A good bunch of resources for the same can be found here.

The Problem Statement

Microsoft provides out of box mechanisms using which the posted XML is converted to the resource object using serialization. This requires an XML structure with certain name spaced to be filled in for the default serialization. Clients (of the application or the ones who cut the cheque) seldom agree to having such dependencies. They require an XML to be passed around and what we do with it is our own business.

XML
<Person xmlns="http://schemas.datacontract.org/2004/07/" 
    xmlns:i="http://www.w3.org/2001/XMLSchema-instance">

<PersonId>55</PersonId>

<PersonName>Sample Name</PersonName>

</Person>

The xmlns tags are irrelevant to most clients.

This article focuses around this problem of having an arbitrary structure and how to achieve the same.

The Solution using Streams

The raw data is transferred using Streams. WCF allows us to capture the stream for RESTfull web services while allowing the benefits of URL Template. We will demonstrate the use of Stream in an example of GET and POST along with an arbitrary XML structure.

The Code

At this point, we assume that a WCF service solution has been created and its web.config has been modified to be used as a RESTfull application. We will only discuss the code segments which are involved with the Stream.

The Resource Structure

For this discussion, we will assume that we are interested in a Person resource which has a structure:

XML
<Person> <!--The container for Person entity-->

<Id>23</Id> <!--The Id of the person -->

<Name>Gaurav Verma</Name> <!--The name of the person-->

</Person> 

This resource is accessed by the URL GET : http://localhost:Port/.../Person/{SessionId}/{PersonId}.

A new person resource is added by POST : http://localhost:Port/.../Person/{SessionId}/
with the required Person XML:

XML
<Person> <!--The container for Person entity--> 

<Name>Gaurav Verma</Name> <!--The name of the person-->

</Person>

In this system, we can start work only after getting a session Id which is obtained by

GET: http://localhost:Port/.../Session 

The concept of session has been introduced so that we can demonstrate use of stream with a variable in the URL.

The Interface

The interface will be similar to any other RESTfull application for a GET request, however for a POST request, the first element will always be a stream.

C#
[ServiceContract] 
public interface IService 
{
[OperationContract]
[WebGet(UriTemplate = "/Sessions", ResponseFormat = WebMessageFormat.Xml)]
Stream GetSession();

[OperationContract] 
[WebGet(UriTemplate = "/Person/{sessionId}/{PersonId}",
    ResponseFormat = WebMessageFormat.Xml)]
Stream GetPerson(string sessionId,string personId); 

[OperationContract]
[WebInvoke(UriTemplate = "/Person/{sessionId}", BodyStyle =
    WebMessageBodyStyle.Bare, Method = "POST")] 
Stream SavePerson(Stream input,string sessionId); 
}

This Interface requests an input stream and returns back an input stream. It should be noted that the first element is always the stream.

Let us detail out each of the interfaces:

C#
[OperationContract]
[WebInvoke(UriTemplate = "/Person/{sessionId}", BodyStyle = 
    WebMessageBodyStyle.Bare, Method = "POST")] 
Stream SavePerson(Stream input,string sessionId);

In the other two Get methods have return type as Streams. We are using streams because whatever string we create and send back in the stream, the client will get back an identical string.

C#
[OperationContract]
[WebGet(UriTemplate = "/Sessions", ResponseFormat = WebMessageFormat.Xml)]
Stream GetSession();

[OperationContract] 
[WebGet(UriTemplate = "/Person/{sessionId}/{PersonId}", 
		ResponseFormat = WebMessageFormat.Xml)]
Stream GetPerson(string sessionId,string personId);

The Method Body

The method body must be capable of converting Stream into the XML structure and back. Let's just jump ahead a little and see what is the structure of these raw streams.

In this sample, we send the Person entity as an XML in a parameter input field and we receive the raw text as parameters=%3CPerson%3E%3CName%3ESample+Name%3C%2FName%3E%3C%2FPerson%3E.

The string has been encoded for transfer. If we extract out the query string parameters from this and decode the parameter string, we get an input of the format:

XML
<Person><Name>Sample Name</Name></Person> 

A structure we want. This is the structure on which we will be working.

POST Method: Capturing Stream and converting it to Entity

The Post method receives a Stream input and a session id. The method converts the stream into a readable XML person entity and saves the result.

C#
public Stream SavePerson(Stream input, string sessionId) 
{ 
    //We may validate the session id here 
    //The input is in a stream reader 
    StreamReader streamReader = new StreamReader(input); 
    // The raw string is read 
    // The raw string is of the format 
    //parameters=%3CPerson%3E%3CName%3ESample+Name%3C%2FName%3E%3C%2FPerson%3E 
    string rawString = streamReader.ReadToEnd(); 
    streamReader.Dispose(); 
    //Convert this into a more readable format 
    NameValueCollection queryString = HttpUtility.ParseQueryString(rawString); 
    //and then extract out the parameters 
    //<Person><Name>Sample Name</Name></Person> 
    string parameters = queryString["parameters"]; 
    //The String is parsed and a Person entity is created 
    string personXML = String.Format(
        "<Result><Status>Person Created with Id {0}</Status><Input>{1}</Input></Result>",
        555, parameters); 
    //Convert the result back into a Stream 
    // A stream is not modified by the .NET Framework 
    Encoding encoding = Encoding.GetEncoding("ISO-8859-1"); 
    WebOperationContext.Current.OutgoingResponse.ContentType = "text/plain"; 
    byte[] returnBytes = encoding.GetBytes(personXML); 
    return new MemoryStream(returnBytes); 
}

GET Method: Converting Entity to XML

The job of the Get method is simple as far as input parsing is concerned, however the Get method must return back a Stream to ensure unrequired name spaces are not sent back.

C#
public Stream GetPerson(string sessionId,string personId) 
{ 
    // We may validate the Session Id and then return a Person from the data store 
    // The required XML is converted into an Entity 
    string personXml = String.Format("<Person><Id>{0}</Id><Name>{1}</Name></Person>",
        personId,"Name of "+personId); 
    Encoding encoding = Encoding.GetEncoding("ISO-8859-1"); 
    WebOperationContext.Current.OutgoingResponse.ContentType = "text/plain"; 
    //The results are sent back. 
    byte[] returnBytes = encoding.GetBytes(personXml); 
    return new MemoryStream(returnBytes); 
}

Encoding

It is to be noted that encoding has been set to text/plain to ensure the structure remains untampered.

Test Client

In various applications where REST is involved and we need to integrate with third parties, I have found it valuable that testing should be done by use of a simple web browser. This proves that even simplest of applications can access the web service. If we can access the server resources using the web browser, then any issue must be in the network access or the client code accessing the application.

This example uses a simple set of web pages to test the REST APIs.

Get Client

This is a simple Get, so all we need to do is run the solution which contains the service and browse to the URL.

http://localhost:11227/RestWebService/Service.svc/Sessions 

for getting a session id, and we get a result like:

XML
<Session>ddd621f7-b535-4f48-b6da-52f6ad7bc0d1</Session> 

or:

http://localhost:11227/RestWebService/Service.svc/
	Person/ddd621f7-b535-4f48-b6da-52f6ad7bc0d1/35

for getting a Person.

We get a result:

XML
<Person><Id>35</Id><Name>Name of 35</Name></Person> 

POST Client

The Post client is a simple HTTP form which posts to the URL http://localhost/.../Person with the required input in the text.

The HTML form for the client is:

HTML
<form 
    action='http://localhost:11227/
	RestWebService/Service.svc/Person/ddd621f7-b535-4f48-b6da-52f6ad7bc0d1'
    method="POST" runat="server" >

Parameters :<input type ="TextBox" id = "parameters" name="parameters" 
    runat="server" Height="120px" Width="1000px" ></input>

<input type="submit"/>

</form>

We enter the value in the input as:

XML
<Person><Name>Sample Name</Name></Person>

and get result:

XML
<Result><Status>Person Created with Id 555</Status></Result>

Conclusion

REST is a mechanism which has been designed for protocol independence. It will be benefited if its resource structure can be arbitrarily hand crafted. The above demonstrated mechanism allows the benefits of URL mapping and an arbitrary resource structure in the .NET environment.

The code lacks many refinements which may be desired in an enterprise application such as validation of input XML using an XSD and others. It even lacks things like saving a resource to the database. This has been done so as to ensure that we keep our focus on the code for REST and Streams and not on other supporting code.

History

  • 28th April, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)