一、前言
在前一个专题快速介绍了KnockoutJs相关知识点,也写了一些简单例子,希望通过这些例子大家可以快速入门KnockoutJs。为了让大家可以清楚地看到KnockoutJs在实际项目中的应用,本专题将介绍如何使用WebApi+Bootstrap+KnockoutJs+Asp.net MVC来打造一个单页面Web程序。这种模式也是现在大多数公司实际项目中用到的。
二、SPA(单页面)好处
在介绍具体的实现之前,我觉得有必要详细介绍了SPA。SPA,即Single Page Web Application的缩写,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。浏览器一开始会加载必需的HTML、CSS和JavaScript,所有的操作都在这张页面上完成,都由JavaScript来控制。单页面程序的好处在于:
更好的用户体验,让用户在Web app感受native app的速度和流畅。 分离前后端关注点,前端负责界面显示,后端负责数据存储和计算,各司其职,不会把前后端的逻辑混杂在一起。 减轻服务器压力,服务器只用生成数据就可以,不用管展示逻辑和页面逻辑,增加服务器吞吐量。MVC中Razor语法写的前端是需要服务器完成页面的合成再输出的。 同一套后端程序,可以不用修改直接用于Web界面、手机、平板等多种客户端。
当然单页面程序除了上面列出的优点外,也有其不足:
不利于SEO。这点如果是做管理系统的话是没影响的
初次加载时间相对增加。因为所有的JS、CSS资源会在第一次加载完成,从而使得后面的页面流畅。对于这点可以使用Asp.net MVC中Bundle来进行文件绑定。关于Bundle的详细使用参考文章:、和。
导航不可用。如果一定要导航需自行实现前进、后退。对于这点,可以自行实现前进、后退功能来弥补。其实现在手机端网页就是这么干的,现在还要上面导航的。对于一些企业后台管理系统,也可以这么做。
对开发人员技能水平、开发成本高。对于这点,也不是事,程序员嘛就需要不断学习来充电,好在一些前端框架都非常好上手。
三、使用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. 创建对应的仓储和模型。这里演示的是一个简单任务管理系统。具体的模型和仓储代码如下:
任务实体类实现:
/// 任务实体
///
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;
}
}
任务仓储类实现:
public static TaskRepository Current
{
get { return _taskRepository.Value; }
}
endregion
region Fields
private readonly List
{
new Task
{
Id =1,Name = "创建一个SPA程序",Description = "SPA(single page web application),SPA的优势就是少量带宽,平滑体验",Owner = "Learning hard",FinishTime = DateTime.Parse(DateTime.Now.AddDays(1).ToString(CultureInfo.InvariantCulture))
},new Task
{
Id =2,Name = "学习KnockoutJs",Description = "KnockoutJs是一个MVVM类库,支持双向绑定",Owner = "Tommy Li",FinishTime = DateTime.Parse(DateTime.Now.AddDays(2).ToString(CultureInfo.InvariantCulture))
},new Task
{
Id =3,Name = "学习AngularJS",Description = "AngularJs是MVVM框架,集MVVM和MVC与一体。",Owner = "李志",FinishTime = DateTime.Parse(DateTime.Now.AddDays(3).ToString(CultureInfo.InvariantCulture))
},new Task
{
Id =4,Name = "学习ASP.NET MVC网站",Description = "Glimpse是一款.NET下的性能测试工具,支持asp.net 、asp.net mvc,EF等等,优势在于,不需要修改原项目任何代码,且能输出代码执行各个环节的执行时间",Owner = "Tonny Li",FinishTime = DateTime.Parse(DateTime.Now.AddDays(4).ToString(CultureInfo.InvariantCulture))
},};
endregion
region Public Methods
public IEnumerable
{
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. 通过Nuget添加Bootstrap和KnockoutJs库。
4. 实现后端数据服务。这里后端服务使用Asp.net WebAPI实现的。具体的实现代码如下:
public IEnumerable
{
return _taskRepository.GetAll().OrderBy(a => 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
{
IEnumerable
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. 使用Asp.net MVC Bundle对资源进行打包。对应的BundleConfig实现代码如下:
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
// 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/app.js"));
}
}
6. 因为我们需要在页面上使得枚举类型显示为字符串。默认序列化时会将枚举转换成数值类型。所以要对WebApiConfig类做如下改动:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",routeTemplate: "api/{controller}/{id}",defaults: new { id = RouteParameter.Optional }
);
// 使得序列化使用驼峰式大小写风格序列化属性
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
// 将枚举类型在序列化时序列化字符串
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new StringEnumConverter());
}
}
注:如果上面没有使用驼峰小写风格序列化的话,在页面绑定数据的时候也要进行调整。如绑定的Name属性的时候直接使用Name大写,如果使用name方式会提示这个属性没有定义错误。由于JS是使用驼峰小写风格对变量命名的。所以建议大家加上使用驼峰小写风格进行序列化,此时绑定的时候只能使用"name"这样的形式进行绑定。这样也更符合JS代码的规范。
7. 修改对应的Layout文件和Index文件内容。
Layout文件具体代码如下: