刚好在一个项目里面需要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功能简介
支持基于Http verb (GET, POST, PUT, DELETE)的CRUD (create, retrieve, update, delete)操作,通过不同的http动作表达不同的含义,这样就不需要暴露多个API来支持这些基本操作。
请求的回复通过Http Status Code表达不同含义,并且客户端可以通过Accept header来与服务器协商格式,例如你希望服务器返回JSON格式还是XML格式。
请求的回复格式支持 JSON,XML,并且可以扩展添加其他格式。
原生支持OData。
支持Self-host或者IIS host。
支持大多数MVC功能,例如Routing/Controller/Action Result/Filter/Model Builder/IOC Container/Dependency Injection。
REST风格服务简介
REST表示表述性状态转移,它代表的是运行在HTTP上的一个简单的无状态的架构,每一个唯一URL代表一个资源。在创建RESTful服务时,应遵循四个基本的设计原则:
使用HTTP方法(动词),使用统一的方式来获取资源(交互的统一接口),即检索资源使用GET,创建资源使用POST, 更新资源使用PUT / PATCH,删除资源使用DELETE。
与资源的交互是无状态的, 因此由客户端发起的每个请求应当包括HTTP请求的所有参数,上下文信息和所需服务器返回数据数据类型等。
资源标识应通过URI来定义,简单来说应该是只使用URI来完成服务器与客户端和资源之间的交互。这些URI可以看作一个RESTful服务提供的接口。
支持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>© @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"> </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,熟练的话,一个小时可以完成这个项目。
其实,我们所有的努力都希望代码写得少一点,效率提高多一点。
作者:凉风有兴
链接:https://www.jianshu.com/p/b677ea6bfb7c
热门评论
你的图都看不到