【读书笔记】WebApi 和 SPA(单页应用)--knockout的使用
2015-06-29 00:48 stoneniqiu 阅读(3363) 评论(3) 编辑 收藏 举报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 = 1, ClientName = "Adam", Location = "Board Room"}, new Reservation { ReservationId = 2, ClientName = "Jacqui", Location = "Lecture Hall"}, new Reservation { ReservationId = 3, 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 + 1; 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.2 Install-Package bootstrap –version 3.0.0 Install-Package knockoutjs –version 3.0.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
你的关注和支持是我写作的最大动力~
书山有路群:452450927