使用 ASP.NET Core 建立原生行動應用程式的後端服務

作者:James Montemagno

行動裝置應用程式可以與 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 應用程式取代 (但用戶端不需要進行任何變更)。

To Do Rest 應用程式在 Android 智慧型手機上執行

功能

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

新的 ASP.NET Web 應用程式對話方塊,當中已選取了 Web API 專案範本

應用程式應回應對連接埠 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 方法 GETPOSTPUTDELETE 呼叫 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

如需 Homebrew 和 jq 安裝的詳細資訊,請參閱 Homebrewjq

讀取項目

您可以藉由對 List 方法傳送 GET 要求來要求項目清單。 List 方法上的 [HttpGet] 屬性表示這項動作應該僅用於處理 GET 要求。 此動作的路由為在控制器上指定的路由。 您不見得需要使用動作名稱作為路由的一部分。 您只需要確認每個動作都有一個唯一且明確的路由。 路由屬性可套用在控制器和方法層級上,以建置特定的路由。

[HttpGet]
public IActionResult List()
{
    return Ok(_todoRepository.All);
}

在終端中呼叫下列 curl 命令:

curl -v -X GET 'http://localhost:5000/api/todoitems/' | jq

上一個 curl 命令包含下列元件:

  • -v: 啟用詳細資訊模式,提供有關 HTTP 回應的詳細資訊,並對於 API 測試和疑難排解非常有用。
  • -X GET: 指定對要求使用 HTTP GET 方法。 雖然 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 原則

其他資源