一、前言

  在前一个专题快速介绍了KnockoutJs相关知识点,也写了一些简单例子,希望通过这些例子大家可以快速入门KnockoutJs。为了让大家可以清楚地看到KnockoutJs在实际项目中的应用,本专题将介绍如何使用WebApi+Bootstrap+KnockoutJs+Asp.net MVC来打造一个单页面Web程序。这种模式也是现在大多数公司实际项目中用到的。

二、SPA(单页面)好处

  在介绍具体的实现之前,我觉得有必要详细介绍了SPA。SPA,即Single Page Web Application的缩写,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。浏览器一开始会加载必需的HTML、CSS和JavaScript,所有的操作都在这张页面上完成,都由JavaScript来控制。

  单页面程序的好处在于:

  1. 更好的用户体验,让用户在Web app感受native app的速度和流畅。
  2. 分离前后端关注点,前端负责界面显示,后端负责数据存储和计算,各司其职,不会把前后端的逻辑混杂在一起。
  3. 减轻服务器压力,服务器只用生成数据就可以,不用管展示逻辑和页面逻辑,增加服务器吞吐量。MVC中Razor语法写的前端是需要服务器完成页面的合成再输出的。
  4. 同一套后端程序,可以不用修改直接用于Web界面、手机、平板等多种客户端。

  当然单页面程序除了上面列出的优点外,也有其不足:

  1. 不利于SEO。这点如果是做管理系统的话是没影响的
  2. 初次加载时间相对增加。因为所有的JS、CSS资源会在第一次加载完成,从而使得后面的页面流畅。对于这点可以使用Asp.net MVC中Bundle来进行文件绑定。关于Bundle的详细使用参考文章:http://www.cnblogs.com/xwgli/p/3296809.htmlhttp://www.cnblogs.com/wangiqngpei557/p/3309812.html
  3. 导航不可用。如果一定要导航需自行实现前进、后退。对于这点,可以自行实现前进、后退功能来弥补。其实现在手机端网页就是这么干的,现在还要上面导航的。对于一些企业后台管理系统,也可以这么做。
  4. 对开发人员技能水平、开发成本高。对于这点,也不是事,程序员嘛就需要不断学习来充电,好在一些前端框架都非常好上手。

三、使用Asp.net MVC+WebAPI+Bootstrap+KnockoutJS实现SPA

  前面详细介绍了SPA的优缺点,接下来,就让我们使用Asp.net MVC+WebAPI+BS+KO来实现一个单页面程序,从而体验下SPA流畅和对原始Asp.net MVC +Razor做出来的页面进行效果对比。

  1. 使用VS2013创建Asp.net Web应用程序工程,勾选MVC和WebAPI类库。具体见下图:

  

  2. 创建对应的仓储和模型。这里演示的是一个简单任务管理系统。具体的模型和仓储代码如下:

  任务实体类实现:

  1. public enum TaskState
  2. {
  3. Active = ,
  4. Completed =
  5. }
  6.  
  7. /// <summary>
  8. /// 任务实体
  9. /// </summary>
  10. public class Task
  11. {
  12. public int Id { get; set; }
  13.  
  14. public string Name { get; set; }
  15. public string Description { get; set; }
  16.  
  17. public DateTime CreationTime { get; set; }
  18.  
  19. public DateTime FinishTime { get; set; }
  20.  
  21. public string Owner { get; set; }
  22. public TaskState State { get; set; }
  23.  
  24. public Task()
  25. {
  26. CreationTime = DateTime.Parse(DateTime.Now.ToLongDateString());
  27. State = TaskState.Active;
  28. }
  29. }

  任务仓储类实现:

  1. /// <summary>
  2. /// 这里仓储直接使用示例数据作为演示,真实项目中需要从数据库中动态加载
  3. /// </summary>
  4. public class TaskRepository
  5. {
  6. #region Static Filed
  7. private static Lazy<TaskRepository> _taskRepository = new Lazy<TaskRepository>(() => new TaskRepository());
  8.  
  9. public static TaskRepository Current
  10. {
  11. get { return _taskRepository.Value; }
  12. }
  13.  
  14. #endregion
  15.  
  16. #region Fields
  17. private readonly List<Task> _tasks = new List<Task>()
  18. {
  19. new Task
  20. {
  21. Id =,
  22. Name = "创建一个SPA程序",
  23. Description = "SPA(single page web application),SPA的优势就是少量带宽,平滑体验",
  24. Owner = "Learning hard",
  25. FinishTime = DateTime.Parse(DateTime.Now.AddDays().ToString(CultureInfo.InvariantCulture))
  26. },
  27. new Task
  28. {
  29. Id =,
  30. Name = "学习KnockoutJs",
  31. Description = "KnockoutJs是一个MVVM类库,支持双向绑定",
  32. Owner = "Tommy Li",
  33. FinishTime = DateTime.Parse(DateTime.Now.AddDays().ToString(CultureInfo.InvariantCulture))
  34. },
  35. new Task
  36. {
  37. Id =,
  38. Name = "学习AngularJS",
  39. Description = "AngularJs是MVVM框架,集MVVM和MVC与一体。",
  40. Owner = "李志",
  41. FinishTime = DateTime.Parse(DateTime.Now.AddDays().ToString(CultureInfo.InvariantCulture))
  42. },
  43. new Task
  44. {
  45. Id =,
  46. Name = "学习ASP.NET MVC网站",
  47. Description = "Glimpse是一款.NET下的性能测试工具,支持asp.net 、asp.net mvc, EF等等,优势在于,不需要修改原项目任何代码,且能输出代码执行各个环节的执行时间",
  48. Owner = "Tonny Li",
  49. FinishTime = DateTime.Parse(DateTime.Now.AddDays().ToString(CultureInfo.InvariantCulture))
  50. },
  51. };
  52.  
  53. #endregion
  54.  
  55. #region Public Methods
  56. public IEnumerable<Task> GetAll()
  57. {
  58. return _tasks;
  59. }
  60.  
  61. public Task Get(int id)
  62. {
  63. return _tasks.Find(p => p.Id == id);
  64. }
  65.  
  66. public Task Add(Task item)
  67. {
  68. if (item == null)
  69. {
  70. throw new ArgumentNullException("item");
  71. }
  72.  
  73. item.Id = _tasks.Count + ;
  74. _tasks.Add(item);
  75. return item;
  76. }
  77.  
  78. public void Remove(int id)
  79. {
  80. _tasks.RemoveAll(p => p.Id == id);
  81. }
  82.  
  83. public bool Update(Task item)
  84. {
  85. if (item == null)
  86. {
  87. throw new ArgumentNullException("item");
  88. }
  89.  
  90. var taskItem = Get(item.Id);
  91. if (taskItem == null)
  92. {
  93. return false;
  94. }
  95.  
  96. _tasks.Remove(taskItem);
  97. _tasks.Add(item);
  98. return true;
  99. }
  100. #endregion
  101. }

  3. 通过Nuget添加Bootstrap和KnockoutJs库。

  4. 实现后端数据服务。这里后端服务使用Asp.net WebAPI实现的。具体的实现代码如下:

  1. /// <summary>
  2. /// Task WebAPI,提供数据服务
  3. /// </summary>
  4. public class TasksController : ApiController
  5. {
  6. private readonly TaskRepository _taskRepository = TaskRepository.Current;
  7.  
  8. public IEnumerable<Task> GetAll()
  9. {
  10. return _taskRepository.GetAll().OrderBy(a => a.Id);
  11. }
  12.  
  13. public Task Get(int id)
  14. {
  15. var item = _taskRepository.Get(id);
  16. if (item == null)
  17. {
  18. throw new HttpResponseException(HttpStatusCode.NotFound);
  19. }
  20.  
  21. return item;
  22. }
  23.  
  24. [Route("api/tasks/GetByState")]
  25. public IEnumerable<Task> GetByState(string state)
  26. {
  27. IEnumerable<Task> results = new List<Task>();
  28. switch (state.ToLower())
  29. {
  30. case "":
  31. case "all":
  32. results = _taskRepository.GetAll();
  33. break;
  34. case "active":
  35. results = _taskRepository.GetAll().Where(t => t.State == TaskState.Active);
  36. break;
  37. case "completed":
  38. results = _taskRepository.GetAll().Where(t => t.State == TaskState.Completed);
  39. break;
  40. }
  41.  
  42. results = results.OrderBy(t => t.Id);
  43. return results;
  44. }
  45.  
  46. [HttpPost]
  47. public Task Create(Task item)
  48. {
  49. return _taskRepository.Add(item);
  50. }
  51.  
  52. [HttpPut]
  53. public void Put(Task item)
  54. {
  55. if (!_taskRepository.Update(item))
  56. {
  57. throw new HttpResponseException(HttpStatusCode.NotFound);
  58. }
  59. }
  60.  
  61. public void Delete(int id)
  62. {
  63. _taskRepository.Remove(id);
  64. }
  65. }

  5. 使用Asp.net MVC Bundle对资源进行打包。对应的BundleConfig实现代码如下:

  1. /// <summary>
  2. /// 只需要补充一些缺少的CSS和JS文件。因为创建模板的时候已经添加了一些CSS和JS文件
  3. /// </summary>
  4. public class BundleConfig
  5. {
  6. // For more information on bundling, visit http://go.microsoft.com/fwlink/?LinkId=301862
  7. public static void RegisterBundles(BundleCollection bundles)
  8. {
  9. bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
  10. "~/Scripts/jquery-{version}.js"));
  11.  
  12. bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
  13. "~/Scripts/jquery.validate*"));
  14.  
  15. // Use the development version of Modernizr to develop with and learn from. Then, when you're
  16. // ready for production, use the build tool at http://modernizr.com to pick only the tests you need.
  17. bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
  18. "~/Scripts/modernizr-*"));
  19.  
  20. bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
  21. "~/Scripts/bootstrap.js",
  22. "~/Scripts/bootstrap-datepicker.min.js"));
  23.  
  24. bundles.Add(new StyleBundle("~/Content/css").Include(
  25. "~/Content/bootstrap.css",
  26. "~/Content/bootstrap-datepicker3.min.css",
  27. "~/Content/site.css"));
  28.  
  29. bundles.Add(new ScriptBundle("~/bundles/knockout").Include(
  30. "~/Scripts/knockout-{version}.js",
  31. "~/Scripts/knockout.validation.min.js",
  32. "~/Scripts/knockout.mapping-latest.js"));
  33.  
  34. bundles.Add(new ScriptBundle("~/bundles/app").Include(
  35. "~/Scripts/app/app.js"));
  36. }
  37. }

  6. 因为我们需要在页面上使得枚举类型显示为字符串。默认序列化时会将枚举转换成数值类型。所以要对WebApiConfig类做如下改动:

  1. public static class WebApiConfig
  2. {
  3. public static void Register(HttpConfiguration config)
  4. {
  5. // Web API 配置和服务
  6.  
  7. // Web API 路由
  8. config.MapHttpAttributeRoutes();
  9.  
  10. config.Routes.MapHttpRoute(
  11. name: "DefaultApi",
  12. routeTemplate: "api/{controller}/{id}",
  13. defaults: new { id = RouteParameter.Optional }
  14. );
  15.  
  16. // 使得序列化使用驼峰式大小写风格序列化属性
  17. config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
  18. // 将枚举类型在序列化时序列化字符串
  19. config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new StringEnumConverter());
  20. }
  21. }

  注:如果上面没有使用驼峰小写风格序列化的话,在页面绑定数据的时候也要进行调整。如绑定的Name属性的时候直接使用Name大写,如果使用name方式会提示这个属性没有定义错误。由于JS是使用驼峰小写风格对变量命名的。所以建议大家加上使用驼峰小写风格进行序列化,此时绑定的时候只能使用"name"这样的形式进行绑定。这样也更符合JS代码的规范。 

  7. 修改对应的Layout文件和Index文件内容。

  Layout文件具体代码如下:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title> Learninghard SPA Application</title>
  7. @Styles.Render("~/Content/css")
  8. @Scripts.Render("~/bundles/modernizr")
  9. </head>
  10. <body>
  11. <div class="navbar navbar-inverse navbar-fixed-top">
  12. <div class="container">
  13. <div class="navbar-header">
  14. <p class="navbar-brand">简单任务管理系统</p>
  15. </div>
  16. <div class="navbar-collapse collapse">
  17. <ul class="nav navbar-nav">
  18. <li class="active"><a href="/">主页</a></li>
  19. </ul>
  20. </div>
  21. </div>
  22. </div>
  23.  
  24. <div class="container body-content" id="main">
  25. @RenderBody()
  26. <hr />
  27. <footer>
  28. <p>&copy; @DateTime.Now.Year - Learninghard SPA Application</p>
  29. </footer>
  30. </div>
  31.  
  32. @Scripts.Render("~/bundles/jquery")
  33. @Scripts.Render("~/bundles/bootstrap")
  34. @Scripts.Render("~/bundles/knockout")
  35. @Scripts.Render("~/bundles/app")
  36. </body>
  37. </html>

  Index页面代码如下:

  1. @{
  2. ViewBag.Title = "Index";
  3. Layout = "~/Views/Shared/_Layout.cshtml";
  4. }
  5.  
  6. <div id="list" data-bind="if:canCreate">
  7. <h2>Tasks</h2>
  8. <div class="table-responsive">
  9. <table class="table table-striped">
  10. <thead>
  11. <tr>
  12. <th>编号</th>
  13. <th>名称</th>
  14. <th>描述</th>
  15. <th>负责人</th>
  16. <th>创建时间</th>
  17. <th>完成时间</th>
  18. <th>状态</th>
  19. <th></th>
  20. </tr>
  21. </thead>
  22. <tbody data-bind="foreach:tasks">
  23. <tr>
  24. <td data-bind="text: id"></td>
  25. <td><a data-bind="text: name, click: handleCreateOrUpdate"></a></td>
  26. <td data-bind="text: description"></td>
  27. <td data-bind="text: owner"></td>
  28. <td data-bind="text: creationTime"></td>
  29. <td data-bind="text: finishTime"></td>
  30. <td data-bind="text: state"></td>
  31. <td><a class="btn btn-xs btn-primary" data-bind="click:remove" href="javascript:void(0)">Remove</a></td>
  32. </tr>
  33. </tbody>
  34. </table>
  35. </div>
  36. <div class="col-sm-4">
  37. <a href="javascript:void(0)" data-bind="click: function(data, event){ setTaskList('all') }">All </a> |
  38. <a href="javascript:void(0)" data-bind="click: function(data, event){ setTaskList('active') }"> Active</a> |
  39. <a href="javascript:void(0)" data-bind="click: function(data, event){ setTaskList('completed') }"> Completed</a>
  40. </div>
  41. <div class="col-sm-2 col-sm-offset-6">
  42. <a href="javascript:void(0)" data-bind="click: handleCreateOrUpdate">添加任务</a>
  43. </div>
  44. </div>
  45.  
  46. <div id="create" style="visibility: hidden">
  47. <h2>添加任务</h2>
  48. <br/>
  49. <div class="form-horizontal">
  50. <div class="form-group">
  51. <label for="taskName" class="col-sm-2 control-label">名称 *</label>
  52. <div class="col-sm-10">
  53. <input type="text" data-bind="value: name" class="form-control" id="taskName" name="taskName" placeholder="名称">
  54. </div>
  55. </div>
  56. <div class="form-group">
  57. <label for="taskDesc" class="col-sm-2 control-label">描述</label>
  58. <div class="col-sm-10">
  59. <textarea class="form-control" data-bind="value: description" rows="3" id="taskDesc" name="taskDesc" placeholder="描述"></textarea>
  60. </div>
  61. </div>
  62. <div class="form-group">
  63. <label for="taskOwner" class="col-sm-2 control-label">负责人 *</label>
  64. <div class="col-sm-10">
  65. <input class="form-control" id="taskOwner" name="taskOwner" data-bind="value: owner" placeholder="负责人">
  66. </div>
  67. </div>
  68. <div class="form-group">
  69. <label for="taskFinish" class="col-sm-2 control-label">预计完成时间 *</label>
  70. <div class="col-sm-10">
  71. <input class="form-control datepicker" id="taskFinish" data-bind="value: finishTime" name="taskFinish">
  72. </div>
  73. </div>
  74. <div class="form-group">
  75. <label for="taskOwner" class="col-sm-2 control-label">状态 *</label>
  76. <div class="col-sm-10">
  77. <select id="taskState" class="form-control" data-bind="value: state">
  78. <option>Active</option>
  79. <option>Completed</option>
  80. </select>
  81.  
  82. </div>
  83. </div>
  84. <div class="form-group">
  85. <div class="col-sm-offset-2 col-sm-10">
  86. <button class="btn btn-primary" data-bind="click:handleSaveClick">Save</button>
  87. <button data-bind="click: handleBackClick" class="btn btn-primary">Back</button>
  88. </div>
  89. </div>
  90. </div>
  91. </div>

  8. 创建对应的前端脚本逻辑。用JS代码来请求数据,并创建对应ViewModel对象来进行前端绑定。具体JS实现代码如下:

  1. var taskListViewModel = {
  2. tasks: ko.observableArray(),
  3. canCreate:ko.observable(true)
  4. };
  5.  
  6. var taskModel = function () {
  7. this.id = 0;
  8. this.name = ko.observable();
  9. this.description = ko.observable();
  10. this.finishTime = ko.observable();
  11. this.owner = ko.observable();
  12. this.state = ko.observable();
  13. this.fromJS = function(data) {
  14. this.id = data.id;
  15. this.name(data.name);
  16. this.description(data.description);
  17. this.finishTime(data.finishTime);
  18. this.owner(data.owner);
  19. this.state(data.state);
  20. };
  21. };
  22.  
  23. function getAllTasks() {
  24. sendAjaxRequest("GET", function (data) {
  25. taskListViewModel.tasks.removeAll();
  26. for (var i = 0; i < data.length; i++) {
  27. taskListViewModel.tasks.push(data[i]);
  28. }
  29. }, 'GetByState', { 'state': 'all' });
  30. }
  31.  
  32. function setTaskList(state) {
  33. sendAjaxRequest("GET", function(data) {
  34. taskListViewModel.tasks.removeAll();
  35. for (var i = 0; i < data.length; i++) {
  36. taskListViewModel.tasks.push(data[i]);
  37. }},'GetByState',{ 'state': state });
  38. }
  39.  
  40. function remove(item) {
  41. sendAjaxRequest("DELETE", function () {
  42. getAllTasks();
  43. }, item.id);
  44. }
  45.  
  46. var task = new taskModel();
  47.  
  48. function handleCreateOrUpdate(item) {
  49. task.fromJS(item);
  50. initDatePicker();
  51. taskListViewModel.canCreate(false);
  52. $('#create').css('visibility', 'visible');
  53. }
  54.  
  55. function handleBackClick() {
  56. taskListViewModel.canCreate(true);
  57. $('#create').css('visibility', 'hidden');
  58. }
  59.  
  60. function handleSaveClick(item) {
  61. if (item.id == undefined) {
  62. sendAjaxRequest("POST", function (newItem) { //newitem是返回的对象。
  63. taskListViewModel.tasks.push(newItem);
  64. }, null, {
  65. name: item.name,
  66. description: item.description,
  67. finishTime: item.finishTime,
  68. owner: item.owner,
  69. state: item.state
  70. });
  71. } else {
  72. sendAjaxRequest("PUT", function () {
  73. getAllTasks();
  74. }, null, {
  75. id:item.id,
  76. name: item.name,
  77. description: item.description,
  78. finishTime: item.finishTime,
  79. owner: item.owner,
  80. state: item.state
  81. });
  82. }
  83.  
  84. taskListViewModel.canCreate(true);
  85. $('#create').css('visibility', 'hidden');
  86. }
  87. function sendAjaxRequest(httpMethod, callback, url, reqData) {
  88. $.ajax("/api/tasks" + (url ? "/" + url : ""), {
  89. type: httpMethod,
  90. success: callback,
  91. data: reqData
  92. });
  93. }
  94.  
  95. var initDatePicker = function() {
  96. $('#create .datepicker').datepicker({
  97. autoclose: true
  98. });
  99. };
  100.  
  101. $('.nav').on('click', 'li', function() {
  102. $('.nav li.active').removeClass('active');
  103. $(this).addClass('active');
  104. });
  105.  
  106. $(document).ready(function () {
  107. getAllTasks();
  108. // 使用KnockoutJs进行绑定
  109. ko.applyBindings(taskListViewModel, $('#list').get(0));
  110. ko.applyBindings(task, $('#create').get(0));
  111. });

  到此,我们的单页面程序就开发完毕了,接下来我们来运行看看其效果。

  从上面运行结果演示图可以看出,一旦页面加载完之后,所有的操作都好像在一个页面操作,完全感觉浏览器页面转圈的情况。对比于之前使用Asp.net MVC +Razor开发的页面,你是否感觉了SPA的流畅呢?之前使用Asp.net MVC +Razor开发的页面,你只要请求一个页面,你就可以感受整个页面刷新的情况,这样用户体验非常不好。

四、与Razor开发模式进行对比

  相信大家从效果上已经看出SPA优势了,接下来我觉得还是有必要与传统实现Web页面方式进行一个对比。与Razor开发方式主要有以下2点不同:

  1. 页面被渲染的时候,数据在浏览器端得到处理。而不是在服务器上。将渲染压力分配到各个用户的浏览器端,从而减少网站服务器的压力。换做是Razor语法,前端页面绑定语句应该就是如下:
  1. @Model IEnumerable<KnockoutJSSPA.Models.Task>
  2. @foreach (var item in Model)
  3. {
  4. <tr>
  5. <td>@item.Name</td>
  6. <td>@item.Description</td>
  7. </tr>
  8. }

  这些都是在服务器端由Razor引擎渲染的。这也是使用Razor开发的页面会看到页面转圈的情况的原因。因为你每切换一个页面的时候,都需要请求服务端进行渲染,服务器渲染完成之后再将html返回给客户端进行显示。

  2. 绑定的数据是动态的。意味着数据模型的改变会马上反应到页面上。这效果归功于KnockoutJs实现的双向绑定机制。

  采用这种方式,对于程序开发也简单了,Web API只负责提供数据,而前端页面也减少了很多DOM操作。由于DOM操作比较繁琐和容易出错。这样也意味着减少了程序隐性的bug。并且,一个后端服务,可以供手机、Web浏览器和平台多个平台使用,避免重复开发。

五、总结

  到此,本文的介绍就介绍了。本篇主要介绍了使用KnockoutJs来完成一个SPA程序。其实在实际工作中,打造单页面程序的模式更多的采用AngularJS。然后使用KnockoutJs也有很多,但是KnockoutJs只是一个MVVM框架,其路由机制需要借助其他一些类库,如我们这里使用Asp.net MVC中的路由机制,你还可以使用director.js前端路由框架。相对于KnockoutJs而言,AngularJs是一个MVVM+MVC框架。所以在下一个专题将介绍使用如何使用AngularJs打造一个单页面程序(SPA)。

  本文所有源码下载:SPAWithKnockoutJs

  另外,如果觉得本文对你有帮助,请帮忙点下推荐或者扫描二维码对我进行打赏,你们的支持也是我继续为大家分享好文章的动力,希望大家可以对我支持。谢谢

[后端人员耍前端系列]KnockoutJs篇:使用WebApi+Bootstrap+KnockoutJs打造单页面程序的更多相关文章

  1. [后端人员耍前端系列]AngularJs篇:30分钟快速掌握AngularJs

    一.前言 对于前端系列,自然少不了AngularJs的介绍了.在前面文章中,我们介绍了如何使用KnockoutJs来打造一个单页面程序,后面一篇文章将介绍如何使用AngularJs的开发一个单页面应用 ...

  2. [后端人员耍前端系列]AngularJs篇:使用AngularJs打造一个简易权限系统

    一.引言 上一篇博文已经向大家介绍了AngularJS核心的一些知识点,在这篇博文将介绍如何把AngularJs应用到实际项目中.本篇博文将使用AngularJS来打造一个简易的权限管理系统.下面不多 ...

  3. [后端人员耍前端系列]KnockoutJs篇:使用KnockoutJs+Bootstrap实现分页

    一.引言 由于最近公司的系统需要改版,改版的新系统我打算使用KnockoutJs来制作Web前端.在做的过程中,遇到一个问题——如何使用KnockoutJs来完成分页的功能.在前一篇文章中并没有介绍使 ...

  4. [后端人员耍前端系列]Bootstrap篇:30分钟快速掌握Bootstrap

    一.引言 很久没有写过博客了,但是最近这段时间都没有闲着,接触了很多方面.比如一些前端框架和组件.还有移动开发React-Native.以及对.NET框架设计的一些重新认识.这些内容在接下来的时间都会 ...

  5. [后端人员耍前端系列]KnockoutJs篇:快速掌握KnockoutJs

    一.引言 之前这个系列文章已经介绍Bootstrap.由于最近项目中,前端是Asp.net MVC + KnockoutJs + Bootstrap来做的.所以我又重新开始写这个系列.今天就让我们来看 ...

  6. 每天记录一点:NetCore获得配置文件 appsettings.json vue-router页面传值及接收值 详解webpack + vue + node 打造单页面(入门篇) 30分钟手把手教你学webpack实战 vue.js+webpack模块管理及组件开发

    每天记录一点:NetCore获得配置文件 appsettings.json   用NetCore做项目如果用EF  ORM在网上有很多的配置连接字符串,读取以及使用方法 由于很多朋友用的其他ORM如S ...

  7. WebApi学习总结系列第二篇(webapi的调试)

    目前使用webapi的调试主要有 1.用接口宿主调试.(宿主形式多样:web.winform.还有就是直接用app进行接口调试) 2.用Fiddler抓Http信息,进行调试. 1.用接口宿主调试. ...

  8. bootstrap学习起步篇:初识bootstrap之表单验证(二)

    学习bootstrap是个过程,它提供给我们的文档上有很详细的说明.包括常用的栅栏布局.页面元素等,这里就不啰嗦了,今天,我就来说下结合jquery的表单验证. 最开始不借助插件,我们需要自己去编写验 ...

  9. webpack + vue + node 打造单页面(入门篇)

    1.node下载地址:http://nodejs.cn/download/,安装完成检查node和npm版本 2.淘宝镜像 : npm install cnpm -g --registry=https ...

随机推荐

  1. 借助无线路由器+2台笔记本+Windows桥接功能,成功绕过了微信聊天记录迁移的BUG

    最近入了台iphone se,在迁移微信聊天记录的时候,遇到个BUG.它的迁移流程是这样的:需要将两台手机连接到同一个WIFI上面,然后新手机扫旧手机上面的二维码,来完成导入.中途遇到的问题是: 此时 ...

  2. python课程第二周重点记录

    python课程第二周重点记录 1.元组的元素不可被修改,元组的元素的元素可以被修改(字典在元组中,字典的值可以被修改) 2.个人感觉方便做加密解密 3.一些方法的使用 sb = "name ...

  3. 用Backbone.js创建一个联系人管理系统(四)

    原文: Build a Contacts Manager Using Backbone.js: Part 4 这一系列教程的第四部分,教我们如何完成对已经存在的Contacts进行编辑和保存. 本教程 ...

  4. 字符串分割函数(New)

    unit Unit1; interface uses  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Form ...

  5. hibernate配置文件中的schema="dbo"在MySQL数据库不可用

    把项目的数据库由SQL Server更改为MySQL之后,发现hibernate报错. 问题在于schema="dbo",使用SQL Sever数据库时正常,使用MySQL数据库需 ...

  6. MyBatis-NET

    http://www.codeproject.com/Articles/894127/MyBatis-NET https://mybatis.github.io/mybatis-3/

  7. hdoj 1016 Prime Ring Problem

    Problem Description A ring is compose of n circles as shown in diagram. Put natural number 1, 2, ... ...

  8. Spring In action chapter1_wiringBeans

    Automatically wiring beans Spring attacks automatic wiring from two angles: Component scanning-Sprin ...

  9. 将excel数据导入到mysql的方法

    文本框被键盘遮挡到了,不会再获取焦点的时候被顶到键盘顶部.解决方案:设置A的Position为绝对定位absolute即可,其他几种定位方式未测试,但是不能是fixed ,正是因为这种定位方式,导致它 ...

  10. 如何定制Activity的标题栏

    requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); setContentView(R.layout.main); //自定义标题栏        mW ...