WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

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

UpdatePanel と半角スペース

by WebSurfer 24. May 2011 22:40

下の画像のように、複数連続していた半角スペースが、非同期ポストバックで再描画されると 1 文字になってしまうという話です。

複数連続していた半角スペースが 1 文字になってしまう

Label に表示する文字列の中の連続する半角スペースをブラウザ上でそのまま表示したいので、Label コントロールの CssClass に white-space: pre; と設定していたとします。さらに、その Label コントロール を UpdatePanel に配置していたとします。

初期画面では、半角スペースは設定したとおり連続してブラウザに表示されます(white-space: pre; の設定がないと、複数連続した半角スペースを Label に設定しても、ブラウザに表示された時は一個になってしまいます)。

ところが、非同期ポストバックをかけて UpdatePanel 内を部分更新すると、初期画面と同様に複数連続した半角スペースを Label に設定しても、ブラウザに表示された時は一個になってしまいます。もちろん CssClass の white-space: pre; の設定は有効な状態でです。

なお、これはブラウザに依存する問題で、IE6 と IE7 で発生します(それ以前のバージョンは未検証)。IE8, IE9, Firefox, Chrome, Safari は期待通り UpdatePanel 内を部分更新しても問題ありません。

何故でしょう?

最初は ASP.NET AJAX に関係する問題と思っていたのですが、そうではなかったです。ヒントはブラウザに依存するというところです。

原因は AJAX の部分レンダリングの際、JavaScript によって innerHTML を書き換えるとき、JavaScript が複数スペースを一個にしてしまうところにありました。

書き換えられた後は一個しかスペースがないので、いくら white-space: pre; としても複数のスペースは表示されないというわけでした。

という訳で、ASP.NET AJAX とは関係ありませんでした。IE6, IE7 で JavaScript を使って innerHTML を書き換えてやるだけで、この問題は再現できます。例えば、以下のコードのように。

<!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>Rewrite innerHTML with multiple white space</title>
    <script type="text/javascript">
    //<![CDATA[
        var str = "<h1>半角スペース>     <5 文字??</h1>";

        function RewriteInnerHTML(id, innerHtml){
            document.getElementById(id).innerHTML = innerHtml;
        }
    //]]>
    </script>
</head>
<body>
    <div id="div1" style="white-space: pre;">
        <h1>半角スペース>     <5 文字</h1>
    </div>
    <input type="button" 
        value="innerHTML 書き換え" 
        id="button1" 
        onclick="RewriteInnerHTML('div1', str);" />
</body>
</html>

上記のコードを IE6 で実行し、[innerHTML 書き換え]ボタンをクリックした時のものがこの記事の一番上に示した画像です。

Tags:

AJAX

About this blog

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

Calendar

<<  January 2020  >>
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
272829303112
3456789

View posts in large calendar