The best thing about the latest Microsoft trends starting with MVC1 is the turn back to the HTTP roots. I came to web development through ASP.NET 1.0 WebForms which successfully shielded me from all these response codes, charsets and headers. With MVC I had to understand how the HTTP actually works (RTFM finally!). WebForms mindset has pretty much locked me out from the cool "third-party" concepts. It is much easier to understand Ajax (not in the form of the UpdatePanel) or Node.js after been exposed to ASP.NET MVC.
But enough of that and let's get to our interface.
For implementation to be HTTP spec-compliant you should supply appropriate header values and status codes to the responses returned from you MVC actions or Node.js methods and throw nice HTTP-compliant exceptions if something went wrong.
Below is representation of API interface tied to HTTP method with a URL sample:
Create a new entity | POST | /myservice/locations |
Retrieve entity (by Id) | GET | /myservice/locations/1500 |
Update an entity | PUT | /myservice/locations/1500 |
Delete an entity | DELETE | /myservice/locations/1500 |
The client-side code for both WebAPI and Node.js consumer is the same:
function Add(json) {
$.ajax({
url: API_URL,
data: json,
cache: false,
type: 'POST',
contentType: 'application/json; charset=utf-8',
statusCode: { 201: function (data) { model.Sale().Id(data.Id); } }
});
}
function Update(id, json) {
$.ajax({
url: API_URL + id,
data: json,
cache: false,
type: 'PUT',
contentType: 'application/json; charset=utf-8',
success: function () { //success; }
})
.fail( function (xhr, textStatus, err) { //failure; });
}
function Delete(id) {
$.ajax({
url: API_URL + id,
cache: false,
type: 'DELETE',
contentType: 'application/json; charset=utf-8',
statusCode: { 204: function () { //success; } }
});
}
Server-side code in WebAPI implementation:
private static readonly ISaleRepository repo=new SaleRepository();
public IEnumerable<Sale> GetAllSales() {
return repo.GetAll();
}
public Sale GetSale(int id) {
Sale item = repo.Get(id);
if (item==null) {
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
return item;
}
[HttpPost]
public HttpResponseMessage CreateSale(Sale item) {
item = repo.Add(item);
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, item);
string uri = Url.Link("DefaultApi", new {id = item.Id});
response.Headers.Location=new Uri(uri);
return response;
}
[HttpPut]
public void UpdateSale(int id, Sale item) {
item.Id = id;
if (!repo.Update(item)) {
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
}
[HttpDelete]
public HttpResponseMessage DeleteSale(int id) {
repo.Remove(id);
return new HttpResponseMessage(HttpStatusCode.NoContent);
}
Some additional considerations:
- Note that there is no error-handling logic in the Controller class to make it more readable and reduce the cyclomatic complexity. All burden of the error-handling is offloaded to the Repository class.
- By default the RC release assumes actions to accept GET or POST requests, so for our Update implementation it would throw the error "The requested resource does not support method PUT" unless the appropriate action is decorated with the good old [HttpPut] attribute (it worked differently in Beta version).
- Bear in mind that Json.NET date converter can accept a wide range of date formats but can fail the default JavaScript one, created by new Date() statement - use overridden MediaTypeFormatter for tough cases.
- Mike Stall's post is the best so far I found which in brief format describes WebAPI's intestines.
Next I will be looking into the Node.js implementation, which should be a bit more straightforward.