使用 ASP.NET Core 建立原生行動應用程式的後端服務
行動裝置應用程式可以與 ASP.NET Core 後端服務通訊。 如需如何從 iOS 模擬器和 Android 模擬器連線到本機 Web 服務的指示,請參閱從 iOS 模擬器和 Android 模擬器連線到本機 Web 服務。
原生行動應用程式範例
本教學課程會示範如何使用 ASP.NET Core 建立後端服務,以支援原生行動裝置應用程式。 它會使用 Xamarin.Forms TodoRest 應用程式作為其原生用戶端,其中包含適用於 Android、iOS 和 Windows 的個別原生用戶端。 您可以遵循連結的教學課程來建立原生應用程式 (並安裝必要的免費 Xamarin 工具),並下載 Xamarin 範例解決方案。 Xamarin 範例包含了 ASP.NET Core Web API 服務專案。該專案已由此文章的 ASP.NET Core 應用程式取代 (但用戶端不需要進行任何變更)。
功能
TodoREST 應用程式支援列出、新增、刪除和更新待辦項目。 每個項目都有識別碼、名稱、記事和標示其是否已完成的屬性。
在上一個範例中,專案的主要檢視會列出每個項目的名稱,並使用勾選記號表示其是否已完成。
點選 +
圖示便會開啟 [新增項目] 對話方塊:
在主要清單畫面中點選項目,便會開啟 [編輯] 對話方塊,讓使用者修改項目的名稱、記事及完成狀態,或是刪除該項目:
若要自行測試您在下一個章節建立的,於您的電腦上執行的 ASP.NET Core 應用程式,更新應用程式的 RestUrl
常數。
Android 模擬器不會在本機電腦上執行,並使用回送 IP (10.0.2.2) 來與本機電腦通訊。 利用 Xamarin.Essentials DeviceInfo 來偵測哪個作業系統正在執行以使用正確的 URL。
瀏覽至 TodoREST
專案,然後開啟 Constants.cs
檔案。 Constants.cs
檔案包含下列組態。
using Xamarin.Essentials;
using Xamarin.Forms;
namespace TodoREST
{
public static class Constants
{
// URL of REST service
//public static string RestUrl = "https://YOURPROJECT.azurewebsites.net:8081/api/todoitems/{0}";
// URL of REST service (Android does not use localhost)
// Use http cleartext for local deployment. Change to https for production
public static string RestUrl = DeviceInfo.Platform == DevicePlatform.Android ? "http://10.0.2.2:5000/api/todoitems/{0}" : "http://localhost:5000/api/todoitems/{0}";
}
}
您可以選擇性地將 Web 服務部署到 Azure 等雲端服務,並更新 RestUrl
。
建立 ASP.NET Core 專案
在 Visual Studio 中建立新的 ASP.NET Core Web 應用程式。 選擇 Web API 範本。 將專案命名為 TodoAPI。
應用程式應回應對連接埠 5000 的所有要求,包括行動用戶端的純文字 HTTP 流量。 更新 Startup.cs
,因此 UseHttpsRedirection 不會在開發環境中執行:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// For mobile apps, allow http traffic.
app.UseHttpsRedirection();
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
注意
直接執行應用程式,而不是在 IIS Express 背後執行。 IIS Express 預設會忽略非本機要求。 在命令提示字元中執行 dotnet run,或從 Visual Studio 工具列的 [偵錯目標] 下拉式功能表中選擇應用程式名稱設定檔。
新增一個模型類別來代表待辦項目。 使用 [Required]
屬性來標記必要欄位:
using System.ComponentModel.DataAnnotations;
namespace TodoAPI.Models
{
public class TodoItem
{
[Required]
public string ID { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Notes { get; set; }
public bool Done { get; set; }
}
}
API 方法需要一些方式才能操作資料。 使用原始 Xamarin 範本使用的相同 ITodoRepository
介面:
using System.Collections.Generic;
using TodoAPI.Models;
namespace TodoAPI.Interfaces
{
public interface ITodoRepository
{
bool DoesItemExist(string id);
IEnumerable<TodoItem> All { get; }
TodoItem Find(string id);
void Insert(TodoItem item);
void Update(TodoItem item);
void Delete(string id);
}
}
此範例中,實作只會使用私用項目集合:
using System.Collections.Generic;
using System.Linq;
using TodoAPI.Interfaces;
using TodoAPI.Models;
namespace TodoAPI.Services
{
public class TodoRepository : ITodoRepository
{
private List<TodoItem> _todoList;
public TodoRepository()
{
InitializeData();
}
public IEnumerable<TodoItem> All
{
get { return _todoList; }
}
public bool DoesItemExist(string id)
{
return _todoList.Any(item => item.ID == id);
}
public TodoItem Find(string id)
{
return _todoList.FirstOrDefault(item => item.ID == id);
}
public void Insert(TodoItem item)
{
_todoList.Add(item);
}
public void Update(TodoItem item)
{
var todoItem = this.Find(item.ID);
var index = _todoList.IndexOf(todoItem);
_todoList.RemoveAt(index);
_todoList.Insert(index, item);
}
public void Delete(string id)
{
_todoList.Remove(this.Find(id));
}
private void InitializeData()
{
_todoList = new List<TodoItem>();
var todoItem1 = new TodoItem
{
ID = "6bb8a868-dba1-4f1a-93b7-24ebce87e243",
Name = "Learn app development",
Notes = "Take Microsoft Learn Courses",
Done = true
};
var todoItem2 = new TodoItem
{
ID = "b94afb54-a1cb-4313-8af3-b7511551b33b",
Name = "Develop apps",
Notes = "Use Visual Studio and Visual Studio for Mac",
Done = false
};
var todoItem3 = new TodoItem
{
ID = "ecfa6f80-3671-4911-aabe-63cc442c1ecf",
Name = "Publish apps",
Notes = "All app stores",
Done = false,
};
_todoList.Add(todoItem1);
_todoList.Add(todoItem2);
_todoList.Add(todoItem3);
}
}
}
在 Startup.cs
中設定實作:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ITodoRepository, TodoRepository>();
services.AddControllers();
}
建立控制器
將新的控制器新增到專案,TodoItemsController。 它應該繼承自 ControllerBase。 新增一個 Route
屬性來表示控制器會處理所有傳送到以 api/todoitems
開頭之路徑的要求。 路由中的 [controller]
權杖會由控制器的名稱取代 (省略 Controller
尾碼),特別在全域路由時將會非常有幫助。 深入了解路由。
控制器需要一個 ITodoRepository
才能發揮功能,請在控制器的建構函式中要求此類型的執行個體。 在執行階段,會使用相依性插入的架構支援來提供這個執行個體。
[ApiController]
[Route("api/[controller]")]
public class TodoItemsController : ControllerBase
{
private readonly ITodoRepository _todoRepository;
public TodoItemsController(ITodoRepository todoRepository)
{
_todoRepository = todoRepository;
}
這個 API 支援四種不同的 HTTP 動詞命令來在資料來源上執行 CRUD (建立、讀取、更新、刪除) 作業。 其中最簡單的便是「讀取」作業,其對應到 HTTP GET
要求。
使用 curl 測試 API
您可以使用各種工具測試 API 方法。 本教學課程會使用下列開放原始碼命令行工具:
- curl: 使用各種通訊協定傳輸資料,包括 HTTP 和 HTTPS。 本教學課程會使用 curl 透過 HTTP 方法
GET
、POST
、PUT
和DELETE
呼叫 API。 - jq:本教學課程中使用的 JSON 處理器,可用來將 JSON 資料格式化,就能從 API 回應中輕鬆讀取資料。
安裝 curl 和 jq
curl 會預先安裝在 macOS 上,並直接在 macOS 終端機應用程式中使用。 如需安裝 curl 的詳細資訊,請參閱 官方 curl 網站。
可以從終端機的 Homebrew 那邊安裝 jq:
若尚未安裝,請使用下列命令安裝 Homebrew:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
請遵循安裝程式所提供的指示。
使用 Homebrew 搭配下列命令安裝 jq:
brew install jq
讀取項目
您可以藉由對 List
方法傳送 GET 要求來要求項目清單。 List
方法上的 [HttpGet]
屬性表示這項動作應該僅用於處理 GET 要求。 此動作的路由為在控制器上指定的路由。 您不見得需要使用動作名稱作為路由的一部分。 您只需要確認每個動作都有一個唯一且明確的路由。 路由屬性可套用在控制器和方法層級上,以建置特定的路由。
[HttpGet]
public IActionResult List()
{
return Ok(_todoRepository.All);
}
上一個 curl 命令包含下列元件:
-v
: 啟用詳細資訊模式,提供有關 HTTP 回應的詳細資訊,並對於 API 測試和疑難排解非常有用。-X GET
: 指定對要求使用 HTTPGET
方法。 雖然 curl 通常可以推斷預期的 HTTP 方法,但此選項會讓它明確。'http://localhost:5000/api/todoitems/'
: 這是要求的目標 URL。 在此執行個體中,它是一個 REST API 端點。| jq
: 此區段與 curl 沒有直接關係。 管道|
是殼層運算子,會從其左邊的命令取得輸出,並將輸出「管線傳送」至右邊的命令。jq
是個命令列 JSON 處理器。 雖然並非必要,但jq
讓傳回的 JSON 資料更容易讀取。
List
方法會傳回一個 200 OK 回應碼,以及所有已序列化為 JSON 的待辦項目:
[
{
"id": "6bb8a868-dba1-4f1a-93b7-24ebce87e243",
"name": "Learn app development",
"notes": "Take Microsoft Learn Courses",
"done": true
},
{
"id": "b94afb54-a1cb-4313-8af3-b7511551b33b",
"name": "Develop apps",
"notes": "Use Visual Studio and Visual Studio for Mac",
"done": false
},
{
"id": "ecfa6f80-3671-4911-aabe-63cc442c1ecf",
"name": "Publish apps",
"notes": "All app stores",
"done": false
}
]
建立項目
根據慣例,建立新的資料項目會對應到 HTTP POST
指令動詞。 Create
方法套用了一個 [HttpPost]
屬性,並且接受一個 TodoItem
執行個體。 由於 item
引數會在 POST 主體中傳遞,此參數會指定 [FromBody]
屬性。
在方法中,項目會經過有效性的檢查,以及是否先前存在過資料存放區中。若沒有發現任何問題,則該項目便會新增至存放庫中。 檢查 ModelState.IsValid
會執行 模型驗證,並且應該要在每個接受使用者輸入的 API 方法中執行。
[HttpPost]
public IActionResult Create([FromBody]TodoItem item)
{
try
{
if (item == null || !ModelState.IsValid)
{
return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
}
bool itemExists = _todoRepository.DoesItemExist(item.ID);
if (itemExists)
{
return StatusCode(StatusCodes.Status409Conflict, ErrorCode.TodoItemIDInUse.ToString());
}
_todoRepository.Insert(item);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotCreateItem.ToString());
}
return Ok(item);
}
此範例使用包含了傳遞給行動裝置用戶端錯誤代碼的 enum
:
public enum ErrorCode
{
TodoItemNameAndNotesRequired,
TodoItemIDInUse,
RecordNotFound,
CouldNotCreateItem,
CouldNotUpdateItem,
CouldNotDeleteItem
}
在終端機中,使用 POST
指令動詞呼叫下列 curl 命令,並在要求本文中以 JSON 格式提供新物件,以測試新增新的項目。
curl -v -X POST 'http://localhost:5000/api/todoitems/' \
--header 'Content-Type: application/json' \
--data '{
"id": "6bb8b868-dba1-4f1a-93b7-24ebce87e243",
"name": "A Test Item",
"notes": "asdf",
"done": false
}' | jq
上一個 curl 命令包含下列選項:
--header 'Content-Type: application/json'
:將Content-Type
標頭設定為application/json
,表示要求本文包含 JSON 資料。--data '{...}'
: 在要求本文中傳送指定的資料。
方法會在回應中傳回新建立的項目。
更新項目
會使用 HTTP PUT
要求來完成修改記錄。 除了這項變更之外,Edit
方法與 Create
方法基本上都完全相同。 若找不到記錄,Edit
動作會傳回一個 NotFound
(404) 回應。
[HttpPut]
public IActionResult Edit([FromBody] TodoItem item)
{
try
{
if (item == null || !ModelState.IsValid)
{
return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
}
var existingItem = _todoRepository.Find(item.ID);
if (existingItem == null)
{
return NotFound(ErrorCode.RecordNotFound.ToString());
}
_todoRepository.Update(item);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotUpdateItem.ToString());
}
return NoContent();
}
若要使用 curl 進行測試,請將動詞變更為 PUT
。 在要求主體中指定更新物件資料。
curl -v -X PUT 'http://localhost:5000/api/todoitems/' \
--header 'Content-Type: application/json' \
--data '{
"id": "6bb8b868-dba1-4f1a-93b7-24ebce87e243",
"name": "A Test Item",
"notes": "asdf",
"done": true
}' | jq
這個方法會在成功時傳回一個 NoContent
(204) 回應 (為了與先前存在的 API 保持一致)。
刪除項目
刪除記錄是藉由對服務提出 DELETE
要求,並傳遞要刪除之項目的識別碼來完成。 與更新一樣,不存在項目的要求會收到 NotFound
回應。 否則,成功的要求會傳回一個 NoContent
(204) 回應。
[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
try
{
var item = _todoRepository.Find(id);
if (item == null)
{
return NotFound(ErrorCode.RecordNotFound.ToString());
}
_todoRepository.Delete(id);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotDeleteItem.ToString());
}
return NoContent();
}
將 HTTP 指令動詞變更為 DELETE
,並在 URL 結尾附加要刪除的資料物件識別碼,以使用 curl 進行測試。 要求主體中不需要任何項目。
curl -v -X DELETE 'http://localhost:5000/api/todoitems/6bb8b868-dba1-4f1a-93b7-24ebce87e243'
防止過度張貼
目前範例應用程式會公開整個 TodoItem
物件。 生產應用程式通常會限制輸入的資料,並使用模型的子集傳回。 背後有多個原因,而安全性是主要原因。 模型的子集通常稱為資料傳輸物件 (DTO)、輸入模型或檢視模型。 本文中會使用 DTO。
DTO 可用來:
- 防止過度張貼。
- 隱藏用戶端不應該檢視的屬性。
- 省略一些屬性,以減少承載大小。
- 包含巢狀物件的壓平合併物件圖形。 壓平合併物件圖形對用戶端會更方便。
若要示範 DTO 方法,請參閱防止過度張貼
常見 Web API 慣例
當您為您的應用程式開發後端服務時,您可能會想要想出一個一致的慣例組或原則來處理跨領域問題。 例如,在先前顯示的服務中,找不到的特定記錄要求會收到 NotFound
回應,而不是 BadRequest
回應。 同樣地,傳送到此服務的命令在傳遞到模型繫結類型時,也會檢查 ModelState.IsValid
並針對無效的模型類型傳回 BadRequest
。
當您找到了適用於您 API 的常見原則時,您通常可以在篩選條件中封裝它。 深入了解如何在 ASP.NET Core MVC 應用程式中封裝常見的 API 原則。