MSBOOKS

プログラミングとか深層学習とか日常の出来事とか

【勉強メモ】ASP.NET CoreでWeb APIを作成するチュートリアルを試してみた その1

はじめに

仕事で.net coreを使った開発するから勉強しといて!って言われ、python、C、Javadeep learningぐらいしかできない僕がはじめてweb apiを勉強したのでメモ書き程度に残しておきます。今回はwindows10上でVisual studio Community 2019を使いました。参考にしたサイトは↓こちら。ちょっと日本語が怪しい気が…。
docs.microsoft.com

このチュートリアルで実現できること

API:説明

  • GET /api/TodoItems:すべての To Do アイテムを取得します。
  • GET /api/TodoItems/{id}:ID でアイテムを取得します。
  • POST /api/TodoItems:新しいアイテムを追加します。
  • PUT /api/TodoItems/{id}:既存のアイテムを更新します。
  • DELETE /api/TodoItems/{id}:アイテムを削除します


アプリのデザインは↓のような感じらしい。MVC(モデル・ビュー・コントローラー)モデルについては多少の知識があったので、少しは理解できた。データベースから取り出したモデルをコントローラーがクライアント(ビュー)に投げたり、逆にクライアントからrequestを受け取ったりという感じでしょうか。
f:id:msteacher:20210804214119p:plain

まあ多分超簡単に言えば、データベースにtodoリストを登録したり更新したりするって感じでしょうか。

APIのテスト

プロジェクトの作成を言われるがまま作成して、apiテストをやるとちゃんとjson形式で結果が返ってきた。これがなんの意味なのかは不明…。

[{"date":"2021-08-05T21:48:33.466844+09:00","temperatureC":18,"temperatureF":64,"summary":"Freezing"},{"date":"2021-08-06T21:48:33.4679942+09:00","temperatureC":34,"temperatureF":93,"summary":"Mild"},{"date":"2021-08-07T21:48:33.4679962+09:00","temperatureC":37,"temperatureF":98,"summary":"Bracing"},{"date":"2021-08-08T21:48:33.4679965+09:00","temperatureC":28,"temperatureF":82,"summary":"Bracing"},{"date":"2021-08-09T21:48:33.4679966+09:00","temperatureC":-15,"temperatureF":6,"summary":"Chilly"}]

モデルクラスの追加

C#のコードを指示通りコピペすると、コントローラーのスキャフォールディングのところで躓くので、以下のようにnamespaceを定義して書いた。C#は全く触ったことなかったので書き方がわからない…、調べたらnamespaceは名前空間と呼ぶらしい。namespaceを区別することで、同じ名前のクラスを使い分けることを可能になるらしく、同じクラス名でもここを分ければ共存(?)できるっぽい?
・TodoItem.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public bool IsComplete { get; set; }
    }
}

Idはリレーショナル データベース内の一意のキーとして機能するらしく主キー的な存在なのかな?ついでにnameはtodoのやることの名前で、IsCompleteは完成したかどうかを判定するbool変数っぽいです。

データベース コンテキストの追加

コンテキストってなんぞや…って思い調べたら、"コンテキストとは、「一連の処理中で引き継いでいく情報の集合体」のイメージです。異なるスレッドやタスクを跨ぐこともできます。"だそうで、ただのデータの集まりのイメージで、その集まりがビューとかに流れていくものなのかな?
・TodoContext.cs

using Microsoft.EntityFrameworkCore;

namespace TodoApi.Models
{
    public class TodoContext : DbContext
    {
        public TodoContext(DbContextOptions<TodoContext> options)
            : base(options)
        {
        }

        public DbSet<TodoItem> TodoItems { get; set; }
    }
}

なんですかここの書き方は…。見慣れない書き方に動揺してしまいマス。少しずつc#にもなれていかねば…。get; set;はゲッターとセッターなのかな?超簡単にかけるやん…。

データベース コンテキストの登録

なんか修正点だけ強調表示されていますが、バージョンの違いかわかりませんが、他の部分も違ってなんか動かなかったので、全削除してまるまるコピー。
・Startup.cs

// Unused usings removed
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<TodoContext>(opt =>
               opt.UseInMemoryDatabase("TodoList"));
            services.AddControllers();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

ConfigureServicesメソッドのservices.AddDbContextはなんとなくDBにこのコンテキストを入れるってイメージできますが、Configureメソッドの方はなにやってるんだかちんぷんかんぷん(泣)
"データベース コンテキストがメモリ内データベースを使用することを指定します。"って意味も正直よくわからないけど、コンテキストがメモリ内のデータベースを通るよってことでいいのかな。

コントローラーのスキャフォールディング

またわけわかんない単語が…。"スキャフォールディングとは、データモデルとなる型を元に、いわゆるCRUD(Create/Read/Upadate/Delete)と呼ばれる追加、読込、変更、削除を行う画面とそのコードを自動で生成する機能のことです。"だそうで、つまりデータベースの追加とかを自動生成してくれるってことっぽいね。これでコンテキスト(TodoContext)をコントローラーに挿入できたっぽい。

PostTodoItem 作成メソッドの確認

"nameof 演算子を使用するために、PostTodoItem で return ステートメントを置き換えます。"とは…。nameof演算子がわからないので調べたら、"nameof演算子を使うと、名前空間/型/メソッド/プロパティ/変数などの単純な名前(=名前空間やクラス名などで修飾されていない名前)の文字列が取得できる。"だそうです。なんで使うかは後に少し書いてありました。
CreatedAtActionメソッドは帰り値として成功するとHTTP201状態コードが返ってくるそう。応答にLocationヘッダーが追加されて返ってくるそうで、Locationヘッダーには、新しく作成されたTo DoアイテムのURIが入っているらしい。
"GetTodoItemアクションを参照して Location ヘッダーの URI を作成します。 C# の nameof キーワードを使って、CreatedAtAction 呼び出しでアクション名をハードコーディングすることを回避しています。"…とは?ちょっとこれは本当に意味がわからなかったので、そういうものとしておきます…。

・TodoItemsController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    {
        private readonly TodoContext _context;

        public TodoItemsController(TodoContext context)
        {
            _context = context;
        }

        // GET: api/TodoItems
        [HttpGet]
        public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
        {
            return await _context.TodoItems.ToListAsync();
        }

        // GET: api/TodoItems/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;
        }

        // PUT: api/TodoItems/5
        // To protect from overposting attacks, enable the specific properties you want to bind to, for
        // more details, see https://go.microsoft.com/fwlink/?linkid=2123754.
        [HttpPut("{id}")]
        public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
        {
            if (id != todoItem.Id)
            {
                return BadRequest();
            }

            _context.Entry(todoItem).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!TodoItemExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return NoContent();
        }

        // POST: api/TodoItems
        // To protect from overposting attacks, enable the specific properties you want to bind to, for
        // more details, see https://go.microsoft.com/fwlink/?linkid=2123754.
        [HttpPost]
        public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
        {
            _context.TodoItems.Add(todoItem);
            await _context.SaveChangesAsync();

            //return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem);
            return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem);
        }

        // DELETE: api/TodoItems/5
        [HttpDelete("{id}")]
        public async Task<ActionResult<TodoItem>> DeleteTodoItem(long id)
        {
            var todoItem = await _context.TodoItems.FindAsync(id);
            if (todoItem == null)
            {
                return NotFound();
            }

            _context.TodoItems.Remove(todoItem);
            await _context.SaveChangesAsync();

            return todoItem;
        }

        private bool TodoItemExists(long id)
        {
            return _context.TodoItems.Any(e => e.Id == id);
        }
    }
}

Postman を使用した PostTodoItem のテスト

www.postman.com
Postmanって使ったことなかったんですが、調べたら"API 開発をする際にアクセス、モック作成、テストなどを個人やプロジェクトで使うことができる API クライアント/サーバーです。"だそうで、web開発未経験の僕からするとちょっと何言ってるかわからないですって感じですが、APIのテストが簡単にできるってイメージですかね。
手順としてはvisual studioの実行部分のIIS ExpressをTodoApiに変更して押してサーバーを立ち上げた状態にします。たぶんDBが立ち上がるのでここで入れたり消したりできるようになってるんでしょう。
f:id:msteacher:20210804223839p:plain

postmanの+ボタンを押してリクエストを送るところを表示します。あとは手順通りにPOSTを実行するとちゃんと登録できたっぽいです。
f:id:msteacher:20210804225539p:plain

Postman で Location ヘッダーの URI をテストする

先程の結果をみるとHeaderが5件返ってきており、Locationのところに"https://localhost:5001/api/TodoItems/1"がありました。
HTTPメソッドをGETにしてURIをこれにすると
f:id:msteacher:20210804230015p:plain
ちゃんとさっき登録したデータが返ってきました。よかった。

おわりに

ここまでで半分くらいですが、知らないことばかりで調べながらだと本当に苦労しました…。いつかこれがスイスイ書けるようになる日がくるのでしょうか…。
ちなみにGET メソッドの確認やその先の内容についてはまた次回勉強しながら書きます。それではまたよろしくおねがいします。