WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

jQuery.ajax を使っての事前検証

by WebSurfer 6. February 2022 15:56

ASP.NET Web Forms アプリで jQuery Ajax を使ってポストバックの際にサーバー側で行われる処理が可能かどうかを事前に調べ、可能と分かったら JavaScript で Button クリックしてポストバックする、可能ではないと分かった場合はポストバックせずメッセージを表示してユーザーに知らせるという話です。

ポストバックして処理完了

元の話は Teratail のスレッド「asp:button 押下前にサーバサイドが応答しているか確認したいためPingのようなことを行いたい」のものです。実際のそのような検証が必要なケースはあまりなさそうですが、せっかく考えた話ですし jQuery Ajax の timeout の使い方が今後の参考になるかもしれないと思ったので備忘録として書いておくことにしました。

Button クリックでポストバックしてサーバー側でユーザーが送信したデータを受けて、データベースサーバーにクエリを投げる等の処理をすることはよくあると思います。

その際、Button クリックでいきなりポストバックするのではなく、その前に jQuery Ajax を使って事前にサーバー側での処理が可能かどうかを調べるというストーリーです。

(いきなりポストバックしても結果をきちんとユーザーに知らせることはできるし、やりすぎるとサーバーに無駄な負荷を増やすし軽くすると検証の意味が薄れるということで、そのような検証の必要性は低いとは思いますがそこは置いときます)

jQuery Ajax で呼び出してサーバー側で事前検証を行う HTTP ジェネリックハンドラを作ります。以下のコードがそのサンプルです。

HTTP ジェネリックハンドラ Handler1.ashx

using System.Web;

namespace WebForms1
{
    public class Handler1 : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            // Button クリックでポストバックされた時にサーバー
            // で行われる処理が可能か事前に検証する。処理に最
            // 長 3 秒かかるとして、ここでは単純に 3 待つ
            System.Threading.Thread.Sleep(3000);
            
            // 検証に成功したら "検証成功" という文字列を返す
            var validationResult = "検証成功";

            context.Response.ContentType = "text/plain";
            context.Response.Write(validationResult);
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

上の Handler1.ashx を jQuery Ajax を使って呼び出します。Handler1.ashx は検証に成功すると "検証成功" という文字列を返すので、それを受けたら JavaScript で Button をクリックしてポストバックします。その結果がこの記事の一番上にある画像です。

Handler1.ashx を呼び出す JavaScript を含む Web Form ページのサンプルコードは以下の通りです。

WebForm1.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs"
    Inherits="WebForms1.WebForm1" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
    <script src="Scripts/jquery-3.4.1.js"></script>
    <script type="text/javascript">
        $(function () {
            $("#btnSec").on("click", function () {
                $.ajax({
                    type: "get",
                    url: "Handler1.ashx",
                    timeout: 5000
                }).done(function (data) {
                    // 呼び出し先 Handler1.ashx で検証が成功すると "検証成功" 
                    // という文字列が返ってきて引数 data に代入される
                    if (data == "検証成功") {
                        var hiddenbutton = document.getElementById("Button1");
                        // Button をクリック
                        hiddenbutton.click();
                    } else {
                        $("#Label1").text("処理できません - 検証結果 NG");
                    }
                }).fail(function (jqXHR, textStatus, errorThrown) {
                    $("#Label1").text("処理できません - " + textStatus);
                });
            });
        });
    </script>
    <style type="text/css">
        .style1
        {
            display: none;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <h2>WebForm1 - jQuery.Ajax を使っての事前検証</h2>
            
            <input id="btnSec" type="button" value="反映" />

            <%--ポストバックを行う Button コントロール。上の style1 で
                隠しボタンに設定。クリックは JavaScript で行う。クリ
                ックされるとポストバックが発生し、サーバー側のイベント
                ハンドラ ClickBtnSection で必要な処理が行われる--%>
            <asp:Button ID="Button1" runat="server" 
                OnClick="ClickBtnSection" CssClass="style1" />

            <asp:Label ID="Label1" runat="server">
                Label1 初期値
            </asp:Label>
        </div>
    </form>
</body>
</html>

WebForm1.aspx.cs

using System;

namespace WebForms1
{
    public partial class WebForm1 : System.Web.UI.Page
    {
        protected void ClickBtnSection(object sender, EventArgs e)
        {
            // サーバー側での必要な処理

            Label1.Text = "Button1 がクリックされました";
        }
    }
}

Handler1.ashx を呼び出して、そこでの処理中にサーバーエラーが発生した場合は以下のようになります。

サーバーエラー

Handler1.ashx を呼び出した後、jQuery Ajax の timeout オプションに設定した時間(上のコード例では 5 秒)待っても応答が返ってこない場合は以下のようになります。

timeout

Handler1.ashx から帰ってきた文字列が "検証成功" 以外の場合は(実際にそういうケースはないかもしれませんが)以下のようになります。

検証結果 NG

Tags: , , ,

Validation

jQuery ajax で部分ビューの呼出・表示 (CORE)

by WebSurfer 7. March 2020 12:25

jQuery ajax を使って、部分ビューを応答として返すアクションメソッドにフォームデータを POST 送信し、返ってきた部分ビューの html をページ内の指定の場所にレンダリングする方法を書きます。

jQuery ajax で部分ビューの呼出・表示

先の記事「ASP.NET Core MVC と Ajax ライブラリ」では、Microsoft の Ajax ライブラリ jquery.unobtrusive-ajax.js を利用して、Ajax で部分ビューを呼び出してページ内の指定の場所に表示する方法を書きました。

それと機能的に同じことを、Microsoft の Ajax ライブラリの助けを借りないで、jQuery ajax を利用して実装する方法です��

CSRF 用の隠しフィールドのデータを含めてフォームデータを全て jQuery ajax を使って POST 送信し、部分ビュー用のアクションメソッドに付与した [ValidateAntiForgeryToken] 属性での検証ができるようにしていることに注目してください。

問題はフォームデータをクライアント側でどのように取得するかですが、jQuery の .serialize() メソッドを使うと form データを application/x-www-form-urlencoded 形式の文字列として取得できます。それを jQuery ajax の data パラメータに設定して POST 送信してやればアクションメソッドで受けることができます。

Controller と View のサンプルコードを以下に書いておきます。コードは ASP.NET Core 3.1 MVC のものです。.NET Framework MVC5 の場合はライブラリ jquery.unobtrusive-ajax.js と AjaxHelper を使う方が簡単でよさそうです。

使っているのは Microsoft が提供する Northwind サンプルデータベースの Customers テーブルです。その CompanyName をドロップダウンリストに表示し、ユーザーが選択してボタンをクリックすると部分ビューを呼び出して、選択した Customers のレコードの詳細を指定した場所(下の View のコードで言うと <div id="result"></div> の中)に書き出すものです。

Controller / Action Method

先の記事「ASP.NET Core MVC と Ajax ライブラリ」のものと同じです。

using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace MvcCoreApp.Controllers
{
  public class AjaxController : Controller
  {
    private readonly NorthwindContext _context;

    public AjaxController(NorthwindContext context)
    {
      _context = context;
    }

    // jQuery ajax で下のアクションメソッド Details
    // を呼んで詳細 Customer データの部分ビューを表示。
    // CSRF 用の隠しフィールドのデータも送信して検証で
    // きるようにする。
    public IActionResult Index2()
    {
      // 全部取得すると長すぎるので Take(10) した
      var list = _context.Customers.Take(10);
      ViewData["customers"] = 
        new SelectList(list, "CustomerId", "CompanyName");
      return View();
    }

    // 部分ビュー用のアクションメソッド
    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult Details(string customerid)
    {
      if (string.IsNullOrEmpty(customerid))
      {
        return NotFound();
      }

      var customer = _context.Customers.Find(customerid);

      if (customer == null)
      {
        return NotFound();
      }

      return PartialView(customer);
    }
  }
}

View (Index2.cshtml)

ボタンには input type="submit" タイプ(クリックすると form を submit する)と input type="button" タイプ(クリックしても何も起こらない)の 2 つを配置して、両方期待通り動くことを確認してみました。

@{
    ViewData["Title"] = "Index2";
}

<h1>Index2</h1>

<form id="form1" asp-action="Details" 
      asp-controller="Ajax" method="post">

    <select id="customerid" name="customerid" 
            asp-items="@ViewBag.customers">
    </select>

    <input type="submit" value='詳細表示 (type="submit")' />
    <input id="button1" type="button"
                         value='詳細表示 (type="button")' />

</form>

<div id="result"></div>

@section Scripts {
    <script type="text/javascript">
        //<![CDATA[

        // [詳細表示 (type="submit")] ボタンをクリック
        // したときの処理

        // ボタンクリックで form が submit されるという
        // 動きになるので、form 要素の submit イベントに
        // リスナをアタッチして jQuert ajax で処理する
        $("#form1").on("submit", function (event) {
            // submit されては困るのでキャンセル
            event.preventDefault();

            // jQuery ajax を使って、部分ビューを応答と
            // して返すアクションメソッド Ajax/Deteils
            // にフォームデータを POST 送信する
            $.ajax({
                type: "POST",
                url: "/Ajax/Details",

                // フォームデータを取得
                data: $(this).serialize(),

                // コールバック function の引数 data に
                // 部分ビューの html ソースが渡される 
                success: function (data) {                    
                    $("#result").empty();
                    $("#result").append(data);
                },

                error: function (jqXHR, status, error) {
                    $('#result').text('Status: ' + status +
                        ', Error: ' + error);
                }
            });
        })

        //]]>
    </script>

    <script type="text/javascript">
        //<![CDATA[

        // [詳細表示 (type="button")] ボタンをクリック
        // したときの処理

        // ボタン要素の click イベントにリスナをアタッチ
        // して jQuert ajax で処理する
        $("#button1").on("click", function () {

            // input type="button" タイプのボタンはクリ
            // ックしても from が submit されることは
            // ないので event.preventDefault(); は不要

            // jQuery ajax を使って、部分ビューを応答と
            // して返すアクションメソッド Ajax/Deteils
            // にフォームデータを POST 送信する
            $.ajax({
                type: "POST",
                url: "/Ajax/Details",

                // フォームデータを取得
                data: $("#form1").serialize(),

                // コールバック function の引数 data に
                // 部分ビューの html ソースが渡される 
                success: function (data) {
                    $("#result").empty();
                    $("#result").append(data);
                },

                error: function (jqXHR, status, error) {
                    $('#result').text('Status: ' + status +
                        ', Error: ' + error);
                }
            });
        })

        //]]>
    </script>
}

部分ビューのコードは、先の記事「ASP.NET Core MVC と Ajax ライブラリ」のものとほとんど同じなので省略します。

Microsoft の Ajax ライブラリ jquery.unobtrusive-ajax.js は ASP.NET Core MVC ではサポートされてないのかもしれません。なので、上記のように jQuery ajax を使うのが正解のような気がします。

Tags: , , ,

CORE

Canvas に複数の画像を順番通り描画

by WebSurfer 24. October 2019 12:30

Canvas に複数の画像を指定した順序で描画し、その後 Canvas に描画された画像を Data Url 形式で取得する方法を書きます。元の話は Teratail のスレッド「CanvasにdrawImageで描写した画像をtoDataURLで取得したい」です。

Canvas と img タグの画像

上の画像がその結果を Chrome で表示したもので、左が Canvas の画像、右が canvas.toDataURL メソッドを使って Data Url 形式で取得した画像データを img 要素の src 属性に設定して表示した結果です。

Canvas に表示する複数の画像の数だけ new Image() で img 要素を生成し、その src 属性に画像の url を設定すると img 要素が画像に読み込みを完了した時点で load イベントが発生するので、それにリスナをアタッチして Canvas への書き込みと Data Url 取得の処理を行うのが基本です。

ただし、問題は、Canvas に描く画像が複数ある場合、img 要素による画像の読み込みにかかる時間が画像のサイズなどによって異なるので、複数ある img 要素の load イベント発生の順番は不定となることです。

もう一つ、canvas.toDataURL メソッドを使って Canvas から Data Url 形式のデータを取得するタイミングも問題です。全ての画像の Canvas への描画が終わるのを待たなければなりません。

上の問題に対応するには、最後の load イベントの発生を待って、そのイベントリスナで複数ある全ての img 要素の画像 を希望する順番に context.drawImage メソッドで Canvas に書き込むのがよさそうです。

context.drawImage は同期メソッドなので、同じリスナ内で、全ての画像の context.drawImage が終わった後に canvas.toDataURL メソッドを記述すれば Data Url 形式のデータを取得できます。(リスナの外に canvas.toDataURL メソッドを置くのはダメです。img 要素の load イベントが発生する前に canvas.toDataURL に制御が飛ぶので)

検証に使ったコード、即ち上の画像を表示したコードを以下に書いておきます。(ASP.NET のページを利用していますが、そこは本題とは関係ないので気にしないでください) 元の画像は 125px x 75px の色が異なる .png 画像 5 枚です。それらを 20px づつ右下にずらしながら描画しています。

<%@ Page Language="C#" AutoEventWireup="true" 
    CodeFile="0076Canvas2.aspx.cs" Inherits="_0076Canvas2" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; 
  charset=utf-8"/>
  <title>0076Canvas2.aspx</title>
  <script src="/Scripts/jquery.js"></script>
  <script type="text/javascript">
  //<![CDATA[
    window.onload = function () {
      // それぞれ 125px x 75px の単色 png 画像
      // 色は名前の通り赤, 青, 緑, 黄, 橙
      var srcs = [
          "/Images/red.png",
          "/Images/blue.png",
          "/Images/green.png",
          "/Images/yellow.png",
          "/Images/orange.png"
      ];

      var canvas = document.getElementById('mycanvas');
      var context = canvas.getContext('2d');

      // canvas に描かれる順番を保証するには以下のようにする。
      // そうすればリスナの中に canvas.toDataURL("image/png");
      // を記述でき Data Url が取得できる
      var images = [];
      var loadCount = 0;
      for (let i = 0; i < srcs.length; i++)
      {
        images[i] = new Image();                
        images[i].onload = function () {
          loadCount++;
          if (loadCount == images.length)
          {
            for (let j = 0; j < images.length; j++)
            {
              context.drawImage(images[j], j * 20, j * 20);
            }

            // image.onload のリスナの中でないと Data Url
            // は取得できないので注意
            var result = canvas.toDataURL("image/png");
            document.getElementById("image1").src = result;
          }
        };
        images[i].src = srcs[i];
      }
    }
    //]]>
  </script>
</head>
<body>
  <form id="form1" runat="server">
  <div style="float: right;">
    <canvas id="mycanvas" width="205" height="155">
    </canvas>
  </div>
  <div>
    <img id="image1" alt="" />
  </div>
  </form>
</body>
</html>

他に jQuery.Deferred() を使う方法もあります。そのコードを以下に書いておきます。

// jQuery.Deferred を使用
var images = [];
var dfds = [];
for (let i = 0; i < srcs.length; i++)
{
    images[i] = new Image();
    let dfd = new $.Deferred();
    dfds[i] = dfd;
    images[i].onload = function () { dfd.resolve(); };
    images[i].src = srcs[i];
}

$.when.apply($, dfds).done(function () {
    for (let i = 0; i < images.length; i++)
    {
        context.drawImage(images[i], i * 20, i * 20);
    }
    var result = canvas.toDataURL("image/png");
    document.getElementById("image1").src = result;
});

全ての img 要素の load イベントが発生し終わるのを待って Canvas への描画と Data Url 形式のデータを取得するというところは同じですが、見かけは何となくカッコいいかも。(笑)

Tags: , , , ,

JavaScript

About this blog

2010年5月にこのブログを立ち上げました。その後ブログ2を追加し、ここはプログラミング関係、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  October 2022  >>
MoTuWeThFrSaSu
262728293012
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar