vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

ASP.NET CoreでPOST (Unityもちょっとだけ)

はじめに

以前 Controller から受け取れるようにしたので、今回はそれに対する POST リクエストで、色々値を受け渡してみよう、という内容です。

文字列を受け取る

まずは Razor 構文の勉強がてら、文字列を Views/Home/Index.cshtml から渡してみます。

Index.cshtml

@page
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
< form method="POST" asp-controller="Home" asp-action="PostSample">
    < div>Name: < input name="bodyText"/>< /div>
    < input type="submit" />
< /form>
  • asp-controller でルーティングを行う Controller を指定できます。
  • asp-action で Submit ボタンを押したときに呼ばれるメソッドを指定できます。
  • TagHelpersのおかげで入力補完も効いて便利ですね。
  • input タグの name で、呼び出すメソッドの引数を指定します。

で、これを Controller で受け取ります。

HomeController.cs

~省略~
[Route("/PostSample")]
[HttpPost]
public void PostSample(string bodyText) {
    // 引数名は Input の name と合わせる必要がある.
    Console.WriteLine(bodyText);
}
~省略~

なお、引数を今回のようにフォームからの入力に限定したい場合は下記のように指定できます。

HomeController.cs

~省略~
[Route("/PostSample")]
[HttpPost]
public void PostSample([FromForm] string bodyText) {
    // 引数名は Input の name と合わせる必要がある.
    Console.WriteLine(bodyText);
}
~省略~

他にも、今回は POST ですが、例えば GET でクエリパラメーターを受け取りたい場合、 [FromQuery] を使います。

注意点を付け加えるとすると、メソッドのオーバーロードはできず、例えば引数なしのメソッドを追加すると、 Submit ボタン押下時に AmbiguousActionException が発生します。

配列を受け取る

次は配列を受け取ってみます。

Razor 構文では @{} で C# のコードが書けるため、下記のようなことができます。

Index.cshtml

@page
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
< form method="POST" asp-controller="Home" asp-action="PostSample">
    < div> Name:
        @{
            for (var i = 0; i < 3; i++) {
                
            }
        }
      < /div>
    < input type="submit" />
< /form>

HomeController.cs

~省略~
[Route("/PostSample")]
[HttpPost]
public void PostSample(string[] messages) {
    Console.WriteLine(messages[0]);
}
~省略~

キーとなるのは name で、これが同じものを配列(または List )として扱うことができます。

実際のユースケースを踏まえて ID を設定していますが、無くても動作には問題ありません。

HTML の左上の要素から順にセットされます。

自作クラスを受け取る

今度は自作クラスを引数として受け取ってみます。

Message.cs

using System;

namespace WebApplication1.Domains {
    public class Message {
        public int Id { get; set; }
        public string Title { get; set; }
        public string BodyText { get; set; }
        public DateTime LastUpdatedDate { get; set; }
    }
}

※今回は簡略化のため BodyText のみを使いますが、他の要素も同じように渡すことができます。

引数として受け取る

今回は値を受け取る方法を二種類試しました。

一つ目は先ほどと同じように、引数として受け取る方法です。

HomeController.cs

[Route("/PostSample")]
[HttpPost]
public void PostSample([FromForm] Message message) {
    Console.WriteLine(message.BodyText);    
}

Index.cshtml

@page
@using WebApplication1.Domains

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
    Message message = new Message();
}
< div>@DateTime.Now< /div>
< form method="POST" asp-controller="Home" asp-action="PostSample">
    < div>Name: < input name="message.BodyText" />< /div>
    < input type="submit" />
< /form>

余談ですが、上記の message.BodyText のようにして値を渡す方法は、 GET でも使用できます。

このような URL でクエリパラメータを渡すと、クラスとして受け取ることができます。

http://localhost:51817/PostSample?message.Id=1&message.BodyText=sample

HomeController.cs

~省略~
[Route("/PostSample")]
public void PostSample([FromQuery] Message message) {
    Console.WriteLine(message.BodyText);
}
~省略~

BindProperty を使う

引数として渡すのではなく、 BindProperty を使うこともできます。

HomeController.cs

~省略~
[BindProperty]
public Message Message { get; set; }

[Route("/PostSample")]
[HttpPost]
public void PostSample() {
    Console.WriteLine(Message.BodyText);    
}
~省略~

Index.cshtml

@page
@using WebApplication1.Domains

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
< div>@DateTime.Now< /div>
< form method="POST" asp-controller="Home" asp-action="PostSample">
    < div>Name: < input name="Message.BodyText" />< /div>
    < input type="submit" />
< /form>

値を他からも使いたい場合などは便利そうです。

asp-for

もう一つ、 Input に name ではなく asp-for を使って指定する方法( ID、Name が自動設定されるとのこと)がありますが、コードビハインドのある Razor View でないと、 viewData が null というエラーが発生したため今回は使用していません。

Unity からリクエストを投げてみる

HTML からだけでなく、 Unity から送ってみるとどうなるでしょうか。

GET リクエス

UnityWebRequest を使ってリクエストを投げることができます。

まずは GET から( ASP.NET Core 側は GET リクエストを受け付けるように変更してください)。

SendSample.cs

using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;

public class SendSample : MonoBehaviour {
    public Button SendButton;

    public void Start() {
        SendButton.onClick.AddListener(() => {
            StartCoroutine(Send());
        });
    }
    private IEnumerator Send() {
        UnityWebRequest request = UnityWebRequest.Get(@"http://localhost:51817/PostSample?message.Id=1&message.BodyText=sample");
        yield return request.SendWebRequest();

        if(request.isNetworkError ||request.isHttpError) {
            Debug.Log(request.error);
        }
        else {
            Debug.Log("Form upload complete!");
        }
    }
}

POST リクエス

ASP.NET Core の View のように、クラスをそのまま渡すことはできないため、 JSON 形式にして渡すことにします。

SendSample.cs

using System.Collections;
using Domains;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;

public class SendSample : MonoBehaviour {
    public Button SendButton;

    public void Start() {
        SendButton.onClick.AddListener(() => {
            var message = new Message {
                Id=6,
                BodyText = "sample",
            };
            var json = JsonUtility.ToJson(message);
            
            StartCoroutine(Send(json));
        });
    }
    private IEnumerator Send(string jsonFile) {
        WWWForm formData = new WWWForm();

        // そのまま渡すと{}がエンコードされてしまい、JSONとして認識されなくなるのでbyte[]に変換.
        byte[] postData = System.Text.Encoding.UTF8.GetBytes (jsonFile);
        
        UnityWebRequest request = UnityWebRequest.Post("http://localhost:51817/PostSample", formData);
        request.SetRequestHeader("Accept", "application/json");
        request.SetRequestHeader("Content-Type", "application/json");
        
        request.uploadHandler = new UploadHandlerRaw(postData);
        
        yield return request.SendWebRequest();
        
        if(request.isNetworkError || request.isHttpError) {
            Debug.Log(request.error);
        }
        else {
            Debug.Log("Form upload complete!");
        }
    }
}

ここで重要なのは、 JSON のデータを渡し忘れないことです(白目)

あとコメントにも書きましたが、JSON データを string でそのまま渡すとエンコードされてしまうそうなので、 byte[] で渡します。

JSON を受け取る

先ほど触れた通り、 Unity と ASP.NET Core でクラスを共有することはできないため、 JSON を受け取る必要が出てきます。

HomeController.cs

~省略~
[Route("/PostSample")]
[HttpPost]
public void PostSample([FromBody]Message message) {
    Console.WriteLine(message.BodyText);
}
~省略~

ポイントは [FromBody] で、これによって自動でデシリアライズされた値を引数として受け取ることができるようになります。

JSON を扱う場合は必ず [FromBody] をつける必要があり、これを外すと空データになってしまいます。

ただし、 Form データなどは受け取ることができない(不正なメディアタイプと怒られる)ため、注意が必要です。

おわりに

とりあえずデータは渡せるようになりました。

前にも書いた気がしますが、 WebSocket の処理と絡めてみたり、いい加減に Model を触ってみたりもしたいところ。。。

あとは Rx とか。

参照

Unity