Book Notes: Pro ASP.NET MVC 5: Web API

 

Create WebServices Project

* Create a new project named WebServices
– select Empty template
– select both MVC and Web API features

mvc5book_WebServices_createProj_1

Model

* Create model class named Reservation with three properties (id, name, location):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
 
namespace WebServices.Models
{
    public class Reservation
    {
        public int ReservationId { get; set; }
        public string ClientName { get; set; }
        public string Location { get; set; }
    }
}

* Create a Helper class named ReservationRespository to act as temp data storage. Normally this could be a DAO object.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
 
namespace WebServices.Models
{
    public class ReservationRespository
    {
        private static ReservationRespository repo = new ReservationRespository();
 
        public static ReservationRespository Current {
            get {
                return repo;
            }
        }
 
        private List<Reservation> data = new List<Reservation> {
            new Reservation {
                ReservationId = 1, ClientName = "Adam", Location = "Board Room"},
            new Reservation {
                ReservationId = 2, ClientName = "Jacqui", Location = "Lecture Hall"},
            new Reservation {
                ReservationId = 3, ClientName = "Russell", Location = "Meeting Room 1"},
        };
 
        public IEnumerable<Reservation> GetAll() {
            return data;
        }
 
        public Reservation Get(int id) {
            return data.Where(r => r.ReservationId == id).FirstOrDefault();
        }
 
        public Reservation Add(Reservation item) {
            item.ReservationId = data.Count + 1;
            data.Add(item);
            return item;
        }
 
        public void Remove(int id) {
            Reservation item = Get(id);
            if (item != null) {
                data.Remove(item);
            }
        }
 
        public bool Update(Reservation item) {
            Reservation storedItem = Get(item.ReservationId);
            if (storedItem != null) {
                storedItem.ClientName = item.ClientName;
                storedItem.Location = item.Location;
                return true;
            } else {
                return false;
            }
        }
    }
}

Install jQuery, Bootstrap, and Knockout Packages

* Install from Tools > NuGet Package Manager:

Install-Package jquery –version 1.10.2
Install-Package bootstrap –version 3.0.0
Install-Package knockoutjs –version 3.0.0

Controller

* Add a controller named HomeController
* Implement CRUD actions:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using WebServices.Models;
 
namespace WebServices.Controllers
{
    public class HomeController : Controller
    {
        private ReservationRespository repo = ReservationRespository.Current;
 
        public ViewResult Index() {
            return View(repo.GetAll());
        }
 
        public ActionResult Add(Reservation item) {
            if (ModelState.IsValid) {
                repo.Add(item);
                return RedirectToAction("Index");
            } else {
                return View("Index");
            }
        }
 
        public ActionResult Remove(int id) {
            repo.Remove(id);
            return RedirectToAction("Index");
        }
 
        public ActionResult Update(Reservation item) {
            if (ModelState.IsValid && repo.Update(item)) {
                return RedirectToAction("Index");
            } else {
                return View("Index");
            }
        }
    }
}

Views

Layout View

* Add a MVC5 layout file called _Layout.cshtml in Views/Shared folder
* The layout page defines two sections named:
Scripts
Body
* It also includes Bootstrap CSS files

@{
    Layout = null;
}
 
<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link href="~/Content/bootstrap.css" rel="stylesheet" />
    <link href="~/Content/bootstrap.min.css" rel="stylesheet" />
    @RenderSection("Scripts")
</head>
<body>
    @RenderSection("Body")
</body>
</html>

Index View

* Create a view named Index in Views > Home folder.
* It references two partial views using the @Html.Partial() function:
Summary
Editor

@using WebServices.Models;
 
@model IEnumerable<Reservation>
@{
    ViewBag.Title = "Reservations";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
 
@section Scripts {
 
}
 
@section Body {
    <div id="summary" class="section panel panel-primary">
        @Html.Partial("Summary", Model)
    </div>
 
    <div id="editor" class="section panel panel-primary">
        @Html.Partial("Editor", new Reservation())
    </div>
}

Summary Partial View

* Create Summary partial view in View > Home directory. Select List as template and make sure to check the Create as partial view option.

mvc5book_WebServices_partialView_Summary

* Edit the page to loop through and display the reservation items:

@model IEnumerable<WebServices.Models.Reservation>
 
<div class="panel-heading">Reservation Summary</div>
<div class="panel-body">
 
    <table class="table table-striped table-condensed">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Location</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var item in Model)
            {
                <tr>
                    <td>@item.ReservationId</td>
                    <td>@item.ClientName</td>
                    <td>@item.Location</td>
                    <td>
                        @Html.ActionLink(
                            "Remove",
                            "Remove",
                            new { id = item.ReservationId },
                            new { @class = "btn btn-xs btn-primary"}
                        )
                    </td>
                </tr>
            }
        </tbody>
    </table>
</div>

Editor Partial View

* Similarly create a partial view named Editor
* Edit the view to include reservation update form:

@model WebServices.Models.Reservation
 
<div class="panel-heading">
    Create Reservation
</div>
<div class="panel-body">
    @using(Html.BeginForm("Add", "Home")) {
        <div class="form-group">
            <label>Client Name</label>
            @Html.TextBoxFor(m => m.ClientName, new {@class="form-control"})
        </div>
        <div class="form-group">
            <label>Location</label>
            @Html.TextBoxFor(m => m.Location, new { @class="form-control"})
        </div>
        <button type="submit" class="btn btn-primary">Save</button>
    }
</div>

Test Index Page

mvc5book_WebServices_debug_1

Web API

* Uses ApiController:
– Action methods return model object instead of ActionResult object
– Returned model object is encoded in JSON format
– Action methods are selected based on the HTTP method (GET, POST, DELETE, PUT) used in the request
– Action methods can also be manually mapped with C# attributes (note the System.Web.Http namespace:
System.Web.Http.HttpGet
System.Web.Http.HttpPost
System.Web.Http.HttpPut
System.Web.Http.HttpDelete
For example:

        [HttpPost]
        public Reservation CreateReservation(Reservation item) {
            return repo.Add(item);
        }

Create API Controller

* Right click Controllers folder and create a controller named WebController

mvc5book_WebServices_WebController_1

* Extends new controller with ApiController

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using WebServices.Models;
 
namespace WebServices.Controllers
{
    public class WebController : ApiController
    {
        private ReservationRespository repo = ReservationRespository.Current;
 
        public IEnumerable<Reservation> GetAllReservations()
        {
            return repo.GetAll();
        }
 
        public Reservation GetReservation(int id)
        {
            return repo.Get(id);
        }
 
        public Reservation PostReservation(Reservation item)
        {
            return repo.Add(item);
        }
 
        public bool PutReservation(Reservation item)
        {
            return repo.Update(item);
        }
 
        public void DeleteReservation(int id)
        {
            repo.Remove(id);
        }
    }
}

* Web API routing rules are defined in App_Start > WebApiConfig.cs
– it does not use action parameter in the route template

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
 
namespace WebServices
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
 
            // Web API routes
            config.MapHttpAttributeRoutes();
 
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

Test Drive Web API

* Start project and point browser to
/api/web
* You get json data with all reservation items as reply:

[{"ReservationId":1,"ClientName":"Adam","Location":"Board Room"},{"ReservationId":2,"ClientName":"Jacqui","Location":"Lecture Hall"},{"ReservationId":3,"ClientName":"Russell","Location":"Meeting Room 1"}]

* If you point to URL to include reservation id:
/api/web/3
* You get the specific reservation item:

{"ReservationId":3,"ClientName":"Russell","Location":"Meeting Room 1"}

Single Page App (SPA) with Knockout

SPA vs MVC

* MVC uses model data in HTML pages, SPA does not
– SPA uses JSON from Web API calls
* SPA has Ajax behavior, MVC does not
* For SPA, data is processed by the browser, while MVC at server

Add Knockout Support

* Note you can also use AngularJS which is another more comprehensive alternative JavaScript framework.

Add Knockout and jQuery Java Script Files

* Add to _Layout.cshtml page

    <script src="~/Scripts/jquery-1.10.2.js"></script>
    <script src="~/Scripts/knockout-3.0.0.js"></script>

Add Knockout Based Model Object

* aka observable objects
* Define a JavaScript model object as Knockout observable objects:

        var model = {
            reservations: ko.observableArray()
        };

Base jQuery Method: sendAjaxRequest

        function sendAjaxRequest(httpMethod, callback, url) {
            $.ajax("/api/web" + (url ? "/" + url : ""),
                {
                    type : httpMethod,
                    success : callback
                }
            );
        }

Define JavaScript CRUD Methods

* Define JavaScript CRUD methods using base sendAjaxRequest method and model object:

        function getAllItems() {
            sendAjaxRequest(
                "GET",
                function (data) {
                    model.reservations.removeAll();
                    for (var i = 0; i < data.length; i++) {
                        model.reservations.push(data[i]);
                    }
                }
            );
        }
 
        // Two ajax calls
        /*function removeItem(item) {
            sendAjaxRequest(
                "DELETE",
                function () {
                    getAllItems(); // Second ajax call
                },
                item.ReservationId
            );
        }*/
 
        // Repalce second ajax call
        function removeItem(item) {
            console.log("removeItem: " + item.ReservationId);
            sendAjaxRequest(
                "DELETE",
                function () {
                    for (var i = 0; i < model.reservations().length; i++) {
                        if (model.reservations()[i].ReservationId == item.ReservationId) {
                            model.reservations.remove(model.reservations()[i]);
                            break;
                        }
                    }
                },
                item.ReservationId
            );
        }

Bind Model to HTML Element with knockout

* Knockout uses data-bind=”type: expression” attributes to bind model object to HTML elements:

        <table class="table table-striped table-condensed">
            <thead>
                <tr><th>ID</th><th>Name</th><th>Location</th><th></th></tr>
            </thead>
            <tbody data-bind="foreach: model.reservations">
                <tr>
                    <td data-bind="text: ReservationsId"></td>
                    <td data-bind="text: ClientName"></td>
                    <td data-bind="text: Location"></td>
                    <td>
                        <button class="btn btn-xs btn-primary"
                                data-bind="click: removeItem">Remove</button>
                    </td>
                </tr>
            </tbody>
        </table>

Start Knockout Binding

        $(document).ready(
            function () {
                getAllItems();
                ko.applyBindings(model);
            }
        );

Complete SPA.cshtml File

@using WebServices.Models
@{
    ViewBag.Title = "SPA";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
 
@section Scripts {
    <script>
        var model = {
            reservations: ko.observableArray()
        };
 
        function sendAjaxRequest(httpMethod, callback, url) {
            $.ajax("/api/web" + (url ? "/" + url : ""),
                {
                    type: httpMethod,
                    success: callback
                }
            );
        }
 
        function getAllItems() {
            sendAjaxRequest(
                "GET",
                function (data) {
                    model.reservations.removeAll();
                    for (var i = 0; i < data.length; i++) {
                        model.reservations.push(data[i]);
                    }
                }
            );
        }
 
        function removeItem(item) {
            console.log("removeItem: " + item.ReservationId);
            sendAjaxRequest(
                "DELETE",
                function () {
                    for (var i = 0; i < model.reservations().length; i++) {
                        if (model.reservations()[i].ReservationId == item.ReservationId) {
                            model.reservations.remove(model.reservations()[i]);
                            break;
                        }
                    }
                },
                item.ReservationId
            );
        }
 
        $(document).ready(
            function () {
                getAllItems();
                ko.applyBindings(model);
            }
        );
    </script>
}
 
@section Body {
    <div id="summary" class="section panel panel-primary">
        <div class="panel-heading">Reservation Summary</div>
        <div class="panel-body">
            <table class="table table-striped table-condensed">
                <thead>
                    <tr><th>ID</th><th>Name</th><th>Location</th><th></th></tr>
                </thead>
                <tbody data-bind="foreach: model.reservations">
                    <tr>
                        <td data-bind="text: ReservationId"></td>
                        <td data-bind="text: ClientName"></td>
                        <td data-bind="text: Location"></td>
                        <td>
                            <button class="btn btn-xs btn-primary"
                                    data-bind="click: removeItem">
                                Remove
                            </button>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
}

Test SPA Page

* Add action method to HomeController to route Home/SPA to SPA.cshtml page:

        public ViewResult SPA() {
            return View("SPA");
        }

* Debug SPA page:

mvc5book_PartyInvites_testSPA_1

Add Create Support

Modify JavaScript

* Extend model object to include displaySummary flag field which is used to show/hide summary section:

        var model = {
            reservations: ko.observableArray(),
            editor: {
                name: ko.observable(""),
                location: ko.observable("")
            },
            displaySummary: ko.observable(true)
        };

* Modify base ajax send function to include post data:

        function sendAjaxRequest(httpMethod, callback, url, reqData) {
            $.ajax("/api/web" + (url ? "/" + url : ""),
                {
                    type: httpMethod,
                    success: callback,
                    data: reqData
                }
            );
        }

* Hide Summary section when Create button is clicked:

        function handleCreateClick() {
            model.displaySummary(false);
        }

* Collect post values and show Summary section when Save button is clicked.

        function handleEditorClick() {
            sendAjaxRequest(
                "POST",
                function (newItem) {
                    model.reservations.push(newItem);
                    model.displaySummary(true);
                },
                null,
                {
                    ClientName: model.editor.name,
                    Location: model.editor.location
                }
             )
        }

Add Create HTML Code

* Add HTML code to collect client name and location values as well as a Create button
* Use Knockout if: and ifnot: functions to show/hide sections

    <div id="editor" class="section panel panel-primary"
         data-bind="ifnot: model.displaySummary">
        <div class="panel-heading">
            Create Reservation
        </div>
        <div class="panel-body">
            <div class="form-group">
                <label>Client Name</label>
                <input class="form-control" data-bind="value: model.editor.name" />
            </div>
            <div class="form-group">
                <label>Location</label>
                <input class="form-control" data-bind="value: model.editor.location" />
            </div>
            <button class="btn btn-primary"
                    data-bind="click: handleEditorClick">
                Save
            </button>
        </div>
    </div>

Final Page

@using WebServices.Models
@{
    ViewBag.Title = "SPA";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
 
@section Scripts {
    <script>
        var model = {
            reservations: ko.observableArray(),
            editor: {
                name: ko.observable(""),
                location: ko.observable("")
            },
            displaySummary: ko.observable(true)
        };
 
        function sendAjaxRequest(httpMethod, callback, url, reqData) {
            $.ajax("/api/web" + (url ? "/" + url : ""),
                {
                    type: httpMethod,
                    success: callback,
                    data: reqData
                }
            );
        }
 
        function getAllItems() {
            sendAjaxRequest(
                "GET",
                function (data) {
                    model.reservations.removeAll();
                    for (var i = 0; i < data.length; i++) {
                        model.reservations.push(data[i]);
                    }
                },
                null
            );
        }
 
        function removeItem(item) {
            sendAjaxRequest(
                "DELETE",
                function () {
                    for (var i = 0; i < model.reservations().length; i++) {
                        if (model.reservations()[i].ReservationId == item.ReservationId) {
                            model.reservations.remove(model.reservations()[i]);
                            break;
                        }
                    }
                },
                item.ReservationId,
                null
            );
        }
 
        function handleCreateClick() {
            model.displaySummary(false);
        }
 
        function handleEditorClick() {
            sendAjaxRequest(
                "POST",
                function (newItem) {
                    model.reservations.push(newItem);
                    model.displaySummary(true);
                },
                null,
                {
                    ClientName: model.editor.name,
                    Location: model.editor.location
                }
             )
        }
 
        $(document).ready(
            function () {
                getAllItems();
                ko.applyBindings(model);
            }
        );
    </script>
}
 
@section Body {
    <div id="summary" class="section panel panel-primary"
         data-bind="if: model.displaySummary">
        <div class="panel-heading">Reservation Summary</div>
        <div class="panel-body">
            <table class="table table-striped table-condensed">
                <thead>
                    <tr><th>ID</th><th>Name</th><th>Location</th><th></th></tr>
                </thead>
                <tbody data-bind="foreach: model.reservations">
                    <tr>
                        <td data-bind="text: ReservationId"></td>
                        <td data-bind="text: ClientName"></td>
                        <td data-bind="text: Location"></td>
                        <td>
                            <button class="btn btn-xs btn-primary"
                                    data-bind="click: removeItem">
                                Remove
                            </button>
                        </td>
                    </tr>
                </tbody>
            </table>
            <button class="btn btn-primary" 
                    data-bind="click: handleCreateClick">Create</button>
        </div>
    </div>
 
    <div id="editor" class="section panel panel-primary"
         data-bind="ifnot: model.displaySummary">
        <div class="panel-heading">
            Create Reservation
        </div>
        <div class="panel-body">
            <div class="form-group">
                <label>Client Name</label>
                <input class="form-control" data-bind="value: model.editor.name" />
            </div>
            <div class="form-group">
                <label>Location</label>
                <input class="form-control" data-bind="value: model.editor.location" />
            </div>
            <button class="btn btn-primary"
                    data-bind="click: handleEditorClick">
                Save
            </button>
        </div>
    </div>
}

Test

* Start page:

mvc5book_PartyInvites_testSPA_21

* Click Create button:

mvc5book_PartyInvites_testSPA_22

* Click Save:

mvc5book_PartyInvites_testSPA_23
This entry was posted in c#, dotNet. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *


*

This site uses Akismet to reduce spam. Learn how your comment data is processed.