剛好在一個項目里面需要WebAPI 和VUE俩块,于是我就將大抵的思路屢一下黎休,做了一個TODO小例程出來。后端使用ASP.NET WebAPI----即可以對接各種客戶端(瀏覽器玉凯,移動設(shè)備)势腮,構(gòu)建http服務(wù)的框架。WebAPI利用Http協(xié)議的各個方面來表達服務(wù)(例如 URI/request response header/caching/versioning/content format)漫仆,因此就省掉很多配置捎拯。也就是可以通過PC端、移動端甚至是移動端的APP來訪問WEBAPI盲厌,通過API對數(shù)據(jù)庫進行操作署照。
最終完成效果如下圖:通過Add Todo按鈕可以增加任務(wù)祸泪,任務(wù)完成了可以點擊完成,任務(wù)欄的字體將會出現(xiàn)刪除線以表示完成建芙,但也可以通過點擊“撤銷”來撤銷完成没隘,當(dāng)然也可以刪除任務(wù)。上面還有一個數(shù)字的禁荸,是總計未完成事件右蒲。那么我們將來完成這個簡單的應(yīng)用。
首先赶熟,我們來完成后端Web API瑰妄。
Web API功能簡介
- 支持基于Http verb (GET, POST, PUT, DELETE)的CRUD (create, retrieve, update, delete)操作,通過不同的http動作表達不同的含義钧大,這樣就不需要暴露多個API來支持這些基本操作翰撑。
- 請求的回復(fù)通過Http Status Code表達不同含義,并且客戶端可以通過Accept header來與服務(wù)器協(xié)商格式啊央,例如你希望服務(wù)器返回JSON格式還是XML格式眶诈。
- 請求的回復(fù)格式支持 JSON,XML瓜饥,并且可以擴展添加其他格式逝撬。
- 原生支持OData。
- 支持Self-host或者IIS host乓土。
- 支持大多數(shù)MVC功能宪潮,例如Routing/Controller/Action Result/Filter/Model Builder/IOC Container/Dependency Injection。
REST風(fēng)格服務(wù)簡介
REST表示表述性狀態(tài)轉(zhuǎn)移趣苏,它代表的是運行在HTTP上的一個簡單的無狀態(tài)的架構(gòu)狡相,每一個唯一URL代表一個資源。在創(chuàng)建RESTful服務(wù)時食磕,應(yīng)遵循四個基本的設(shè)計原則:
- 使用HTTP方法(動詞)尽棕,使用統(tǒng)一的方式來獲取資源(交互的統(tǒng)一接口),即檢索資源使用GET彬伦,創(chuàng)建資源使用POST滔悉, 更新資源使用PUT / PATCH,刪除資源使用DELETE单绑。
- 與資源的交互是無狀態(tài)的回官, 因此由客戶端發(fā)起的每個請求應(yīng)當(dāng)包括HTTP請求的所有參數(shù),上下文信息和所需服務(wù)器返回數(shù)據(jù)數(shù)據(jù)類型等搂橙。
- 資源標(biāo)識應(yīng)通過URI來定義歉提,簡單來說應(yīng)該是只使用URI來完成服務(wù)器與客戶端和資源之間的交互。這些URI可以看作一個RESTful服務(wù)提供的接口。
- 支持JSON或/和XML等多種格式作為數(shù)據(jù)傳輸格式唯袄。
我的開發(fā)工具搭配比較詭異弯屈,使用E5服務(wù)器CPU、WIN10 64操作系統(tǒng)恋拷,IDE用VS2015卻配著SQL2008R2,為什么SQL版本這么低厅缺,不知道蔬顾,問我的客戶去。
廢話不說了湘捎,打開VS2015诀豁,新建一個項目再說--我把項目命名為WebAPI2Todo:
注意項目選擇模板為Web API,核心引用將MVC和Web API全部點上。
在這個項目中使用NuGet軟件包管理器安裝Entity Framework 6窥妇、Jquery舷胜、Bootstrap、vue活翩,右鍵單擊“Model”文件夾烹骨,新建一個數(shù)據(jù)類TodoList.cs:
public class TodoList
{
public int ID { get; set; }
public string Name { get; set; }
public bool Task { get; set; }
}
右鍵單擊Controllers文件夾,新建一個包含視圖的控制器:
模型類選擇我們剛剛生成的TodoList材泄,如
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
// 為了防止“過多發(fā)布”攻擊沮焕,請啟用要綁定到的特定屬性,有關(guān)
// 詳細信息拉宗,請參閱 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
// 為了防止“過多發(fā)布”攻擊,請啟用要綁定到的特定屬性旦事,有關(guān)
// 詳細信息魁巩,請參閱 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单料,在提交到數(shù)據(jù)庫的時候肯定會出錯埋凯,并且有提示 SQL Network Interfaces, error: 52 - 無法定位 Local Database Runtime 安裝
類似的提示,我就奇怪了扫尖,剛剛我已經(jīng)定位了一個數(shù)據(jù)服務(wù)器了白对,為什么還會提示本地錯誤。
打開Web.config一看换怖,原來在生成控制器的時候它就默認生成了一個本地的數(shù)據(jù)庫連接
<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中新建一個空數(shù)據(jù)庫甩恼,取名為WebAPI2Todo,然后打開VS2015的程序包管理器控制臺,依次輸入以下命令:
1.Enable-Migrations -ContextTypeName WebAPI2Todo.Models.WebAPI2TodoContext
2.add-migration Initial
3.update-database
現(xiàn)在運行Create吧条摸,相信我不會讓你失望的悦污。至此,一個帶有視圖的CRUD 操作的項目已經(jīng)基本完成钉蒲,但我們是想要用VUE通過API進行數(shù)據(jù)操作切端,通過VUE的雙向綁定數(shù)據(jù)功能進行開發(fā)。那么就要繼續(xù)向下看了顷啼。
在根目錄下創(chuàng)建新文件夾 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 ,以此來實現(xiàn)使用 Entity Framework 進行數(shù)據(jù)庫的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;
}
}
}
基本上躬厌,現(xiàn)在后端已經(jīng)完成了差不多了马昨,下面我們進行前端開發(fā)。
在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列表已經(jīng)完成:
<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>
效果如圖:
可以看出煮嫌,Vue.js用了非常少的代碼量寫出了一個本地的TODO應(yīng)用笛谦,但這完全是本地的,你做任何的增加昌阿、刪除都改變不了什么饥脑,刷新一下又從初始值開始。
那么懦冰,首先灶轰,加入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,//計算未完成總數(shù)
todos: '',//空的數(shù)據(jù)
newTodo: { id: null, Name: '', Task: false }//新建默認數(shù)據(jù)
},
mounted() {
this.getData('/api/todo/GetAllTodo');//通過this.getData的URL取得API數(shù)據(jù)
},
computed: {
remaining: function () {
return this.num;
}
},//這里我們聲明了一個計算屬性 remaining,通過返回this.num的值做為未完成總數(shù)
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取得數(shù)據(jù)庫數(shù)據(jù),并取得未完成事件總數(shù)
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 }
},//添加任務(wù)刷钢,并且將數(shù)據(jù)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);
},//刪除任務(wù)笋颤,如果刪除的任務(wù)是未完成的,那么將對未完成總數(shù)-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;
}
},//完成任務(wù)或者撤銷任務(wù)
});
</script>
加入了WEBAPI連接代碼内地,代碼量從30行增加到50行伴澄,50行的JavaScript可以完成這一系列的功能已經(jīng)非常了不起了。
至此阱缓,一個簡單的web單頁應(yīng)用已經(jīng)完成非凌,后端使用ASP.NET、數(shù)據(jù)庫使用MSSQL荆针,前端使用Bootstrap敞嗡、Vue.js颁糟、axios,熟練的話喉悴,一個小時可以完成這個項目棱貌。
其實,我們所有的努力都希望代碼寫得少一點箕肃,效率提高多一點婚脱。