With REST there is not a lot required on the client as far as sending requests goes. As long as you can send HTTP GET requests you are good to go and there are very few programming stacks that don’t allow for some form of doing that. Of course just doing an HTTP GET is going to give you some data back and you still have to understand that data but that is an application specific issue.
In order to make life even easier the ASP.NET Web API adds some client support in the form of the HttpClient class. But first lets see what happens if we just hit an ASP.NET Web API endpoint with a simple client.
I am using Entity Framework Code First here and the model and context look like this:
1: public class Product
2: {
3: public int ProductID { get; set; }
4: public string ProductName { get; set; }
5: public decimal UnitPrice { get; set; }
6: }
7:
8: public class NorthwindContext : DbContext
9: {
10: public DbSet<Product> Products { get; set; }
11: }
1: public class ProductsController : ApiController
2: {
3: private NorthwindContext _db = new NorthwindContext();
4:
5: // GET /api/<controller>
6: public IEnumerable<Product> Get()
7: {
8: return _db.Products.ToList();
9: }
10:
11: // GET /api/<controller>/5
12: public Product Get(int id)
13: {
14: return _db.Products.Single(p => p.ProductID == id);
15: }
16:
17: protected override void Dispose(bool disposing)
18: {
19: if (disposing && _db != null)
20: {
21: _db.Dispose();
22: _db = null;
23: }
24: base.Dispose(disposing);
25: }
26: }
1: static void Main(string[] args)
2: {
3: var baseUrl = @"http://localhost.:6876/api/products";
4: var xml = XElement.Load(baseUrl);
5:
6: foreach (var product in xml.Descendants("Product"))
7: {
8: Console.WriteLine("Product '{0}' costs {1:C}", (string)product.Element("ProductName"), (decimal)product.Element("UnitPrice"));
9: }
10: }
Unfortunately it doesn’t and we receive an XmlException with the message “Data at the root level is invalid”. The reason is that the XElement sends the request to the server without specifying an accept header. And where in the previous WCF Web API this would have worked because XML was the default type if nothing was specified this has changed. With the ASP.NET Web API the default is JSON. So this request returns a JSON data stream the XElement can’t handle. What we need to do is specify we want XML using the HTTP Accept header. There are different ways we can do this but here I am going to the the client side support in the form of the HttpClient.
Using the HttpClient
First we need to add a NuGet package named System.Net.HTTP that contains the required classes
With that in place we can start using the HttpClient instead. The code is a bit longer but that is mainly because we still need to set the HTTP Accept header to return XML instead of the default JSON
1: static void Main(string[] args)
2: {
3: var baseUrl = @"http://localhost.:6876/api/products";
4: var client = new HttpClient();
5: client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
6: var stream = client.GetStreamAsync(baseUrl).Result;
7: var xml = XElement.Load(stream);
8:
9: foreach (var product in xml.Descendants("Product"))
10: {
11: Console.WriteLine("Product '{0}' costs {1:C}", (string)product.Element("ProductName"), (decimal)product.Element("UnitPrice"));
12: }
13: }
And this works just fine
Loading a single product is just as easy
1: var baseUrl = @"http://localhost.:6876/api/products";
2: var client = new HttpClient();
3: client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
4: var stream = client.GetStreamAsync(baseUrl + @"\42").Result;
5: var product = XElement.Load(stream);
6:
7: Console.WriteLine("Product '{0}' costs {1:C}", (string)product.Element("ProductName"), (decimal)product.Element("UnitPrice"));
Adding new data using the HttpClient
Adding a new product is almost just as easy. First we need to create a valid XML document containing a new product, then turn it into a steam and we can post it.
1: var newProduct = new XElement("Product", new XElement("ProductName", "ASP.NET Web API"), new XElement("UnitPrice", 0));
2: var stream = new MemoryStream();
3: newProduct.Save(stream);
4: stream.Position = 0;
5:
6: var baseUrl = @"http://localhost.:6876/api/products";
7: var client = new HttpClient();
8: var content = new StreamContent(stream);
9: content.Headers.ContentType = new MediaTypeHeaderValue("application/xml");
10:
11: var response = client.PostAsync(baseUrl, content).Result;
12: Console.WriteLine("StatusCode: {0}", response.StatusCode);
This code might seem a bit verbose but that is mainly because the client is real simple and uses POX and therefore a StreamContent. There are also richer ways of doing so with declared types and the ObjectContent<T>. The result is a predictable OK status with the new product being added to the database.
Of course this should really have been a 201 Created with the URL pointing to the location of the new resource but we still need to look at how to control the response message from the ApiController.
Enjoy!
[f1]
[f2]