• 【读书笔记】WebApi 和 SPA(单页应用)--knockout的使用


          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;
                }
            }
    
        }
    View Code

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

         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>
    }
    View Code

    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>
    }
    View Code

    运行起来:

     

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

     

             

  • 相关阅读:
    零碎
    Python学习 day03 (续day02)
    Python学习 day02
    Python学习 Day1
    线性表——顺序表
    纠删码简介
    小数转化为分数
    C语言多线程操作
    转载:RAMCloud
    转载:全球级分布式数据库Google Spanner
  • 原文地址:https://www.cnblogs.com/stoneniqiu/p/4603850.html
Copyright © 2020-2023  润新知