ASP.NET Core を使用してネイティブ モバイル アプリのバックエンド サービスを作成する

作成者: James Montemagno

モバイル アプリは、ASP.NET Core バックエンド サービスと通信できます。 iOS シミュレーターと Android エミュレーターからローカル Web サービスに接続する手順については、「Connect to Local Web Services from iOS Simulators and Android Emulators」 (iOS シミュレーターと Android エミュレーターからローカル Web サービスに接続する) を参照してください。

サンプル バックエンド サービス コードの表示またはダウンロード

サンプル ネイティブ モバイル アプリ

このチュートリアルでは、ASP.NET Core を使用してネイティブ モバイル アプリをサポートするバックエンド サービスを作成する方法について説明します。 Xamarin Forms TodoRest アプリをネイティブ クライアントとして使用します。これには、Android、iOS、Windows 用個別のネイティブ クライアントが含まれています。 リンクされたチュートリアルに従ってネイティブ アプリを作成し (必要な無料の Xamarin ツールをインストールして)、Xamarin サンプル ソリューションをダウンロードすることができます。 Xamarin サンプルには、ASP.NET Core Web API サービス プロジェクトが含まれています。この記事の ASP.NET Core アプリで、このプロジェクトを置き換えます (クライアント側に変更は必要ありません)。

Android スマートフォン上で動作する To Do Rest アプリケーション

機能

この ToDoREST アプリは、To-Do 項目の一覧表示、追加、削除、更新をサポートしています。 各項目には、ID、名前、メモ、完了したかどうかを示すプロパティがあります。

前の例の中で、項目のメイン ビューには各項目の名前が表示され、それが完了しているかどうかがチェックマークで示されます。

+ アイコンをタップすると、項目の追加ダイアログが開きます。

項目の追加ダイアログ

メイン リスト画面の項目をタップすると、編集ダイアログが開き、項目の名前、メモ、完了の設定を変更したり、項目を削除したりすることができます。

項目の編集ダイアログ

次のセクションで作成する 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}";
    }
}

必要に応じて、Azure などのクラウド サービスに Web サービスをデプロイし、RestUrl を更新できます。

ASP.NET Core プロジェクトの作成

Visual Studio で新しい ASP.NET Core Web アプリケーションを作成します。 Web API テンプレートを選択します。 プロジェクトに TodoAPI という名前を付けます。

Web API プロジェクト テンプレートが選択されている [新しい ASP.NET Core Web アプリケーション] ダイアログ

アプリでは、モバイル クライアントのクリアテキスト HTTP トラフィックを含め、ポート 5000 に対して行われたすべての要求に応答する必要があります。 UseHttpsRedirection が開発で実行されないように Startup.cs を更新します。

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();
    });
}

Note

IIS Express の後ろではなく、直接、アプリを実行します。 IIS Express では、ローカル以外の要求は既定で無視されます。 コマンド プロンプトから dotnet run を実行するか、Visual Studio ツールバーの [デバッグ ターゲット] ドロップダウンからアプリ名のプロファイルを選択します。

To-Do 項目を表すモデル クラスを追加します。 必須フィールドを [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 は、データ ソースに対して CRUD (Create (作成)、Read (読み取り)、Update (更新)、Delete (削除)) 操作を実行する 4 つの異なる HTTP 動詞をサポートしています。 これらのうち最も簡単なのは、HTTP GET 要求に対応する読み取り操作です。

curl を使用して API をテストする

API メソッドは、さまざまなツールを使用してテストできます。 このチュートリアルでは、次のオープン ソースのコマンド ライン ツールを使用します。

  • curl: HTTP や HTTPS などのさまざまなプロトコルを使用してデータを転送します。 このチュートリアルでは curl を使用し、HTTP メソッド GETPOSTPUTDELETE を使用して API を呼び出します。
  • jq: API 応答からの JSON データを書式設定して読みやすくするために、このチュートリアルで使用する JSON プロセッサ。

curl と jq をインストールする

curl は macOS上 にプレインストールされており、macOS ターミナル アプリケーション内で直接使用されます。 curl のインストールについて詳しくは、公式の curl Web サイトを参照してください。

jq はターミナルから、Homebrew を使用してインストールできます。

まだインストールしていない場合は、次のコマンドを使用して 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 としてシリアル化されたすべての Todo 項目を返します。

[
  {
    "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

このメソッドが成功した場合は、既存の API との整合性を保つために、NoContent (204) 応答が返されます。

アイテムを削除する

レコードの削除は、サービスに対して DELETE 要求を行い、削除する項目の ID を渡すことで行われます。 更新と同様に、存在しない項目への要求は 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 の末尾に削除するデータ オブジェクトの ID を追加して、curl でテストします。 要求の本文内には何も必要ありません。

curl -v -X DELETE 'http://localhost:5000/api/todoitems/6bb8b868-dba1-4f1a-93b7-24ebce87e243'

過剰な投稿を防止する

現在、サンプル アプリでは TodoItem オブジェクト全体が公開されています。 通常、運用環境のアプリでは、モデルのサブセットを使用して入力されるデータおよび返されるデータが制限されています。 その背景には複数の理由があり、セキュリティは主なものです。 モデルのサブセットは、通常、データ転送オブジェクト (DTO)、入力モデル、またはビュー モデルと呼ばれます。 この記事では DTO を使用しています。

DTO は次の目的で使用できます。

  • 過剰な投稿を防止する。
  • クライアントが表示しないことになっているプロパティを非表示にする。
  • ペイロード サイズを減らすには、いくつかのプロパティを省略します。
  • 入れ子になったオブジェクトを含むオブジェクト グラフをフラット化する。 フラット化されたオブジェクト グラフは、クライアントにとってより便利になる可能性があります。

DTO アプローチの詳細については、「過剰な投稿を防止する」を参照してください。

一般的な Web API 規約

アプリのバックエンド サービスを開発する際には、横断的関心事を処理するための一貫した一連の規則またはポリシーを考え出す必要があります。 たとえば、前に示したサービス内では、見つからなかった特定のレコードに対する要求は、BadRequest 応答ではなく、NotFound 応答を受け取りました。 同様に、モデルにバインドされた種類で渡されたこのサービスに対するコマンドは、常に ModelState.IsValid を確認し、無効なモデルの種類の場合に BadRequest を返していました。

API の共通ポリシーを特定した場合、通常はそのポリシーをフィルターにカプセル化できます。 詳細については、ASP.NET Core MVC アプリケーションで一般的な API ポリシーをカプセル化する方法に関するページを参照してください。

その他のリソース