窓を作っては壊していた人のブログ

提督の窓や成績の窓を作ってました.今では適当に好き勝手に開発をしてます.

ASP.NET Core MVC + Razor Pages環境下でajaxでバイナリのフォームをPOSTしたりする時に行ったこと

備忘録です.

現在研究の一環で音声を使ったWebアプリケーションの開発を行っています. プロトタイプとしてASP.NET Coreを使って音声信号処理をC# 側で,その他のインタフェースをHTML5側で行うような構成をとっています.

今後もASP.NET Coreで運用し続けるのであれば,アクションなどをButtonにバインドしたり,またModelを定義していい感じにシリアライズとか出来るようにするのですが, WebAssemblyの導入も検討しているため,どっちにもすぐに変えやすい,すなわちRazor記法などを一切使わず,APIのみをASP.NET Core側が提供するといった形が望ましいです.

この用件を満たために行ったことを以降で説明します.

PageModelのMethod(Action)を呼び出せるようにする

ASP.NET Core MVCなどは完全な初心者だったため,命名規則など全くわかりませんでした. ドキュメントを眺めてみると,どうもPOSTだったらOnPost~とかそういう名前にすれば良いようです(ページURL忘れた).

ということでこんな感じになります.

public ActionResult OnPostGetUserMcep() // PostなのにGetとは...
{
    return null; // Fix Me
}

PageModelにこの様に実装すれば,例えばこのページが/Hogeでアクセスできるのであれば, /Hoge?handler=GetUserMcepに対してPOSTメソッドでアクセスすればこのActionが実行されることになります.

それでは実際に試してみましょう.

...

えぇ...Bad Request...

さて困りました.問題があるようには見えないのですが,どうもだめっぽいです. それでASP.NET Core Ajax Razor Pagesとかで検索してみると,こんな記事が見つかります.

www.talkingdotnet.com

このページを読むとCSRF関係の対策のやつ関係っぽいことがわかります.

このページを参考にTokenを発行してあげて,リクエストに突っ込んであげると無事にPOSTが通りました.

以下サンプルのfetchリクエス

return fetch('/Hoge?handler=GetUserMcep',
      {
        method:"POST",
        body: obj,
        headers: {
            "XSRF-TOKEN": document.querySelector('input[name="__RequestVerificationToken"][type="hidden"]').value
        },
        credentials: 'include'
      });

Request Bodyからバイナリデータを受けとる

デバッガで眺めてたら取れそうだったのでこんな感じで受け取ります.

byte[] tmp;

using (var st = new MemoryStream())
{
    await Request.Body.CopyToAsync(st);
    st.Position = 0;

    tmp = st.ToArray();
}

これだけ. FetchでFileObjectを投げたり,バイナリデータを投げた場合はこんな感じで取るといいと思います.

multipart/form-data なRequestからバイナリデータを受け取る

Fetchでwavファイルを投げた時,そのまま受け取りたくなったので以下のようにして受け取りました.

byte[] wavFile;
var wavform = Request.Form.Files?.FirstOrDefault(v => v.Name == "wavform");

if (wavform != null)
{
    using (var st = new MemoryStream())
    {
        await wavform.CopyToAsync(st);
        st.Seek(0, SeekOrigin.Begin);
        wavFile = new byte[st.Length];
        await st.ReadAsync(wavFile, 0, wavFile.Length);
    }
}

Request.From.Filesからは,FileObjectとして投げられたものを受け取れるので非常に簡単でいいですね.

File以外にもTypedArrayとかを投げることもあるので,そのやり方を確認してみましょう.

var targetArr = Request.Form.FirstOrDefault(v => v.Name == "arr").Value[0].Split(',').Select<string, float>(float.Parse).ToArray();

...たぶんJSONとかでいい感じにやったほうがいい気がするんですけどよくわからないです.

とりあえず自分の目的としていることはこんな感じで実現ができそうでした.

バイナリデータをHTML5(クライアント)側に返す

C# で加工した音声ファイル(float型)のものをクライアント側に返して再生をしたり描画をするためにどうやって返すかを確認してみました.

これは非常に簡単でFileとしてbyteArrayを投げつければいいようです.

var byteArr = new byte[myData.Length * 4];
Buffer.BlockCopy(myData, 0, byteArr, 0, myData.Length * 4); // float32で返すので,4byte

return File(byteMcep, "application/octet-stream");

まとめ

  • Action名はOn{Method名}{Action名}
  • FileObjectやTypedArrayなどを単体で送った際はRequest.BodyをbyteArrayにコピーして使う
  • multipart/form-dataなものにTypedArrayを送った際はRequest.Formから,FileObjectであればRequest.Filesから引っ張ってくる
  • Binaryを返す場合はActionResultにFileを使って,octet-streamとかで返す

お気持ち

TypedArrayをFormで送るとシリアライズされてくっそ長い文字列になるから非常に辛い.

これとWebDNNを組み合わせて使っているのですが,なかなか楽しい感じある.