WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

jQuery AJAX と Web サービス

by WebSurfer 4. June 2011 21:05

注意:
今頃になって何ですが、 jQuery Ajax の仕様が変わって、バージョン 1.5 以降では dataFilter に指定したメソッドで JSON 文字列を JavaScript オブジェクトにパースできなくなりました。詳しい説明はこの記事の下の方にある「2013/8/9 追記」を見てください。

先の記事 ASP.NET AJAX と Web サービス では、AJAX 対応 ASP.NET Web ページから Web サービスにアクセスする例を書きましたが、この記事では jQuery を使って AJAX 通信を行う例を書きます。

ASP.NET .aspx ファイルでない Web ページでは、ScriptManager コントロールを使用できません。従って、先の記事に書きましたプロキシクラスは利用できません。

この記事では、静的な html ページから、jQuery を使った AJAX 通信を行い、Web サービスからデータを JSON 形式で取得する方法について説明します。

基本的には、先の記事で紹介したページ Handling JSON Arrays ... に記載されていたサンプルとほぼ同じです。

ただし、d パラメータ有り(.NET 3.5 以降)/無し(.NET 2.0 以前)の両方に対応するためのコードと、ブラウザに実装されているネイティブの JSON パーサーを使えるように手を加えました。

それらの機能を追加しても、先の記事で書いた ScriptManager を利用してプロキシを自動生成する場合と同等以上に簡単と思いますがいかがでしょう。マイクロソフトが jQuery を Visual Studio 2010 に統合した理由が分かるような気がします。

以下のコードは、実際に動かして試せるよう 実験室 にアップしましたので、興味のある方は試してみてください。

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

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>jQUery AJAX and Web Service</title>
  <script src="Scripts/jquery-1.4.1.js" type="text/javascript">
  </script>
  <script type="text/javascript">
  //<![CDATA[
  function getCars() {
    $.ajax({
      type: "POST",
      url: "097_jQueryAjaxAndWebService.asmx/GetCarsByDoors",
      data: '{"doors":' + $("#ddlDoors").val() + '}',

      // JSON を期待する場合必須(下の追記参照)
      contentType: "application/json; charset=utf-8",

      // success ハンドラで d の有無を判定しないで済むように
      // dataFilter を使う。
      // その際、dataType: "json" は不用。
      // dataType は応答データをどのように deserialize する
      // かを jQuery に指示するもの。"json" が指定されてい
      // ると jQuery は内部的に eval メソッドを使って 
      // deserialize する。dataType: "json" が指定してある
      // と  dataFilter と二重に deserialize されてしまう。

      dataFilter: function (data) {
        // ブラウザの native JSON パーサがあればそれを
        // 使う(セキュリティ対策)  
        var msg = "";            
        if (typeof (JSON) !== 'undefined' &&
          typeof (JSON.parse) === 'function') {
          msg = JSON.parse(data);
        } else {
          msg = eval('(' + data + ')');
        }

        // .NET 3.5 で追加された d パラメータの処置。
        if (msg.hasOwnProperty('d')) {
          return msg.d;
        } else {
          return msg;
        }
      },
      success: function (cars) {
        $('#output').empty();
        $.each(cars, function (index, car) {
          $('#output').append(
            '<p><strong>' + car.Make + ' ' +
            car.Model + '</strong><br /> Year: ' +
            car.Year + '<br />Doors: ' +
            car.Doors + '<br />Colour: ' +
            car.Colour + '<br />Price: £' +
            car.Price + '</p>');
          });
        },
        // failure を error に訂正。下の追記参照。
        // failure: function (msg) {
        //   $('#output').text(msg);
        // }
        error: function (jqXHR, textStatus, errorThrown) {
          $('#output').text('textStatus: ' + textStatus + 
            ', errorThrown: ' + errorThrown);
        }
    });
  }
  //]]>
  </script>
</head>
<body>
  <div>
    Number of doors: 
    <select name="ddlDoors" id="ddlDoors">
	  <option value="2">2</option>
	  <option value="3">3</option>
	  <option value="4">4</option>
	  <option value="5">5</option>
    </select>   
  </div>
  <input type="button" 
    id="Button1" 
    value="Get Cars" 
    onclick="getCars();" /> 
  <div id="output"></div>
</body>
</html>

------ 2011/9/3 追記 ------

上記コードの $.ajax のオプション設定の中の failure は、参考にしたサイト Handling JSON Arrays returned from ASP.NET Web Services with jQuery のコードをコピペしたものですが、どうも間違っているようです。

jQuery の本家のサイトの jQuery.ajax() の説明を見ると、オプション設定の中に failure というのはなく、error が正しいはずです。実際に試してみましたが、失敗しても制御は failure に設定した function には飛ばず、error に設定した function に飛びました。

という訳で、訂正しました。やはり、何も考えずにコピペして、しかも検証もしないというのはダメですね。(汗)

----- 2012/7/16 追記(2014/3/24 一部訂正) -----

jQuery Ajax で contentType: "application/json; charset=utf-8" を設定しないと、Web サービスのメソッドから返されるデータは xml 形式になってしまいます。結果、パースエラーになり error に設定したメソッドに制御が飛びます。

jQuery のサイトの API Documentation の説明によると、jQuery.ajax() の contentType のデフォルトは 'application/x-www-form-urlencoded; charset=UTF-8' とのことですが、何も設定しないと要求ヘッダには Content-Type: そのものが設定されません(1.4.1 でしか試してませんが)。その場合でも、Web サービスのメソッドが返すデータ合は Xml 形式になります。

なお、要求ヘッダが Content-Type: application/x-www-form-urlencoded もしくは何も指定しない場合は ScriptMethodAttribute クラスResponseFormat プロパティ を Json に指定しても無視されます。

jQuery.post() や jQuery.get() では contentType が指定できないので注意してください。

----- 2012/12/23 追記 -----

POST する JSON 文字列が {doors:5} となっており、正しく {"doors":5} となってなかったので以下のように訂正しました。

data: "{doors: " + $('#ddlDoors').val() + "}",
 ↓↓↓
data: '{"doors":' + $("#ddlDoors").val() + '}',

以前の形式でも問題なく Web サービスのメソッドは呼び出され、応答は正しく帰ってきましたが、気分の問題ということで直しました。(笑)

------ 2013/8/9 追記 ------

jQuery バージョン 1.4(多分それ以前のバージョンも)では、dataFilter のメソッドの戻り値が string 型以外のときは、そのまま結果を success のメソッドの引数に渡します(string 型の時に限り戻り値をパースしてから success のメソッドに渡します)。

なので、上のサンプルコードのように、dataFilter のメソッドで JSON をパースして JavaScript オブジェクトに変換すると、string 型ではないので、JavaScript オブジェクトがそのまま success のメソッドの引数 cars に渡され、問題なく結果が表示されます。

ところが、バージョン 1.5 以降では、上のサンプルコードのように dataFilter のメソッドで JSON 文字列を JavaScript オブジェクトにパースすると、 success のメソッドの引数 cars に渡されるのは null になり、結果スクリプトエラーとなってしまいます。その理由は以下の通りです。

  1. サーバーからの応答の Content-Type が application/json の場合、dataFilter の結果を parseJSON: function(data){...} で定義されているメソッドの引数 data に渡す。
  2. parseJSON: function(data){...} メソッドは、引数として受け取った data をパースして JavaScript オブジェクトを戻り地として返すようになっているが、typeof data !== "string" の場合は、パース等何もしないで null を返す。
  3. dataFilter のメソッドで JSON 文字列をパースして JavaScript オブジェクトに変換してしまうと、data は string 型ではないので null が返される。
  4. 結果、success のメソッドの引数 cars に null が渡され、スクリプトエラーとなる。

従って、バージョ��� 1.5 以降では、パースは jQuery に任せて(dataFilter のメソッドは記述しないで)、d パラメータの処置のみを success のメソッドで行うことになります。上記のサンプルコードの function getCars() は以下のように修正してください。

function getCars() {
  $.ajax({
    type: "POST",

    url: "097_jQueryAjaxAndWebService.asmx/GetCarsByDoors",

    data: '{"doors":' + $("#ddlDoors").val() + '}',

    contentType: "application/json; charset=utf-8",

    success: function (cars) {
      if (cars.hasOwnProperty('d')) {
          cars = cars.d;
      }
      $('#output').empty();
      $.each(cars, function (index, car) {
        $('#output').append(
          '<p><strong>' + car.Make + ' ' +
          car.Model + '</strong><br /> Year: ' +
          car.Year + '<br />Doors: ' +
          car.Doors + '<br />Colour: ' +
          car.Colour + '<br />Price: £' +
          car.Price + '</p>');
        });
      },

      error: function (jqXHR, textStatus, errorThrown) {
        $('#output').text('textStatus: ' + textStatus + 
          ', errorThrown: ' + errorThrown);
      }
  });
}

Tags: ,

AJAX

ASP.NET AJAX と Web サービス

by WebSurfer 4. June 2011 17:52

ASP.NET AJAX Web ページから Web サービスにアクセス

AJAX 対応 ASP.NET Web ページからクライアントスクリプトを使って Web サービスにアクセスする方法を、MSDN ライブラリの ASP.NET AJAX での Web サービスの使用 を参考に実装してみました。

参考にした MSDN ライブラリのページに記載されていたサンプルコードではあまり面白くないし、jQuery AJAX を利用してアクセスする方法と比較してみたかったので、ググって探したページ Handling JSON Arrays returned from ASP.NET ... に記載されていたサンプルを使って、呼び出し側を ASP.NET AJAX Web ページに変更して実装してみました。

非同期要求の要となるのが、プロキシクラスに定義されている JavaScript のメソッド類です。クライアントとサーバーの通信におけるプロキシクラスの位置づけなどの説明は上に紹介した MSDN ライブラリを参照してください。

ASP.NET Web ページに ScriptManager コントロールを追加し、その Services 要素に asp:ServiceReference 要素を追加し、Path 属性に Web サービスの URL 設定することにより、自動的にプロキシクラスが生成されます。

自動生成されたプロキシクラスがページの読み込み時にブラウザにダウンロードされるように、初期画面の html ソースに以下のような外部スクリプトファイルへの参照が含まれます。(下記で js は web.config の設定がデバッグモードになっていると jsdebug になります)

<script src="WebService.asmx/js" type="text/javascript">
</script>

このプロキシクラスに定義されたメソッドを使用して Web サービスメソッドに対して JavaScript の非同期要求を行います。

ブラウザとサーバー間で交換されるデータのシリアル化形式としては JSON が使用されます。Web サービスが JSON 形式のデータを返すようにするには、Web サービスクラスに、System.Web.Script.Services 名前空間の ScriptService 属性を付与します。

(1) Web サービス、(2) aspx ページでプロキシクラスを操作するための JavaScript、(3) aspx ページのコードを、その順に以下にアップしておきます。Web サービスのコードは、上に紹介したページのサンプルと同じです。(リンク切れになると困るのでここに貼っておきます)

jQuery AJAX を利用した場合と比較してのメリットは、.NET 3.5 で追加された d パラメータがプロキシで適切に処置されるところと、コードが若干少なくなる点でしょうか。

以下のコードは、実際に動かして試せるよう 実験室 にアップしましたので、興味のある方は試してみてください。

(1) Web サービス (097_jQueryAjaxAndWebService.asmx)

クライアントスクリプトから呼び出すことができるように、クラスに ScriptService 属性を追加しているところがポイントです。

<%@ WebService Language="C#" Class="CarService" %>

using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Web.Script.Services;
using System.Collections.Generic;
using System.Linq;

public class Car
{
    public string Make;
    public string Model;
    public int Year;
    public int Doors;
    public string Colour;
    public float Price;
}

/// <summary>
/// Summary description for CarService
/// </summary>

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class CarService : WebService
{
    List<Car> Cars = new List<Car>{
        new Car{Make="Audi",Model="A4",Year=1995,
            Doors=5,Colour="Red",Price=2995f},
        new Car{Make="Ford",Model="Focus",Year=2002,
            Doors=5,Colour="Black",Price=3250f},
        new Car{Make="BMW",Model="5 Series",Year=2006,
            Doors=4,Colour="Grey",Price=24950f},
        new Car{Make="Renault",Model="Laguna",Year=2000,
            Doors=5,Colour="Red",Price=3995f},
        new Car{Make="Toyota",Model="Previa",Year=1998,
            Doors=5,Colour="Green",Price=2695f},
        new Car{Make="Mini",Model="Cooper",Year=2005,
            Doors=2,Colour="Grey",Price=9850f},
        new Car{Make="Mazda",Model="MX 5",Year=2003,
            Doors=2,Colour="Silver",Price=6995f},
        new Car{Make="Ford",Model="Fiesta",Year=2004,
            Doors=3,Colour="Red",Price=3759f},
        new Car{Make="Honda",Model="Accord",Year=1997,
            Doors=4,Colour="Silver",Price=1995f}
    };
    
    [WebMethod]
    public List<Car> GetAllCars()
    {
        return Cars;
    }

    [WebMethod]
    public List<Car> GetCarsByDoors(int doors)
    {
        var query = from c in Cars
                    where c.Doors == doors
                    select c;

        return query.ToList();
    }
}

(2) JavaScript (097_ASPNETAjaxAndWebService.js)

Succeeded コールバック関数の引数に渡されるのは JSON の文字列ではなく、パース済みの JavaScript オブジェクトとなる点に注意してください。セキュリティ対策のため .NET 3.5 で JSON 文字列に追加された d パラメータはプロキシクラスで適切に除去されるようです。

var serviceProxy;

// プロキシの初期化とコールバック関数の設定
function pageLoad() {
    serviceProxy = new CarService();
    serviceProxy.set_defaultSucceededCallback(Succeeded);
    serviceProxy.set_defaultFailedCallback(Failed);
}

// ボタンクリックで呼び出されるサービスメソッド  
function getCars(doors) {
    serviceProxy.GetCarsByDoors(doors);
}

// AJAX 通信が成功したときに呼び出され、戻ってきたデータ
// を処置するコールバック関数。
// 引数 cars は JSON 文字列ではなく、パース済みのオブジェ
// クト。.NET 3.5 で追加された d パラメータはプロキシで
// 適切に処置されるらしい。
function Succeeded(cars) {
    $('#output').empty();
    $.each(cars, function (index, car) {
        $('#output').append(
            '<p><strong>' + car.Make + ' ' +
            car.Model + '</strong><br /> Year: ' +
            car.Year + '<br />Doors: ' +
            car.Doors + '<br />Colour: ' +
            car.Colour + '<br />Price: £' +
            car.Price + '</p>');
    });
}

// 通信に失敗したとき呼び出されるコールバック関数。 
function Failed(error, userContext, methodName) {
    if (error !== null) {
        var msg = "An error occurred: " +
            error.get_message();
        $('#output').text(msg);
    }
}

if (typeof (Sys) !== "undefined") {
    Sys.Application.notifyScriptLoaded();
}

(3) apsx ページ (097_ASPNETAjaxAndWebService.aspx)

プロキシクラスを自動生成するために、ScriptManager コントロールを追加し、その Services 要素に asp:ServiceReference 要素を追加し、Path 属性に Web サービスの URL 設定するところがポイントです。

<%@ 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">

</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>ASP.NET AJAX and Web Service</title>
  <script src="Scripts/jquery-1.4.1.js" type="text/javascript">
  </script>
</head>
<body>
  <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="scriptManager">
      <Services>
        <asp:ServiceReference 
          Path="~/097_jQueryAjaxAndWebService.asmx" />
      </Services>
      <Scripts>
        <asp:ScriptReference 
          Path="~/097_ASPNETAjaxAndWebService.js" />
      </Scripts>
    </asp:ScriptManager>
    <div>
      Number of doors: 
      <asp:DropDownList ID="ddlDoors" runat="server">
        <asp:ListItem>2</asp:ListItem> 
        <asp:ListItem>3</asp:ListItem>
        <asp:ListItem>4</asp:ListItem>
        <asp:ListItem>5</asp:ListItem>
      </asp:DropDownList>   
    </div>
    <input 
      type="button" 
      id="Button1" 
      value="Get Cars" 
      onclick="getCars($('#<%= ddlDoors.ClientID %>').val());" /> 
    <div id="output"></div>
  </form>
</body>
</html>

----- 2012/7/16 追記(2014/3/24 一部訂正) -----

Web サービスのメソッドに ScriptService 属性を追加することにより JSON 形式のデータが返されるというわけではありません。MSDN ライブラリの ScriptServiceAttribute クラス にもそのようなことは書いてなくて、"Web サービス メソッドを ECMAScript (JavaScript) から起動する" ためだけのようです。

Web サービスのメソッドが返すのは、Json か Xml 形式のいずれかになりますが、それを決めるのは要求ヘッダの Content-Type の設定のようです。

MSDN ライブラリには Content-Type で決まるというような記述は見つけられませんでしたが、自分が試した限りでは以下の通りでした。

  1. Content-Type: application/x-www-form-urlencoded もしくは指定しない場合は Xml
  2. Content-Type: application/json の場合は Json

この記事のように、プロキシクラスを利用する場合は、自動的に要求ヘッダに Content-Type: application/json と指定され、Json 形式でデータが帰ってきます。

jQuery AJAX と Web サービス のページで紹介した jQuery Ajax を使う場合は、contentType に application/json を指定しないと、Web サービスのメソッドが返すデータ合は Xml 形式になってしまうので注意が必要です。

(注) jQuery のサイトの API Documentation の説明によると、jQuery.ajax() の contentType のデフォルトは 'application/x-www-form-urlencoded; charset=UTF-8' とのことですが、何も設定しないと要求ヘッダには Content-Type: そのものが設定されません(1.4.1 でしか試してませんが)。その場合でも、Web サービスのメソッドが返すデータ合は Xml 形式になります。

なお、要求ヘッダが Content-Type: application/x-www-form-urlencoded もしくは何も指定しない場合は ScriptMethodAttribute クラスResponseFormat プロパティ を Json に指定しても無視されます。

ただし、要求ヘッダが Content-Type: application/json の場合は ResponseFormat プロパティの設定は意味があって、Xml に設定すると Web サービスのメソッドが返すデータは Xml 形式になります。

Tags: ,

AJAX

ModalPopup でプログレス表示

by WebSurfer 29. May 2011 17:43

AJAX による部分ページ更新のプログレスを表示するには、普通、UpdateProgress コントロールを利用しますが、代わりに ModalPopup を使った例を書いてみました。

ModalPopup はクライアントのコードで表示/非表示にできますので、リクエストの開始と完了のイベントがクライアント側で取得できれば、そのイベントハンドラでコントロールできます。

イベントは PageRequestManager クライアントオブジェクトに用意されている beginRequest と endRequest を使っています。PageRequestManager クラスの詳しい説明は MSDN ライブラリ を参照してください。

以下のコードは、実際に動かして試せるよう 実験室 にアップしましたので、興味のある方は試してみてください。

<%@ Page Language="C#" %>
<%@ Register Assembly="AjaxControlToolkit" 
    Namespace="AjaxControlToolkit" 
    TagPrefix="asp" %>

<!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 Button_Click(object sender, EventArgs e)
  {
    System.Threading.Thread.Sleep(5000);
  }
</script>

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

      function pageLoad(sender, args) {
          if (args.get_isPartialLoad() === false) {
              manager =
            Sys.WebForms.PageRequestManager.getInstance();
              manager.add_beginRequest(OnBeginRequest);
              manager.add_endRequest(OnEndRequest);
          }
      }

      function OnBeginRequest(sender, args) {
          var modalPopupBehavior =
          $find('programmaticModalPopupBehavior');
          modalPopupBehavior.show();
      }

      function OnEndRequest(sender, args) {
          var modalPopupBehavior =
          $find('programmaticModalPopupBehavior');
          modalPopupBehavior.hide();
      }

      function AbortPostBack() {
          if (manager.get_isInAsyncPostBack()) {
              manager.abortPostBack();
          }
      }
  //]]>
  </script>

  <style type="text/css">
    /*Modal Popup*/
    .modalBackground {
      background-color: Gray;
      filter: alpha(opacity=70);
      opacity: 0.7;
    }

    #UpdatePanel1 { 
      width: 200px; 
      height: 200px; 
      border: gray 2px solid;
      position: relative;
      float: left; 
      margin-left: 10px; 
      margin-top: 10px;
    }

    .progress {
      height: 50px;
      width: 400px;
      background-color: White;
    }
  </style>
</head>
<body>
  <form id="form1" runat="server">
    <asp:ToolkitScriptManager 
      ID="ToolkitScriptManager1" 
      runat="server">
    </asp:ToolkitScriptManager>
    <asp:UpdatePanel ID="UpdatePanel1"  
      runat="server">
      <ContentTemplate>
        UpdatePanel
        <hr />            
        <%=DateTime.Now.ToString() %> <br />
        <asp:Button ID="Button1" 
          runat="server" 
          Text="Refresh Panel" 
          OnClick="Button_Click" />    
        <br /><br />
        [Refresh Panel]ボタンをクリックすると、
        5 秒後にこのパネル内が更新されます。
        その間 ModalPopup が表示されます。
      </ContentTemplate>
    </asp:UpdatePanel>

    <asp:Button ID="DummyButton" 
      runat="server" 
      style="display: none;" />

    <asp:Panel ID="Panel1" 
      runat="server" 
      CssClass="progress">
      <asp:Image ID="Image1" 
        runat="server" 
        ImageUrl="~/img/grid-loading.gif" />
      非同期ポストバックで更新中です・・・
      <input type="button" 
        value="中止" 
        onclick="AbortPostBack()" />
    </asp:Panel>
    <asp:ModalPopupExtender ID="ModalPopupExtender1" 
      runat="server" 
      TargetControlID="DummyButton"
      BehaviorID="programmaticModalPopupBehavior"
      PopupControlID="Panel1" 
      BackgroundCssClass="modalBackground" />
  </form>
</body>
</html>

Tags: , ,

AJAX

About this blog

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

Calendar

<<  November 2020  >>
MoTuWeThFrSaSu
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456

View posts in large calendar