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.
<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 Stream
s. 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:
<Person>
<Id>23</Id>
<Name>Gaurav Verma</Name>
</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:
<Person>
<Name>Gaurav Verma</Name>
</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
.
[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:
[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 Stream
s. We are using stream
s because whatever string
we create and send back in the stream
, the client will get back an identical string
.
[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 stream
s.
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:
<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.
public Stream SavePerson(Stream input, string sessionId)
{
StreamReader streamReader = new StreamReader(input);
string rawString = streamReader.ReadToEnd();
streamReader.Dispose();
NameValueCollection queryString = HttpUtility.ParseQueryString(rawString);
string parameters = queryString["parameters"];
string personXML = String.Format(
"<Result><Status>Person Created with Id {0}</Status><Input>{1}</Input></Result>",
555, parameters);
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.
public Stream GetPerson(string sessionId,string personId)
{
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";
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:
<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:
<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:
<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:
<Person><Name>Sample Name</Name></Person>
and get result:
<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 Stream
s and not on other supporting code.
History
- 28th April, 2009: Initial post