WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

Object 型と列挙型引数のオーバーロード

by WebSurfer 2013年8月10日 12:48

SqlParameterCollection.Add メソッド には以下のオーバーロードがあります。

  • Add (Object)
  • Add (SqlParameter)
  • Add (String, SqlDbType)
  • Add (String, Object)
  • Add (String, SqlDbType, Int32)
  • Add (String, SqlDbType, Int32, String)

このうち、Add(String, Object) は Add(String, SqlDbType) との曖昧さの問題で廃止され、代わりに AddWithValue メソッド の使用が推奨されています。

何が曖昧かと言うと、MSDN ライブラリの SqlParameterCollection.Add メソッド (String, Object) の説明に書いてありますように、第 2 引数に 0(整数ゼロ)を渡すと、Add (String, Object) ではなくて、Add (String, SqlDbType) オーバーロードが呼び出されることです。

第 2 引数に 0 を渡せばもちろん、(int)0 でも Add (String, SqlDbType) オーバーロードが呼び出されます。

Add (String, Object) オーバーロードを呼び出すには、第 2 引数に Convert.ToInt32(0) または (object)0 を渡さなければなりません。

何故でしょう? 以下のような理由だと思います。(多少推測があります)

まず、SqlDbType が列挙型というところが要注意です。MSDN ライブラリ Enumeration Types (C# Programming Guide) によると、0(整数ゼロ)は列挙型のデフォルト値とのことで、特別な意味を持つようです。

それゆえ、0 や (int)0 は SqlDbType 列挙型のデフォルト値と判断されるらしく、Add (String, Object) ではなくて、Add (string, SqlDbType) オーバーロードが呼ばれます。

Convert.ToInt32(0) は(Object 型から派生した)int 型、(object)0 は Object 型と判断されて、Add (String, Object) オーバーロードが呼ばれます。

ちなみに、もし Add (String, Int32) というオーバーロードがあったとすると、第 2 引数は 0 でも (int)0 でも Convert.ToInt32(0) でも、Add (String, Int32) オーバーロードが呼ばれるはずです。

検証するため以下のサンプルを作ってみましたが、サンプルコード内のコメントにあるように期待通りの結果となりました。

namespace MyConsoleApplication
{
    enum Days { Sat, Sun, Mon, Tue, Wed, Thu, Fri };

    class TestClass
    {
        public void Add1(object obj)
        {
            Console.WriteLine("Add1(object)");
        }

        public void Add1(Days d)
        {
            Console.WriteLine("Add1(Days)");
        }

        public void Add2(object obj)
        {
            Console.WriteLine("Add2(object)");
        }

        public void Add2(Days d)
        {
            Console.WriteLine("Add2(Days)");
        }

        public void Add2(int i)
        {
            Console.WriteLine("Add2(int)");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            TestClass tc = new TestClass();

            tc.Add1(0);
            tc.Add1(1);
            tc.Add1((int)0);
            tc.Add1(Convert.ToInt32(0));
            tc.Add1((object)0);

            Console.WriteLine("--------------------");

            tc.Add2(0);
            tc.Add2(1);
            tc.Add2((int)0);
            tc.Add2(Convert.ToInt32(0));
            tc.Add2((object)0);

            // 結果は:

            // Add1(Days)
            // Add1(object)
            // Add1(Days)
            // Add1(object)
            // Add1(object)
            // --------------------
            // Add2(int)
            // Add2(int)
            // Add2(int)
            // Add2(int)
            // Add2(object)
        }        
    }
}

Tags: ,

.NET Framework

MVC は maxJsonLength を無視

by WebSurfer 2013年8月7日 13:32

Ajax を使用する際、クライアントとサーバー間でやり取りするデータには JSON (JavaScript Object Notation) 形式の UTF-8 文字列を使うのがデファクトスタンダードになっています。

ASP.NET Web Forms アプリケーションで Ajax を使って Web サービスと通信を行う場合も、先の記事 ASP.NET AJAX と Web サービスjQuery AJAX と Web サービス でも書きましたように、データのやり取りには JSON 形式の文字列を使っています。

クライアントからサーバーへ送信できる JSON 文字列の長さは、セキュリティ対策のため、デフォルトで 102,400 文字に制限されています。この値は、web.config の jsonSerialization 要素 (ASP.NET 設定スキーマ) の設定によって変更できます。以下のような感じです。

<system.web.extensions>
    <scripting>
        <webServices>
            <jsonSerialization maxJsonLength="3000000"/>
        </webServices>
    </scripting>
</system.web.extensions>

上の maxJsonLength 要素の設定により、JSON 文字列のデシリアライズに使用されている(と思われる)JavaScriptSerializer クラスの MaxJsonLength プロパティの設定が書き換えられるようで、デフォルト値の 2,097,152 文字を超えて JSON 文字列を送信することも可能です。(どこまで可能かは未検証ですが、2,097,152 文字を超えて可能なのは ASP.NET 4 Web Forms および MVC3 で確認しました)

ところが、呼び出す相手が Web サービスのメソッドでなく、MVC のアクションメソッドの場合、web.config の maxJsonLength 要素の設定は無視され、文字数制限は JavaScriptSerializerクラスの MaxJsonLength プロパティのデフォルト値 2,097,152 文字となります。

このあたりは、いろいろググって調べている時に、stackoverflow の記事 Can I set an unlimited length for maxJsonLength in web.config? を見つけて分かりました。そのページの Answers の 2 つ目に書いてあります。

いろいろ調べましたが、クライアントからサーバーに送信される JSON 文字列の長さ制限を変更する方法は見つかりませんでした。JavaScriptSerializer クラスの MaxJsonLength プロパティのデフォルト値 2,097,152 文字で固定となってしまいます。

ただし、逆方向のサーバーからクライアントに送信される JSON 文字列の長さ制限(MaxJsonLength プロパティのデフォルト値 2,097,152 文字)を緩和するための workaround はあります。詳しくは、上に紹介した stackoverflow のページに書いてありますので見てください。

クライアントからサーバーに送信する JSON データの文字数の制限を変更する方法はないか調べているときにわかったこと(何故変更できないか)を以下に書いておきます。

MVC のアクションメソッドを呼び出す場合、2,097,152 文字を超えてクライアントからサーバーにJSON データを送信すると「ArgumentException: JSON JavaScriptSerializer を使用したシリアル化または逆シリアル化中にエラーが発生しました。文字列の長さが maxJsonLength プロパティで設定されている値を超えています。」というエ��ーとなります。

スタックトレースを見ると JavaScriptSerializer クラスの DeserializeObject メソッドで JSON 文字列を受け取って、それデシリアライズするときに例外(ArgumentException・・・その条件の一つが「input の長さが MaxJsonLength の値を超えています。」)が発生したことが分かります。

さらに、スタックトレースから、そのメソッドは JsonValueProviderFactory クラスの GetDeserializedObject メソッドで使われているのが分かります。CodePlex のサイトで、その ソースコード を調べたら以下のようになっていました。

private static object GetDeserializedObject(
                    ControllerContext controllerContext)
{
  if (!controllerContext.HttpContext.Request.
          ContentType.StartsWith("application/json", 
                       StringComparison.OrdinalIgnoreCase))
  {
    // not JSON request
    return null;
  }

  StreamReader reader = new StreamReader(
        controllerContext.HttpContext.Request.InputStream);
  string bodyText = reader.ReadToEnd();
  if (String.IsNullOrEmpty(bodyText))
  {
    // no JSON data
    return null;
  }

  JavaScriptSerializer serializer = new JavaScriptSerializer();
  object jsonData = serializer.DeserializeObject(bodyText);
  return jsonData;
} 

上のコードの通り、GetDeserializedObject メソッドで使われている JavaScriptSerializer オブジェクトの MaxJsonLength プロパティの設定を変更する手段はないので、MVC のアクションメソッドを呼び出している限りは制限を 2,097,152 文字を超えて設定する手段はなさそうです。

どうしても 2,097,152 文字を超えて設定する必要があれば(実際、そんな必要があるのか分かりませんが)、アクションメソッドではなく、web サービス(.asmx)を作ってそのメソッドを呼び出すようにして、web.config の maxRequestLength 要素を 2,097,152 文字を超えた値に設定することで対応が可能です。

aspnet:MaxJsonDeserializerMembers について

上記の JSON 文字数の制限に加えて、2011 年 12 月 30 日に公開されたセキュリティ更新プログラム MS11-100 で HTTP 要求内の JSON メンバーの最大数がデフォルトで 1,000 に制限されるようになりました。

この 1,000 という数は、クライアントからサーバーに送信される JSON オブジェクト内の key:value アイテムの総数です。

デフォルト値 1,000 の変更は、web.config の ASP.NET appSettings 要素で aspnet:MaxJsonDeserializerMembers キーを設定することで可能です。

この制限を越えると JsonValueProviderFactory の EntryLimitedDictionary.Add メソッドで InvalidOperationException がスローされます。

ちなみにエラーメッセージは「InvalidOperationException: JSON リクエストが大きすぎるため、逆シリアル化できませんでした。」となります。

なお、web サービス(.asmx)のメソッドにも aspnet:MaxJsonDeserializerMembers による制限が有効かどうかは未確認です。

Tags: , ,

MVC

クライアント側での検証結果の表示

by WebSurfer 2013年8月1日 15:36
2017/3/31 追記
ASP.NET 4.5 でクライアント側での検証用スクリプトが大幅に変更されており、この記事の要である Page_Validators はインラインでページには埋め込まれませんが、WebResource.axd ハンドラでダウンロードされるスクリプトファイルに定義されており、以下の記事のコードをそのまま利用して「クライアント側での検証結果の表示」は可能です。

RequiredFieldValidator, RegularExpressionValidator 等の検証コントロールを利用してユーザー入力の検証を行う場合、クライアント側(ブラウザ)で行われた検証結果をチェックする方法を備忘録として書いておきます。

クライアント側での検証

検証コントロールをページに配置すると、デフォルト(即ち、EnableClientScript プロパティが true)では、クライアント側(ブラウザ)でユーザー入力を検証するためのスクリプトを ASP.NET が自動的に生成してくれます。

検証用のスクリプト本体は、検証コントロールのアセンブリに埋め込まれており、それを外部ファイルとして HTTP ハンドラ(WebResource.axd)を利用して取得する仕組みになっています。(ちなみに、インラインでページに埋め込まないのは、ブラウザでキャッシュできるようにするためです)

さらに、form 要素の onsubmit 属性に以下のようにスクリプトが設定されます。

onsubmit="javascript:return WebForm_OnSubmit();"

WebForm_OnSubmit メソッドの定義は ASP.NET が自動生成し、インラインで html コードに含めます。今回のサンプルコード(この記事の一番下にアップしたもの)の場合は以下のようなスクリプトが生成されます。

<script type="text/javascript">
//<![CDATA[
function WebForm_OnSubmit() {
  OnClientValidation();

  if (typeof(ValidatorOnSubmit) == "function" && 
      ValidatorOnSubmit() == false) return false;
  return true;
}
//]]>
</script>

上のコードで、if (typeof(ValidatorOnSubmit) から return true; までは、検証コントロールによって追加された検証用のスクリプトです。これにより、WebForm_OnSubmit メソッドは、クライアント側(ブラウザ)でのユーザー入力の検証結果が NG の場合 false を返すので、submit イベントがキャンセルされます(結果、ポストバックはかかりません)。

その前にある OnClientValidation(); は、Page_Load メソッドの中で RegisterOnSubmitStatement メソッドを使って登録した、自作のメソッドです。head 要素内にインラインで定義してあります。一番下のサンプルコードを参照してください。これが今回の記事のテーマです。

OnClientValidation メソッドで使っている Page_Validators は、ASP.NET が検証コントロールからレンダリングした span 要素の DOM の配列で、これに検証結果その他の情報が含まれています。今回のサンプルコードでは以下のようにインラインで定義されます。

Page_Validators の定義以外にもクライアント側(ブラウザ)での検証に必要なスクリプトがインラインで多々定義されています。詳細は、この記事の一番最後にアップしたサンプルコードを実行したときにレンダリングされる html ソースを見てください。

<script type="text/javascript">
//<![CDATA[
var Page_Validators =  new Array(
  document.getElementById("RequiredFieldValidator1"), 
  document.getElementById("RegularExpressionValidator1"), 
  document.getElementById("RequiredFieldValidator2"), 
  document.getElementById("RegularExpressionValidator2"), 
  document.getElementById("RequiredFieldValidator3"), 
  document.getElementById("CustomValidator1"));
//]]>
</script>

この Page_Validators を使って、全ての検証用コントロールのクライアント側(ブラウザ)での検証結果を取得し、alert を使って表示してみました。この記事の一番上の画像がその結果です。

なお、Page_Validators という名前は Microsoft の公式文書で公開されているわけではなく、将来、予告なしで変更される可能性もありますので注意してください。(ASP.NET 3.5, ASP.NET 4 は Page_Validators という名前であることを確認しましたが)

上の画像を表示したサンプルコードは以下の通りです。実際に動かして試すことができるよう 実験室 にアップしました。興味のある方は試してみてください。

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

  protected void Button1_Click(object sender, EventArgs e)
  {
    // クライアント側での検証が OK となって初めてポスト
    // バックがかかり、このメソッドに制御が飛んでくる。
    // ブラウザで JavaScript が有効になっており、クライ
    // アント側で検証が行われている限り、Page.IsValid
    // が false になることはない("Page is NOT valid"
    // という文字が Label に表示されることはない)はず。
    if (Page.IsValid)
    {
      Label1.Text = "Page is valid";
    }
    else
    {
      Label1.Text = "Page is NOT valid";
    }
  }

  protected void Page_Load(object sender, EventArgs e)
  {        
    // RegisterOnSubmitStatement メソッドにより form 要素
    // の onsubmit 属性に以下のスクリプトが設定される。
    //
    // onsubmit="javascript:return WebForm_OnSubmit();"
    //
    // この WebForm_OnSubmit メソッドの中に、第三引数(以
    // 下の例では cstext)に渡したスクリプトが設定される。
    // これにより、ポストバックする直前にクライアントスク
    // リプトを起動し、必要があればポストバックをキャンセ
    // ルすることもできる。
       
    string csname = "OnSubmitScript";
    Type cstype = this.GetType();
    ClientScriptManager cs = Page.ClientScript;
    if (!cs.IsOnSubmitStatementRegistered(cstype, csname))
    {
      string cstext = "OnClientValidation();";
      cs.RegisterOnSubmitStatement(cstype, csname, cstext);
    }

    // 注:
    // Validator を Page に配置すると WebForm_OnSubmit
    // メソッドの中には検証用のクライアントスクリプトも
    // 追加される。順序は、まず上記 cstext に指定した
    // スクリプト、次に Validator の検証用スクリプトと
    // なる。
  }

  // CustomValidator のサーバー側での検証用
  protected void CustomValidator1_ServerValidate(
    object source, ServerValidateEventArgs args)
  {
    string membership = args.Value.ToLower();

    if (membership == "gold" || membership == "silver")
    {
      args.IsValid = true;
    }
    else
    {
      args.IsValid = false;
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>WebSurfer's Page - 実験室</title>
  <script type="text/javascript">
  //<![CDATA[

    // CustomValidator のクライアント側での検証用
    function CustomValidator1_ClientValidate(sender, args) {
      var membership = args.Value.toLowerCase();
      if (membership === "gold" || membership === "silver") {
        args.IsValid = true;
      } else {
        args.IsValid = false;
      }
    }

    // クライアント側全 Validator の検証結果を取得。
    // Page_Validators は ASP.NET が Validator から
    // レンダリングした span 要素の DOM の配列。
    // 詳しくは html ソースに含まれる定義を参照。
    // 将来、Page_Validators と言う名前その他は予告
    // なしで変更される可能性もあるので注意。
    function OnClientValidation() {
      var msg = "クライアント側での検証結果:\n";
      for (var i = 0; i < Page_Validators.length; i++) {
        var validator = Page_Validators[i];
        var ctrl = 
          document.getElementById(validator.controltovalidate);
        if (ctrl != null) {
          if (validator.isvalid) {
            msg += "○: " + validator.id + "/" + 
              ctrl.id + "/\"" + ctrl.value + "\"\n";
          }
          else {
            msg += "×: " + validator.id + "/" + 
              ctrl.id + "/\"" + ctrl.value + "\"\n";
          }
        }
      }
      alert(msg);
    }

  //]]>
    </script>
</head>
<body>
  <form id="form1" runat="server">
  <table>
    <tr>
      <td>
        User Name
      </td>
      <td>
        <asp:TextBox ID="username" runat="server">
        </asp:TextBox>
      </td>
      <td>
        <asp:RequiredFieldValidator 
          ID="RequiredFieldValidator1" 
          runat="server" 
          ErrorMessage="ユーザー名は必須入力です。" 
          ControlToValidate="username" 
          ForeColor="Red" 
          Display="Dynamic">
        </asp:RequiredFieldValidator>
        <asp:RegularExpressionValidator 
          ID="RegularExpressionValidator1" 
          runat="server" 
          ErrorMessage=
            "半角アルファベットで 40 文字以内にしてください。" 
          ControlToValidate="username" 
          ForeColor="Red" 
          ValidationExpression="^[a-zA-Z''-'\s]{1,40}$" 
          Display="Dynamic">
        </asp:RegularExpressionValidator>
      </td>
    </tr>
    <tr>
      <td>
        Email
      </td>
      <td>
        <asp:TextBox ID="email" runat="server">
        </asp:TextBox>
      </td>
      <td>
        <asp:RequiredFieldValidator 
          ID="RequiredFieldValidator2" 
          runat="server" 
          ErrorMessage="メールアドレスは必須入力です。" 
          ControlToValidate="email" 
          ForeColor="Red" 
          Display="Dynamic">
        </asp:RequiredFieldValidator>
        <asp:RegularExpressionValidator 
          ID="RegularExpressionValidator2" 
          runat="server" 
          ErrorMessage=
            "メールアドレスの形式が正しくありません。" 
          ControlToValidate="email"
          ForeColor="Red" 
          ValidationExpression=
            "\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" 
          Display="Dynamic">
        </asp:RegularExpressionValidator>
      </td>
    </tr>
    <tr>
      <td>
        Membership
      </td>
      <td>
        <asp:TextBox ID="membership" runat="server">
        </asp:TextBox>
      </td>
      <td>
        <asp:RequiredFieldValidator 
          ID="RequiredFieldValidator3" 
          runat="server" 
          ErrorMessage="メンバーシップは必須入力です。" 
          ControlToValidate="membership" 
          ForeColor="Red" 
          Display="Dynamic">
        </asp:RequiredFieldValidator>
        <asp:CustomValidator 
          ID="CustomValidator1" 
          runat="server" 
          ErrorMessage=
            "Gold または Silver でなければなりません。" 
          ControlToValidate="membership"
          ForeColor="Red" 
          OnServerValidate="CustomValidator1_ServerValidate" 
          Display="Dynamic" 
          ClientValidationFunction=
            "CustomValidator1_ClientValidate" >
        </asp:CustomValidator>
      </td>
    </tr>
  </table>
  <asp:Button ID="Button1" 
    runat="server" 
    Text="送信" 
    OnClick="Button1_Click" />
  <br />
  <asp:Label ID="Label1" runat="server">
  </asp:Label>
  </form>
</body>
</html>

Tags:

Validation

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年3月  >>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar