WebSurfer's Home

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

jQuery.ajax の data の型

by WebSurfer 2015年10月5日 14:38

jQuery.ajax を利用してデータをサーバーに送信するには、以下のような形で data オプションに送信するデータを設定しますが、そのデータ(下のコードで言うと jsonString)の型は何にすべきかという話を書きます。

function apiHeroesPost() {
    $.ajax({
        type: "POST",
        url: "api/heroes",
        data: jsonString,
        contentType: "application/json; charset=utf-8",
        success: function (data) {
            // ・・・省略・・・
        },
        error: function (jqXHR, textStatus, errorThrown) {
            // ・・・省略・・・
        }
    });
}

答を先に書くと、

  1. GET 要求(クエリ文字列としてデータを送信)の場合は JavaScript オブジェクト
  2. POST 要求(コンテンツとしてデータを送信)の場合、(a) 受け取る側が application/x-www-form-urlencoded 形式を期待している場合は JavaScript オブジェクト、(b) ASP.NET Web API のように JSON 文字列を期待している場合は JSON 文字列

を data オプションに設定します。上のコードは POST 要求で「(b) ASP.NET Web API のように JSON 文字列を期待している」場合です。以下、POST 要求については (b) を前提に書きます

jQuery の API Dcoumentation の説明によると、設定できる型は PlainObject or String or Array となっていますが、どれでもいいという訳ではありません。

その説明にある "It is converted to a query string, if not already a string. It's appended to the url for GET-requests." というところがポイントです。

具体的には、例えば、processData は設定しない(デフォルトの true)という条件で、

var j = { Id: 5, Name: "日本語" };  // j はオブジェクト
var jsonString = JSON.stringify(j); // jsonString は文字列

とした場合、GET 要求なら data:j とし、POST 要求なら data:jsonString とします。

data:j とすると j は application/x-www-form-urlencoded 形式に変換されて Id=5&Name=%E6%97%A5%E6%9C%AC%E8%AA%9E となり、GET 要求の際 URL にクエリ文字列として追加されます。(%E6%97%A5 ... %AA%9E は "日本語" の UTF-8 パーセントエンコーディング)

しかし、data:jsonString とすると、文字列は無変換なので、GET 要求の際そのままクエリ文字列として URL に追加されます。下の画像は Fiddler2 による GET 要求のキャプチャです。反転表示した部分を見てください。クエリ文字列が {"Id":5,"Name":"日本語"} となってるのが分かるでしょうか? (ブラウザに依存すると思いますが、IE9 では "日本語" は Shift_JIS になり、下の画像では 22 93 FA 96 7B 8C EA 22 というバイト列になっています)

不正なクエリ文字列

POST 要求の場合も、data の変換の仕方は同じ(オブジェクトはクエリ文字列形式に変換、文字列は無変換)になります。しかし、POST 要求はコンテンツとしてデータを送信しますので、GET 要求と違って data:j(j はオブジェクト)ではダメです。

data:jsonString として文字列を設定すればそのまま UTF-8 コードのコンテンツとして送信されますので、jsonString が有効な JSON 文字列なら期待通りの結果が得られるはずです。(注:contentType: "application/json; charset=utf-8" が前提です)

たぶん普通は、GET と POST の使い分けは以下のようにする(参考:jQuery - AJAX get() and post() Methods)と思います。

  • GET - Requests data from a specified resource
  • POST - Submits data to be processed to a specified resource

なので、データを送信する際は POST 要求、jQuery.ajax の data には JSON 文字列を設定ということしか頭になかったです。それでちょっとハマってしまったので、備忘録として残しておくことにしました。

Tags: ,

AJAX

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

jQuery Ajax で xml 形式のデータ受け取り

by WebSurfer 2012年7月17日 21:37

jQuery Ajax で Web サービスを呼び出し、xml 形式のデータを受け取る場合の話です。

まず、Web サービスのメソッドの戻り値の型の形式として XML を指定するときは、ScriptMethodAttribute 属性 を追加し、ResponseFormat プロパティ を Xml に設定します。さらに、メソッドが XmlDocument オブジェクト を返すようにします。以下のような感じです。

[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Xml)]
public XmlDocument GetXmlDocument()
{
    // Xml 形式の文字列を組み立て。
    string _xmlString = CreateXmlString();

    XmlDocument xmlDoc = new XmlDocument();
    xmlDoc.LoadXml(_xmlString);
    return xmlDoc;
}

こうしておくと、XmlDocument オブジェクトはサーバーで Xml 形式の文字列にシリアル化され、このメソッドを呼んだクライアントに返されます。また、応答ヘッダには Content-Type: text/xml が含まれるようになります。

ScriptMethod 属性の ResponseFormat プロパティを Xml に設定しておかない場合、Web サービスのメソッドが返すデータの形式は、クライアントからの要求ヘッダの Content-Type の指定によるようです。

クライアントからサーバーへデータを送信する場合、要求ヘッダの Content-Type は、application/x-www-form-urlencoded または application/json のいずれかになるはずです(データを送信しない場合、Content-Type を指定しないこともありますが)。

前者は普通に form データをサーバーに POST する時のデータ形式、後者は Ajax でのデータ交換フォーマットのデファクトスタンダード(?)である JSON 形式です。

要求ヘッダの Content-Type が application/json となっている場合、ScriptMethod 属性の ResponseFormat プロパティを Xml に設定しておかないと、戻り値が Xml 形式にならない(といって、正しい Json 形式にもならない)ので注意してください。

要求ヘッダの Content-Type を設定しない、または application/x-www-form-urlencoded とした場合は、ResponseFormat プロパティの設定に関係なく、戻り値は Xml 形式になります。(ResponseFormat プロパティを Json 設定しても無視されて、戻り値は Xml 形式になります。)

クライアントで Web サービスからのデータを受け取るのは jQuery Ajax の success ハンドラです。jQuery Ajax は、サーバーからの応答ヘッダに含まれる Contetnt-Type を見て success ハンドラに渡すデータの形式を決めます。

jQuery API の解説ページ jQuery.ajax() に次の説明があります。

"The text and xml types return the data with no processing. The data is simply passed on to the success handler, either through the responseText or responseXML property of the jqXHR object, respectively."

上のサンプルに書いたコードの場合、Web サービスからの応答ヘッダに Contente-Type: text/xml が含まれますので、success ハンドラに渡される data は jqXHR.responseXML から取得されます。

jqXHR.responseXML は何かというと、MSDN ライブラリの responseXML property に書いてあるように、XML DOM オブジェクト(ただのシリアル化された文字列ではなく)になります。

シリアル化された文字列を表示したい場合は、IE であれば xml プロパティが使えます。詳しくは、MSDN ライブラリの IXMLDOMDocument/DOMDocument Members を参照してください。

ただし、xml プロパティは IE 専用なので、Mozilla 系のブラウザの場合は以下のように XMLSerializer を使用できます。

function getXmlDocument() {
  $.post("163-ScriptMethodAttribute.asmx/GetXmlDocument",
    function (data, textStatus, jqXHR) {
      $('#output').empty();
      $('#output').text(data.xml || 
        (new XMLSerializer()).serializeToString(data));
    }
  );
}

または、jqXHR.responseText から取得することも可能です。

Tags: , ,

AJAX

About this blog

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

Calendar

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

View posts in large calendar