Using .Net Web API 2 with jQuery and HTML5

In this article I will demonstrate how to use the web API V2 to create an HTTP Specification compliant Restful API.

Using the Web API provides us with an opportunity to take advantage of the power REST to create HTTP compliant APIs. Some of the advantages of using REST are:

  1. Stateless; just like HTTP, Restful services / end points manage no session data which means multi-tenant scalability.
  2. To take advantage of HTTP cache and proxy server to help handling high load.
  3. Restful APIs are naturally organized; a very complex application into simple resources (No black magic here, you will do the organization).
  4. To make your application easy for new clients to use and consume its offerings.
  5. To use standard verb names (POST, GET, PUT and DELETE). Consumers will understand the call just by looking at the URI.
  6. Standardized Status Codes; if a Get method doesn't exits, you will get a standard 404 error.
  7. Content Negotiation (This is not part of REST but almost all restful API support it).

Enough talking, let's see how can we to take advantage of REST to build our sample app.

Use Case

As a school administrator, I want to Create a Student with the following properties:

  • Id (Must be Unique)
  • First Name
  • Last Name
  • Age

Using my browser, I want to be able to create and view all students. An error must be visible if I inadvertently create a new student and assign it the Id of an existing student.

Proof of Concept (POC)

Based on the provided use case, we will create a student model and a students’ controller. In the controller we will create three different methods:

  1. Get will be used to get a list of students and return them to caller with a status code of 200
  2. Get(int id) will be used to get a student based on the Id provided by the calling client. If no student is found, our response will include a '404 not found' with a custom message.
  3. Post([FromBody] Student student) will be used to add a new student to the existing list. Once we confirm the student is not null, we will compare the student Id with all of our student records; then based on our finding, we will perform the following:
  • If we detect a duplicate Id in our students’ list, our response will be an error response containing an http conflict error code (409) and a custom message indicating the problem with the record
  • If the new student Id doesn’t exist in our students list, we will add it to the list then send the caller a response containing the created student and it's URI in the http location header.

Environment

Visual Studio 2013 (I will be creating an Empty project) and use jQuery and Web API 2.0

Implementation Code

The Student Domain Model:

public class Student
   {
       public string FirstName { get; set; }
       public string LastName { get; set; }
       public int Age { get; set; }
       public int Id { get; set; }
   }

The Students Repository Class:

public class StudentsRepository
   {
       static IList<Student> _students;
       public  IList<Student> Students
       {
           get
           {
               if (_students == null)
                   _students = CreateStudents();
               return _students;
           }
       }
 
       private  IList<Student> CreateStudents()
       {
           return new List<Student> { new Student { Id = 1, FirstName = "John", LastName = "Doe", Age = 20 },
               new Student { Id = 2, Age = 30, LastName = "Doe", FirstName = "Jane" } ,
               new Student { Id = 3, FirstName = "John Junior", LastName = "Doe", Age = 11 } };
       }
   }

The StudentsController class:

public class StudentsController : ApiController
{
    private StudentsRepository _repo;
    public StudentsController()
    {
        this._repo = new StudentsRepository();
    }
 
 
 
    public IHttpActionResult Get()
    {
        return Ok<IList<Student>>(_repo.Students); // Use this with framework 4 -Request.CreateResponse<IList<Student>>(HttpStatusCode.OK,Students );
 
    }
    public HttpResponseMessage Get(int id)
    {
        var student = (from s in _repo.Students
                                                                     where s.Id == id
                                                                     select s).FirstOrDefault();
 
        var response = (null != student) ? Request.CreateResponse<Student>(HttpStatusCode.Found, student)
                        : Request.CreateErrorResponse(HttpStatusCode.NotFound,
                        string.Format("No student found with the specified Id of {0}", id));
        return response;
    }
 
    public HttpResponseMessage Post([FromBody] Student student)
    {
        HttpResponseMessage response = null;
        var check = _repo.Students.FirstOrDefault(s => s.Id == student.Id);
        if (student != null && check == null)
        {
            _repo.Students.Add(student);
            response = Request.CreateResponse<Student>(HttpStatusCode.Created, student);
            response.Headers.Location = new Uri(String.Format("{0}/{1}", Request.RequestUri, student.Id));
        }
        else
        {
            response = Request.CreateErrorResponse(HttpStatusCode.Conflict, String.Format(": Id {0} is already associated with a student", student.Id));
        }
        return response;
    }
}

The HTML page:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <style type="text/css">
        a {
            color: white;
        }
 
            a:link {
                text-decoration: underline;
                color: white;
            }
 
            a:visited {
                text-decoration: none;
            }
 
            a:active {
                text-decoration: none;
            }
 
            a:hover {
                text-decoration: underline;
                color: red;
            }
 
        #center {
            width: 900px;
            margin-left: auto;
            margin-right: auto;
        }
 
        fieldset legend {
            color: #FFF;
        }
 
        h1 {
            margin: 0 auto;
            text-align: center;
        }
 
        table, td, th {
            border: 1px solid #ffd800;
            text-align: center;
        }
 
        th {
            background-color: #ffd800;
            color: white;
        }
 
        form {
            float: right;
            width: 845px;
            display: none;
            margin-right: 20px;
        }
    </style>
</head>
 
<body style="background-color: rgba(5, 4, 0, 0.97); color:#FFF ">
    <section>
 
        <header>
            <h1>Student Registration</h1>
        </header>
 
        <aside id="data" style="float:left">
 
 
            <div id="Result" style="width:894px; color:#FFF;margin-bottom:10px ;margin-left:80px "> </div>
            <table id="students" style="width: 900px; margin-left: 80px; display:none">
                <thead>
                    <tr>
                        <td>Id</td>
                        <td>First Name</td>
                        <td> Last Name</td>
                        <td> Age</td>
                    </tr>
                </thead>
                <tbody></tbody>
            </table>
 
            <form method="post" id="createStudent">
                <fieldset>
                    <legend>Student Registration</legend>
                    <label for="Id">Id</label>    <input type="number" id="Id" name="Id" title="Id" required />
 
                    <label for="FirstName">First Name</label>  <input type="text" id="FirstName" name="FirstName" title="FirstN ame" required />
                    <label for="LastName">Last Name</label> <input type="text" id="LastName" name="LastName" title="LastName" required />
                    <label for="Age">Age</label>   <input type="number" id="Age" name="Age" title="Age" required style="clear:right" />
                    <input type="submit" title="Create Student" name="Create" value="Create Student" style="margin-top:10px; text-align:center;float:right" />
                </fieldset>
 
            </form>
        </aside>
        <section id="ControlCenter" style="float:right; margin-right:80px">
            <header><h2>Control Center</h2></header>
            <button id="getData" name="getData" style="clear:both" onclick="GetAllStudents();">Show Students Data</button>
            <br />
            <button id="ShowHideForm">Show Registration Form</button>
        </section>
    </section>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.0.min.js"></script>
    <script src="Scripts/students.js"></script>
</body>
</html>

Here is the javascipt file students.js:

/// <reference path="jquery-1.10.2.intellisense.js" />
var uri = '/api/Students';
var $result = $('#Result');
var msg;
$('#ShowHideForm').click(function (event) {
    $('#createStudent').toggle();
 
    if ($(this).text() === 'Hide Registration Form') {
        $(this).text('Show Registration Form');
    } else {
        $(this).text('Hide Registration Form');
    }
})
function showNotification(sucess, message) {
    if (sucess === true) {
        $result.css('background-color', "#a1965a");
    }
    else {
        $result.css('background-color', "#FF0000");
    }
    $result.text(message);
    $result.show();
}
function GetAllStudents() {
    $('#getData').text('Refresh Data');
    $result.hide();
    $('#students tbody').empty();
    $.getJSON(uri)
        .done(function (data) {
            $('#students').show();
            $.each(data, function (key, item) {
                $('#students tbody').append('<tr><td>' + item.id + '</td> <td>' + item.firstName + '</td> <td>' + item.lastName + '</td> <td>' + item.age + '</td></tr>');
            });
        })
        .fail(function (jqXHR, textStatus, err) {
            var error = $.parseJSON(jqXHR.responseText);
            msg = "Failed to get student data Error message is " + error.message;
            showNotification(false, msg);
        });
};
$('#createStudent').submit(function (event) {
    var uri = '/api/Students';
    var data = $("#createStudent").serialize();
    $.ajax({
        url: uri,
        data: data,
        type: "POST",
    })
    .done(function (data, status, jqXHR) {
        msg = "Success, the resource uri can be found at ";
        showNotification(true, msg);
        $('<a>', {
            text: 'Go to new resource',
            title: 'View Resource location',
            href: jqXHR.getResponseHeader("Location")
        }).appendTo('#Result');
        GetAllStudents();
        $result.show();
    })
    .fail(function (jqXHR, textStatus, err) {
        var error = $.parseJSON(jqXHR.responseText);
        msg = "Error: The server responded with the following error (" + err + ") " + error.message;
        showNotification(false, msg);
    })
    event.preventDefault();
});

Finally, here is the WebApiConfig class:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        //apply camel case property formatting using jsonFormatter
        var formatters = GlobalConfiguration.Configuration.Formatters;
        var jsonFormatter = formatters.JsonFormatter;
        var settings = jsonFormatter.SerializerSettings;
        settings.Formatting = Formatting.Indented;
        settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
 
        // Web API routes
        config.MapHttpAttributeRoutes();
 
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new
        { id = RouteParameter.Optional }
        );
    }
}

Now that we have all pieces, let's try to step into each piece to be understand the how and why.

  1. The repository class is used to act as a data source of all students records.
  2. The StudentsController is the controller where all access to the students' URI live (Get, Get(int id) and Post).
  3. The HTML page serves as our presentation page.
  4. students.js is where all the JavaScript functions live (GetAllStudents and the ajax call which is triggered when the form's submit button is clicked).
  5. Clicking on Show Student Data button will trigger a call to GetAllStudents (in students.js) which calls the API Get method.
  6. Clicking on Show Registration Form will cause the form to toggle its display property (Show or Hide).
  7. Clicking on Create Student button will fire the form's submit event (in students.js line 41). If the function call is successful (in ajax done callback), we will refresh all students' data and show the user a success message along with the new resource URI; otherwise the control flow will end up in the fail callback function (line 60). At this point we will show the user an error (Conflict due to a duplicate Id).

Once you run the project (either by creating your own or by downloading the example below), click on 'Show Student Data' to see the default list of students.

To create a new student, click on Show Registration form and enter the student data then click create to post the form to the server.

A successfully created Student will result in something similar to the following screen capture:

Creating a student with a duplicate Id will result in something similar to the following screen capture:

Recommendation

Always remember that the Web API is just a framework, Rest is a Methodology, and SOA is a paradigm. None of them will force you to write proper web APIs. You must write your APIs according to HTTP specifications. For example, when your API receives a POST request to create a resource, it must return the newly created resource URI in the HTTP location header as part of response object.

Download the final solution WebApiDemo.zip (4.03 mb)

Comments (3) -

Love your blog and the info that you are providing, keep the good stuff coming.

Just wanted to say thank you for this Sammy

Martin Cedillo 11/6/2014 5:34:01 PM

Sammy the link to download the final solution don´t work, can you fixed it please?, your blog is very good to understand this complex and interesting themes

Add comment