So far, in this series of posts, we have got to the stage where we can retrieve different representations of a resource and we can delete a resource using either the HTTP verb DELETE or an overloaded POST. In this entry we will allow the client to PUT a resource onto the server.
PUT versus POST
PUT is part of the standard interface we defined in Part 7. If a client uses the PUT they are either creating or modifying a resource. They will pass the new representation of the resource in the body of the request. PUT is similar to POST, however the difference is crucial. With a PUT, the URI contains the resource’s existing or proposed location. With a POST the URI does not indicate the resource’s location. For example you might POST the details of a new product to:
but you would PUT the details to:
With the first URI the client is not specifying a Product Id, therefore the server must create an Id and inform the client of the new URI within its response. The client using the second URI has decided which Product Id it wishes to work with. If the server already has a product with the Id supplied, it will update that product. If the product doesn’t exist, the server will create it.
Why might you choose either POST or PUT over the other HTTP verb? Well consider what happens if the client makes a request and never hears back from the server. The client does not know if the request:
- Failed to reach the server
- Reached the server then failed within the server
- Reached the server, was actioned correctly, but the response failed to reach the client
If a POST request fails, the client cannot ask the server whether it had created the new Product or not, because the client doesn’t know what Id was assigned to the new product. If it resends the request it risks a second copy of the product being created on the server.
With PUT, the client does know the Id, so could ask the server if the Product exists. However, the client doesn’t actually need to care if the product exists or not, if there is a failure, it could just send the original PUT request again. If the original message failed on the way to the server or reached the server then failed, the new PUT request will create a new product. If the product was created the first time round, but the response failed to return, the new PUT request will just overwrite the original version of the Product with identical data. Therefore, regardless of which of the above failures occurs, the client can resend the PUT request knowing that it will not cause any harm.
PUT is considered to be an Idempotent method. This means a client can call it again and again safe in the knowledge that after the first successful call, all successive calls will have no effect.
Creating the Product
The first step in providing the functionality to support product creation is to add a method on the ProductController to handle the Item action when the PUT HTTP verb is received. The second step is to deserialise the incoming representation into a Product object. I added a function to the BaseController to handle the deserialisation:
public T DeserializeIncomingRepresentation<T>(string name, out ActionResult actionResult) where T : class
T deserializedRepresentation = null;
actionResult = null;
// Read the representation from the request
StreamReader reader = new StreamReader(Request.InputStream);
string representation = reader.ReadToEnd();
// Get the format from the querystring
string representationFormat = HttpContext.Request.QueryString["format"] ?? string.Empty;
// Decide which method to use
deserializedRepresentation = DeserializeXmlRepresentation<T>(representation, name + "Xml", out actionResult);
deserializedRepresentation = DeserializeJsonRepresentation<T>(representation, out actionResult);
throw new NotImplementedException("Incoming XHTML is not yet supported!");
DeserializeIncomingRepresentation retrieves the body of the incoming request, checks the expected format of the representation then uses the appropriate deserialiser to turn the representation into an object. JSON representations are supported out of the box. XHTML representations I will cover in my next post.
For XML we need to to transform the incoming representation into the internal schema, then use the standard XmlSerializer to deserialise the incoming data. I decided to create an XsltSerializer into which I put the serialisation code already used when serving resources and added a Deserializer method to work the other way:
public T Deserialize<T>(string serializedObject, string transformFileName, ControllerContext controllerContext) where T : class
XslCompiledTransform xslCompiledTransform = FindTransform(controllerContext, transformFileName);
string representationAsInternalXml = Xslt.Apply(serializedObject, xslCompiledTransform);
Deserialize finds the transform to execute, applies it, then deserialises the object.
Once the Product object has been created, the ProductController checks that the Id embedded in the representation matches the URI used to make the request. If a category was passed in the URI, this is also checked against the representation.
The nice thing about getting to this stage of the implementation is that things are starting to come together with less effort. The majority of the coding for this post was refactoring code into the XsltSerializer. For free we get support for clients that cannot send PUT requests, instead, they can use the _method querystring parameter introduced in my last post. Hopefully it will be a simple task to build on the PUT functionality to add support for POST.
What About XHTML?
In the DeserializeIncomingRepresentation code above you will notice that incoming XHTML (i.e. the default representation) will cause a NotImplementedException to be thrown. Way back in my second post in this series I justified why XHTML was a valid representation and even wrote that in their book, RESTful Web Services, Leonard Richardson and Sam Ruby say it is their preferred representation. After all, XHTML is just XML so should be simple to parse and transform with a standard XML library. Well, as I discovered, that is all well and good in theory!
In practice it is not too simple. The XSLT required to convert from the incoming XHTML to the internal XML is not too difficult… but loading the document into .Net’s parser is another story! The first issue I hit was the DTD specified at the start of the representation. In the .Net world, having a DTD causes the XML parser to resolve the DTD, which in turn means it attempts to download the DTD. This fails with the XmlException:
An error has occurred while opening external DTD 'http://www.w3.org/TR/xhtml1/DTD/xhtml1.dtd': The remote server returned an error: (300) Ambiguous Redirect.
I could delete the DTD reference from the incoming representation, however I then hit another snag. The DTD defines some additional entities (e.g. © which represents the copyright symbol, which is included in my outgoing representation, so could be included in an incoming representation). As these entities are not part of the plain XML standard and are defined within the DTD, I then get another XmlException:
Reference to undeclared entity 'copy'. Line 58, position 23.
There are ways around this… I just need to pick the one I think most suitable. So my next post will cover handling incoming XHTML and then I’ll return to supporting POST to create a product.
In the meantime, here is the C# code for the web service so far: RESTfulMVCWebService08.zip (47.88 kb)