Ajaxでサーバにデータを送信する(ASP.NET MVC) - 開発備忘録2

こんばんは。クソコード製造機です。

今回はMVC C# でAjaxを使用する際のコーディングについて記載します。

なんとなく苦手意識があるので、克服したいです。

前提

詳述はしないですが、用語について下記に記載します。

ASP.NETとは

ASP.NET は、HTML、CSS、JavaScript を使用して優れた Web サイトと Web アプリケーションを構築するための無料の Web フレームワークです。Web API を作成し、Web ソケットなどのリアルタイム テクノロジを使用することもできます。

https://learn.microsoft.com/ja-jp/aspnet/overview

あんまりよくわかんないのですが、WebサイトとかWebアプリを作るための仕組みみたいなものです。

MVCとは

モデル ビュー コントローラー (MVC: Model-View-Controller) アーキテクチャ パターンでは、アプリケーションがモデル、ビュー、およびコントローラーという 3 つの主要なコンポーネントに分離されます。(中略)MVC パターンを使用すると、アプリケーションのそれぞれの側面 (入力ロジック、ビジネス ロジック、および UI ロジック) を分離するアプリケーションを作成できます。

https://learn.microsoft.com/ja-jp/aspnet/mvc/overview/older-versions-1/overview/asp-net-mvc-overview

ASP.NETでWebアプリを作る際の、ソースのデザインパターンのことです。MVCパターンに従って実装するとなんかいい感じにソースを作ることができます。

Ajaxとは

AJAX によって、ウェブページ全体を再読み込みせずに、 HTML ページの DOM の一部分を更新することができます。 AJAX は非同期処理も可能です。つまり、ウェブページのある部分を再読み込みしようとする間もコードは実行され続けます

https://developer.mozilla.org/ja/docs/Glossary/AJAX

ajaxは画面の一部のみを更新したり、非同期処理を実現するための技術です。
Webサイトとかでくるくるをクリックしてリロードするのはこれの逆で、画面全体を更新しています。

画面全体を更新するよりも、一部のみ更新する方が、処理の効率が良い場合が多いです。

TwitterのTLの更新にもajaxを使っているみたいです。
更新前のTLのツイートたちを残した上で、新規のツイートたちだけ上にくるよう更新する感じでしょうか。知りませんが。

実現したいこと - Ajaxでいろんなデータを非同期で送りたい

記事タイトル通りなのですが、今回実現したいことはWebアプリのクライアント(ブラウザとか)からサーバへデータを送信する際に、Ajaxを使うことです。

なんでAjaxを使いたいかですが、例えばトリコの登場人物のフルコースを一覧で出力する最低なWebアプリがあるとします。
右端の「選択」ボタンを押すと、選択した行のデータをサーバに送信して、処理結果を画面に表示する仕様です。

フルコース一覧表示画面

上の画像だと、処理結果として「誰のフルコースを選択したか」のメッセージを表示しています。

もしAjaxを使わない場合、サーバへデータを送信した後、画面全体を更新して処理結果を表示することになります。
あの文字列を出すためだけに、わざわざフルコースの一覧表も一から読み込み直す必要があります。

今回はグルメ四天王のデータだけなのでまだいいですが、これが増えていくと、一覧表を読み込む時間もおのずと増えます。
ボタンを押すたびにそんな時間がかかるのは、かなりストレスです。

そんな感じなのでAjaxが使いたいです。

やったこと① 単一のデータをサーバに送る

まずは単一のテキストデータをサーバに送ってみます。
今回は、選択ボタンを押した行の人物名をサーバに送信し、「〇〇のフルコースを選択しました」というテキストをサーバ側で作成し、クライアント側で表示します。

用意したコードは下記になります(要点ではない部分は省略しています)。

Model

public class ShowFullCoursesModel
{
   public List<FullCourse> FullCourseList{ get; set; }

   public void SetFullCourseData()
   {
       var torikoFullCourse = new FullCourse()
       {
           PersonName = "トリコ",
           Appetizer = "BBコーン",
           Soup = "センチュリースープ",
           FishDish = "オウガイ ~遠い海の記憶~",
           MeatDish = "完象 -エンドマンモス-",
           MainDish = "GOD",
           Salad = "エア",
           Dessert = "虹の実",
           Drink = "ビリオンバードの卵"
       };

       this.FullCourseList = new List<FullCourse>()
       {
           torikoFullCourse
       };
   }
}

public class FullCourse
{
    //人物名
    public string PersonName { get; set; }
    //前菜
    public string Appetizer {  get; set; }
    //スープ
    public string Soup {  get; set; }
    //魚料理
    public string FishDish { get; set; }
    //肉料理
    public string MeatDish { get; set; }
    //主菜
    public string MainDish { get; set; }
    //サラダ
    public string Salad { get; set; }
    //デザート
    public string Dessert { get; set; }
    //ドリンク
    public string Drink { get; set; }
}


Viewに渡すModelとして、ShowFullCoursesModelというクラスを作成しました。画面上で一覧表示されるフルコースのデータリストを持つクラスです。

また、個々人のフルコースのデータを定義するクラスとして、FullCourseクラスを作成しました。

View(Scriptのみ)

function SelectButtonClick(index) {
   var personName = document.getElementById("PersonName(" + index + ")").innerHTML;
        
   $.ajax({
       url: '@Url.Content("~/ShowFullCourses/SelectFullCourse")',
       type: 'POST',
       data: { personName: personName },
       dataType: 'json'
   })
   .done(function (message){ 
       document.getElementById("Message").innerHTML = message;
   })
}

ボタンを押した際の動作について、scriptを記載しています。
画面に描画された要素から人物名を取得し、ajaxでサーバに送信しています。

ajax部分のコードでは、
・送信する際のURL(コントローラ名/アクションメソッド)
・通信時のHTTPメソッド
・送信するデータ
・サーバからの応答データの種類
を指定します。

done~以降は、サーバから応答が来た後の処理です。誰のフルコースを選んだかのメッセージを画面に表示しています。

Controller

public class ShowFullCoursesController : Controller
{
    public ActionResult Index()
    {
        var model = new ShowFullCoursesModel();
        model.SetFullCourseData();

        return View("Index", model);
   }

     public ActionResult SelectFullCourse(string personName)
     {
     var message = string.Format("{0}のフルコースを選択しました", personName);

        return Json(message);
     }
}

SelectFullCourseメソッドで、クライアント側から送信された人物名を受け取り、誰のフルコースを選択したかの文字列を作成します。

メソッドの引数は、Viewのscriptで指定した名前と一致させる必要があります。

やったこと② 複数のデータをサーバに送る

選択したフルコースの人物名だけでなく、主菜もサーバに送ってみます。

View (Script)

function SelectButtonClick(index) {
   var personName = document.getElementById("PersonName(" + index + ")").innerHTML;
   var mainDish = document.getElementById("MainDish(" + index + ")").innerHTML;

   var data = {};
   data.personName = personName;
   data.mainDish = mainDish;

   $.ajax({
      url: '@Url.Content("~/ShowFullCourses/SelectFullCourse")',
      type: 'POST',
      data: data,
      dataType: 'json'
   })
   .done(function (message){
      document.getElementById("Message").innerHTML = message;
   })
}

サーバに渡すデータが複数になる場合は、送信するデータのオブジェクトを作成し、そこにプロパティを追加することで実現します。

追加したプロパティの名前は、先ほどと同じくControllerのアクションメソッドの引数名と一致させる必要があります。

Controller

public class ShowFullCoursesController : Controller
{
    public ActionResult Index()
    {
        var model = new ShowFullCoursesModel();
        model.SetFullCourseData();

        return View("Index", model);
   }

     public ActionResult SelectFullCourse(string personName, string mainDish)
     {
     var message = string.Format(
        "{0}のフルコースを選択しました。主菜は{1}です",
        personName,
             mainDish);

        return Json(message);
     }
}

引数の名前を、scriptで指定した名前と一致させることで、値を受け取ることができます。

やったこと③ 複数のデータを、任意のクラスのプロパティにセットして送信する

やったこと②では、二つのテキストデータを別々に引数で受け取っていましたが、
ここでは選択した行のデータが全て、FullCourse型の一つの引数にセットされた状態で、サーバ側が受け取れるようにしようと思います。

どうしてそうしたいかですが、今回のように選択した行のデータがあまりにも多いと、それすべてをサーバに送信した際に、アクションメソッドの引数がとんでもないことになります。

それを回避する分、scriptの記述も増えるので、どっちもどっち感はありますが、、、

ただ、せっかく行データ用のクラスを定義しているので、サーバ側ではそのクラスのオブジェクトとして送信さ れたデータを扱ってみたいです。

View(Script)

  function SelectButtonClick(index) {
        var personName = document.getElementById("PersonName(" + index + ")").innerHTML;
        var appetizer = document.getElementById("Appetizer(" + index + ")").innerHTML;
        var soup = document.getElementById("Soup(" + index + ")").innerHTML;
        var fishDish = document.getElementById("FishDish(" + index + ")").innerHTML;
        var meatDish = document.getElementById("MeatDish(" + index + ")").innerHTML;
        var mainDish = document.getElementById("MainDish(" + index + ")").innerHTML;
        var salad = document.getElementById("Salad(" + index + ")").innerHTML;
        var dessert = document.getElementById("Dessert(" + index + ")").innerHTML;
        var drink = document.getElementById("Drink(" + index + ")").innerHTML;

        var data = {
            PersonName: personName,
            Appetizer: appetizer,
            Soup: soup,
            FishDish: fishDish,
            MeatDish: meatDish,
            MainDish: mainDish,
            Salad: salad,
            Dessert: dessert,
            Drink: drink,
        };

        $.ajax({
            url: '@Url.Content("~/ShowFullCourses/SelectFullCourse")',
            type: 'POST',
            data: JSON.stringify(data),
            contentType: 'application/json',
        })
        .done(function (message) {
            document.getElementById("Message").innerHTML = message;
        })
    }

ポイントのひとつは、送信データのオブジェクトのプロパティの定義です。
必ず、アクションメソッドの引数の型のプロパティと名前が一致するようにします。

二つ目は、ajaxの記述です。
送信するデータは、JSON.stringify()でJavaScriptからJsonデータへと整形します。
そして、サーバへ送るデータがJsonデータであることを、contentTypeで定義しておきます。

Controller

public ActionResult SelectFullCourse([FromBody]FullCourse fullCourseData)
{
    var message = "";

    //文字列作成処理

    return Json(message);
}

ポイントは、アクションメソッドの引数です。scriptで定義していた行データの型の引数を受け取るようにします。

また、引数の属性として[FromBody]属性を指定しなければならない場合があります。
どうやら.Net Framework版のMVCでは、[FromBody]属性を指定しなくてもデータを受け取れるようなのですが、ASP .NET Core MVCでは必要となるようです。

http://surferonwww.info/BlogEngine/post/2021/04/12/frombodyattribute-is-required-for-model-binding-json-to-action-method-of-mvc.aspx

結び

Webアプリは苦手なのでこれからも勉強します。

この記事が気に入ったらサポートをしてみませんか?