ASP.NET WEBAPI制作TODO小应用,前端由VUE.JS、axios构建

刚好在一个项目里面需要WebAPI 和VUE,于是我就将大抵的思路屡一下,做了一个TODO小例程出来。后端使用ASP.NET WebAPI----即可以对接各种客户端(浏览器,移动设备),构建http服务的框架。WebAPI利用Http协议的各个方面来表达服务(例如 URI/request response header/caching/versioning/content format),因此就省掉很多配置。也就是可以通过PC端、移动端甚至是移动端的APP来访问WEBAPI,通过API对数据库进行操作。

最终完成效果如下图:通过Add Todo按钮可以增加任务,任务完成了可以点击完成,任务栏的字体将会出现删除线以表示完成,但也可以通过点击“撤销”来撤销完成,当然也可以删除任务。上面还有一个数字的,是总计未完成事件。那么我们将来完成这个简单的应用。

首先,我们来完成后端Web API。

Web API功能简介

  1. 支持基于Http verb (GET, POST, PUT, DELETE)的CRUD (create, retrieve, update, delete)操作,通过不同的http动作表达不同的含义,这样就不需要暴露多个API来支持这些基本操作。
  2. 请求的回复通过Http Status Code表达不同含义,并且客户端可以通过Accept header来与服务器协商格式,例如你希望服务器返回JSON格式还是XML格式。
  3. 请求的回复格式支持 JSON,XML,并且可以扩展添加其他格式。
  4. 原生支持OData。
  5. 支持Self-host或者IIS host。
  6. 支持大多数MVC功能,例如Routing/Controller/Action Result/Filter/Model Builder/IOC Container/Dependency Injection。

REST风格服务简介

REST表示表述性状态转移,它代表的是运行在HTTP上的一个简单的无状态的架构,每一个唯一URL代表一个资源。在创建RESTful服务时,应遵循四个基本的设计原则:

  1. 使用HTTP方法(动词),使用统一的方式来获取资源(交互的统一接口),即检索资源使用GET,创建资源使用POST, 更新资源使用PUT / PATCH,删除资源使用DELETE。
  2. 与资源的交互是无状态的, 因此由客户端发起的每个请求应当包括HTTP请求的所有参数,上下文信息和所需服务器返回数据数据类型等。
  3. 资源标识应通过URI来定义,简单来说应该是只使用URI来完成服务器与客户端和资源之间的交互。这些URI可以看作一个RESTful服务提供的接口。
  4. 支持JSON或/和XML等多种格式作为数据传输格式。

我的开发工具搭配比较诡异,使用E5服务器CPU、WIN10 64操作系统,IDE用VS2015却配着SQL2008R2,为什么SQL版本这么低,不知道,问我的客户去。

废话不说了,打开VS2015,新建一个项目再说--我把项目命名为WebAPI2Todo:

mark

注意项目选择模板为Web API,核心引用将MVC和Web API全部点上。

在这个项目中使用NuGet软件包管理器安装Entity Framework 6、Jquery、Bootstrap、vue,右键单击“Model”文件夹,新建一个数据类TodoList.cs:

 public class TodoList
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public bool Task { get; set; }
    }

右键单击Controllers文件夹,新建一个包含视图的控制器:

mark

模型类选择我们刚刚生成的TodoList,如

mark
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using WebAPI2Todo.Models;

namespace WebAPI2Todo.Controllers
{
    public class TodoListsController : Controller
    {
        private WebAPI2TodoContext db = new WebAPI2TodoContext();

        // GET: TodoLists
        public ActionResult Index()
        {
            return View(db.TodoLists.ToList());
        }

        // GET: TodoLists/Details/5
        public ActionResult Details(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            TodoList todoList = db.TodoLists.Find(id);
            if (todoList == null)
            {
                return HttpNotFound();
            }
            return View(todoList);
        }

        // GET: TodoLists/Create
        public ActionResult Create()
        {
            return View();
        }

        // POST: TodoLists/Create
        // 为了防止“过多发布”攻击,请启用要绑定到的特定属性,有关 
        // 详细信息,请参阅 http://go.microsoft.com/fwlink/?LinkId=317598。
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind(Include = "ID,Name,Task")] TodoList todoList)
        {
            if (ModelState.IsValid)
            {
                db.TodoLists.Add(todoList);
                db.SaveChanges();
                return RedirectToAction("Index");
            }

            return View(todoList);
        }

        // GET: TodoLists/Edit/5
        public ActionResult Edit(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            TodoList todoList = db.TodoLists.Find(id);
            if (todoList == null)
            {
                return HttpNotFound();
            }
            return View(todoList);
        }

        // POST: TodoLists/Edit/5
        // 为了防止“过多发布”攻击,请启用要绑定到的特定属性,有关 
        // 详细信息,请参阅 http://go.microsoft.com/fwlink/?LinkId=317598。
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit([Bind(Include = "ID,Name,Task")] TodoList todoList)
        {
            if (ModelState.IsValid)
            {
                db.Entry(todoList).State = EntityState.Modified;
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(todoList);
        }

        // GET: TodoLists/Delete/5
        public ActionResult Delete(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            TodoList todoList = db.TodoLists.Find(id);
            if (todoList == null)
            {
                return HttpNotFound();
            }
            return View(todoList);
        }

        // POST: TodoLists/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteConfirmed(int id)
        {
            TodoList todoList = db.TodoLists.Find(id);
            db.TodoLists.Remove(todoList);
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                db.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}

这样就默认生成了一个带有CRUD的视图,非常快捷,但是这个时候你若是去运行Create,在提交到数据库的时候肯定会出错,并且有提示 SQL Network Interfaces, error: 52 - 无法定位 Local Database Runtime 安装 类似的提示,我就奇怪了,刚刚我已经定位了一个数据服务器了,为什么还会提示本地错误。

打开Web.config一看,原来在生成控制器的时候它就默认生成了一个本地的数据库连接

 <add name="WebAPI2TodoContext" connectionString="Data Source=(localdb)\MSSQLLocalDB; Initial Catalog=WebAPI2TodoContext-20170617024457; Integrated Security=True; MultipleActiveResultSets=True; AttachDbFilename=|DataDirectory|WebAPI2TodoContext-20170617024457.mdf"
      providerName="System.Data.SqlClient" />

把它修改一下即可:

   <add name="WebAPI2TodoContext" connectionString="Data Source=.;Initial Catalog=WebAPI2Todo;User ID=sa;Password=WINdows2008"
      providerName="System.Data.SqlClient" />

你可以在MSSQL中新建一个空数据库,取名为WebAPI2Todo,然后打开VS2015的程序包管理器控制台,依次输入以下命令:

1.Enable-Migrations -ContextTypeName WebAPI2Todo.Models.WebAPI2TodoContext
2.add-migration Initial
3.update-database

现在运行Create吧,相信我不会让你失望的。至此,一个带有视图的CRUD 操作的项目已经基本完成,但我们是想要用VUE通过API进行数据操作,通过VUE的双向绑定数据功能进行开发。那么就要继续向下看了。

在根目录下创建新文件夹 Interface ,然后再新增一个接口 ITodoListRepository.cs ,代码如下:

 interface ITodoListRepository
    {
        IEnumerable<TodoList> GetAll();
        TodoList Get(int id);
        TodoList Add(TodoList item);
        bool Update(TodoList item);
        bool Delete(int id);
    }

在根目录下新建文件夹 Repositories ,新建类TodoListRepository.cs ,以此来实现使用 Entity Framework 进行数据库的CRUD 的操作方法。

    public class TodoListRepository: ITodoListRepository
    {
        private WebAPI2TodoContext db = new WebAPI2TodoContext();

        public IEnumerable<TodoList> GetAll()
        {
             return db.TodoLists.ToList();
        }

        public TodoList Get(int id)
        {
            
            return db.TodoLists.Find(id);
        }

        public TodoList Add(TodoList item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }
            
            db.TodoLists.Add(item);
            db.SaveChanges();
            return item;
        }

        public bool Update(TodoList item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }
            var todo = db.TodoLists.Single(t => t.ID == item.ID);
            todo.Name = item.Name;
            todo.Task = item.Task;
            db.SaveChanges();
            return true;
        }

        public bool Delete(int id)
        {
            // TO DO : Code to remove the records from database
            TodoList ts = db.TodoLists.Find(id);
            db.TodoLists.Remove(ts);
            db.SaveChanges();
            return true;
        }
    }

右键单击 Controllers 文件夹并添加新控制器,模板选择Web API 2 控制器-空,取名为'TodoController.cs':

    public class TodoController : ApiController
    {
        static readonly ITodoListRepository repository = new TodoListRepository();
        public IEnumerable GetAllTodo()
        {
            return repository.GetAll();
        }

        public TodoList PostTodo(TodoList item)
        {
            return repository.Add(item);
        }

        public IEnumerable PutTodo(int id, TodoList todo)
        {
            todo.ID = id;
            if (repository.Update(todo))
            {
                return repository.GetAll();

            }
            else
            {
                return null;
            }
        }

        public bool DeleteTodo(int id)
        {
            if (repository.Delete(id))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }

基本上,现在后端已经完成了差不多了,下面我们进行前端开发。

在Home控制器中增加一个Test,右击ActionResult Test()添加视图。(注意在默认模板中加载vue.js:打开Views--Shared--_Layout.cshtml:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
    <link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
    <link href="~/Content/bootstrap.min.css" rel="stylesheet" type="text/css" />
    <script src="~/Scripts/modernizr-2.6.2.js"></script>
    <script src="~/Scripts/vue.js"></script>
</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                </ul>
            </div>
        </div>
    </div>

    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
        </footer>
    </div>

    <script src="~/Scripts/jquery-1.10.2.min.js"></script>
    <script src="~/Scripts/bootstrap.min.js"></script>
</body>
</html>

Test视图加入vue.js代码,我们一个基于本地TODO列表已经完成:

<style>
    .Task {
        text-decoration: line-through;
    }
</style>
<nav class="navbar"></nav>
<div class="container" id="app">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">My Tasks</div>
                <div class="panel-body">
                    <h1>My Todos({{ remaining }})</h1>
                    <ul class="list-group">
                        <li class="list-group-item" :class="{'Task':todo.Task}"  v-for="(todo,index) in todos">
                            {{todo.Name}}
                            <button class="btn btn-success btn-xs pull-right" v-on:click="toggleTodo(index)" v-if="todo.Task">撤销</button>
                            <button class="btn btn-primary btn-xs pull-right" v-on:click="toggleTodo(index)" v-else>完成</button>
                            <span class="pull-right">&nbsp;</span> 
                            <button class="btn btn-warning btn-xs pull-right" v-on:click="deleteTodo(index)">删除</button>
</li>
                    </ul>
                    <form v-on:submit.prevent="addTodo(newTodo)">
                        <div class="form-group">
                            <input type="text" v-model="newTodo.Name" class="form-control" placeholder="输入新事件" />
                        </div>
                        <div class="form-group">
                            <button class="btn btn-success" type="submit">Add Todo</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
    new Vue({
        el: "#app",
        data: {
            todos: [
                { id: 1, Name: 'Learn Vue.js', Task: true },
                { id: 2, Name: '吃宵夜', Task: false }
            ],
            newTodo: { id: null, Name: '',Task:false }
        },
        computed: {
            remaining: function () {
                return this.todos.filter(function (todo) {
                    return !todo.Task;
                }).length;
            }
        },
        methods: {
            addTodo(newTodo) {
                this.todos.push(newTodo);
                this.newTodo = { id: null, Name: '', Task: false }
            },
            deleteTodo(index) {
                this.todos.splice(index,1)
            },
            toggleTodo(index) {
                this.todos[index].Task = !this.todos[index].Task;
            }
            
        }
    })
</script>

效果如图:

mark

可以看出,Vue.js用了非常少的代码量写出了一个本地的TODO应用,但这完全是本地的,你做任何的增加、删除都改变不了什么,刷新一下又从初始值开始。

那么,首先,加入axios:

  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>

然后在new Vue 上面加入Vue.prototype.$http = axios;

全部代码如下:

<script>
    Vue.prototype.$http = axios;
    new Vue({
        el: "#app",
        data: {
            num:0,//计算未完成总数
            todos: '',//空的数据
            newTodo: { id: null, Name: '', Task: false }//新建默认数据
        },
        mounted() {
            this.getData('/api/todo/GetAllTodo');//通过this.getData的URL取得API数据
          
        },
        computed: {
            remaining: function () {
                return this.num;
             }
        },//这里我们声明了一个计算属性 remaining,通过返回this.num的值做为未完成总数
        methods: {//VUE事件处理器
             getData(url){
                this.$http.get(url).then((response) => {
                    this.todos = response.data;
                    this.num = this.todos.filter(function (todo) {
                        return todo.Task == false;
                    }).length;
                });
             },//this.getData取得数据库数据,并取得未完成事件总数
            addTodo(newTodo) {
                this.todos.push(newTodo);
                this.num++;
                this.$http.post('/api/todo/PostTodo', newTodo).then(response=>console.log(response));
                this.newTodo = { id: null, Name: '', Task: false }
            },//添加任务,并且将数据post到URL
            deleteTodo(index,id) {
                if (!this.todos[index].Task) {
                    this.num--;
                }
                this.$http.delete('/api/todo/DeleteTodo/' +id).then(response=>console.log(response));
                this.todos.splice(index, 1);
            },//删除任务,如果删除的任务是未完成的,那么将对未完成总数-1
            toggleTodo(index,id) {
                var thistodo = !this.todos[index].Task;
                var thisdata={
                    Name:this.todos[index].Name,
                    Task:thistodo
                };
                this.$http.put('/api/todo/puttodo/'+id,thisdata
                    ).then(response=>console.log(response));
                thistodo ? this.num-- : this.num++;
                this.todos[index].Task = thistodo;
                           }
        },//完成任务或者撤销任务
        
    });
    </script>

加入了WEBAPI连接代码,代码量从30行增加到50行,50行的JavaScript可以完成这一系列的功能已经非常了不起了。

至此,一个简单的web单页应用已经完成,后端使用ASP.NET、数据库使用MSSQL,前端使用Bootstrap、Vue.js、axios,熟练的话,一个小时可以完成这个项目。

其实,我们所有的努力都希望代码写得少一点,效率提高多一点。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容