Using GET, POST, MERGE and DELETE for CRUD operations
One of the sad things about REST is that of the various websites cited as being RESTful, most of them don't actually do it properly. In many cases, these sites simply expose
an update method as a GET URL (updatearticle?articleid=29&title='blah'). Further, not many web servers have POST or DELETE enabled by default! Lucky for us, IIS isn't that
crippled and we can perform these operations straight out of the box. Also, worthy of note, is that if you've mapped the INSERT, DELETE and UPDATE operation for an entity to stored procedures, then performing updates via HTTP verbs will also invoke those stored procedures. This will keep your DBAs happy.
To do this, though, we'll need to create a 'client' application that makes the HTTP verb calls. For this tutorial, we will create an ASP.NET web application as a client,
although you can create a windows form app and follow along, the code will stay pretty much the same.
Using HTTP GET to read data
Right click on your solution, and add a new project of type ASP.NET Web Application, you can call it ADODSClient. Once Default.aspx is ready, add a huge textbox to it and two buttons.
<asp:TextBox ID="textBoxOutput" runat="server" TextMode="MultiLine" Width="100%" Height="500"></asp:TextBox>
<br />
<br />
<asp:Button ID="buttonGet" runat="server" OnClick="ButtonGet_Click" Text="Get (ATOM)" />
<asp:Button ID="buttonGetJson" runat="server" OnClick="ButtonGetJson_Click" Text="Get (JSON)" />
I mentioned earlier that ADO.NET Data Services can expose data as an ATOM feed or as JSON. By default, it does ATOM. To get JSON, you need to set the HTTP Accept header to
application/json and to get ATOM, either leave it blank or you can explicitly specify application/atom+xml. The web service will return data in the
format specified in the Accept header and will send a bad status code if you specify a format that isn't supported yet.
We're going to get Article data in both formats, which is why there are two buttons; the textbox is to view the resulting data. To actually get the data out, you'll need to use an HttpWebRequest. Place
this code in the codebehind file for Default.aspx
protected void ButtonGet_Click(object sender, EventArgs e)
{
HttpWebRequest wr = (HttpWebRequest)HttpWebRequest.Create("http://localhost:49958/PublishingDataService.svc/Article");
if ((wr.GetResponse().ContentLength > 0))
{
System.IO.StreamReader sr = new System.IO.StreamReader(wr.GetResponse().GetResponseStream());
textBoxOutput.Text = sr.ReadToEnd();
}
}
Build, and run the web application. You will also need the data service project running so that HttpWebRequest can access it and request data. In the code
above, the .Method property of HttpWebRequest hasn't been specified - it is GET by default. The Accept header has not been specified either - the web server will
default to ATOM. When you click the ATOM button, the textbox gets populated with the XML returned. In a real world application, you would either load the string into an
XMLDocument and look for the nodes you want, or you would request the URL to the property or entity that you want directly.
Now let's have a look at what JSON looks like. Handle the other button's click event; the code is exactly the same as above, except for the Accept property now being specified.
protected void ButtonGetJson_Click(object sender, EventArgs e)
{
HttpWebRequest wr = (HttpWebRequest)HttpWebRequest.Create("http://localhost:49958/PublishingDataService.svc/Article");
wr.Accept = "application/json";
if ((wr.GetResponse().ContentLength > 0))
{
System.IO.StreamReader sr = new System.IO.StreamReader(wr.GetResponse().GetResponseStream());
textBoxOutput.Text = sr.ReadToEnd();
}
}
If you build and run it now, the second button produces a JSON output in the textbox.
Using HTTP POST to INSERT data
When it comes to modifying or inserting data, the actual body of the request becomes important. When doing a POST, the body must be in either a JSON or
ATOM format and must represent a node as you'd see in the GET request. To show you with an example, browse to and view source of
http://localhost:49958/PublishingDataService.svc/Author
The <m:properties> node contains all the data specific to the Author being displayed. The actual data is in nodes prefixed with a 'd'
and the whole thing is contained inside a <content> node inside an <entry> node. This is what we will need to send in
the POST body.
Add a new page to the web application, call it AddAuthor.aspx. Give it a textbox for FirstName, a textbox for LastName and another large textbox to write
the result out to. Also, add two buttons to the page - we will perform a POST in both the ATOM and JSON formats.
First Name: <asp:TextBox ID="textBoxFirstName" runat="server"></asp:TextBox>
<br />
Last Name: <asp:TextBox ID="textBoxLastName" runat="server"></asp:TextBox>
<br />
<asp:Button ID="buttonCreateAtom" runat="server" OnClick="ButtonCreateAtom_Click" Text="Create (ATOM)" />
<asp:Button ID="buttonCreateJson" runat="server" OnClick="ButtonCreateJson_Click" Text="Create (JSON)" />
<br /><br />
<asp:TextBox ID="textBoxResult" runat="server" TextMode = "MultiLine" Width="100%" Height="500" />
In the click event for the ATOM button, start by creating the payload by concatenating values from the FirstName and LastName textboxes.
string payload = String.Concat("<entry xmlns:d=\"http://schemas.microsoft.com/ado/2007/08/dataservices\"",
"xmlns:m=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\"",
"xmlns=\"http://www.w3.org/2005/Atom\">",
"<content type=\"application/xml\">",
"<m:properties>",
"<d:FirstName>",textBoxFirstName.Text, "</d:FirstName> ",
"<d:LastName>",textBoxLastName.Text,"</d:LastName>",
"</m:properties>",
"</content>",
"</entry>");
The entry node also includes the namespaces, but the properties node does not contain a <d:AuthorID> node, since we are performing an INSERT. The new ID will be part of the result. Next, an HttpWebRequest, but set the .Method property to POST. Further, the Accept header needs to be set as does the Content-Type so that we can specify the format we are submitting in and expecting in the result.
HttpWebRequest wr = (HttpWebRequest)HttpWebRequest.Create("http://localhost:49958/PublishingDataService.svc/Author");
wr.Method = "POST";
wr.Accept = "application/atom+xml";
wr.ContentType = "application/atom+xml";
Because this is a POST, the payload data needs to be written to the request stream as binary, so a UTF8Encoding object is used to get the binary data out. Write it out, then get a response and write results to the textbox.
UTF8Encoding encoding = new UTF8Encoding();
wr.ContentLength = encoding.GetByteCount(payload);
using (Stream requestStream = wr.GetRequestStream())
{
requestStream.Write(encoding.GetBytes(payload), 0, encoding.GetByteCount(payload));
}
HttpWebResponse response = wr.GetResponse() as HttpWebResponse;
using (Stream responseStream = response.GetResponseStream())
{
using (StreamReader reader = new StreamReader(responseStream))
{
textBoxResult.Text = reader.ReadToEnd();
}
}
Build and load the page, fill in some values for the new author's first and last name, and click the Create (ATOM) button. The result textbox should fill up with XML in ATOM format, of which one of the nodes is <d:AuthorID>, containing the AuthorID of our new author.
To do this with the JSON format, handle the other button's click event, and use this code:
string payload = String.Concat( "{__metadata:{\"Uri\":\"/Author/\", ",
"\"Type\":\"PublishingModel.Author\"}, " ,
" \"FirstName\":\"",textBoxFirstName.Text,
"\", \"LastName\": \"", textBoxLastName.Text ,"\"",
" })");
HttpWebRequest wr = (HttpWebRequest)HttpWebRequest.Create("http://localhost:49958/PublishingDataService.svc/Author");
wr.Method = "POST";
UTF8Encoding encoding = new UTF8Encoding();
wr.ContentLength = encoding.GetByteCount(payload);
wr.Accept = "application/json";
wr.ContentType = "application/json";
using (Stream requestStream = wr.GetRequestStream())
{
requestStream.Write(encoding.GetBytes(payload), 0, encoding.GetByteCount(payload));
}
HttpWebResponse response = wr.GetResponse() as HttpWebResponse;
using (Stream responseStream = response.GetResponseStream())
{
using (StreamReader reader = new StreamReader(responseStream))
{
textBoxResult.Text = reader.ReadToEnd();
}
}
Again, load the page and click the JSON button - the output is in JSON and contains the AuthorID of the newly created author. The JSON format is not
exactly readable and working with it is actually quite painful. You will spend most of your time figuring out where to put the quotes and braces and still
get it wrong, but it has been included here as an example to demonstrate how it can be done. You would typically use the JSON format if you're working
extensively with third party Javascript libraries or dealing heavily with AJAX in your application. For the rest of this tutorial, however, I will only use
ATOM. If you wish to use JSON, then that's left as an exercise to you.
Using HTTP MERGE to UPDATE data
Once you've got your head around POST, MERGE is easy enough. The same principles apply - only send what needs to be sent. In the case of POST, it was
everything except for the ID field. In the case of MERGE, you need to send only the fields that need updating. The ID field will not change, so that will
not be sent. Instead, like before, you are performing the operation against a specific entity, which means that the URL requested will represent the Article
that you will update in this example, such as /Article(23).
To make things a bit more interesting here, let's also add some requirements to the page. The page should first let the user choose from a set of articles. Next, let the user load the article, modify it and then click Update. Don't groan, you're doing this to combine bits of the knowledge gained so far in this tutorial.
Add a new page to the web project, call it UpdateArticle.aspx. It needs a DropDownList for the articles, a load button, two textboxes (title, body) and an update button.
<asp:DropDownList ID="ddlTitles" runat="server">
</asp:DropDownList><asp:Button ID="buttonLoadArticle" runat="server" OnClick="ButtonLoadArticle_Click" Text="Load" />
<br />
<br />
Title: <asp:TextBox ID="textBoxTitle" runat="server" Width="500"></asp:TextBox><br />
Body: <asp:TextBox ID="textBoxBody" runat="server" TextMode="MultiLine" Width="100%" height="500"></asp:TextBox><br />
<br /><br />
<asp:Button ID="buttonUpdate" runat="server" Text="Update" OnClick="ButtonUpdate_Click" />
First step - populate the DropDownList with article titles. This is a simple GET request, followed by some XML parsing
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
//1. Load the DDL with values.
//Get against the Article entities
string articleXml = String.Empty;
HttpWebRequest wr =
(HttpWebRequest) HttpWebRequest.Create("http://localhost:49958/PublishingDataService.svc/Article");
if ((wr.GetResponse().ContentLength > 0))
{
System.IO.StreamReader sr = new System.IO.StreamReader(wr.GetResponse().GetResponseStream());
articleXml = sr.ReadToEnd();
}
XmlDocument doc = new XmlDocument();
doc.InnerXml = articleXml;
//To handle the XML namespaces
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("base", "http://localhost:49958/PublishingDataService.svc/");
nsmgr.AddNamespace("d", "http://schemas.microsoft.com/ado/2007/08/dataservices");
nsmgr.AddNamespace("m", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata");
nsmgr.AddNamespace("def", "http://www.w3.org/2005/Atom");
foreach (XmlNode idNode in doc.SelectNodes("/def:feed/def:entry", nsmgr))
{
string id = idNode.SelectSingleNode("./def:content/m:properties/d:ArticleID", nsmgr).InnerText;
string title = idNode.SelectSingleNode("./def:content/m:properties/d:Title", nsmgr).InnerText;
ddlTitles.Items.Add(new ListItem() {Text = title, Value = id});
}
}
}
What's not nice about this is that all of the Article data is requested. All we really want here is the ID and the Title of the Articles, but we're
getting the Body nodes as well. There isn't an easy way to only request specific nodes in this version of ADO.NET Data Services, so we'll have to live with
this for now.
Load and run the page, and the DropDownList should now be populated with the article titles. Moving on to the load button next, it's the same process as
before - request an article specific ID, parse the XML that comes back.
string articleId = ddlTitles.SelectedValue;
string title = String.Empty;
string body = String.Empty;
HttpWebRequest wrBody = (HttpWebRequest)HttpWebRequest.Create
("http://localhost:49958/PublishingDataService.svc/Article(" + articleId + ")/Title/$value");
if ((wrBody.GetResponse().ContentLength > 0))
{
System.IO.StreamReader sr = new System.IO.StreamReader(wrBody.GetResponse().GetResponseStream());
title = sr.ReadToEnd();
}
HttpWebRequest wrBody = (HttpWebRequest)HttpWebRequest.Create
("http://localhost:49958/PublishingDataService.svc/Article(" + articleId + ")/Title/$value");
wrBody.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore);
if ((wrBody.GetResponse().ContentLength > 0))
{
System.IO.StreamReader sr = new System.IO.StreamReader(wrBody.GetResponse().GetResponseStream());
title = sr.ReadToEnd();
}
textBoxTitle.Text = title;
textBoxBody.Text = body;
Instead of getting all the data back this time, we made two HttpWebRequests to get each value out individually using $value. Again, run the
page, choose a title, and press load. The textboxes should get populated with the article title and body.
To update, the process is just like POST, only that the .Method property is different, as are the payload contents. Oh, and we're going to be lazy and write the output to the body textbox.
protected void ButtonUpdate_Click(object sender, EventArgs e)
{
string payload = String.Concat("<entry xmlns:d=\"http:/<span>/</span>schemas.microsoft.com/ado/2007/08/dataservices\"",
"xmlns:m=\"http:/<span>/</span>schemas.microsoft.com/ado/2007/08/dataservices/metadata\"",
"xmlns=\"http:/<span>/</span>www.w3.org/2005/Atom\">",
"<content type=\"application/xml\">",
"<m:properties>",
"<d:Title>", textBoxTitle.Text, "</d:Title> ",
"<d:Body>", textBoxBody.Text, "</d:Body>",
"</m:properties>",
"</content>",
"</entry>");
HttpWebRequest wr = (HttpWebRequest)HttpWebRequest.Create("http://localhost:49958/PublishingDataService.svc/Article(" + ddlTitles.SelectedValue + ")");
wr.Method = "MERGE";
UTF8Encoding encoding = new UTF8Encoding();
wr.ContentLength = encoding.GetByteCount(payload);
wr.Accept = "application/atom+xml";
wr.ContentType = "application/atom+xml";
using (Stream requestStream = wr.GetRequestStream())
{
requestStream.Write(encoding.GetBytes(payload), 0, encoding.GetByteCount(payload));
}
HttpWebResponse response = wr.GetResponse() as HttpWebResponse;
using (Stream responseStream = response.GetResponseStream())
{
using (StreamReader reader = new StreamReader(responseStream))
{
textBoxBody.Text = response.StatusCode + "," + response.StatusDescription;
}
}
}
That's it, run the code and try updating the article. You'll see the textbox populated with "NoContent" - this indicates that the request was successful.
Using HTTP DELETE to DELETE data
DELETE is a simple operation. You don't need to send a payload across, just perform an HTTP DELETE request against the entity's URL. Because we're now being lazy, add a button next to the DropDownList on UpdateArticle.aspx.
<asp:DropDownList ID="ddlTitles" runat="server"></asp:DropDownList>
<asp:Button ID="buttonLoadArticle" runat="server" OnClick="ButtonLoadArticle_Click" Text="Load" />
<asp:Button ID="buttonDelete" runat="server" OnClick="ButtonDelete_Click" Text="DELETE!" />
...
Handle its click event and again, write the output to the textbox.
HttpWebRequest wr = (HttpWebRequest)HttpWebRequest.Create
("http://localhost:49958/PublishingDataService.svc/Article(" + ddlTitles.SelectedValue + ")");
wr.Method = "DELETE";
HttpWebResponse response = (HttpWebResponse)wr.GetResponse();
textBoxBody.Text = response.StatusCode.ToString();
And there you go - you've implemented CRUD operations using HTTP verbs and have officially earned bragging rights. Next, we'll talk about Linq and how it can make working with ADO.NET Data Services much, much easier.
Comments
There are no comments yet. Be the first to comment!