行動裝置應用程式可以與 ASP.NET Core 後端服務通訊。 如需從 iOS 模擬器和 Android 模擬器連線本機 Web 服務的指示,請參閱 從 Android 模擬器和 iOS 模擬器連線到本機 Web 服務,。
範例原生行動應用程式
本教學課程會示範如何使用 ASP.NET Core 建立後端服務,以支援原生行動裝置應用程式。 它會使用 .NET MAUI 應用程式 作為其原生用戶端。 此範例包含 ASP.NET Core Web API 服務專案,本文說明如何建置。
在 Android 智慧型手機上運行的 
功能
TodoREST 應用程式 支援列出、新增、刪除及更新待辦事項。 每個專案都有標識碼、名稱、附註和屬性,指出是否已完成。
在上一個範例中,專案的主要檢視會列出每個項目的名稱,並使用勾選記號表示其是否已完成。
點選 + 圖示會流覽至 [新增專案] 頁面:
點選主頁面上的專案會巡覽至編輯頁面,其中可以修改專案的名稱、筆記和完成設定,也可以刪除專案:
若要針對下一節中建立的 ASP.NET Core 應用程式進行測試,如果您將其裝載在網路上,請更新應用程式的 RestUrl 常數。 否則,應用程式會與裝載於本機電腦上的 ASP.NET Core 應用程式通訊。
Android 模擬器不會在本機電腦上執行,並使用回送 IP (10.0.2.2) 來與本機電腦通訊。 使用 .NET MAUI的 DeviceInfo 類別來偵測應用程式執行中的作系統,以使用正確的 URL。
瀏覽至 TodoREST 專案,然後開啟 Constants.cs 檔案。
Constants.cs 檔案包含下列組態。
namespace TodoREST
{
public static class Constants
{
// URL of REST service
//public static string RestUrl = "https://dotnetmauitodorest.azurewebsites.net/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 LocalhostUrl = DeviceInfo.Platform == DevicePlatform.Android ? "10.0.2.2" : "localhost";
public static string Scheme = "https"; // or http
public static string Port = "5001";
public static string RestUrl = $"{Scheme}://{LocalhostUrl}:{Port}/api/todoitems/{{0}}";
}
}
您可以選擇性地將 Web 服務部署到 Azure 等雲端服務,並更新 RestUrl。
建立 ASP.NET Core 專案
在 Visual Studio 中建立新的 ASP.NET Core Web 應用程式。 選擇 Web API 範本。 將專案命名為 TodoAPI。
應用程式應該回應透過 HTTPS 對埠 5001 發出的所有要求。
注意
直接執行應用程式,而不是在 IIS Express 背後執行。 IIS Express 預設會忽略非本機要求。 在命令提示字元中執行 dotnet run,或從 Visual Studio 工具列的 [偵錯目標] 下拉式功能表中選擇應用程式名稱設定檔。
新增模型類別來代表 todo 項目。 使用 [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 方法需要定義才能使用數據。 使用範例使用的相同 ITodoRepository 介面:
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 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 Code",
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);
}
}
}
在 Program.cs 中設定實作:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddSingleton<TodoAPI.Interfaces.ITodoRepository, TodoAPI.Services.TodoRepository>();
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
建立控制器
將新的控制器新增到專案,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 要求來要求項目清單。
[HttpGet] 方法上的 List 屬性表示這項動作應該僅用於處理 GET 要求。 此動作的路由為在控制器上指定的路由。 您不一定需要將動作名稱用作路由的一部分。 您只需要確認每個動作都有一個唯一且明確的路由。 路由屬性可套用在控制器和方法層級上,以建置特定的路由。
[HttpGet]
public IActionResult List()
{
return Ok(_todoRepository.All);
}
上一個 curl 命令包含下列元件:
-
-v: 啟用詳細資訊模式,提供有關 HTTP 回應的詳細資訊,並對於 API 測試和疑難排解非常有用。 -
-X GET: 指定對要求使用 HTTPGET方法。 雖然 curl 通常可以推斷預期的 HTTP 方法,但此選項會讓它明確。 -
'https://localhost:5001/api/todoitems/': 這是要求的目標 URL。 在此情況下,它是一個 REST API 端點。 -
| jq: 此區段與 curl 沒有直接關係。 管道|是一種將左邊命令的輸出「傳遞」到右邊命令的 shell 運算子。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 Code",
"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 'https://localhost:5001/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 'https://localhost:5001/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 'https://localhost:5001/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 原則。