本教程介紹使用 ASP.NET Core 構(gòu)建 Web API 的基礎(chǔ)知識(shí)。
在本教程中,你將了解:
在結(jié)束時(shí),你會(huì)獲得可以管理存儲(chǔ)在關(guān)系數(shù)據(jù)庫中的“待辦事項(xiàng)”的 Web API。
本教程將創(chuàng)建以下 API:
API | 說明 | 請(qǐng)求正文 | 響應(yīng)正文 |
---|---|---|---|
GET /api/todo | 獲取所有待辦事項(xiàng) | None | 待辦事項(xiàng)的數(shù)組 |
GET /api/todo/{id} | 按 ID 獲取項(xiàng) | None | 待辦事項(xiàng) |
POST /api/todo | 添加新項(xiàng) | 待辦事項(xiàng) | 待辦事項(xiàng) |
PUT /api/todo/{id} | 更新現(xiàn)有項(xiàng) | 待辦事項(xiàng) | None |
DELETE /api/todo/{id} | 刪除項(xiàng) | None | None |
下圖顯示了應(yīng)用的設(shè)計(jì)。
項(xiàng)目模板會(huì)創(chuàng)建 values API。 從瀏覽器調(diào)用 Get 方法以測試應(yīng)用。
按 Ctrl+F5 運(yùn)行應(yīng)用。 Visual Studio 啟動(dòng)瀏覽器并導(dǎo)航到 https://localhost:<port>/api/values
,其中 <port>
是隨機(jī)選擇的端口號(hào)。
如果出現(xiàn)詢問是否應(yīng)信任 IIS Express 證書的對(duì)話框,則選擇“是”。 在接下來出現(xiàn)的“安全警告”對(duì)話框中,選擇“是”。
會(huì)返回以下 JSON:
JSON
["value1","value2"]
模型是一組表示應(yīng)用管理的數(shù)據(jù)的類。 此應(yīng)用的模型是單個(gè) TodoItem 類。
在“解決方案資源管理器”中,右鍵單擊項(xiàng)目。 選擇“添加” > “新建文件夾”。 將文件夾命名為“Models”。
右鍵單擊“Models”文件夾,然后選擇“添加” > “類”。 將類命名為 TodoItem,然后選擇“添加”。
將模板代碼替換為以下代碼:
C#
namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
}
Id 屬性用作關(guān)系數(shù)據(jù)庫中的唯一鍵。
模型類可位于項(xiàng)目的任意位置,但按照慣例會(huì)使用 Models 文件夾。
數(shù)據(jù)庫上下文是為數(shù)據(jù)模型協(xié)調(diào) Entity Framework 功能的主類。 此類由 Microsoft.EntityFrameworkCore.DbContext 類派生而來。
在 ASP.NET Core 中,服務(wù)(如數(shù)據(jù)庫上下文)必須向依賴關(guān)系注入 (DI) 容器進(jìn)行注冊(cè)。 該容器向控制器提供服務(wù)。
使用以下突出顯示的代碼更新 Startup.cs:
C#
// Unused usings removed
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using TodoApi.Models;
namespace TodoApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the
//container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP
//request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for
// production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
}
}
前面的代碼:
右鍵單擊 Controllers 文件夾。
選擇 添加 > 新建項(xiàng)。
在“添加新項(xiàng)”對(duì)話框中,選擇“API 控制器類”模板。
將類命名為 TodoController,然后選擇“添加”。
前面的代碼:
若要提供檢索待辦事項(xiàng)的 API,請(qǐng)將以下方法添加到 TodoController 類中:
C#
// GET: api/Todo
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
{
return await _context.TodoItems.ToListAsync();
}
// GET: api/Todo/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return todoItem;
}
這些方法實(shí)現(xiàn)兩個(gè) GET 終結(jié)點(diǎn):
通過從瀏覽器調(diào)用兩個(gè)終結(jié)點(diǎn)來測試應(yīng)用。 例如:
以下 HTTP 響應(yīng)通過調(diào)用 GetTodoItems 來生成:
JSON
[
{
"id": 1,
"name": "Item1",
"isComplete": false
}
]
[HttpGet] 屬性表示響應(yīng) HTTP GET 請(qǐng)求的方法。 每個(gè)方法的 URL 路徑構(gòu)造如下所示:
在下面的 GetTodoItem 方法中,"{id}" 是待辦事項(xiàng)的唯一標(biāo)識(shí)符的占位符變量。 調(diào)用 GetTodoItem 時(shí),URL 中 "{id}" 的值會(huì)在 id 參數(shù)中提供給方法。
C#
// GET: api/Todo/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return todoItem;
}
GetTodoItems 和 GetTodoItem 方法的返回類型是 ActionResult<T> 類型。 ASP.NET Core 自動(dòng)將對(duì)象序列化為 JSON,并將 JSON 寫入響應(yīng)消息的正文中。 在假設(shè)沒有未經(jīng)處理的異常的情況下,此返回類型的響應(yīng)代碼為 200。 未經(jīng)處理的異常將轉(zhuǎn)換為 5xx 錯(cuò)誤。
ActionResult 返回類型可以表示大范圍的 HTTP 狀態(tài)代碼。 例如,GetTodoItem 可以返回兩個(gè)不同的狀態(tài)值:
本教程使用 Postman 測試 Web API。
添加以下 PostTodoItem 方法:
C#
// POST: api/Todo
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem item)
{
_context.TodoItems.Add(item);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetTodoItem), new { id = item.Id }, item);
}
正如 [HttpPost] 屬性所指示,前面的代碼是 HTTP POST 方法。 該方法從 HTTP 請(qǐng)求正文獲取待辦事項(xiàng)的值。
CreatedAtAction 方法:
添加以下 PutTodoItem 方法:
C#
// PUT: api/Todo/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem item)
{
if (id != item.Id)
{
return BadRequest();
}
_context.Entry(item).State = EntityState.Modified;
await _context.SaveChangesAsync();
return NoContent();
}
PutTodoItem 與 PostTodoItem 類似,但是使用的是 HTTP PUT。 響應(yīng)是 204(無內(nèi)容)。 根據(jù) HTTP 規(guī)范,PUT 請(qǐng)求需要客戶端發(fā)送整個(gè)更新的實(shí)體,而不僅僅是更改。 若要支持部分更新,請(qǐng)使用 HTTP PATCH。
如果在調(diào)用 PutTodoItem 時(shí)出錯(cuò),請(qǐng)調(diào)用 GET 以確保數(shù)據(jù)庫中有項(xiàng)目。
本示例使用內(nèi)存數(shù)據(jù)庫,每次啟動(dòng)應(yīng)用時(shí)都必須對(duì)其進(jìn)行初始化。 在進(jìn)行 PUT 調(diào)用之前,數(shù)據(jù)庫中必須有一個(gè)項(xiàng)。 調(diào)用 GET 以確保在進(jìn)行 PUT 調(diào)用之前數(shù)據(jù)庫中有一個(gè)項(xiàng)。
更新 id = 1 的待辦事項(xiàng)并將其名稱設(shè)置為“feed fish”:
JSON
{
"ID":1,
"name":"feed fish",
"isComplete":true
}
下圖顯示 Postman 更新:
添加以下 DeleteTodoItem 方法:
C#
// DELETE: api/Todo/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return NoContent();
}
DeleteTodoItem 響應(yīng)是 204(無內(nèi)容)。
使用 Postman 刪除待辦事項(xiàng):
示例應(yīng)用允許刪除所有項(xiàng),但是在刪除最后一項(xiàng)后,模型類構(gòu)造函數(shù)會(huì)在下次調(diào)用 API 時(shí)創(chuàng)建一個(gè)新項(xiàng)。
在本部分中,添加了使用 jQuery 調(diào)用 Web API 的 HTML 頁面。 jQuery 啟動(dòng)請(qǐng)求,并用 API 響應(yīng)中的詳細(xì)信息更新頁面。
配置應(yīng)用提供靜態(tài)文件并啟用默認(rèn)文件映射:
C#
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for
// production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseHttpsRedirection();
app.UseMvc();
}
在項(xiàng)目目錄中創(chuàng)建 wwwroot 文件夾。
將一個(gè)名為 index.html 的 HTML 文件添加到 wwwroot 目錄。 用以下標(biāo)記替代其內(nèi)容:
HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>To-do CRUD</title>
<style>
input[type='submit'], button, [aria-label] {
cursor: pointer;
}
#spoiler {
display: none;
}
table {
font-family: Arial, sans-serif;
border: 1px solid;
border-collapse: collapse;
}
th {
background-color: #0066CC;
color: white;
}
td {
border: 1px solid;
padding: 5px;
}
</style>
</head>
<body>
<h1>To-do CRUD</h1>
<h3>Add</h3>
<form action="javascript:void(0);" method="POST" onsubmit="addItem()">
<input type="text" id="add-name" placeholder="New to-do">
<input type="submit" value="Add">
</form>
<div id="spoiler">
<h3>Edit</h3>
<form class="my-form">
<input type="hidden" id="edit-id">
<input type="checkbox" id="edit-isComplete">
<input type="text" id="edit-name">
<input type="submit" value="Save">
<a onclick="closeInput()" aria-label="Close">✖</a>
</form>
</div>
<p id="counter"></p>
<table>
<tr>
<th>Is Complete</th>
<th>Name</th>
<th></th>
<th></th>
</tr>
<tbody id="todos"></tbody>
</table>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" rel="external nofollow"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="site.js"></script>
</body>
</html>
將名為 site.js 的 JavaScript 文件添加到 wwwroot 目錄。 用以下代碼替代其內(nèi)容:
JavaScript
const uri = "api/todo";
let todos = null;
function getCount(data) {
const el = $("#counter");
let name = "to-do";
if (data) {
if (data > 1) {
name = "to-dos";
}
el.text(data + " " + name);
} else {
el.text("No " + name);
}
}
$(document).ready(function() {
getData();
});
function getData() {
$.ajax({
type: "GET",
url: uri,
cache: false,
success: function(data) {
const tBody = $("#todos");
$(tBody).empty();
getCount(data.length);
$.each(data, function(key, item) {
const tr = $("<tr></tr>")
.append(
$("<td></td>").append(
$("<input/>", {
type: "checkbox",
disabled: true,
checked: item.isComplete
})
)
)
.append($("<td></td>").text(item.name))
.append(
$("<td></td>").append(
$("<button>Edit</button>").on("click", function() {
editItem(item.id);
})
)
)
.append(
$("<td></td>").append(
$("<button>Delete</button>").on("click", function() {
deleteItem(item.id);
})
)
);
tr.appendTo(tBody);
});
todos = data;
}
});
}
function addItem() {
const item = {
name: $("#add-name").val(),
isComplete: false
};
$.ajax({
type: "POST",
accepts: "application/json",
url: uri,
contentType: "application/json",
data: JSON.stringify(item),
error: function(jqXHR, textStatus, errorThrown) {
alert("Something went wrong!");
},
success: function(result) {
getData();
$("#add-name").val("");
}
});
}
function deleteItem(id) {
$.ajax({
url: uri + "/" + id,
type: "DELETE",
success: function(result) {
getData();
}
});
}
function editItem(id) {
$.each(todos, function(key, item) {
if (item.id === id) {
$("#edit-name").val(item.name);
$("#edit-id").val(item.id);
$("#edit-isComplete")[0].checked = item.isComplete;
}
});
$("#spoiler").css({ display: "block" });
}
$(".my-form").on("submit", function() {
const item = {
name: $("#edit-name").val(),
isComplete: $("#edit-isComplete").is(":checked"),
id: $("#edit-id").val()
};
$.ajax({
url: uri + "/" + $("#edit-id").val(),
type: "PUT",
accepts: "application/json",
contentType: "application/json",
data: JSON.stringify(item),
success: function(result) {
getData();
}
});
closeInput();
return false;
});
function closeInput() {
$("#spoiler").css({ display: "none" });
}
可能需要更改 ASP.NET Core 項(xiàng)目的啟動(dòng)設(shè)置,以便對(duì) HTML 頁面進(jìn)行本地測試:
有多種方式可以獲取 jQuery。 在前面的代碼片段中,庫是從 CDN 中加載的。
此示例調(diào)用 API 的所有 CRUD 方法。 以下是 API 調(diào)用的說明。
jQuery ajax 函數(shù)將 GET 請(qǐng)求發(fā)送至 API,這將返回表示待辦事項(xiàng)數(shù)組的 JSON。 如果請(qǐng)求成功,則調(diào)用 success 回調(diào)函數(shù)。 在該回調(diào)中使用待辦事項(xiàng)信息更新 DOM。
JavaScript
$(document).ready(function() {
getData();
});
function getData() {
$.ajax({
type: "GET",
url: uri,
cache: false,
success: function(data) {
const tBody = $("#todos");
$(tBody).empty();
getCount(data.length);
$.each(data, function(key, item) {
const tr = $("<tr></tr>")
.append(
$("<td></td>").append(
$("<input/>", {
type: "checkbox",
disabled: true,
checked: item.isComplete
})
)
)
.append($("<td></td>").text(item.name))
.append(
$("<td></td>").append(
$("<button>Edit</button>").on("click", function() {
editItem(item.id);
})
)
)
.append(
$("<td></td>").append(
$("<button>Delete</button>").on("click", function() {
deleteItem(item.id);
})
)
);
tr.appendTo(tBody);
});
todos = data;
}
});
}
Ajax 函數(shù)發(fā)送 POST,請(qǐng)求正文中包含待辦事項(xiàng)。 將 accepts 和 contentType 選項(xiàng)設(shè)置為 application/json,以便指定接收和發(fā)送的媒體類型。 待辦事項(xiàng)使用 JSON.stringify 轉(zhuǎn)換為 JSON。當(dāng) API 返回成功狀態(tài)的代碼時(shí),將調(diào)用 getData 函數(shù)來更新 HTML 表。
JavaScript
function addItem() {
const item = {
name: $("#add-name").val(),
isComplete: false
};
$.ajax({
type: "POST",
accepts: "application/json",
url: uri,
contentType: "application/json",
data: JSON.stringify(item),
error: function(jqXHR, textStatus, errorThrown) {
alert("Something went wrong!");
},
success: function(result) {
getData();
$("#add-name").val("");
}
});
}
更新待辦事項(xiàng)與添加類似。 url 更改為添加項(xiàng)的唯一標(biāo)識(shí)符,并且 type 為 PUT。
JavaScript
$.ajax({
url: uri + "/" + $("#edit-id").val(),
type: "PUT",
accepts: "application/json",
contentType: "application/json",
data: JSON.stringify(item),
success: function(result) {
getData();
}
});
若要?jiǎng)h除待辦事項(xiàng),請(qǐng)將 AJAX 調(diào)用上的 type 設(shè)為 DELETE 并指定該項(xiàng)在 URL 中的唯一標(biāo)識(shí)符。
JavaScript
$.ajax({
url: uri + "/" + id,
type: "DELETE",
success: function(result) {
getData();
}
});
更多建議: