{"id":11266,"date":"2016-02-03T11:04:25","date_gmt":"2016-02-03T16:04:25","guid":{"rendered":"http:\/\/jianmingli.com\/wp\/?p=11266"},"modified":"2023-04-28T09:01:52","modified_gmt":"2023-04-28T14:01:52","slug":"book-notes-pro-asp-net-mvc-5-web-api","status":"publish","type":"post","link":"https:\/\/jianmingli.com\/wp\/?p=11266","title":{"rendered":"Book Notes: Pro ASP.NET MVC 5: Web API"},"content":{"rendered":"<div class='toc wptoc'>\n<h2>Contents<\/h2>\n<ol class='toc-odd level-1'>\n\t<li>\n\t\t<a href=\"#Create_WebServices_Project\">Create WebServices Project<\/a>\n\t<\/li>\n\t<li>\n\t\t<a href=\"#Model\">Model<\/a>\n\t<\/li>\n\t<li>\n\t\t<a href=\"#Install_jQuery_Bootstrap_and_Knockout_Packages\">Install jQuery, Bootstrap, and Knockout Packages<\/a>\n\t<\/li>\n\t<li>\n\t\t<a href=\"#Controller\">Controller<\/a>\n\t<\/li>\n\t<li>\n\t\t<a href=\"#Views\">Views<\/a>\n\t\t<ol class='toc-even level-2'>\n\t\t\t<li>\n\t\t\t\t<a href=\"#Layout_View\">Layout View<\/a>\n\t\t\t<\/li>\n\t\t\t<li>\n\t\t\t\t<a href=\"#Index_View\">Index View<\/a>\n\t\t\t\t<ol class='toc-odd level-3'>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<a href=\"#Summary_Partial_View\">Summary Partial View<\/a>\n\t\t\t\t\t<\/li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<a href=\"#Editor_Partial_View\">Editor Partial View<\/a>\n\t\t\t\t\t<\/li>\n\t\t\t\t<\/ol>\n\t\t\t<li>\n\t\t\t\t<a href=\"#Test_Index_Page\">Test Index Page<\/a>\n\t\t\t<\/li>\n\t\t<\/ol>\n\t<li>\n\t\t<a href=\"#Web_API\">Web API<\/a>\n\t\t<ol class='toc-even level-2'>\n\t\t\t<li>\n\t\t\t\t<a href=\"#Create_API_Controller\">Create API Controller<\/a>\n\t\t\t<\/li>\n\t\t\t<li>\n\t\t\t\t<a href=\"#Test_Drive_Web_API\">Test Drive Web API<\/a>\n\t\t\t<\/li>\n\t\t<\/ol>\n\t<li>\n\t\t<a href=\"#Single_Page_App_SPA_with_Knockout\">Single Page App (SPA) with Knockout<\/a>\n\t\t<ol class='toc-even level-2'>\n\t\t\t<li>\n\t\t\t\t<a href=\"#SPA_vs_MVC\">SPA vs MVC<\/a>\n\t\t\t<\/li>\n\t\t\t<li>\n\t\t\t\t<a href=\"#Add_Knockout_Support\">Add Knockout Support<\/a>\n\t\t\t\t<ol class='toc-odd level-3'>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<a href=\"#Add_Knockout_and_jQuery_Java_Script_Files\">Add Knockout and jQuery Java Script Files<\/a>\n\t\t\t\t\t<\/li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<a href=\"#Add_Knockout_Based_Model_Object\">Add Knockout Based Model Object<\/a>\n\t\t\t\t\t<\/li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<a href=\"#Base_jQuery_Method:_sendAjaxRequest\">Base jQuery Method: sendAjaxRequest<\/a>\n\t\t\t\t\t<\/li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<a href=\"#Define_JavaScript_CRUD_Methods\">Define JavaScript CRUD Methods<\/a>\n\t\t\t\t\t<\/li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<a href=\"#Bind_Model_to_HTML_Element_with_knockout\">Bind Model to HTML Element with knockout<\/a>\n\t\t\t\t\t<\/li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<a href=\"#Start_Knockout_Binding\">Start Knockout Binding<\/a>\n\t\t\t\t\t<\/li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<a href=\"#Complete_SPA.cshtml_File\">Complete <em>SPA.cshtml<\/em> File<\/a>\n\t\t\t\t\t<\/li>\n\t\t\t\t<\/ol>\n\t\t\t<li>\n\t\t\t\t<a href=\"#Test_SPA_Page\">Test SPA Page<\/a>\n\t\t\t<\/li>\n\t\t\t<li>\n\t\t\t\t<a href=\"#Add_Create_Support\">Add Create Support<\/a>\n\t\t\t\t<ol class='toc-odd level-3'>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<a href=\"#Modify_JavaScript\">Modify JavaScript<\/a>\n\t\t\t\t\t<\/li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<a href=\"#Add_Create_HTML_Code\">Add Create HTML Code<\/a>\n\t\t\t\t\t<\/li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<a href=\"#Final_Page\">Final Page<\/a>\n\t\t\t\t\t<\/li>\n\t\t\t\t<\/ol>\n\t\t\t<li>\n\t\t\t\t<a href=\"#Test\">Test<\/a>\n\t\t\t<\/li>\n<\/ol>\n<\/ol>\n<\/ol>\n<\/div>\n<div class='wptoc-end'>&nbsp;<\/div>\n<span id=\"Create_WebServices_Project\"><h2>Create WebServices Project<\/h2><\/span>\n<p>* Create a new project named <strong>WebServices<\/strong><br \/>\n&#8211; select <em>Empty <\/em>template<br \/>\n&#8211; select <strong>both <\/strong><em>MVC <\/em>and <em>Web API<\/em> features<\/p>\n<span id=\"\"><h6><a href=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/01\/mvc5book_WebServices_createProj_1.jpg\" rel=\"attachment wp-att-11268\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/01\/mvc5book_WebServices_createProj_1-300x224.jpg\" alt=\"mvc5book_WebServices_createProj_1\" width=\"300\" height=\"224\" class=\"aligncenter size-medium wp-image-11268\" srcset=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/01\/mvc5book_WebServices_createProj_1-300x224.jpg 300w, https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/01\/mvc5book_WebServices_createProj_1.jpg 760w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/h6><\/span>\n<span id=\"Model\"><h2>Model<\/h2><\/span>\n<p>* Create model class named <strong>Reservation<\/strong> with three properties (id, name, location):<\/p>\n<pre lang=\"csharp\">\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Web;\r\n\r\nnamespace WebServices.Models\r\n{\r\n    public class Reservation\r\n    {\r\n        public int ReservationId { get; set; }\r\n        public string ClientName { get; set; }\r\n        public string Location { get; set; }\r\n    }\r\n}\r\n<\/pre>\n<p>* Create a Helper class named <strong>ReservationRespository <\/strong>to act as temp data storage. Normally this could be a DAO object.<\/p>\n<pre lang=\"csharp\">\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Web;\r\n\r\nnamespace WebServices.Models\r\n{\r\n    public class ReservationRespository\r\n    {\r\n        private static ReservationRespository repo = new ReservationRespository();\r\n\r\n        public static ReservationRespository Current {\r\n            get {\r\n                return repo;\r\n            }\r\n        }\r\n\r\n        private List<Reservation> data = new List<Reservation> {\r\n            new Reservation {\r\n                ReservationId = 1, ClientName = \"Adam\", Location = \"Board Room\"},\r\n            new Reservation {\r\n                ReservationId = 2, ClientName = \"Jacqui\", Location = \"Lecture Hall\"},\r\n            new Reservation {\r\n                ReservationId = 3, ClientName = \"Russell\", Location = \"Meeting Room 1\"},\r\n        };\r\n\r\n        public IEnumerable<Reservation> GetAll() {\r\n            return data;\r\n        }\r\n\r\n        public Reservation Get(int id) {\r\n            return data.Where(r => r.ReservationId == id).FirstOrDefault();\r\n        }\r\n\r\n        public Reservation Add(Reservation item) {\r\n            item.ReservationId = data.Count + 1;\r\n            data.Add(item);\r\n            return item;\r\n        }\r\n\r\n        public void Remove(int id) {\r\n            Reservation item = Get(id);\r\n            if (item != null) {\r\n                data.Remove(item);\r\n            }\r\n        }\r\n\r\n        public bool Update(Reservation item) {\r\n            Reservation storedItem = Get(item.ReservationId);\r\n            if (storedItem != null) {\r\n                storedItem.ClientName = item.ClientName;\r\n                storedItem.Location = item.Location;\r\n                return true;\r\n            } else {\r\n                return false;\r\n            }\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<span id=\"Install_jQuery_Bootstrap_and_Knockout_Packages\"><h2>Install jQuery, Bootstrap, and Knockout Packages<\/h2><\/span>\n<p>* Install from <em>Tools > NuGet Package Manager<\/em>:<\/p>\n<pre lang=\"bash\">\r\nInstall-Package jquery \u2013version 1.10.2\r\nInstall-Package bootstrap \u2013version 3.0.0\r\nInstall-Package knockoutjs \u2013version 3.0.0\r\n<\/pre>\n<span id=\"Controller\"><h2>Controller<\/h2><\/span>\n<p>* Add a controller named <strong>HomeController<\/strong><br \/>\n* Implement CRUD actions:<\/p>\n<pre lang=\"xml\">\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Web;\r\nusing System.Web.Mvc;\r\nusing WebServices.Models;\r\n\r\nnamespace WebServices.Controllers\r\n{\r\n    public class HomeController : Controller\r\n    {\r\n        private ReservationRespository repo = ReservationRespository.Current;\r\n\r\n        public ViewResult Index() {\r\n            return View(repo.GetAll());\r\n        }\r\n\r\n        public ActionResult Add(Reservation item) {\r\n            if (ModelState.IsValid) {\r\n                repo.Add(item);\r\n                return RedirectToAction(\"Index\");\r\n            } else {\r\n                return View(\"Index\");\r\n            }\r\n        }\r\n\r\n        public ActionResult Remove(int id) {\r\n            repo.Remove(id);\r\n            return RedirectToAction(\"Index\");\r\n        }\r\n\r\n        public ActionResult Update(Reservation item) {\r\n            if (ModelState.IsValid && repo.Update(item)) {\r\n                return RedirectToAction(\"Index\");\r\n            } else {\r\n                return View(\"Index\");\r\n            }\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<span id=\"Views\"><h2>Views<\/h2><\/span>\n<span id=\"Layout_View\"><h3>Layout View<\/h3><\/span>\n<p>* Add a MVC5 layout file called <strong>_Layout.cshtml<\/strong> in <em>Views\/Shared<\/em> folder<br \/>\n* The layout page defines two sections named:<br \/>\n&#8211; <strong>Scripts<\/strong><br \/>\n&#8211; <strong>Body<\/strong><br \/>\n* It also includes <em>Bootstrap <\/em>CSS files<\/p>\n<pre lang=\"xml\">\r\n@{\r\n    Layout = null;\r\n}\r\n\r\n<!DOCTYPE html>\r\n\r\n<html>\r\n<head>\r\n    <meta name=\"viewport\" content=\"width=device-width\" \/>\r\n    <title>@ViewBag.Title<\/title>\r\n    <link href=\"~\/Content\/bootstrap.css\" rel=\"stylesheet\" \/>\r\n    <link href=\"~\/Content\/bootstrap.min.css\" rel=\"stylesheet\" \/>\r\n    @RenderSection(\"Scripts\")\r\n<\/head>\r\n<body>\r\n    @RenderSection(\"Body\")\r\n<\/body>\r\n<\/html>\r\n<\/pre>\n<span id=\"Index_View\"><h3>Index View<\/h3><\/span>\n<p>* Create a view named <strong>Index <\/strong>in <em>Views > Home<\/em> folder.<br \/>\n* It references two <em>partial views<\/em> using the <em>@Html.Partial()<\/em> function:<br \/>\n&#8211; <strong>Summary<\/strong><br \/>\n&#8211; <strong>Editor<\/strong><\/p>\n<pre lang=\"xml\">\r\n@using WebServices.Models;\r\n\r\n@model IEnumerable<Reservation>\r\n@{\r\n    ViewBag.Title = \"Reservations\";\r\n    Layout = \"~\/Views\/Shared\/_Layout.cshtml\";\r\n}\r\n\r\n@section Scripts {\r\n\r\n}\r\n\r\n@section Body {\r\n    <div id=\"summary\" class=\"section panel panel-primary\">\r\n        @Html.Partial(\"Summary\", Model)\r\n    <\/div>\r\n\r\n    <div id=\"editor\" class=\"section panel panel-primary\">\r\n        @Html.Partial(\"Editor\", new Reservation())\r\n    <\/div>\r\n}\r\n<\/pre>\n<span id=\"Summary_Partial_View\"><h4>Summary Partial View<\/h4><\/span>\n<p>* Create <strong>Summary<\/strong> partial view in <em>View > Home<\/em> directory. Select <em>List <\/em>as template and make sure to check the <em>Create as partial view<\/em> option.<\/p>\n<span id=\"_1\"><h6><a href=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/01\/mvc5book_WebServices_partialView_Summary.jpg\" rel=\"attachment wp-att-11269\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/01\/mvc5book_WebServices_partialView_Summary-300x168.jpg\" alt=\"mvc5book_WebServices_partialView_Summary\" width=\"300\" height=\"168\" class=\"aligncenter size-medium wp-image-11269\" srcset=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/01\/mvc5book_WebServices_partialView_Summary-300x168.jpg 300w, https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/01\/mvc5book_WebServices_partialView_Summary.jpg 589w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/h6><\/span>\n<p>* Edit the page to loop through and display the reservation items:<\/p>\n<pre lang=\"xml\">\r\n@model IEnumerable<WebServices.Models.Reservation>\r\n\r\n<div class=\"panel-heading\">Reservation Summary<\/div>\r\n<div class=\"panel-body\">\r\n\r\n    <table class=\"table table-striped table-condensed\">\r\n        <thead>\r\n            <tr>\r\n                <th>ID<\/th>\r\n                <th>Name<\/th>\r\n                <th>Location<\/th>\r\n            <\/tr>\r\n        <\/thead>\r\n        <tbody>\r\n            @foreach (var item in Model)\r\n            {\r\n                <tr>\r\n                    <td>@item.ReservationId<\/td>\r\n                    <td>@item.ClientName<\/td>\r\n                    <td>@item.Location<\/td>\r\n                    <td>\r\n                        @Html.ActionLink(\r\n                            \"Remove\",\r\n                            \"Remove\",\r\n                            new { id = item.ReservationId },\r\n                            new { @class = \"btn btn-xs btn-primary\"}\r\n                        )\r\n                    <\/td>\r\n                <\/tr>\r\n            }\r\n        <\/tbody>\r\n    <\/table>\r\n<\/div>\r\n<\/pre>\n<span id=\"Editor_Partial_View\"><h4>Editor Partial View<\/h4><\/span>\n<p>* Similarly create a partial view named <strong>Editor<\/strong><br \/>\n* Edit the view to include reservation update form:<\/p>\n<pre lang=\"xml\">\r\n@model WebServices.Models.Reservation\r\n\r\n<div class=\"panel-heading\">\r\n    Create Reservation\r\n<\/div>\r\n<div class=\"panel-body\">\r\n    @using(Html.BeginForm(\"Add\", \"Home\")) {\r\n        <div class=\"form-group\">\r\n            <label>Client Name<\/label>\r\n            @Html.TextBoxFor(m => m.ClientName, new {@class=\"form-control\"})\r\n        <\/div>\r\n        <div class=\"form-group\">\r\n            <label>Location<\/label>\r\n            @Html.TextBoxFor(m => m.Location, new { @class=\"form-control\"})\r\n        <\/div>\r\n        <button type=\"submit\" class=\"btn btn-primary\">Save<\/button>\r\n    }\r\n<\/div>\r\n<\/pre>\n<span id=\"Test_Index_Page\"><h3>Test Index Page<\/h3><\/span>\n<span id=\"_2\"><h6><a href=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/01\/mvc5book_WebServices_debug_1.jpg\" rel=\"attachment wp-att-11270\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/01\/mvc5book_WebServices_debug_1-300x178.jpg\" alt=\"mvc5book_WebServices_debug_1\" width=\"300\" height=\"178\" class=\"aligncenter size-medium wp-image-11270\" srcset=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/01\/mvc5book_WebServices_debug_1-300x178.jpg 300w, https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/01\/mvc5book_WebServices_debug_1-768x457.jpg 768w, https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/01\/mvc5book_WebServices_debug_1.jpg 937w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/h6><\/span>\n<span id=\"Web_API\"><h2>Web API<\/h2><\/span>\n<p>* Uses ApiController:<br \/>\n&#8211; Action methods return <em>model<\/em> object instead of <em>ActionResult<\/em> object<br \/>\n&#8211; Returned model object is encoded in <em>JSON<\/em> format<br \/>\n&#8211; Action methods are selected based on the HTTP method (GET, POST, DELETE, PUT) used in the request<br \/>\n&#8211; Action methods can also be manually mapped with C# attributes (note the <em>System.Web.Http<\/em> namespace:<br \/>\nSystem.Web.Http.HttpGet<br \/>\nSystem.Web.Http.HttpPost<br \/>\nSystem.Web.Http.HttpPut<br \/>\nSystem.Web.Http.HttpDelete<br \/>\nFor example:<\/p>\n<pre lang=\"csharp\">\r\n        [HttpPost]\r\n        public Reservation CreateReservation(Reservation item) {\r\n            return repo.Add(item);\r\n        }\r\n<\/pre>\n<span id=\"Create_API_Controller\"><h3>Create API Controller<\/h3><\/span>\n<p>* Right click <em>Controllers <\/em>folder and create a controller named <strong>WebController<\/strong><\/p>\n<span id=\"_3\"><h6><a href=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_WebServices_WebController_1.jpg\" rel=\"attachment wp-att-11389\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_WebServices_WebController_1.jpg\" alt=\"mvc5book_WebServices_WebController_1\" width=\"581\" height=\"223\" class=\"aligncenter size-full wp-image-11389\" srcset=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_WebServices_WebController_1.jpg 581w, https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_WebServices_WebController_1-300x115.jpg 300w\" sizes=\"auto, (max-width: 581px) 100vw, 581px\" \/><\/a><\/h6><\/span>\n<p>* Extends new controller with <em>ApiController<\/em><\/p>\n<pre lang=\"csharp\">\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Net;\r\nusing System.Net.Http;\r\nusing System.Web.Http;\r\nusing WebServices.Models;\r\n\r\nnamespace WebServices.Controllers\r\n{\r\n    public class WebController : ApiController\r\n    {\r\n        private ReservationRespository repo = ReservationRespository.Current;\r\n\r\n        public IEnumerable<Reservation> GetAllReservations()\r\n        {\r\n            return repo.GetAll();\r\n        }\r\n\r\n        public Reservation GetReservation(int id)\r\n        {\r\n            return repo.Get(id);\r\n        }\r\n\r\n        public Reservation PostReservation(Reservation item)\r\n        {\r\n            return repo.Add(item);\r\n        }\r\n\r\n        public bool PutReservation(Reservation item)\r\n        {\r\n            return repo.Update(item);\r\n        }\r\n\r\n        public void DeleteReservation(int id)\r\n        {\r\n            repo.Remove(id);\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>* Web API routing rules are defined in <em>App_Start > WebApiConfig.cs<\/em><br \/>\n&#8211; it does not use <em>action<\/em> parameter in the route template<\/p>\n<pre lang=\"csharp\">\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Web.Http;\r\n\r\nnamespace WebServices\r\n{\r\n    public static class WebApiConfig\r\n    {\r\n        public static void Register(HttpConfiguration config)\r\n        {\r\n            \/\/ Web API configuration and services\r\n\r\n            \/\/ Web API routes\r\n            config.MapHttpAttributeRoutes();\r\n\r\n            config.Routes.MapHttpRoute(\r\n                name: \"DefaultApi\",\r\n                routeTemplate: \"api\/{controller}\/{id}\",\r\n                defaults: new { id = RouteParameter.Optional }\r\n            );\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<span id=\"Test_Drive_Web_API\"><h3>Test Drive Web API<\/h3><\/span>\n<p>* Start project and point browser to<br \/>\n&#8211;<em>\/api\/web<\/em><br \/>\n* You get json data with all reservation items as reply:<\/p>\n<pre lang=\"json\">\r\n[{\"ReservationId\":1,\"ClientName\":\"Adam\",\"Location\":\"Board Room\"},{\"ReservationId\":2,\"ClientName\":\"Jacqui\",\"Location\":\"Lecture Hall\"},{\"ReservationId\":3,\"ClientName\":\"Russell\",\"Location\":\"Meeting Room 1\"}]\r\n<\/pre>\n<p>* If you point to URL to include reservation id:<br \/>\n&#8211;<em>\/api\/web\/3<\/em><br \/>\n* You get the specific reservation item:<\/p>\n<pre lang=\"json\">\r\n{\"ReservationId\":3,\"ClientName\":\"Russell\",\"Location\":\"Meeting Room 1\"}\r\n<\/pre>\n<span id=\"Single_Page_App_SPA_with_Knockout\"><h2>Single Page App (SPA) with Knockout<\/h2><\/span>\n<span id=\"SPA_vs_MVC\"><h3>SPA vs MVC<\/h3><\/span>\n<p>* MVC uses model data in HTML pages, SPA does not<br \/>\n&#8211; SPA uses JSON from Web API calls<br \/>\n* SPA has Ajax behavior, MVC does not<br \/>\n* For SPA, data is processed by the browser, while MVC at server<\/p>\n<span id=\"Add_Knockout_Support\"><h3>Add Knockout Support<\/h3><\/span>\n<p>* Note you can also use <em>AngularJS <\/em>which is another more comprehensive alternative JavaScript framework.<\/p>\n<span id=\"Add_Knockout_and_jQuery_Java_Script_Files\"><h4>Add Knockout and jQuery Java Script Files<\/h4><\/span>\n<p>* Add to <em>_Layout.cshtml<\/em> page<\/p>\n<pre lang=\"xml\">\r\n    <script src=\"~\/Scripts\/jquery-1.10.2.js\"><\/script>\r\n    <script src=\"~\/Scripts\/knockout-3.0.0.js\"><\/script>\r\n<\/pre>\n<span id=\"Add_Knockout_Based_Model_Object\"><h4>Add Knockout Based Model Object<\/h4><\/span>\n<p>* aka <em>observable objects<\/em><br \/>\n* Define a JavaScript <em>model <\/em>object as Knockout <em>observable<\/em> objects:<\/p>\n<pre lang=\"javascript\">\r\n        var model = {\r\n            reservations: ko.observableArray()\r\n        };\r\n<\/pre>\n<span id=\"Base_jQuery_Method:_sendAjaxRequest\"><h4>Base jQuery Method: sendAjaxRequest<\/h4><\/span>\n<pre lang=\"javascript\">\r\n        function sendAjaxRequest(httpMethod, callback, url) {\r\n            $.ajax(\"\/api\/web\" + (url ? \"\/\" + url : \"\"),\r\n                {\r\n                    type : httpMethod,\r\n                    success : callback\r\n                }\r\n            );\r\n        }\r\n<\/pre>\n<span id=\"Define_JavaScript_CRUD_Methods\"><h4>Define JavaScript CRUD Methods<\/h4><\/span>\n<p>* Define JavaScript CRUD methods using base <em>sendAjaxRequest <\/em>method and <em>model <\/em>object:<\/p>\n<pre lang=\"javascript\">\r\n        function getAllItems() {\r\n            sendAjaxRequest(\r\n                \"GET\",\r\n                function (data) {\r\n                    model.reservations.removeAll();\r\n                    for (var i = 0; i < data.length; i++) {\r\n                        model.reservations.push(data[i]);\r\n                    }\r\n                }\r\n            );\r\n        }\r\n\r\n        \/\/ Two ajax calls\r\n        \/*function removeItem(item) {\r\n            sendAjaxRequest(\r\n                \"DELETE\",\r\n                function () {\r\n                    getAllItems(); \/\/ Second ajax call\r\n                },\r\n                item.ReservationId\r\n            );\r\n        }*\/\r\n\r\n        \/\/ Repalce second ajax call\r\n        function removeItem(item) {\r\n            console.log(\"removeItem: \" + item.ReservationId);\r\n            sendAjaxRequest(\r\n                \"DELETE\",\r\n                function () {\r\n                    for (var i = 0; i < model.reservations().length; i++) {\r\n                        if (model.reservations()[i].ReservationId == item.ReservationId) {\r\n                            model.reservations.remove(model.reservations()[i]);\r\n                            break;\r\n                        }\r\n                    }\r\n                },\r\n                item.ReservationId\r\n            );\r\n        }\r\n<\/pre>\n<span id=\"Bind_Model_to_HTML_Element_with_knockout\"><h4>Bind Model to HTML Element with knockout<\/h4><\/span>\n<p>* Knockout uses <strong><em>data-bind=\"type: expression\"<\/em><\/strong> attributes to bind <em>model <\/em>object to HTML elements:<\/p>\n<pre lang=\"html\">\r\n        <table class=\"table table-striped table-condensed\">\r\n            <thead>\r\n                <tr><th>ID<\/th><th>Name<\/th><th>Location<\/th><th><\/th><\/tr>\r\n            <\/thead>\r\n            <tbody data-bind=\"foreach: model.reservations\">\r\n                <tr>\r\n                    <td data-bind=\"text: ReservationsId\"><\/td>\r\n                    <td data-bind=\"text: ClientName\"><\/td>\r\n                    <td data-bind=\"text: Location\"><\/td>\r\n                    <td>\r\n                        <button class=\"btn btn-xs btn-primary\"\r\n                                data-bind=\"click: removeItem\">Remove<\/button>\r\n                    <\/td>\r\n                <\/tr>\r\n            <\/tbody>\r\n        <\/table>\r\n<\/pre>\n<span id=\"Start_Knockout_Binding\"><h4>Start Knockout Binding<\/h4><\/span>\n<pre lang=\"javascript\">\r\n        $(document).ready(\r\n            function () {\r\n                getAllItems();\r\n                ko.applyBindings(model);\r\n            }\r\n        );\r\n<\/pre>\n<span id=\"Complete_SPA.cshtml_File\"><h4>Complete <em>SPA.cshtml<\/em> File<\/h4><\/span>\n<pre lang=\"xml\">\r\n@using WebServices.Models\r\n@{\r\n    ViewBag.Title = \"SPA\";\r\n    Layout = \"~\/Views\/Shared\/_Layout.cshtml\";\r\n}\r\n\r\n@section Scripts {\r\n    <script>\r\n        var model = {\r\n            reservations: ko.observableArray()\r\n        };\r\n\r\n        function sendAjaxRequest(httpMethod, callback, url) {\r\n            $.ajax(\"\/api\/web\" + (url ? \"\/\" + url : \"\"),\r\n                {\r\n                    type: httpMethod,\r\n                    success: callback\r\n                }\r\n            );\r\n        }\r\n\r\n        function getAllItems() {\r\n            sendAjaxRequest(\r\n                \"GET\",\r\n                function (data) {\r\n                    model.reservations.removeAll();\r\n                    for (var i = 0; i < data.length; i++) {\r\n                        model.reservations.push(data[i]);\r\n                    }\r\n                }\r\n            );\r\n        }\r\n\r\n        function removeItem(item) {\r\n            console.log(\"removeItem: \" + item.ReservationId);\r\n            sendAjaxRequest(\r\n                \"DELETE\",\r\n                function () {\r\n                    for (var i = 0; i < model.reservations().length; i++) {\r\n                        if (model.reservations()[i].ReservationId == item.ReservationId) {\r\n                            model.reservations.remove(model.reservations()[i]);\r\n                            break;\r\n                        }\r\n                    }\r\n                },\r\n                item.ReservationId\r\n            );\r\n        }\r\n\r\n        $(document).ready(\r\n            function () {\r\n                getAllItems();\r\n                ko.applyBindings(model);\r\n            }\r\n        );\r\n    <\/script>\r\n}\r\n\r\n@section Body {\r\n    <div id=\"summary\" class=\"section panel panel-primary\">\r\n        <div class=\"panel-heading\">Reservation Summary<\/div>\r\n        <div class=\"panel-body\">\r\n            <table class=\"table table-striped table-condensed\">\r\n                <thead>\r\n                    <tr><th>ID<\/th><th>Name<\/th><th>Location<\/th><th><\/th><\/tr>\r\n                <\/thead>\r\n                <tbody data-bind=\"foreach: model.reservations\">\r\n                    <tr>\r\n                        <td data-bind=\"text: ReservationId\"><\/td>\r\n                        <td data-bind=\"text: ClientName\"><\/td>\r\n                        <td data-bind=\"text: Location\"><\/td>\r\n                        <td>\r\n                            <button class=\"btn btn-xs btn-primary\"\r\n                                    data-bind=\"click: removeItem\">\r\n                                Remove\r\n                            <\/button>\r\n                        <\/td>\r\n                    <\/tr>\r\n                <\/tbody>\r\n            <\/table>\r\n        <\/div>\r\n    <\/div>\r\n}\r\n<\/pre>\n<span id=\"Test_SPA_Page\"><h3>Test SPA Page<\/h3><\/span>\n<p>* Add action method to <em>HomeController <\/em>to route <em>Home\/SPA<\/em> to <em>SPA.cshtml<\/em> page:<\/p>\n<pre lang=\"csharp\">\r\n        public ViewResult SPA() {\r\n            return View(\"SPA\");\r\n        }\r\n<\/pre>\n<p>* Debug SPA page:<\/p>\n<span id=\"_4\"><h6><a href=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_PartyInvites_testSPA_1.jpg\" rel=\"attachment wp-att-11284\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_PartyInvites_testSPA_1-300x124.jpg\" alt=\"mvc5book_PartyInvites_testSPA_1\" width=\"300\" height=\"124\" class=\"aligncenter size-medium wp-image-11284\" srcset=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_PartyInvites_testSPA_1-300x124.jpg 300w, https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_PartyInvites_testSPA_1.jpg 547w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/h6><\/span>\n<span id=\"Add_Create_Support\"><h3>Add Create Support<\/h3><\/span>\n<span id=\"Modify_JavaScript\"><h4>Modify JavaScript<\/h4><\/span>\n<p>* Extend model object to include <em>displaySummary <\/em>flag field which is used to show\/hide summary section:<\/p>\n<pre lang=\"javascript\">\r\n        var model = {\r\n            reservations: ko.observableArray(),\r\n            editor: {\r\n                name: ko.observable(\"\"),\r\n                location: ko.observable(\"\")\r\n            },\r\n            displaySummary: ko.observable(true)\r\n        };\r\n<\/pre>\n<p>* Modify base ajax send function to include post data:<\/p>\n<pre lang=\"javascript\">\r\n        function sendAjaxRequest(httpMethod, callback, url, reqData) {\r\n            $.ajax(\"\/api\/web\" + (url ? \"\/\" + url : \"\"),\r\n                {\r\n                    type: httpMethod,\r\n                    success: callback,\r\n                    data: reqData\r\n                }\r\n            );\r\n        }\r\n<\/pre>\n<p>* Hide Summary section when Create button is clicked:<\/p>\n<pre lang=\"javascript\">\r\n        function handleCreateClick() {\r\n            model.displaySummary(false);\r\n        }\r\n<\/pre>\n<p>* Collect post values and show Summary section when Save button is clicked.<\/p>\n<pre lang=\"javascript\">\r\n        function handleEditorClick() {\r\n            sendAjaxRequest(\r\n                \"POST\",\r\n                function (newItem) {\r\n                    model.reservations.push(newItem);\r\n                    model.displaySummary(true);\r\n                },\r\n                null,\r\n                {\r\n                    ClientName: model.editor.name,\r\n                    Location: model.editor.location\r\n                }\r\n             )\r\n        }\r\n<\/pre>\n<span id=\"Add_Create_HTML_Code\"><h4>Add Create HTML Code<\/h4><\/span>\n<p>* Add HTML code to collect client name and location values as well as a <em>Create <\/em>button<br \/>\n* Use Knockout <em>if:<\/em> and <em>ifnot:<\/em> functions to show\/hide sections<\/p>\n<pre lang=\"xml\">\r\n    <div id=\"editor\" class=\"section panel panel-primary\"\r\n         data-bind=\"ifnot: model.displaySummary\">\r\n        <div class=\"panel-heading\">\r\n            Create Reservation\r\n        <\/div>\r\n        <div class=\"panel-body\">\r\n            <div class=\"form-group\">\r\n                <label>Client Name<\/label>\r\n                <input class=\"form-control\" data-bind=\"value: model.editor.name\" \/>\r\n            <\/div>\r\n            <div class=\"form-group\">\r\n                <label>Location<\/label>\r\n                <input class=\"form-control\" data-bind=\"value: model.editor.location\" \/>\r\n            <\/div>\r\n            <button class=\"btn btn-primary\"\r\n                    data-bind=\"click: handleEditorClick\">\r\n                Save\r\n            <\/button>\r\n        <\/div>\r\n    <\/div>\r\n<\/pre>\n<span id=\"Final_Page\"><h4>Final Page<\/h4><\/span>\n<pre lang=\"xml\">\r\n@using WebServices.Models\r\n@{\r\n    ViewBag.Title = \"SPA\";\r\n    Layout = \"~\/Views\/Shared\/_Layout.cshtml\";\r\n}\r\n\r\n@section Scripts {\r\n    <script>\r\n        var model = {\r\n            reservations: ko.observableArray(),\r\n            editor: {\r\n                name: ko.observable(\"\"),\r\n                location: ko.observable(\"\")\r\n            },\r\n            displaySummary: ko.observable(true)\r\n        };\r\n\r\n        function sendAjaxRequest(httpMethod, callback, url, reqData) {\r\n            $.ajax(\"\/api\/web\" + (url ? \"\/\" + url : \"\"),\r\n                {\r\n                    type: httpMethod,\r\n                    success: callback,\r\n                    data: reqData\r\n                }\r\n            );\r\n        }\r\n\r\n        function getAllItems() {\r\n            sendAjaxRequest(\r\n                \"GET\",\r\n                function (data) {\r\n                    model.reservations.removeAll();\r\n                    for (var i = 0; i < data.length; i++) {\r\n                        model.reservations.push(data[i]);\r\n                    }\r\n                },\r\n                null\r\n            );\r\n        }\r\n\r\n        function removeItem(item) {\r\n            sendAjaxRequest(\r\n                \"DELETE\",\r\n                function () {\r\n                    for (var i = 0; i < model.reservations().length; i++) {\r\n                        if (model.reservations()[i].ReservationId == item.ReservationId) {\r\n                            model.reservations.remove(model.reservations()[i]);\r\n                            break;\r\n                        }\r\n                    }\r\n                },\r\n                item.ReservationId,\r\n                null\r\n            );\r\n        }\r\n\r\n        function handleCreateClick() {\r\n            model.displaySummary(false);\r\n        }\r\n\r\n        function handleEditorClick() {\r\n            sendAjaxRequest(\r\n                \"POST\",\r\n                function (newItem) {\r\n                    model.reservations.push(newItem);\r\n                    model.displaySummary(true);\r\n                },\r\n                null,\r\n                {\r\n                    ClientName: model.editor.name,\r\n                    Location: model.editor.location\r\n                }\r\n             )\r\n        }\r\n\r\n        $(document).ready(\r\n            function () {\r\n                getAllItems();\r\n                ko.applyBindings(model);\r\n            }\r\n        );\r\n    <\/script>\r\n}\r\n\r\n@section Body {\r\n    <div id=\"summary\" class=\"section panel panel-primary\"\r\n         data-bind=\"if: model.displaySummary\">\r\n        <div class=\"panel-heading\">Reservation Summary<\/div>\r\n        <div class=\"panel-body\">\r\n            <table class=\"table table-striped table-condensed\">\r\n                <thead>\r\n                    <tr><th>ID<\/th><th>Name<\/th><th>Location<\/th><th><\/th><\/tr>\r\n                <\/thead>\r\n                <tbody data-bind=\"foreach: model.reservations\">\r\n                    <tr>\r\n                        <td data-bind=\"text: ReservationId\"><\/td>\r\n                        <td data-bind=\"text: ClientName\"><\/td>\r\n                        <td data-bind=\"text: Location\"><\/td>\r\n                        <td>\r\n                            <button class=\"btn btn-xs btn-primary\"\r\n                                    data-bind=\"click: removeItem\">\r\n                                Remove\r\n                            <\/button>\r\n                        <\/td>\r\n                    <\/tr>\r\n                <\/tbody>\r\n            <\/table>\r\n            <button class=\"btn btn-primary\" \r\n                    data-bind=\"click: handleCreateClick\">Create<\/button>\r\n        <\/div>\r\n    <\/div>\r\n\r\n    <div id=\"editor\" class=\"section panel panel-primary\"\r\n         data-bind=\"ifnot: model.displaySummary\">\r\n        <div class=\"panel-heading\">\r\n            Create Reservation\r\n        <\/div>\r\n        <div class=\"panel-body\">\r\n            <div class=\"form-group\">\r\n                <label>Client Name<\/label>\r\n                <input class=\"form-control\" data-bind=\"value: model.editor.name\" \/>\r\n            <\/div>\r\n            <div class=\"form-group\">\r\n                <label>Location<\/label>\r\n                <input class=\"form-control\" data-bind=\"value: model.editor.location\" \/>\r\n            <\/div>\r\n            <button class=\"btn btn-primary\"\r\n                    data-bind=\"click: handleEditorClick\">\r\n                Save\r\n            <\/button>\r\n        <\/div>\r\n    <\/div>\r\n}\r\n<\/pre>\n<span id=\"Test\"><h3>Test<\/h3><\/span>\n<p>* Start page:<\/p>\n<span id=\"_5\"><h6><a href=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_PartyInvites_testSPA_21.jpg\" rel=\"attachment wp-att-11290\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_PartyInvites_testSPA_21-300x120.jpg\" alt=\"mvc5book_PartyInvites_testSPA_21\" width=\"300\" height=\"120\" class=\"aligncenter size-medium wp-image-11290\" srcset=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_PartyInvites_testSPA_21-300x120.jpg 300w, https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_PartyInvites_testSPA_21.jpg 713w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/h6><\/span>\n<p>* Click <em>Create <\/em>button:<\/p>\n<span id=\"_6\"><h6><a href=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_PartyInvites_testSPA_22.jpg\" rel=\"attachment wp-att-11291\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_PartyInvites_testSPA_22-278x300.jpg\" alt=\"mvc5book_PartyInvites_testSPA_22\" width=\"278\" height=\"300\" class=\"aligncenter size-medium wp-image-11291\" srcset=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_PartyInvites_testSPA_22-278x300.jpg 278w, https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_PartyInvites_testSPA_22.jpg 281w\" sizes=\"auto, (max-width: 278px) 100vw, 278px\" \/><\/a><\/h6><\/span>\n<p>* Click Save:<\/p>\n<span id=\"_7\"><h6><a href=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_PartyInvites_testSPA_23.jpg\" rel=\"attachment wp-att-11292\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_PartyInvites_testSPA_23-300x170.jpg\" alt=\"mvc5book_PartyInvites_testSPA_23\" width=\"300\" height=\"170\" class=\"aligncenter size-medium wp-image-11292\" srcset=\"https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_PartyInvites_testSPA_23-300x170.jpg 300w, https:\/\/jianmingli.com\/wp\/wp-content\/uploads\/2016\/02\/mvc5book_PartyInvites_testSPA_23.jpg 569w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/h6><\/span>\n","protected":false},"excerpt":{"rendered":"<p>Create WebServices Project * Create a new project named WebServices &#8211; select Empty template &#8211; select both MVC and Web API features Model * Create model class named Reservation with three properties (id, name, location): using System; using System.Collections.Generic; using &hellip; <a href=\"https:\/\/jianmingli.com\/wp\/?p=11266\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_exactmetrics_skip_tracking":false,"_exactmetrics_sitenote_active":false,"_exactmetrics_sitenote_note":"","_exactmetrics_sitenote_category":0,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[5,8],"tags":[],"class_list":["post-11266","post","type-post","status-publish","format-standard","hentry","category-c","category-dotnet"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p8cRUO-2VI","_links":{"self":[{"href":"https:\/\/jianmingli.com\/wp\/index.php?rest_route=\/wp\/v2\/posts\/11266","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/jianmingli.com\/wp\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/jianmingli.com\/wp\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/jianmingli.com\/wp\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/jianmingli.com\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=11266"}],"version-history":[{"count":15,"href":"https:\/\/jianmingli.com\/wp\/index.php?rest_route=\/wp\/v2\/posts\/11266\/revisions"}],"predecessor-version":[{"id":11390,"href":"https:\/\/jianmingli.com\/wp\/index.php?rest_route=\/wp\/v2\/posts\/11266\/revisions\/11390"}],"wp:attachment":[{"href":"https:\/\/jianmingli.com\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=11266"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/jianmingli.com\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=11266"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/jianmingli.com\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=11266"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}