ASP.NET Core Web API ハンズオン 参加しました

今夜はこちらのオンラインハンズオンに参加しました.

csharp-tokyo.connpass.com

一度ASP.NET Coreに触れてみたかったこと,Live Shareによるハンズオン形式を体験してみたかった.
久しぶりにWeb系に触れる…(2週間ぶりだけど).

【追記 2020/05/23 開催者のブログを記載しました.】

tech.tanaka733.net

Live Share

docs.microsoft.com

Live Share では、使っているプログラミング言語や構築しているアプリの種類に関係なく、リアルタイムで他のユーザーと共同で編集やデバッグができます。 現在のプロジェクトを瞬時に安全に共有したり、共同でデバッグ セッションを開始したり、ターミナル インスタンスを共有したり、localhost の Web アプリを転送したり、音声通話などを行うことができます。

従来のペア プログラミングとは異なり、開発者は Visual Studio Live Share で、各自の好みのエディター設定 (テーマ、キー バインドなど) を維持しながら、独自のカーソルを持ち連携することができます。 これにより、次から次へとシームレスに移行し、独自にアイデアやタスクを調べることができます。 この共同作業をしながら独立性を保てる機能により、実際会って行うコラボレーションのようなエクスペリエンスが得られます。

初めて使いました.リアルタイムに進行役のコーディングを追うことができます.
ターミナルインスタンス共有とある通り,PowerShellタブも共有できるという凄さ.(つまり,ログとかも見られるということ.)
今回音声通話機能はLive Shareの機能を使わず,TeamsとSlackを利用しております.

Hands-on

基本的にこちらに書かれている資料に沿って進めてました.

github.com

VSCodeで参加したけど,.NET系をやるならVSCodeではなく,VSにすべきだったなあと反省.
VSCodeでも最後まで出来たのですが,ハンズオン進行役がVSで行っていたので,こちらに合わせるべきでした.

Source Code etc.

Program.cs

.NET Coreにおけるエントリーポイント.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace WebAPIHandsOn
{
    public class Program
    {
        // エントリーポイント
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            // Host起動
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

Startup.cs

ASP.NET Core起動時設定を行う.ConfigureServicesでDI設定を行う.
ASP.NET CoreはDI前提で構築されている.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

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

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // DI追加設定したい物を置く.
            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            // 環境設定
            // 開発環境の場合,例外のスタックトレースを表示させる
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            // リダイレクト設定
            app.UseHttpsRedirection();
            // ルーティング設定
            app.UseRouting();
            // 認証・認可設定
            app.UseAuthorization();
            // エンドポイント設定
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

Controllers

MVCのController.
APIのControllerはControllerBaseを拡張させて,ApiController属性を付与させる.

今回のハンズオンで追加したControllerは以下の2つ.

Lab1Controller.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Docs.Samples;
using Microsoft.Extensions.Configuration;

namespace WebAPIHandsOn.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class Lab1Controller : ControllerBase
    {
        private readonly IConfiguration configuration;

        public Lab1Controller(IConfiguration configuration)
        {
            this.configuration = configuration;
            var env = configuration["ASPNETCORE_ENVIRONMENT"];
        }

        [HttpGet]   // GET /api/lab1
        public IActionResult ListProducts()
        {
            return ControllerContext.MyDisplayRouteInfo();
        }

        [HttpGet("{id}")]   // GET /api/lab1/xyz
        public IActionResult GetProduct(string id)
        {
            return ControllerContext.MyDisplayRouteInfo(id);
        }

        [HttpGet("int/{id:int}")] // GET /api/lab1/int/3
        public IActionResult GetIntProduct(int id)
        {
            return ControllerContext.MyDisplayRouteInfo(id);
        }

        [HttpGet("int2/{id}")]  // GET /api/lab1/int2/3
        public IActionResult GetInt2Product(int id)
        {
            return ControllerContext.MyDisplayRouteInfo(id);
        }
    }
}

Lab2Controller.cs

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

namespace WebAPIHandsOn.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class Lab2Controller : ControllerBase
    {
        private static HashSet<Pet> petsInMemoryStore = new HashSet<Pet>();

        //TODOステータスコードの明示
        //TODO FromBodyは推論するので省略可能
        [HttpPost]
        [ProducesResponseType(StatusCodes.Status201Created)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        public ActionResult<Pet> Create(/* [FromBody]*/Pet pet)
        {
            if (pet.Name?.Length <= 2)
            {
                return BadRequest("名前は3文字以上!");
            }
            pet.Id = petsInMemoryStore.Any() ?
                     petsInMemoryStore.Max(p => p.Id) + 1 : 1;
            petsInMemoryStore.Add(pet);

            return CreatedAtAction(nameof(GetById), new { id = pet.Id }, pet);
        }

        [HttpGet("{id:int}")]
        public ActionResult<Pet> GetById(int id)
        {
            return petsInMemoryStore.FirstOrDefault(p => p.Id == id);
        }

        [HttpGet("getbyname")]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public ActionResult<List<Pet>> GetByName(
                [FromQuery] string name)
        {
            List<Pet> pets = null;

            if (name != null)
            {
                if (name.Length <= 2)
                {
                    return BadRequest("名前は3文字以上!");
                }
                pets = petsInMemoryStore.Where(p => p.Name == name).ToList();
            }
            else
            {
                pets = petsInMemoryStore.ToList();
            }

            if (!pets.Any())
            {
                return NotFound("いないよ");
            }

            return pets;
        }
    }
}

ちなみにPets.csは以下な感じ.
こちらはMVCでいうModelだと思う.

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

namespace WebAPIHandsOn
{
    public class Pet
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

少しハマった所

主にVSCode設定関連ですね..NET Core開発だとVSにすべきというところ.

Rick.Docs.Samples.RouteInfo の Nuget インストール

以下のように,VSCodeのターミナルに叩く.

dotnet add package Rick.Docs.Samples.RouteInfo --version 1.0.0.4

Invoke-RestMethodでSSL/LTS接続エラー

こんなエラーが出た.

Invoke-RestMethod : 接続が切断されました: SSL/TLS のセキュリティで保護されているチャネルに対する信頼関係を確立できませんでした。

httpsの場合にはSSLサーバ証明書のチェックをしており,自己証明書の使用しているとエラーが出る.
これの場合の対処法としては,以下のサイトで自力でググって解決.

zassinojunin.jp

対処法③を利用して,VSCodePowerShellターミナルで以下を叩いてから,Invoke-RestMethodを実行したらできました.

add-type @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy

感想

.NET Core WEB API 完全に理解した.
過去にWeb開発(Javaだけど)してなかったら,各々どういう機能かわからないままコーディングしてた説.
ルーティング設定,リダイレクト,認証・認可,GET・POST,DI,MVCjson設定とか….
色々知らないといけないWeb開発ってやっぱり難しい…と改めて認識する.
色々知らなくていい開発なんてないだろボケ

収穫としてはLive Shareがどんな感じか体験できたことですね.遠隔ペアプロやコードレビューに使えそうだ.