WebApi + Bootstrap + KnockoutJs to create a single page program, webapiknockoutjs
I. Preface
In the previous topic, I quickly introduced KnockoutJs-related knowledge points and wrote some simple examples. I hope you can quickly get started with KnockoutJs through these examples. This topic describes how to use WebApi + Bootstrap + KnockoutJs + Asp.net MVC to create a single-page Web program. This model is also used by most companies in actual projects.
Ii. SPA (single page) Benefits
Before introducing the specific implementation, I think it is necessary to introduce SPA in detail. SPA, short for Single Page Web Application, is a Web Application that loads a Single HTML Page and dynamically updates the Page when the user interacts with the Application. At the beginning, the browser loads necessary HTML, CSS, and JavaScript. All operations are completed on this page and controlled by JavaScript.
The benefit of a single page program is:
A better user experience allows users to experience the speed and smoothness of native apps in Web apps.
Separation of front-end concerns, front-end is responsible for interface display, back-end is responsible for data storage and computing, perform their respective duties, do not mix the front-end logic together.
To reduce the pressure on the server, the server only needs to generate data, instead of displaying logic and page logic to increase the server throughput. In MVC, The frontend written by Razor syntax requires the server to merge pages and then output them.
The same backend program can be directly used for multiple clients, such as Web interfaces, mobile phones, and tablets, without modification.
Of course, in addition to the advantages listed above, the single page program also has its shortcomings:
Not conducive to SEO. This does not affect the management system.
The initial loading time is relatively increased. Because all JS and CSS resources are loaded for the first time, the subsequent pages are smooth. You can use Bundle in Asp.net MVC to bind files. For detailed usage of Bundle, refer to: Bundle.
Navigation is unavailable. If you need to navigate, you must move forward and backward. You can make up for this by implementing the forward and backward functions on your own. In fact, this is what we do on the mobile phone webpage. Now we need to navigate it. This can also be done for some enterprise background management systems.
High developer skill level and development cost. This is not the case. Programmers need to constantly learn and charge resources. Fortunately, some front-end frameworks are very easy to use.
Iii. Use Asp.net MVC + WebAPI + Bootstrap + KnockoutJS to implement SPA
We have introduced in detail the advantages and disadvantages of SPA. Next, let's use Asp.net MVC + WebAPI + BS + KO to implement a single page program, in this way, the SPA is smooth and the page effects of the original Asp.net MVC + Razor are compared.
1. Use VS2013 to create an Asp.net Web application project, and check the MVC and WebAPI class libraries. For details, see:
2. Create a warehouse and model. Here is a simple task management system. The specific model and warehousing Code are as follows:
Task entity class implementation:
Public enum TaskState {Active = 1, Completed = 2} // <summary> // Task entity /// </summary> public class Task {public int Id {get; set;} public string Name {get; set;} public string Description {get; set;} public DateTime CreationTime {get; set;} public DateTime FinishTime {get; set ;} public string Owner {get; set;} public TaskState State {get; set;} public Task () {CreationTime = DateTime. parse (DateTime. now. toLongDateString (); State = TaskState. active ;}}
Task warehousing implementation:
/// <Summary> /// the example data is directly used for demonstration in the warehouse, real projects require dynamic loading from the database // </summary> public class TaskRepository {# region Static Filed private static Lazy <TaskRepository> _ taskRepository = new Lazy <TaskRepository> (() => new TaskRepository (); public static TaskRepository Current {get {return _ taskRepository. value ;}# endregion # region Fields private readonly List <Task> _ tasks = new List <Task> () {new Task {Id = 1, Name = "Create a SPA program", Description = "SPA (single page web application), the advantage of SPA is a small amount of bandwidth, smooth Experience ", owner = "Learning hard", FinishTime = DateTime. parse (DateTime. now. addDays (1 ). toString (CultureInfo. invariantCulture)}, new Task {Id = 2, Name = "Learning KnockoutJs", Description = "KnockoutJs is an MVVM class library that supports two-way binding", Owner = "Tommy Li ", finishTime = DateTime. parse (DateTime. now. addDays (2 ). toString (CultureInfo. invariantC Ulture)}, new Task {Id = 3, Name = "Learning AngularJS", Description = "AngularJs is a MVVM framework that integrates MVVM and MVC. ", Owner =" Li Zhi ", FinishTime = DateTime. parse (DateTime. now. addDays (3 ). toString (CultureInfo. invariantCulture)}, new Task {Id = 4, Name = "Learning ASP. net mvc Website ", Description =" Glimpse is. the performance testing tool under. NET supports asp.net, asp.net mvc, EF, and so on. The advantage is that it does not need to modify any code of the original project and can output the execution time of each stage of code execution ", owner = "Tonny Li", FinishTime = DateTime. parse (DateTime. now. addDays (4 ). toString (CultureInfo. invariantCulture) },}; # endregion # region Public Methods public IEnumerable <Task> GetAll () {return _ tasks;} public Task Get (int id) {return _ tasks. find (p => p. id = id);} public Task Add (Task item) {if (item = null) {throw new ArgumentNullException ("item");} item. id = _ tasks. count + 1; _ tasks. add (item); return item;} public void Remove (int id) {_ tasks. removeAll (p => p. id = id);} public bool Update (Task item) {if (item = null) {throw new ArgumentNullException ("item");} var taskItem = Get (item. id); if (taskItem = null) {return false;} _ tasks. remove (taskItem); _ tasks. add (item); return true ;}# endregion}
3. Add the Bootstrap and KnockoutJs libraries through Nuget.
4. Implement backend data services. The backend service is implemented using the Asp.net WebAPI. The specific implementation code is as follows:
/// <Summary> // Task WebAPI, which provides data services // </summary> public class TasksController: ApiController {private readonly TaskRepository _ taskRepository = TaskRepository. current; public IEnumerable <Task> GetAll () {return _ taskRepository. getAll (). orderBy (a =>. id);} public Task Get (int id) {var item = _ taskRepository. get (id); if (item = null) {throw new HttpResponseException (HttpStatusCode. notFound) ;} Return item;} [Route ("api/tasks/GetByState")] public IEnumerable <Task> GetByState (string state) {IEnumerable <Task> results = new List <Task> (); switch (state. toLower () {case "": case "all": results = _ taskRepository. getAll (); break; case "active": results = _ taskRepository. getAll (). where (t => t. state = TaskState. active); break; case "completed": results = _ taskRepository. getAll (). where (t => T. state = TaskState. completed); break;} results = results. orderBy (t => t. id); return results;} [HttpPost] public Task Create (Task item) {return _ taskRepository. add (item);} [HttpPut] public void Put (Task item) {if (! _ TaskRepository. Update (item) {throw new HttpResponseException (HttpStatusCode. NotFound) ;}} public void Delete (int id) {_ taskRepository. Remove (id );}}
5. Use Asp.net MVC Bundle to package resources. The corresponding BundleConfig implementation code is as follows:
/// <Summary> /// you only need to add some missing CSS and JS files. Because some CSS and JS files have been added when creating the template /// </summary> public class BundleConfig {// For more information on bundling, visit http://go.microsoft.com/fwlink? LinkId = 301862 public static void RegisterBundles (BundleCollection bundles) {bundles. Add (new ScriptBundle ("~ /Bundles/jquery "). Include ("~ /Scripts/jquery-{version}. js "); bundles. Add (new ScriptBundle ("~ /Bundles/jqueryval "). Include ("~ /Scripts/jquery. validate * "); // Use the development version of Modernizr to develop with and learn from. then, when you're re // ready for production, use the build tool at http://modernizr.com to pick only the tests you need. bundles. add (new ScriptBundle ("~ /Bundles/modernizr "). Include ("~ /Scripts/modernizr-* "); bundles. Add (new ScriptBundle ("~ /Bundles/bootstrap "). Include ("~ /Scripts/bootstrap. js ","~ /Scripts/bootstrap-datepicker.min.js "); bundles. Add (new StyleBundle ("~ /Content/css "). Include ("~ /Content/bootstrap.css ","~ /Content/bootstrap-datepicker3.min.css ","~ /Content/site.css "); bundles. Add (new ScriptBundle ("~ /Bundles/knockout "). Include ("~ /Scripts/knockout-{version}. js ","~ /Scripts/knockout. validation. min. js ","~ /Scripts/knockout. mapping-latest.js "); bundles. Add (new ScriptBundle ("~ /Bundles/app "). Include ("~ /Scripts/app. js "));}}
6. Because we need to display the enumeration type as a string on the page. By default, the enumerated values are converted to numerical values during serialization. Make the following changes to the WebApiConfig class:
Public static class WebApiConfig {public static void Register (HttpConfiguration config) {// Web API configuration and service // Web API route config. mapHttpAttributeRoutes (); config. routes. mapHttpRoute (name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new {id = RouteParameter. optional}); // uses the camper case-style serialization attribute config. formatters. jsonFormatter. serializerSettings. contractResolver = new CamelCasePropertyNamesContractResolver (); // serialize the config string when the enumeration type is serialized. formatters. jsonFormatter. serializerSettings. converters. add (new StringEnumConverter ());}}
Note:If the upper case is not serialized in the lower case, adjust the data bound to the page. If the Name attribute is directly capitalized when it is bound, the system prompts that the attribute is not incorrectly defined if the Name attribute is used. Because JavaScript uses the lower case camper style to name variables. Therefore, we recommend that you use the lower case camper for serialization. In this case, you can only bind data in the form of "name. This is more in line with JS code specifications.
7. modify the content of the corresponding Layout and Index files.
The code of the Layout file is as follows:
<! DOCTYPE html>
8. Create the corresponding frontend script logic. Use JS Code to request data and create the corresponding ViewModel object for front-end binding. The specific JS implementation code is as follows:
Var taskListViewModel = {tasks: ko. observableArray (), canCreate: ko. observable (true)}; var taskModel = function () {this. id = 0; this. name = ko. observable (); this. description = ko. observable (); this. finishTime = ko. observable (); this. owner = ko. observable (); this. state = ko. observable (); this. fromJS = function (data) {this. id = data. id; this. name (data. name); this. description (data. description); this. FinishTime (data. finishTime); this. owner (data. owner); this. state (data. state) ;}}; function getAllTasks () {sendAjaxRequest ("GET", function (data) {taskListViewModel. tasks. removeAll (); for (var I = 0; I <data. length; I ++) {taskListViewModel. tasks. push (data [I]) ;}}, 'getbystate', {'state': 'all'});} function setTaskList (state) {sendAjaxRequest ("GET ", function (data) {taskListViewModel. tasks. re MoveAll (); for (var I = 0; I <data. length; I ++) {taskListViewModel. tasks. push (data [I]) ;}}, 'getbystate', {'state': state});} function remove (item) {sendAjaxRequest ("DELETE", function () {getAllTasks () ;}, item. id);} var task = new taskModel (); function handleCreateOrUpdate (item) {task. fromJS (item); initDatePicker (); taskListViewModel. canCreate (false); Parameters ('{create'}.css ('visibility ', 'visable ');} Function handleBackClick () {taskListViewModel. canCreate (true); condition ('{create'}.css ('visibility ', 'hidd');} function handleSaveClick (item) {if (item. id = undefined) {sendAjaxRequest ("POST", function (newItem) {// newitem is the returned object. TaskListViewModel. tasks. push (newItem) ;}, null, {name: item. name, description: item. description, finishTime: item. finishTime, owner: item. owner, state: item. state}) ;}else {sendAjaxRequest ("PUT", function () {getAllTasks () ;}, null, {id: item. id, name: item. name, description: item. description, finishTime: item. finishTime, owner: item. owner, state: item. state});} taskListViewModel. canCreate (True); Parameters ('{create'}.css ('visibility ', 'den den');} function sendAjaxRequest (httpMethod, callback, url, reqData) {$. ajax ("/api/tasks" + (url? "/" + Url: ""), {type: httpMethod, success: callback, data: reqData});} var initDatePicker = function () {$ ('# create. datepicker '). datepicker ({autoclose: true}) ;}$ ('. nav '). on ('click', 'lil', function () {$ ('. nav li. active '). removeClass ('active'); $ (this ). addClass ('active') ;}); $ (document ). ready (function () {getAllTasks (); // use KnockoutJs to bind ko. applyBindings (taskListViewModel, $ ('# list '). get (0); ko. applyBindings (task, $ ('# create '). get (0 ));});
At this point, our single page program has been developed. Next we will run it to see its effect.
From the preceding running result demo, we can see that after the page is loaded, all operations seem to be performed on a page, and the browser page is completely circled. Compared with the previous pages developed using Asp.net MVC + Razor, do you feel the smoothness of SPA? In the previous pages developed using Asp.net MVC + Razor, you only need to request a page and you will be able to feel the page refresh situation, so that the user experience is very poor.
Iv. Comparison with the Razor Development Mode
I believe that we can see the advantages of SPA in terms of results. Next, I think it is necessary to compare it with the traditional Web page method. Different from Razor development methods, there are two main differences:
1. When the page is rendered, the data is processed on the browser side. Instead of on the server. Distribute the rendering pressure to the browser end of each user to reduce the pressure on the website server. In the Razor syntax, the front-end page binding statement should be as follows:
@Model IEnumerable<KnockoutJSSPA.Models.Task> @foreach (var item in Model){ <tr> <td>@item.Name</td> <td>@item.Description</td> </tr>}
These are all rendered by the Razor engine on the server side. This is also the reason why pages developed using Razor can be circled. Every time you switch a page, you need to request the server for rendering. After rendering, the server returns the html to the client for display.
2. The bound data is dynamic. This means that changes to the data model are immediately reflected on the page. This effect is attributed to the bidirectional binding mechanism implemented by KnockoutJs.
This method is also simple for program development. Web APIs are only responsible for providing data, while front-end pages also reduce DOM operations. DOM operations are cumbersome and error-prone. This also reduces the hidden bugs in the program. In addition, a backend service can be used by mobile phones, Web browsers, and platforms to avoid repeated development.
V. Summary
At this point, this article introduces. This article describes how to use KnockoutJs to complete a SPA program. In fact, in actual work, AngularJS is used to create a single page program. There are also many KnockoutJs, but KnockoutJs is just an MVVM framework, and its routing mechanism needs to use some other class libraries. For example, we use the Routing Mechanism in Asp.net MVC here. You can also use director. js frontend routing framework. Compared with KnockoutJs, AngularJs is a MVVM + MVC framework. In the next topic, we will introduce how to use AngularJs to create a single page program (SPA ).
Download all source code in this article: SPAWithKnockoutJs
The above is all the content of this article, hoping to help you learn.