20191009のC#に関する記事は10件です。

Unity玉転がしチュートリアル 2-1.カメラの移動

この記事の対象者

  • Unity入門したい人
  • 最初の一歩が踏み出せない人

OSとか環境とか

  • Windows 10 Pro
  • macOS Mojave
  • Unity 2019.2.8f1
  • Rider 2019.2.2

補足

  • 公式動画にて利用しているのはMacなので、Windowsユーザーはある程度脳内変換して見る事
  • 筆者はWindows、Macの両方の環境で確認。Ubuntuとかでは検証してない。
  • 基本Unityは英語メニューで利用
  • 間違いがあったらツッコミ大歓迎

公式

https://unity3d.com/jp/learn/tutorials/projects/roll-ball-tutorial/moving-camera

カメラの移動

現状のままだとカメラが固定なので、プレイヤーオブジェクトが非常に見づらい
→カメラとプレイヤーオブジェクトを紐付ければ万事解決じゃない?

下準備としてカメラの位置調整

PositionのYを10,Zを-10
RotationのXを45
に調整して、少し上から見下ろす形にする

image.png

Playerの子としてMainCameraを設定

image.png

この状態にすると、Playerオブジェクトが動かした場合(移動や回転)Main Cameraも一緒に移動する
※上記設定後「あー余裕だわー」とPlayして玉を動かすと高速なラートみたいな視点になるので落ち着きましょう

カメラとプレイヤーの関係性

あくまで「プレイヤーの子としてカメラが設定された」だけで、相対的な位置関係は変わっていない
なので
・親のSphereオブジェクトが動く
→回転して移動する
・子のMain Cameraオブジェクトが動く
→親のSphereオブジェクトに連動して同じく「回転」する

なので、Main CameraがSphereの子の状態だと視点がラートになる
カメラとプレイヤーはスクリプトで紐付ける必要がある

カメラへのスクリプトの追加

CameraControllerという名前でスクリプトを追加します
※カメラとスクリプトの紐付けは行っている事

追加するスクリプトは以下

CameraController.cs
using System.Collections;
using UnityEngine;

public class CameraController : MonoBehaviour
{
    public GameObject player;
    private Vector3 offset;

    void Start()
    {
        offset = transform.position - player.transform.position;
    }

    void LateUpdate()
    {
        transform.position = player.transform.position + offset;
    }
}

内容については単純なので割愛

ポイントとしては、毎フレーム処理する時にUpdateを使わずにLateUpdateを利用する事

ライフサイクルだとGame Logicに当たる箇所の

image.png

はじめに呼ばれるのがUpdate
最後に呼ばれるのがLateUpdate
となる

真・カメラとプレイヤーの紐付け

スクリプトを保存して、UnityエディタでCameraControllerのPlayer部分を見ると「None」となっているので紐付け

image.png

紐付ける方法は動画ではPlayerオブジェクトをドラッグしているが、フィールドの右にある小さい○をクリックして
出てくるダイアログで選択してもOK

これでPlayをすると、ボールの動きを追従するカメラの出来上がりとなる

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AutoCad.NET Intersect 列挙型の説明

クラス階層

Autodesk.AutoCAD.DatabaseServices.Intersect Enumeration

定義

[Wrapper("AcDb::Intersect")]
public enum Intersect {
    OnBothOperands, // 0
    ExtendThis,     // 1
    ExtendArgument, // 2
    ExtendBoth      // 3
}

ObjectARX 定義

// AcDb::Intersect Enumeration
enum Intersect {
    kOnBothOperands = 0,
    kExtendThis = 1,
    kExtendArg = 2,
    kExtendBoth = 3
};

説明

定 義 名 説 明
OnBothOperands 両方の図形の延長線上の交点は含めない(実際の交点のみ含める)
ExtendThis この図形の延長線上の交点も含める
ExtendArgument 引数図形の延長線上の交点も含める
ExtendBoth 両方の図形の延長線上の交点も含める
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#によるProperty Based TestingのためのFsCheckの使い方(随時更新)

C#によるProperty Based TestingのためのFsCheckの使い方

GUIアプリ開発にてProperty Based Testing用のライブラリ、FsCheckを利用しました。日本語の記事が少なく、苦労しましたので、自分の備忘録も含めてここにまとめてみたいと思います。

Property Based Testingとは

Property Based Testingとは、ある関数のテストに用いる変数を一般化して、その関数の性質を検証する手法のことです。主に関数型言語の世界で広く使用される概念になります。

Property Based Testingの対となる概念としてExample Based Testing(意訳すると例題による検証)があり、これは具体的な変数値と想定する答えをあらかじめ用意しておき、検証する手法になります。Xunitなどを用いて広く実施されているテストに相当します。

Property Based TestingやExample Based Testingについてはすでに多くの記事がありますので詳細な説明はここでは割愛いたします。詳しくは下記参考記事をご覧ください。

参考記事:
An introduction to property-based testing(英語の記事)

Property Based Testing(日本語の記事)

動作環境

本記事は以下の環境で動作を確認しました。
VisualStudio2017
.Net Framework 4.8

FsCheckとは

Property Based Testingを実現できるF#製のライブラリになります。現在、F#、C#、VBで利用できるようです。
GitHub:https://github.com/fscheck/FsCheck

テストを実施する

「FsCheck」と「FsCheck.Xunit」をNuGetインストールして、usingを用いて呼び出しておきましょう。

UnitTest.cs
using FsCheck;
using FsCheck.Xunit;

テストクラスの作り方・動かし方

例題として以下の関数を検証したいと思います。
参考記事:Property-Based Testing with C#

CalcMethods.cs
public static int Add(int x, int y)
{
     return x + y;
}

記述の方法はXunitと似ています。特徴は判定結果をAssertするのではなく「Property」という型を返すことです。条件式をToPropertyメソッドを使ってProperty型に変換します。
ここでは足し算の性質の一つを検証しています。

UnitTest.cs
[Property]
public Property EqualMultiplyWithTwo(int x)
{
    return (x * 2 == CalcMethods.Add(x, x)).ToProperty();
}

成功した場合

テストを実行してみましょう。下図のように確かにテストを実行できることを確認できました。

test_result_success.PNG

テストに成功した場合のテストケースの分析を行うこともできす。
詳しくはテスト数を確認するをご覧ください。

失敗した場合

わざと失敗するテストケースを書いてみます。

UnitTest.cs
[Property]
public Property EqualMultiplyWithTwo(int x)
{
    return (x * 2 + 1 == CalcMethods.Add(x, x)).ToProperty();
}

実行してみますと、失敗のメッセージと共にOriginalShrunkという値が表示されていると分かります。

image.png

Originalはテストが失敗した時のテストの入力値、Shrunkは対象テストを失敗させることのできる最小の入力値を表しています。Shrunkに表示されたテストケースを考察することで不具合の原因を見つけやすくなります。

FsCheckのTips

テストに名前を付ける(DisplayName)

テストに名前を付けるにはDisplayNameを宣言します。

UnitTest.cs
[Property(DisplayName ="同じ数を足したら二倍になる")]
public Property EqualMultiplyWithTwo(int x)
{
    return (x * 2 == CalcMethods.Add(x, x)).ToProperty();
}

テストプロジェクトを開いてみると、テストケース名が表示されています。

TestExploror.PNG

入力値を制限したい(When)

FsCheckはテスト関数の引数に自動的に値を割り当ててくれます。(デフォルトでは100回ランダムに値を発生させます。)しかし、割り算の検証など特定の数を引数として代入してほしくないときもあります。

テスト対象の関数

CalcMethods.cs
public static double Divide(int x, int y)
{
    return x / y;
}

分母が0になる場合を除いて検証します。

UnitTest.cs
[Property(DisplayName = "割り算の検証")]
public Property CheckDivide(int x, int y)
{
    Func<bool> property = () => x / y == CalcMethods.Divide(x, y);
    return property.When(y != 0);
}

例外を検証する(Prop.thorws)

テスト対象の関数として以下のような関数を用意します。(if文がなくともDivideByZeroExceptionの例外が発生しますが、分かりやすさのため記述しています。)

CalcMethods.cs
public static double DivideWithException(int x, int y)
{
    if (y == 0) { throw new DivideByZeroException("分母が0になっています。"); }

    return x / y;
}

Prop.thorwsと遅延処理を組み合わせて例外を検証できます。

UnitTest.cs
[Property(DisplayName = "割り算の検証(例外処理)")]
public Property CheckDivideWithException(int x)
{
    return Prop.Throws<DivideByZeroException, double>(new Lazy<double>(() => CalcMethods.DivideWithException(x, 0)));
}

テスト数を確認する(Trivial)

Trivialを用いて、特定の条件を満たすテストが何回実行されたのか確認できます。

[Property(DisplayName = "同じ数を足したら二倍になる(テスト数の検証)")]
public Property EqualMultiplyWithTwoCheckCases(int x)
{
    return (x * 2 == CalcMethods.Add(x, x)).Trivial(x > 0);
}

テストエクスプローラにて該当テストの「Output」をクリックすると(下図参照)、条件に該当するテストが何%実施されたのかを確認できます。

TestExploror_2.PNG

TestExploror_3.PNG

他にもClassifyプロパティを用いる複数の条件でテストケースを調べることができます。

テスト失敗時の計算結果を出力する(Label)

テストが失敗した際、計算結果を出力したい場面があります。そんなときに使用するのがLabelプロパティです。(テストが成功した時の出力方法は知りません。。教えてください。)

[Property(DisplayName = "同じ数を足したら二倍になる(関数値の出力)")]
public Property EqualMultiplyWithTwoOutput(int x)
{
    var val1 = x + x + 1;

    return (val1 == CalcMethods.Add(x, x)).Label(String.Format("val1:{0}",val1));
}

参考記事

https://www.codit.eu/blog/property-based-testing-with-c/

https://fscheck.github.io/FsCheck/ja/index.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#でのSQLiteの基本的な使い方

この間C#フォームでちょっとしたツールを作った。その際SQLiteを使ったので、忘れないように基本的な使い方をまとめておく。

インストール

Nuget上でSQLiteを検索するとやたら候補が出てくるが、SQL文を直接実行する場合、System.Data.SQLite.Coreがコンパクトで良さげ。

接続

データベースに接続するにはSQLiteConnectionにconnectionStringという引数を渡してインスタンス化した後Openを呼ぶ。このconnectionStringというのが厄介で、最初てっきりデータベースのパスを渡せば良いものと思い込んでいたのだが、様々なパラメータをセミコロンで区切った文字列を渡す模様。パスもそのパラメータの一つで、Data Source=に続ける。またusingで囲うと勝手にCloseしてくれるので楽。

using (var connection = new SQLiteConnection("Data Source=データベースのパス"))
{
    connection.Open();
}

SQL文を実行する

CreteCommandでSQLiteCommandを作ってCommandTextにSQL文を入れてExcuteNonQueryで実行。自動でコミットされるっぽい。

var command = connection.CreateCommand();
command.CommandText = "CREATE TABLE IF NOT EXISTS racers (" +
    "id INTEGER PRIMARY KEY," +
    "name TEXT," +
    "ability TEXT"
    ")";
command.ExecuteNonQuery();

パラメータが必要なSQL文を実行する

SQL文は所詮文字列なので変数をそのまま結合…させてはいけない。ライブラリが提供するプレースホルダーを使うべし。さもなくばSQLインジェクションで死ぬ。いくつか方法があるのだが、@名前を使うとSQLiteParameterが簡潔な記述で済ませられるのでオヌヌメ。

var command = connection.CreateCommand();
command.CommandText = "INSERT OR IGNORE INTO racers VALUES (" +
    "@id," +
    "@name," +
    "@ability" +
    ")";
command.Parameters.Add(new SQLiteParameter("@id", 1));
command.Parameters.Add(new SQLiteParameter("@name", "チョコボ"));
command.Parameters.Add(new SQLiteParameter("@ability", "ダッシュ"));

コマンドをリセットする

Resetですべてのパラメータがリセットされ、まっさらな状態で再度SQL文を実行できる。

command.Reset()

SELECTで取得した値を取り出す

ExcuteReaderで得られるインスタンスをReadで一レコードずつ読んでいく。

command.CommandText = "SELECT id, name, ability FROM racers";
var reader = command.ExecuteReader())
while (reader.Read())
{
    Console.WriteLine(reader["id"]);
    Console.WriteLine(reader["name"]);
    Console.WriteLine(reader["ability"]);
}

また取り出した値はobject型なのでキャストする必要があるが、単純にキャストすると値がDBNullだった場合にエラーを吐くので、以下のようにキャストすると安全かも。

var id = DBNull.Value.Equals(reader["id"]) ? 0 : (long)reader["id"];

大量のINSERTをトランザクションを使って高速化

BeginTransactionとCommitで重たい処理を囲むと劇的に高速化される。逆に囲まないと遅すぎて日が暮れる。

var transaction = connection.BeginTransaction();
var command = connection.CreateCommand();
for (var i = 0; i < 999999; i++)
{
    command.Reset();
    command.CommandText = "INSERT OR IGNORE INTO racers VALUES (" +
        "@id," +
        "@name," +
        "@ability" +
        ")";
    command.Parameters.Add(new SQLiteParameter("@id", i));
    command.Parameters.Add(new SQLiteParameter("@name", "チョコボ"));
    command.Parameters.Add(new SQLiteParameter("@ability", "ダッシュ"));
    command.ExecuteNonQuery();
}
transaction.Commit();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[随時更新]超初心者がUnityで気を付けること

プログラミング全般

・ちゃんとした変数名、関数名をつけよう。void Test()とかint countとか作っちゃだめだぞ

GameObject関係

・GameObject.Findは重いから使わない(使っても精々Start、Awake系だけ)

Transform関係

・transform.rotationは理解できるまで触らない
Quatanionは複雑な値なので、RotateやeulerAnglesを使おう

・scaleの比率を1:1:1以外にした上で回転させると予想外のことが起きやすい。やめとこう。

・transform.Findで参照できるのは直近の子供だけ
全ての子供を取りたい時はGetComponentsInChildren()を使おう(Tは欲しいクラス。Transformも取れる)。ただし重い。

・子の中の、常に特定の物を取りたいなら、transform.GetChild()を使うとよい

その他

別PCにプレハブを移動させたい時は、Assets/ExportPackageからパッケージにして持っていこう

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity玉転がしチュートリアル 1-2.プレイヤーの移動

この記事の対象者

  • Unity入門したい人
  • 最初の一歩が踏み出せない人

OSとか環境とか

  • Windows 10 Pro
  • macOS Mojave
  • Unity 2019.2.8f1
  • Rider 2019.2.2

補足

  • 公式動画にて利用しているのはMacなので、Windowsユーザーはある程度脳内変換して見る事
  • 筆者はWindows、Macの両方の環境で確認。Ubuntuとかでは検証してない。
  • 基本Unityは英語メニューで利用
  • 間違いがあったらツッコミ大歓迎

公式のリンク

https://unity3d.com/jp/learn/tutorials/projects/roll-ball-tutorial/moving-player

プレイヤーの移動

追加したSphereがキーボードで動く様に調整する

移動時の条件

・ゲームエリア全体を転がる
・壁にぶつかる
・常に接地している(宙に浮かない)
・アイテムに接触したらアイテムを取得する

→物理演算が必要
Rigidbodyコンポーネントをアタッチする必要がある

アタッチする方法

  1. アタッチする対象のゲームオブジェクトを選択(今回はPlayerゲームオブジェクト)
  2. Component>Physics>Rigidbodyを選択

rigidbody.png

上記のようにアタッチされる
アタッチした要素は並び替えが可能となるので、要素が増えたりしたら並び替え等でキレイキレイする

キーボードの入力について

動きを制御するスクリプトをアタッチして実現する

スクリプトを格納するフォルダ「Scripts」を作成し、その中にスクリプトを格納するのが良い

追加方法は何個かあるのだが、今回は「Add Component」から追加する方法が有効
メニューよりNew Scriptという項目から追加
→スクリプトを別で用意しても良いが、そのスクリプトをアタッチする手間が省けるので楽
PlayerControllerという名前でスクリプトを追加

NewScript.png

これでこのPlayerオブジェクトにスクリプトがアタッチされる

スクリプトの記述

基本スクリプトファイルをダブルクリックして紐付いたエディタが起動する

※筆者はRiderを使ってコーディングするので、Riderが起動するが、こだわりがなければVisualStudio2019 Communityあたりが起動する

PlayerController.csの記述方法等については動画参照の事ですが何点か補足

  • 動画でも説明されているが、**公式のドキュメントを読むのは基本

 Unityに限った事ではないので、なんか文字沢山書いてあるなーと思わないで
 公式のドキュメントはちゃんと読みましょう!
 何なら公式ドキュメントの読み方も超丁寧に動画にしてくれている
 Just Do It!

  • ライフサイクル

https://docs.unity3d.com/ja/2019.1/Manual/ExecutionOrder.html
を参照すれば良さそう

出来上がったものがこちら!

PlayController.cs
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public float speed;
    public Rigidbody rb;
    // See also:https://docs.unity3d.com/ja/2019.1/Manual/ExecutionOrder.html

    private void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    private void FixedUpdate()
    {
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");

        Vector3 movement = new Vector3(moveHorizontal,0.0f,moveVertical);
        rb.AddForce(movement * speed);
    }

}

Publicで定義した変数はUnityのエディタ内で直接値を編集出来るのが特徴的なので覚えておく事

public.png

これでボールの制御が出来るようになるので大勝利

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

やさC#(ラムダ式)

最初聞いた時はちんぷんかんぷん

認識的には。。

int sample(int a)
{
return a+5;
}

これをラムダ式だと一行で

a=>a+5

と左辺に引数、右辺には戻り値を計算する式が入る
今回は引数が一つなので左辺の()は省略してます。

複数系

int sample(int a,int b)
{
return a+b;
}

これもラムダ式だと一行で

(a,b)=>a+b

引数が二つ以上の場合には引数を「 , 」で繋いで()で囲む

補足1

int以外の戻り値を持つメソッドの場合

bool add(int b)
{
   return b >=0;
}

引数の値が0以上なら[true]、そうでなければ[false]を返すメソッド

b => b >= 0

と戻り値を返すメソッドを簡潔に書く事が出来る認識です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

かみ砕いてみたC#(ラムダ式)

ラクダ式?って何?
最初聞いた時はちんぷんかんぷんでした。

現在の認識的には。。

int sample(int a)
{
return a+5;
}

これをラムダ式だと一行で

a=>a+5

と左辺に引数、右辺には戻り値を計算する式が入る
今回は引数が一つなので左辺の()は省略してます。

複数系

int sample(int a,int b)
{
return a+b;
}

これもラムダ式だと一行で

(a,b)=>a+b

引数が二つ以上の場合には引数を「 , 」で繋いで()で囲む

補足1

int以外の戻り値を持つメソッドの場合

bool add(int b)
{
   return b >=0;
}

引数の値が0以上なら[true]、そうでなければ[false]を返すメソッド

b => b >= 0

と戻り値を返すメソッドを簡潔に書く事が出来る認識です。
見慣れたら呼び出しも楽なんでしょうな。。

ただ企業さんによって使ってはいけないとされてる場合もあると。。

便利ではありますが。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Guid.ToString()の実行時間

急に気になったのでToString()ってどれくらい時間かかるのか試してみました。自分用メモなのでクソ記事です。

使ったコード

using System;
using System.Diagnostics;
using System.Linq;

namespace ConsoleApp1
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            for (var i = 0; i < 10; i++)
            {
                var guids = Enumerable.Range(0, 100000).Select(x => Guid.NewGuid());

                var sw = new Stopwatch();
                sw.Start();
                var array = guids
                    .Select(x => x.ToString())
                    .ToArray();

                sw.Stop();
                Console.WriteLine($"{i}: Time -> {sw.Elapsed} Average -> {sw.Elapsed / 100000}");
            }
        }
    }
}

Guidを10万個生成して全部ToString()してArrayに入れてます。ToArray()の実行時間がどのくらいかって問題もあるけどまあ良いでしょう。

比較には下の.Select(x => x.ToString())をコメントアウトした物を使います。

using System;
using System.Diagnostics;
using System.Linq;

namespace ConsoleApp1
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            for (var i = 0; i < 10; i++)
            {
                var guids = Enumerable.Range(0, 100000).Select(x => Guid.NewGuid());

                var sw = new Stopwatch();
                sw.Start();
                var array = guids
                    //.Select(x => x.ToString())
                    .ToArray();

                sw.Stop();
                Console.WriteLine($"{i}: Time -> {sw.Elapsed} Average -> {sw.Elapsed / 100000}");
            }
        }
    }
}

結果

有り

0: Time -> 00:00:00.0285858 Average -> 00:00:00.0000003
1: Time -> 00:00:00.0314567 Average -> 00:00:00.0000003
2: Time -> 00:00:00.0277603 Average -> 00:00:00.0000003
3: Time -> 00:00:00.0360149 Average -> 00:00:00.0000004
4: Time -> 00:00:00.0272300 Average -> 00:00:00.0000003
5: Time -> 00:00:00.0430407 Average -> 00:00:00.0000004
6: Time -> 00:00:00.0270002 Average -> 00:00:00.0000003
7: Time -> 00:00:00.0369192 Average -> 00:00:00.0000004
8: Time -> 00:00:00.0442467 Average -> 00:00:00.0000004
9: Time -> 00:00:00.0241581 Average -> 00:00:00.0000002

無し

0: Time -> 00:00:00.0093016 Average -> 00:00:00.0000001
1: Time -> 00:00:00.0086810 Average -> 00:00:00.0000001
2: Time -> 00:00:00.0084526 Average -> 00:00:00.0000001
3: Time -> 00:00:00.0086880 Average -> 00:00:00.0000001
4: Time -> 00:00:00.0092142 Average -> 00:00:00.0000001
5: Time -> 00:00:00.0083043 Average -> 00:00:00.0000001
6: Time -> 00:00:00.0082775 Average -> 00:00:00.0000001
7: Time -> 00:00:00.0088381 Average -> 00:00:00.0000001
8: Time -> 00:00:00.0083037 Average -> 00:00:00.0000001
9: Time -> 00:00:00.0082184 Average -> 00:00:00.0000001

明らかに違いますね。とはいえこの位ならそこまで気にするような値でも無いのかな。基準がいまいち分からないけどなんかそんな感じがします。終わり。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FromBodyで指定したPostリクエストがnullを受け取る件

どういうこと?

こんなコントローラを書いたとする。例なのでバリデーションのチェックや例外とかは気にしない。

[HttpPost("/api/post_something")]
public async Task<ActionResult<bool>> PostSomething([FromBody] SomeRequest request)
{
  var ret = await SomeService.DoSomething(request);
  return Ok(ret);
}

ここでSomeRequestは自分で定義したクラスだ。例えばこんな感じだとしよう。

public class SomeRequest
{
  public string Memo { get; set; }
  public User UserInfo { get; set; }
}

Userクラスは名前と年齢を持つだけのクラスとする。折角なのでコンストラクタも定義しておくとしよう。

public class User
{
  public string Name { get; set; }
  public uint Age { get; set; }

  public User(string name, uint age)
  {
    Name = name;
    Age = age;
  }
}

至ってシンプル。それっぽいjsonを書いてリクエストを投げてみよう。

{
  "memo": "これは試験的に投げるリクエストだよ。",
  "user_info":
  {
    "name": "ほげ子",
    "age": 17
  }
}

するとrequestはSomeRequestのインスタンスに正しくマッピングされず、nullになる・・・。
一体何故?

解答

Userクラスにデフォルトコンストラクタを定義していないのが原因。こうすれば正しくマップされる。

public class User
{
  public string Name { get; set; }
  public uint Age { get; set; }

  public User() {}

  public User(string name, uint age)
  {
    Name = name;
    Age = age;
  }
}

経緯

複数のクラスを入れ子にしたクラスのメンバーを持ったリクエストメッセージを指定したら、どうにもリクエストが正しくマップされなくて小一時間悩んだのがきっかけ。例はかなり単純なのですぐに気づきますが、何のエラーも出ないので遭遇した時は頭を抱えました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む