Getting Started with Web API

I first heard about ASP.NET Web API several months ago in MSDN magazine. My primary role is architecting cross-product solutions between my company’s customers and other software vendors, so I was instantly interested. The “wow” part for me was that the consumer can specify whether they’d like to communicate using JSON or XML, and Web API does the rest. The MVC-like structure promises improved unit-testability, and I’m excited to simplify access to our data for third parties on all platforms by leveraging HTTP.

And so, I created my first “Hello, world”-style application. In terms of creating the Web API service, I just followed MSDN’s Your First ASP.NET Web API tutorial. It’s very easy to follow, and I recommend starting there.

Overly-Summarized Tutorial

To get you somewhat up to speed, here’s a so-summarized-that-it’s-probably-not-even-useful summary of the tutorial. A functional web API is created by defining a control that derives from ApiController.

namespace HelloWebAPI.Controllers
{
    using HelloWebAPI.Models;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Web.Http;

    public class ProductsController : ApiController
    {

        Product[] products = new Product[] 
        { 
            new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 }, 
            new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, 
            new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } 
        };

        public IEnumerable<Product> GetAllProducts()
        {
            return products;
        }

        public Product GetProductById(int id)
        {
            var product = products.FirstOrDefault((p) => p.Id == id);
            if (product == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
            return product;
        }

        public IEnumerable<Product> GetProductsByCategory(string category)
        {
            return products.Where(
                (p) => string.Equals(p.Category, category,
                    StringComparison.OrdinalIgnoreCase));
        }
    }
}

Routing will map the controller methods to URIs as follows:

Controller Method URI
GetAllProducts /api/products
GetProductById /api/products/id
GetProductsByCategory /api/products/?category=category

Finally, the API can be accessed using JavaScript/jQuery like so:

    <script type="text/javascript">
        $(document).ready(function () {
            // Send an AJAX request
            $.getJSON("api/products/",
            function (data) {
                // On success, 'data' contains a list of products.
                $.each(data, function (key, val) {

                    // Format the text to display.
                    var str = val.Name + ': $' + val.Price;

                    // Add a list item for the product.
                    $('<li/>', { text: str })    
                    .appendTo($('#products'));   
                });
            });
        });
        
        function find() {
            var id = $('#prodId').val();
            $.getJSON("api/products/" + id,
                function (data) {
                    var str = data.Name + ': $' + data.Price;
                    $('#product').text(str);
                })
            .fail(
                function (jqXHR, textStatus, err) {
                    $('#product').text('Error: ' + err); 
                });
        }   
    </script>

Accepting Complex Types

The tutorial is great, but the controller methods only accept integers and strings. I need to accept and process complex types in order to do most tasks, like record searching and updating. It took me a little bit of Googling and trial & error to figure out, but I was able to modify the tutorial example to accept a complex object with minimal changes.

The first thing I needed was a complex type. I created an empty class named GetDto. I then added the new parameter to the GetProductById method in the controller. The final step was adding the HttpPost attribute to the method. (A POST request must be used since complex objects must be read from the request body, and GET requests do not have a body.) That’s it for the controller changes. In order to test, I added a name field to my DTO and appended it to the response.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Http;
using HelloWebApi.Models;

namespace HelloWebApi.Controllers
{
    public class GetDto
    {
        public string name { get; set; }
    }

    public class ProductsController : ApiController
    {
        Product[] products = new Product[] 
        { 
            new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 }, 
            new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, 
            new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } 
        };

        public IEnumerable<Product> GetAllProducts()
        {
            return products;
        }

        [HttpPost]
        public Product GetProductById(int id, GetDto dto)
        {
            var product = products.FirstOrDefault((p) => p.Id == id);
            if (product == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
            product.Name += dto.name;
            return product;
        }

        public IEnumerable<Product> GetProductsByCategory(string category)
        {
            return products.Where(
                (p) => string.Equals(p.Category, category,
                    StringComparison.OrdinalIgnoreCase));
        }
    }
}

The modifications to the calling jQuery code were less intuitive because the tutorial used the getJSON method. I had to rewrite that code to use the ajax method, so the request would be sent as a POST with my complex object sent as the request body. Here’s what I came up with:

    <script type="text/javascript">
        $(document).ready(function () {
            // Send an AJAX request
            $.getJSON("api/products/",
            function (data) {
                // On success, 'data' contains a list of products.
                $.each(data, function (key, val) {

                    // Format the text to display.
                    var str = val.Name + ': $' + val.Price;

                    // Add a list item for the product.
                    $('<li/>', { text: str })    
                    .appendTo($('#products'));   
                });
            });
        });
        
        function find() {
            var id = $('#prodId').val();
            $.ajax({
                url: "api/products/" + id,
                type: "POST",
                data: JSON.stringify({ name: id }),
                dataType: "json",
                contentType: "application/json; charset=utf-8",
                success: function (data) {
                    var str = data.Name + ': $' + data.Price;
                    $('#product').text(str);
                }
            });
        }   
    </script>

Calling from C#

jQuery is nice and all, but most of the work I do is C#. So my final goal for this first look was to invoke the API from C#. I created a simple WPF test app and used another MSDN tutorial, Calling a Web API From a WPF Application. This tutorial was also quite good, so I won’t get into too much detail. Here’s the end result that allows me to access my service from within my WPF application. Easy-peasy.

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Windows;
using HelloWebApi.Models;

namespace HelloWebApiTestClient
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void OnTestClick(object sender, RoutedEventArgs e)
        {
            HttpClient client = new HttpClient();
            ProductsCollection _products = new ProductsCollection();

            client.BaseAddress = new Uri("http://localhost:14095");
            client.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/json"));

        
            try
            {
                btnGetProducts.IsEnabled = false;

                var response = await client.GetAsync("api/products");
                response.EnsureSuccessStatusCode(); // Throw on error code.

                var products = await response.Content.ReadAsAsync<IEnumerable<Product>>();
                _products.CopyFrom(products);
            }
            catch (Newtonsoft.Json.JsonException jEx)
            {
                // This exception indicates a problem deserializing the request body.
                MessageBox.Show(jEx.Message);
            }
            catch (HttpRequestException ex)
            {
                MessageBox.Show(ex.Message);
            }
            finally
            {
                btnGetProducts.IsEnabled = true;
            }
        }
    }
}

Next Up: Security

I’m pretty happy with what I’ve seen so far. I watched a webcast on how easy it is to secure Web API using a variety of methods. So that’s what’s next for me. Yay for new stuff!

Advertisements

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s