Web API从MVC4开始出现,可以服务于Asp.Net下的任何web应用,本文将介绍Web api在单页应用中的使用。什么是单页应用?Single-Page Application最常用的定义:一个最初内容只包含html和JavaScript,后续操作通过Restful风格的web服务传输json数据来响应异步请求的一个web应用。SPA的优势就是少量带宽,平滑体验,劣势就是只用JavaScript这些平滑的操作较难实现,不像MVC应用,我们可以异步form,partview。不用担心,我们有利器:knockoutjs。

一、工程准备

1.新建一个Api工程。

2.创建模型和仓库

Reservation:

  public class Reservation
{
public int ReservationId { get; set; }
public string ClientName { get; set; }
public string Location { get; set; }
}

ReservationRespository:在真实的项目中,仓库都需要有接口和依赖注入,但是这里我们把重点放到后,将这个部分简化。所以也没有用数据库,全部放到内存里面。

 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 = , ClientName = "Adam", Location = "Board Room"},
new Reservation {
ReservationId = , ClientName = "Jacqui", Location = "Lecture Hall"},
new Reservation {
ReservationId = , 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 + ;
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;
}
} }

用来为我们的单页应用提供数据的增删改查。

3.用Nuget添加Juqery,Bootstrap,Knockoutjs。

Install-Package jquery –version 1.10.
Install-Package bootstrap –version 3.0.
Install-Package knockoutjs –version 3.0.

4.创建Api控制器。

  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);
} [HttpPost]
public Reservation PostReservation(Reservation item)
{
return repo.Add(item);
}
[HttpPut]
public bool PutReservation(Reservation item)
{
return repo.Update(item);
}
public void DeleteReservation(int id)
{
repo.Remove(id);
}
}

二、Web Api

这个时候运行访问 /api/web ,chrome下是xml数据,而ie下是json数据,这是因为Web api会根据请求中的http header 的接收类型来返回客户端“喜欢”的格式。

Web api的路由和普通的mvc路由略有不同,它是可以根据请求的操作类型(get,post,delete)以及参数来匹配对应的方法。

 public static void Register(HttpConfiguration config)
{
// Web API 配置和服务
// Web API 路由
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}

如果你还是想使用api/{controller}/{action}/{id}这样的方式,加个action就好

 config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);

WebApi的一些基本原理和操作大家可以移步 小牛之路 webapi  这里我就不再赘述了。 该文也详细的介绍了使用ajax和Ajax.BeginForm的方法来进行交互。

三、Knockout

SPA将更多的任务移到了浏览器上,这样就需要保存应用的状态,需要可以更新的数据模型,一系列用户可以通过UI元素触发的逻辑操作。这样就意味着需要一个微型的MVC模式。而微软为此提供的类库就是Knockout,准确的说,Knockout是MVVM模式,下面就用它完成一个简单的应用。

1.在Shared文件夹中新增_Layout.cshtml,并引用相关类库。

<!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" />
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/bootstrap.min.js"></script>
<script src="~/Scripts/knockout-3.0.0.js"></script>
</head>
<body>
@RenderSection("Body")
</body>
@RenderSection("Scripts",false)
</html>

2.添加HomeController。添加Index视图。

 public class HomeController : Controller
{
public ViewResult Index()
{
return View();
}
}

控制器不需要其他的视图和逻辑处理,因为这些都交给浏览器去处理了。

Knockout的感觉和WPF很像,简单的说,就是定义好模型和事件,绑定到元素上面就行了。我们先实现所有数据的读取和删除。

1)定义模型

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

ko.observableArray()相当于是集合.如果是单个模型,用ko.observable("")。例如:name: ko.observable("")。而reservations相当于是model的一个属性。

2)定义异步方法。

   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) {
sendAjaxRequest("DELETE", function () {
getAllItems();
}, item.ReservationId);
}

这两个方法只和数据相关,不用我们去关心dom的处理。

3)应用绑定。

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

全部脚本:

@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) {
sendAjaxRequest("DELETE", function () {
getAllItems();
}, item.ReservationId);
}
$(document).ready(function () {
getAllItems();
ko.applyBindings(model);
}); </script>
}

4)绑定到元素

绑定的表达式是

data-bind="type: expression" 

绑定到一个集合:

<tbody data-bind="foreach: model.reservations">

绑定到对象的属性

<td data-bind="text: ReservationId"></td>

绑定到一个事件

<button class="btn btn-xs btn-primary"  data-bind="click: removeItem">Remove</button>

那全部的html的如下:

@section Body {
<div id="summary" class="section panel panel-primary">
@*@Html.Partial("Summary", Model)*@
<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>
}

运行起来:

点击Remove马上删除。平滑的异步体验。但是我们看removeitem方法就有点奇怪,是删除之后又调用了一次getAllitems()来更新数据,那么我们也可以直接更新model

...
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);
}
...

但上面的KO语法有点奇怪,model.reservations()[i].ReservationId,调用集合中的某个对象时,要像函数一样调用。KO试图去维持标准的JavaScript语法,但还有些奇怪的地方,初次使用有些困惑,那也只能说,你会很快习惯的。

当然还有同学有疑问了。这和@Razor的方式有什么不同?主要有三个不同。

1.模型数据不再包含在html元素中了。换句话说就是在html加载之后,浏览器才使用异步请求更新数据。用F12打开,看不到任何数据。只有绑定的表达式。

2.当视图被渲染的时候,数据在浏览器端得到处理,而不是在服务器上。

换做是Razor语法,上面的语句就是这样:

<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>

这都是在服务器端由Razor引擎渲染。

3.绑定的数据是“活”的,意味着数据模型的改变会马上反应到有foreach和text绑定的内容中,F12,进入Console执行:model.reservations.pop() 取出一条数据,我们发现数据马上更新了。

所以Web api只需要提供基本的增删改查就行了。而且在前端也让程序员少写了很多dom操作,而dom操作是脚本最为繁琐和容易出错的地方。接下来继续完善这个demo。我们加入编辑部分。修改model如下

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

增加了一个editor,包含name和Location两个属性。

修改sendAjaxRequest 方法 增加传输的数据对象。

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

增加一个编辑的方法:

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

这个方法的意思是,当提交的时候,用post方式将editor的name和Location传递给api,web api会根据post方式自动匹配到PostReservation方法(MVC的模型绑定会自动讲这两个值生成一个Reservation对象)。然后将返回的Reservation对象添加到model中。这个时候页面就会更新。

html:

<div id="editor" class="section panel panel-primary">
<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>

然后运行,我们添加数据之后,马上更新。相对于传统的方法,我们需要获取dom中的数据,然后提交到后台,然后得到返回的数据更新table。

进而我们可以控制元素的显示。修改model。添加displaysummary。初始化为一个bool值。

      var model = {
reservations: ko.observableArray(),
editor: {
name: ko.observable(""),//相当于两个变量。
location: ko.observable(""),
},
displaySummary: ko.observable(true)
};

添加隐藏方法,修改handleCreateClick

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
});
}

绑定到元素: 这里用到了if表达式。

<div id="summary" class="section panel panel-primary"
data-bind="if: model.displaySummary">
<div class="panel-heading">Reservation Summary</div>
...
</div>

运行,就可以自在切换了。

小结:以上只是Knockout的简单介绍,但是和Web api配合起来确实感觉不错。更多Knockout的api请参考官网。knockoutjs 。 希望本文对你有帮助。

demo 下载:SPA

参考书籍:Apress Pro Asp.Net MVC5  此书在我的读书群里面有下载,q群:452450927。欢迎爱读书,爱分享的朋友加入。

参考文章:小牛之路 web api

【读书笔记】WebApi 和 SPA(单页应用)--knockout的使用的更多相关文章

  1. 前端 SPA 单页应用数据统计解决方案 (ReactJS / VueJS)

    前端 SPA 单页应用数据统计解决方案 (ReactJS / VueJS) 一.百度统计的代码: UV PV 统计方式可能存在问题 在 SPA 的前端项目中 数据统计,往往就是一个比较麻烦的事情,Re ...

  2. [vue]spa单页开发及vue-router基础

    - 了解spa页面跳转方式:(2种) spa: 单页跳转方式 开发(hash模式): https://www.baidu.com/#2313213 生产(h5利于seo): history.pushS ...

  3. Javascript 与 SPA单页Web富应用

    书单推荐 # <单页Web应用:JavaScript从前端到后端> http://download.csdn.net/detail/epubitbook/8720475 # <MVC ...

  4. 【MySQL 读书笔记】SQL 刷脏页可能造成数据库抖动

    开始今天读书笔记之前我觉得需要回顾一下当我们在更新一条数据的时候做了什么. 因为 WAL 技术的存在,所以当我们执行一条更新语句的时候是先写日志,后写磁盘的.当我们在内存中写入了 redolog 之后 ...

  5. 大熊君学习html5系列之------History API(SPA单页应用的必备)

    一,开篇分析 Hi,大家好!大熊君又和大家见面了,(*^__^*) 嘻嘻……,这系列文章主要是学习Html5相关的知识点,以学习API知识点为入口,由浅入深的引入实例, 让大家一步一步的体会" ...

  6. 基于VUE的SPA单页应用开发-加载性能篇

    1.基于异步数据的vue页面刷新 先看看基于异步数据的vue页面刷新后,都发生了啥- 如图所示: 图1 基于异步数据的vue页面刷新 网络请求图 步骤如下: step1:请求页面: step2:请求页 ...

  7. 前端学习之路之SPA(单页应用)设计原理

    SPA设计 1.设计意义 前后端分离 减轻服务器压力 增强用户体验 Prerender预渲染优化SEO 前后端分离:前端做业务逻辑,后端处理数据和接口,耦合度减少,开发效率提高. 减轻服务器压力:一个 ...

  8. 大熊君学习html5系列之------History API(SPA单页应用的必备------重构完结版)

    一,开篇分析 Hi,大家好!大熊君又和大家见面了,(*^__^*) 嘻嘻……,这系列文章主要是学习Html5相关的知识点,以学习API知识点为入口,由浅入深的引入实例, 让大家一步一步的体会" ...

  9. 《Linux内核设计与实现》读书笔记(十六)- 页高速缓存和页回写

    好久没有更新了... 主要内容: 缓存简介 页高速缓存 页回写 1. 缓存简介 在编程中,缓存是很常见也很有效的一种提高程序性能的机制. linux内核也不例外,为了提高I/O性能,也引入了缓存机制, ...

随机推荐

  1. odoo10 费用报销

    odo10 对费用报销进行了改进,恢复了 8.0 及之前版本具有的 单个报销包含多个 明细内容的功能. 使用步骤大致如下: 根据管理需要设立 相应的科目和分析帐户 科目 分析帐户 建立费用目录 员工录 ...

  2. UI设计中px、pt、ppi、dpi、dp、sp之间的关系

    UI设计中px.pt.ppi.dpi.dp.sp之间的关系 武汉AAA数字艺术教育 2015-07-24 14:19:50 职业教育 pi px 阅读(3398) 评论(0) 声明:本文由入驻搜狐公众 ...

  3. HTML5之tabindex属性

    1 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title&g ...

  4. javascript里的继承

    js里面继承的方式, 1. 类式继承,通过构造函数的继承 function extend(subClass, superClass){ var F = function(){}; F.prototyp ...

  5. H5前端性能测试快速入门

    前言 说到H5测试,对于做WEB测试的同学来说再熟悉不过了,它包括页H5功能测试,前端性能测试,浏览器兼容性能测试,以及服务端性能测试.那本文谈到的则是H5前端性能测试,并希望通过阅读本文后,能够知道 ...

  6. second class

    nothing no very good. 1.look at shuruo.html,after display:inline-block; li's width as the content; S ...

  7. [转]MySQL关键性能监控(QPS/TPS)

    原文链接:http://www.cnblogs.com/chenty/p/5191777.html 工作中尝尝会遇到各种数据库性能调优,除了查看某条SQL执行时间长短外,还需要对系统的整体处理能力有更 ...

  8. template学习一函数模板

    要点: 1.模板参数在实体化的时候不能自动类型转换,只有非模板函数才可以 例如: int max(int,int); template <class T> T max(T,T); 在调用的 ...

  9. Linux系统安装VMware tools

    VMware tools还是挺有用的,在两个不同系统随意拖拽文件,甚至文字的复制粘贴,功能强大. 废话不多说... 虽然VMware tools的安装帮助文档写的不错但是实际操作中发现,...还是有坑 ...

  10. ubuntu上搭建review board代码评审站点

    Reviewboard是一个开源个人可以免费使用的代码评审框架,貌似现在有越来越多的公司也开始使用reviewboard作为公司的代码评审工具. 今天早上试了一下,搭建过程非常方便简单,按照网页提示即 ...