분류 전체보기 (328)
.NET (111)
S/W tip (35)
etc (63)
DB (34)
HOT item~! (48)
Disign pettens (4)
UX (6)
나의 S/W (2)
개발관련 이슈 (16)
Diary (1)
웹플러스 (1)
calendar
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
tags
archive
link
ColorSwitch 00 01 02
출처 : http://mrtint.tistory.com/694

오늘 회사에서 한 1~2시간 동안 헤메였던 문제였다. 뭐든 다 그렇듯 생각보다 방법은 엄청 쉬웠다는게 좀 -_-;; 일단 이번 문제를 해결하면서 왠지 조금은 ASP.NET 에 대해서 이해를 하기 시작했다고 해야하나.. 암튼 그런게 좀 있다 -_-;; 여튼 한번 상황을 정리 해보자면 이러하다..

<상황>

비동기식으로 되어있었던 DropDownList 를 UpdatePanel / Trigger 의 AsyncPostBackTrigger 로 등록하고자 했다. 이유인 즉슨 DropDownList 두개를 등록해서 하나는 대분류, 하나는 소분류의 형태로 만들고자 했기 때문이다.


위와 같은 형태로 말이다.. 좌측의 List (대분류) 를 선택하면 그에 해당되는 목록이 나오는 우측의 List를 만들기 위함이었다. 비동기식으로 만들어버리는 UpdatePanel 속에 있으니 OnSelectedIndexChanged 라는 속성이 작동하지 않았기 때문에 이 해당 Control만 동기식으로 변경하도록 변경하고자 했다.



<문제>

  1. <Triggers>  
  2.             <asp:AsyncPostBackTrigger ControlID="linkBtn" EventName=""/>  
  3.             <asp:AsyncPostBackTrigger ControlID="highLevelTroubleType" EventName="SelectedIndexChanged"/>  
  4.             <asp:AsyncPostBackTrigger ControlID="lowLevelTroubleType"/>  
  5.         </Triggers>  
위처럼 Trigger를 등록했다. ControlID를 아주 올바르게 썼으며.. LOG로 출력해보았을때도 ID는 분명 상기에 기재한것과 동일했다. 하지만 아래처럼 (InvalidOperationException 발생 후덜덜 ㅠㅠ) 찾을 수 없는 컨트롤이라는 메시지가 나왔는데, 여러가지 추측을 해봤었다. 그중에서 페이지 로드를 하면서 Control을 생성하는 도중에 문제가 생기는 거라고 생각했지만 전혀 관계 없는 추측이었다. 한 시간정도 서핑을 해본 끝에 뭔가 얻은 아이디어가 하나 있었는데, 비슷한 Issue 들이 있어서 포럼을 통해 의견을 나눈 예제들을 살펴보면 구조적으로 좀 다른 형태를 띠고 있었다.



 
<해결>

위의 linkBtn 이라는 ControlID를 가진 컨트롤은 UpdatePanel의 '직속 자식' Control 이었다. 하지만 내가 정의한 highLevelTroubleType 은 UpdatePanel 의 자식 Control 인 GridView 에 속하는 '손자' 형태의 Control 이었다. 살펴보면 linkBtn 만 정상적으로 작동하는게 이상하기도 하다. 그래서 이와 관련하여 몇가지 조회를 해보니, '손자' ControlID를 표현하는 방식이 따로 있었다. 그래서 변경된 Trigger 태그의 모습은 아래와 같다.

  1. <Triggers>  
  2.     <asp:AsyncPostBackTrigger ControlID="linkBtn" EventName=""/>  
  3.     <asp:AsyncPostBackTrigger ControlID="DetailsView1$highLevelTroubleType" EventName="SelectedIndexChanged"/>  
  4.     <asp:AsyncPostBackTrigger ControlID="DetailsView1$lowLevelTroubleType"/>  
  5. </Triggers>  
이렇게 올바른 ControlID 를 심어주게 되면 정상적으로 컴파일이 되어 작동한다. 처음에는 오타를 친줄 알고 열심히 철자를 검사하고 있었는데.. 어찌보면 오타가 맞다. 뭔가 ASP.NET 의 컨트롤들의 구조를 제대로 이해하지 못하고 있었던 탓이기도 하니까...


▣  ASP.net 클라이언트 콜백 - .NET/ASP.NET - 2011. 6. 14. 20:15
출처 : http://nyjin.tistory.com/10

1. 개요

페이지 전체를 다시 요청 하지 않고, 클라이언트 스크립트를 이용하여 서버의 데이터를 가져와 이미 생성된 페이지에 사용 하는 방법

2. 특징

● 전체 포스트백 주기(postback cycle)을 모두 거치지 않고 페이지의 값을 변경 할 수 있음

● 필요한 부분만 갱신 하므로, 페이지의 깜빡임과 재배치 작업을 거치지 않음

3. Postback vs Callback

3.1 Postback

폼의 HTTP POST 요청이 전송 되면, 그 요청은 IPostbackEventHandler 인터페이스에서 처리된다.

즉, 페이지를 초기화 하고, 뷰 상태를 로드 하며, 데이터를 처리한 다음, 포스트 백 이벤트를 동작하고, 페이지를 브라우저 상에서 볼 수 있도록 랜더링 한다.

이 과정에서 브라우저를 페이지를 다시 로드 하고, 깜박임과 동시에 페이지를 재배치한다.

3.2 Callback

폼의 Click 이벤트가 발생 되면, 이벤트 처리기(자바스크립트 함수)에 전달하여 비동기 요청을 서버에 전송한다.

ICallbackEventHandler 인터페이스는 앞서 포스트백을 처리 하기 위해서 IPostbackEventHandler 인터페이스처럼 해당 비동기 요청을 HTTP 파이프라인에 전달한다.

하지만 여기서 일반적인 이벤트 처리와는 다르게 몇가지 단계를 제외 하고 있다.

이벤트 정보가 로드되면 그 결과는 스크립트 콜백 개체로 반환되고, 클라이언트 스크립트에서는 페이지를 다시 갱신하지 않고도 기존 페이지에 정보를 반영 할 수 있게 된다.


4. 콜백 사용하기

예제를 통해 ASP.NET 2.0에서 콜백 이벤트를 어떻게 사용 하는지 알아 보도록 하자.

먼저 아래와 같은 화면 구성을 하도록 한다.


시나리오는 다음과 같다.

텍스트 박스에 고객의 고객코드를 넣고 버튼을 클릭 하면, 미리 정의된 클라이언트 스크립트가 호출 되게 된다.

클라이언트 스크립트에서는 서버 쪽에 CallbackEvent를 호출하고, 서버에서 처리가 완료되어 콜백 객체를 클라이언트가 받아 이를 처리 하도록 하는 것이다.

그럼 코드를 보도록하자.

4.1 .aspx page

  1. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Callback2.aspx.cs" Inherits="Callback2" %>  
  2.   
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
  4.   
  5. <html xmlns="http://www.w3.org/1999/xhtml" >  
  6. <head runat="server">  
  7.     <title>제목 없음</title>  
  8.     <script type="text/javascript">  
  9.         function GetCustomer()   
  10.         {   
  11.             var customerCode = document.forms[0].TextBox1.value;   
  12.             UseCallback(customerCode, "");   
  13.         }   
  14.         function GetCustDetailsFromServer(result, context)   
  15.         {   
  16.             var i = result.split("|");   
  17.             document.getElementById('customerID').innerHTML    = i[0];   
  18.             document.getElementById('companyName').innerHTML   = i[1];   
  19.             document.getElementById('contactName').innerHTML   = i[2];   
  20.             document.getElementById('contactTitle').innerHTML  = i[3];   
  21.             document.getElementById('address').innerHTML       = i[4];   
  22.             document.getElementById('city').innerHTML          = i[5];   
  23.             document.getElementById('region').innerHTML        = i[6];   
  24.             document.getElementById('postalCode').innerHTML    = i[7];   
  25.             document.getElementById('country').innerHTML       = i[8];   
  26.             document.getElementById('phone').innerHTML         = i[9];   
  27.             document.getElementById('fax').innerHTML           = i[10];   
  28.         }   
  29.     </script>  
  30. </head>  
  31. <body>  
  32.     <form id="form1" runat="server">  
  33.     <div>  
  34.         <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>    
  35.         <input id="Button1" type="button" value="고객의 상세정보 보기" onclick="GetCustomer()" /><br />  
  36.         <br />  
  37.         <table>  
  38.             <tr>  
  39.                 <td style="width: 100px">  
  40.                     고객ID</td>  
  41.                 <td style="width: 100px"><span id="customerID" />  
  42.                 </td>  
  43.             </tr>  
  44.             <tr>  
  45.                 <td style="width: 100px">  
  46.                     회사명</td>  
  47.                 <td style="width: 100px"><span id="companyName" />  
  48.                 </td>  
  49.             </tr>  
  50.             <tr>  
  51.                 <td style="width: 100px">  
  52.                     연락처</td>  
  53.                 <td style="width: 100px"><span id="contactName" />  
  54.                 </td>  
  55.             </tr>  
  56.             <tr>  
  57.                 <td style="width: 100px">  
  58.                     계약명</td>  
  59.                 <td style="width: 100px"><span id="contactTitle" />  
  60.                 </td>  
  61.             </tr>  
  62.             <tr>  
  63.                 <td style="width: 100px">  
  64.                     주소</td>  
  65.                 <td style="width: 100px"><span id="address" />  
  66.                 </td>  
  67.             </tr>  
  68.             <tr>  
  69.                 <td style="width: 100px">  
  70.                     도시</td>  
  71.                 <td style="width: 100px"><span id="city" />  
  72.                 </td>  
  73.             </tr>  
  74.             <tr>  
  75.                 <td style="width: 100px">  
  76.                     지역</td>  
  77.                 <td style="width: 100px"><span id="region" />    
  78.                 </td>  
  79.             </tr>  
  80.             <tr>  
  81.                 <td style="width: 100px">  
  82.                     우편번호</td>  
  83.                 <td style="width: 100px"><span id="postalCode" />  
  84.                 </td>  
  85.             </tr>  
  86.             <tr>  
  87.                 <td style="width: 100px">  
  88.                     국가</td>  
  89.                 <td style="width: 100px"><span id="country" />  
  90.                 </td>  
  91.             </tr>  
  92.             <tr>  
  93.                 <td style="width: 100px">  
  94.                     전화번호</td>  
  95.                 <td style="width: 100px"><span id="phone" />  
  96.                 </td>  
  97.             </tr>  
  98.             <tr>  
  99.                 <td style="width: 100px">  
  100.                     팩스</td>  
  101.                 <td style="width: 100px"><span id="fax" />  
  102.                 </td>  
  103.             </tr>  
  104.         </table>  
  105.     </div>  
  106.     </form>  
  107. </body>  
  108. </html>  

4.2 C#(Code behind)

  1. using System;   
  2. using System.Data;   
  3. using System.Configuration;   
  4. using System.Collections;   
  5. using System.Web;   
  6. using System.Web.Security;   
  7. using System.Web.UI;   
  8. using System.Web.UI.WebControls;   
  9. using System.Web.UI.WebControls.WebParts;   
  10. using System.Web.UI.HtmlControls;   
  11. using System.Data.SqlClient;   
  12.   
  13. public partial class Callback2 : System.Web.UI.Page, ICallbackEventHandler   
  14. {   
  15.     private string _callbackResult = null;   
  16.     protected void Page_Load(object sender, EventArgs e)   
  17.     {   
  18.         if(!Page.IsPostBack)   
  19.         {   
  20.             string cbReference = Page.ClientScript.GetCallbackEventReference( this"arg""GetCustDetailsFromServer""context");   
  21.             string cbScript = "function UseCallback(arg, context)" + "{" + cbReference + "; }";   
  22.             Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "UseCallback", cbScript, true);   
  23.         }   
  24.     }  
  25.  
  26.     #region ICallbackEventHandler 멤버   
  27.   
  28.     public string  GetCallbackResult()   
  29.     {   
  30.         return _callbackResult;   
  31.         //throw new Exception("The method or operation is not implemented.");   
  32.     }   
  33.   
  34.     public void  RaiseCallbackEvent(string eventArgument)   
  35.     {   
  36.         SqlConnection conn = new SqlConnection("Data Source=10.1.14.114;Initial Catalog=Northwind;User ID=sa;Password=1234;");   
  37.         SqlCommand cmd = new SqlCommand("Select * From Customers Where CustomerID = '" + eventArgument + "' ", conn);   
  38.         conn.Open();   
  39.         SqlDataReader MyReader;   
  40.         MyReader = cmd.ExecuteReader(CommandBehavior.CloseConnection);   
  41.   
  42.         string[] MyValues = new string[11];   
  43.         while(MyReader.Read())   
  44.         {   
  45.             MyValues[0]  = MyReader["CustomerID"].ToString();   
  46.             MyValues[1]  = MyReader["CompanyName"].ToString();   
  47.             MyValues[2]  = MyReader["ContactName"].ToString();   
  48.             MyValues[3]  = MyReader["ContactTitle"].ToString();   
  49.             MyValues[4]  = MyReader["Address"].ToString();   
  50.             MyValues[5]  = MyReader["City"].ToString();   
  51.             MyValues[6]  = MyReader["Region"].ToString();   
  52.             MyValues[7]  = MyReader["PostalCode"].ToString();   
  53.             MyValues[8]  = MyReader["Country"].ToString();   
  54.             MyValues[9]  = MyReader["Phone"].ToString();   
  55.             MyValues[10] = MyReader["Fax"].ToString();   
  56.         }   
  57.         _callbackResult = String.Join("|", MyValues);   
  58.   
  59.     }  
  60.  
  61.     #endregion   
  62. }  

4.3 소스 분석

4.3.1 Server Side

Page 로드 시의 클라이언트 스크립트를 등록 하는 부분이다.

  1. string cbReference = Page.ClientScript.GetCallbackEventReference( this"arg""GetCustDetailsFromServer""context");   
  2. string cbScript = "function UseCallback(arg, context)" + "{" + cbReference + "; }";   
  3. Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "UseCallback", cbScript, true);  

이 문맥은 거꾸로 보는게 오히려 편한 듯 하다.

3번째 줄의 문맥을 살펴보면 [클라이언트 -> 서버 CallbackEvent]를 호출 할 함수를 지정 하는 부분인데, 2번째 인자인 "UseCallback" 함수가 이 역할을 하게 된다.

그리고 2번째 줄은 [클라이언트 -> 서버 CallbackEvent]를 호출 하고, [처리된 서버 데이터 -> 클라이언트]로 처리 하는 일련의 내용들을 정의 하는 부분이다.

마지막으로 1번째 줄의 문맥의 내용은 서버측에서 처리된 콜백 객체를 받을 클라이언트 측 처리 함수를 지정 하는 부분인데, 3번째 인자의 값이 클라이언트 처리 함수의 이름이 된다. 이 코드에서는 GetCusDetailsFromServer가 되겠다.

  1. public string  GetCallbackResult()   
  2.     {   
  3.         return _callbackResult;   
  4.         //throw new Exception("The method or operation is not implemented.");   
  5.     }   
  6.   
  7.     public void  RaiseCallbackEvent(string eventArgument)   
  8.     {   
  9.         SqlConnection conn = new SqlConnection("Data Source=10.1.14.114;Initial Catalog=Northwind;User ID=sa;Password=1234;");   
  10.         SqlCommand cmd = new SqlCommand("Select * From Customers Where CustomerID = '" + eventArgument + "' ", conn);   
  11.         conn.Open();   
  12.         SqlDataReader MyReader;   
  13.         MyReader = cmd.ExecuteReader(CommandBehavior.CloseConnection);   
  14.   
  15.         string[] MyValues = new string[11];   
  16.         while(MyReader.Read())   
  17.         {   
  18.             MyValues[0]  = MyReader["CustomerID"].ToString();   
  19.             MyValues[1]  = MyReader["CompanyName"].ToString();   
  20.             MyValues[2]  = MyReader["ContactName"].ToString();   
  21.             MyValues[3]  = MyReader["ContactTitle"].ToString();   
  22.             MyValues[4]  = MyReader["Address"].ToString();   
  23.             MyValues[5]  = MyReader["City"].ToString();   
  24.             MyValues[6]  = MyReader["Region"].ToString();   
  25.             MyValues[7]  = MyReader["PostalCode"].ToString();   
  26.             MyValues[8]  = MyReader["Country"].ToString();   
  27.             MyValues[9]  = MyReader["Phone"].ToString();   
  28.             MyValues[10] = MyReader["Fax"].ToString();   
  29.         }   
  30.         _callbackResult = String.Join("|", MyValues);   
  31.   
  32.     }  

ICallbackEventHandler 인터페이스를 상속 받게 되면 GetCallbackResult, RaiseCallbackEvent 함수를 구현 해야 되는데, RaiseCallbackEvent 함수는 클라이언트 측에서 CallbackEvent를 호출 하면 불리는 함수가 된다. 보통 RaiseCallbackEvent 함수는 클라이언트의 요청을 처리하는 기능으로 사용 되게 된다.

그리고 GetCallbackResult는 CallbackEvent가 처리된 객체를 클라이언트에 반환 할 때 불리는 함수이다.

4.3.2 Client Side

  1. <INPUT id=Button1 onclick=GetCustomer() value="고객의 상세정보 보기" type=button>  

             버튼의 onclick 이벤트에 GetCustomer 함수를 호출하고 있다.

  1. function GetCustomer()   
  2. {   
  3.     var customerCode = document.forms[0].TextBox1.value;   
  4.     UseCallback(customerCode, "");   
  5. }   
  6. function GetCustDetailsFromServer(result, context)   
  7. {   
  8.     var i = result.split("|");   
  9.     document.getElementById('customerID').innerHTML    = i[0];   
  10.     document.getElementById('companyName').innerHTML   = i[1];   
  11.     document.getElementById('contactName').innerHTML   = i[2];   
  12.     document.getElementById('contactTitle').innerHTML  = i[3];   
  13.     document.getElementById('address').innerHTML       = i[4];   
  14.     document.getElementById('city').innerHTML          = i[5];   
  15.     document.getElementById('region').innerHTML        = i[6];   
  16.     document.getElementById('postalCode').innerHTML    = i[7];   
  17.     document.getElementById('country').innerHTML       = i[8];   
  18.     document.getElementById('phone').innerHTML         = i[9];   
  19.     document.getElementById('fax').innerHTML           = i[10];   
  20. }  

GetCustomer 함수를 호출 하게 되면, 텍스트 박스의 데이터를 가져와 서버쪽 CallbackEvent를 호출 할 함수를 UseCallback 함수를 호출 하게 된다.

이 함수는 클라이언트 콜백 처리기를 생성하여, 서버 측 콜백 이벤트 함수를 호출 한다.

4.4 이벤트 처리 정리

위의 내용을 바탕으로 콜백 이벤트의 처리 절차를 정리 해 보자.

4.4.1 등록

Page Load에서 다음의 함수를 지정한다.

  • UseCallback : 서버 CallbackEvent 호출 할 클라이언트 측 함수
  • GetCustDetailsFromServer : 서버 CallbackEvent 반환 객체를 처리 클라이언트 함수

4.4.2 처리

버튼 이벤트가 발생되면 다음과 같이 수행 된다.

  1. GetCustomer : 클라이언트
  2. UseCallback : 클라이언트
  3. RaiseCallbackEvent : 서버
  4. GetCallbackResult : 서버
  5. GetCustDetailsFromServer : 클라이언트

5. 참고

http://resources.esri.com/help/9.3/arcgisserver/adf/dotnet/developer/ADF/ajax_aspnet.htm
Professional ASP.NET 2.0

▣  Update 팁과 트릭 - .NET/ASP.NET - 2011. 6. 14. 20:12

출처 : http://msdn.microsoft.com/ko-kr/magazine/cc163413.aspx#S3

UpdatePanel 팁과 트릭
Jeff Prosise

좋든 나쁘든, UpdatePanel 컨트롤은 ASP.NET AJAX 커뮤니티의 가장 주된 화두입니다. 좋은 이유는 UpdatePanel를 사용할 경우 부분 페이지 렌더링이 무척 쉬워지기 때문이고, 나쁜 이유는 단순성과 용이성으로 인해 효율성이 떨어질 뿐만 아니라 아이러니하게도 대역폭 손실이 발생하기 때문입니다.
UpdatePanel이 일반 웹 페이지에 AJAX의 기능을 이용할 수 있게 해 주긴 하지만 우리가 보통 AJAX에서 기대하는 효율성을 자동으로 가져다 주지는 않습니다. 예를 들어 UpdatePanel 컨트롤이 서버의 콘텐츠를 업데이트하기 위해 서버를 대상으로 비동기 AJAX 콜백을 수행할 때 보기 상태를 비롯하여 일반적인 ASP.NET 포스트백에 포함되는 모든 항목이 요청에 포함된다는 사실을 알고 계십니까? 대부분의 개발자는 AJAX를 사용하면 보기 상태가 필요 없다고 생각합니다. 그러나 AJAX의 UpdatePanel에 있어서는 예외입니다.
UpdatePanel 컨트롤을 사용하려면 그 결과에 대해 알아야 합니다. 성능의 관점에서 보면 응용 프로그램에 UpdatePanel을 사용하는 대신 웹 메서드나 페이지 메서드에 대한 비동기 호출을 사용하는 것이 더 나은 경우가 많습니다. 이렇게 하면 회선을 통해 전송되는 데이터의 양이 10배 이상 감소됩니다. 개발자가 페이지에 JavaScript를 사용하여 UI 업데이트를 명시적으로 처리해야 하는 경우 기본적으로 웹 메서드나 페이지 메서드에 대한 비동기 호출을 사용하도록 전환해야 합니다.
또한 ASP.NET AJAX 토론 포럼은 이미 UpdatePanel 사용자 지정에 대한 질문으로 넘치고 있습니다. 그 중에는 Microsoft® AJAX 라이브러리의 JavaScript 클래스로, 클라이언트 측 UpdatePanels 지원을 제공하는 PageRequestManager만 이해하면 궁금증이 해결되는 경우가 많습니다.
ASP.NET AJAX가 제공되는 데 발맞춰 사용자 지정 방법, 최적화 방법 등 UpdatePanel에 대해 이 칼럼을 통해 좀 더 자세히 알아보겠습니다.

주요 업데이트
Microsoft의 개발자가 불쌍하게 여겨질 때가 있습니다. 일을 제대로 못하면 대중의 질타를 받기가 십상이기 때문이죠. 그리고 가끔은 너무 일을 잘해서 질타를 받는 경우도 있습니다. 예를 들어 최근에 필자는 한 고객으로부터 ASP.NET AJAX UpdatePanel이 너무 잘 작동한다고 불평하는 전자 메일을 받았습니다.
UpdatePanel을 사용하면 ASP.NET 페이지가 서버에 포스트백될 때 나타나는 깜박임 현상을 손쉽게 해결하여 깜박임 없이 원활하게 업데이트가 이루어지도록 할 수 있습니다. UpdatePanel은 포스트백을 비동기 콜백(XML-HTTP 요청)으로 변환하고 클라이언트에서 JavaScript를 사용하여 UpdatePanel 컨트롤로 캡슐화된 페이지의 일부를 새로 고치는 방식으로 작동합니다. 그러면 브라우저에서 포스트백을 수행하는 동안 페이지를 다시 그리지 않으므로 깜박임 현상이 사라집니다.
고객이 불평한 내용은 페이지 중 일부가 새 콘텐츠로 업데이트되었다는 사실을 사용자가 알아채지 못할 수도 있다는 것이었습니다. 그 고객은 "고객이 중요한 업데이트를 놓치지 않도록 ASP.NET AJAX 팀에서 UpdatePanel이 약간만 깜박이도록 만들 수는 없느냐"고 물어왔습니다.
안타깝게도 ASP.NET AJAX 팀은 UpdatePanel이 깜박이도록 만들 생각이 아마 없을 겁니다. 애초에 UpdatePanel을 개발한 이유가 깜박임을 없애는 것이었으니까요. 그러나 좋은 소식도 있습니다. 브라우저에서 약간의 AJAX 작업만 하면 업데이트된 UpdatePanel로 사용자의 주의를 끌 수 있습니다. 비법은 바로 Microsoft AJAX 라이브러리(ASP.NET AJAX의 절반을 구성하는 JavaScript 클래스의 라이브러리)의 Sys.WebForms.PageRequestManager 클래스를 사용하는 것입니다. PageRequestManager는 UpdatePanel에 의해 실행되는 비동기 콜백을 관리할 뿐만 아니라 비동기 콜백이 완료되면 UpdatePanel의 콘텐츠를 업데이트하는 역할도 담당합니다.
PageRequestManager는 업데이트가 발생하기 전후에 브라우저에서 이벤트를 발생시킵니다. JavaScript에서 이러한 이벤트를 연결하고 업데이트된 콘텐츠로 사용자의 주의를 끄는 코드를 실행할 수 있습니다. 이때 핵심이 되는 이벤트는 pageLoaded인데, 이 이벤트는 브라우저에 페이지가 로드될 때마다 발생합니다(ASP.NET의 Page_Load에 해당). 또한 UpdatePanel 컨트롤을 대신하여 실행된 비동기 콜백이 완료되거나 UpdatePanel의 콘텐츠가 업데이트될 때마다 발생합니다. 다음 코드 두 줄로 pageLoaded 이벤트에 대한 JavaScript 처리기를 등록할 수 있습니다. 이 두 줄의 코드는 한 줄로 합칠 수도 있습니다.
var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_pageLoaded(pageLoaded);
이 코드의 첫째 줄에서는 페이지의 PageRequestManager 개체에 대한 참조를 가져옵니다. 그리고 둘째 줄에서는 pageLoaded라는 JavaScript 함수를 pageLoaded 이벤트의 처리기로 등록합니다.
pageLoaded 이벤트 처리기가 호출되면 역시 Microsoft AJAX 라이브러리의 클래스 중 하나인 Sys.WebForms.PageLoadedEventArgs 형식의 인수를 받습니다. PageLoadedEventArgs에는 콘텐츠가 업데이트된 모든 UpdatePanel을 열거하기 위해 호출할 수 있는 get_panelsUpdated 메서드가 포함되어 있습니다. 기본적으로 UpdatePanel은 JavaScript를 사용하여 DIV를 깜박이거나 강조 표시하거나, 또는 사용자의 주의를 끄는 다른 효과를 구현하는 클라이언트 측 DIV에 지나지 않습니다.
그림 1에 나열된 코드는 pageLoaded 이벤트를 사용하여 업데이트를 강조하는 한 가지 방법을 보여 줍니다. 이 JavaScript는 업데이트가 발생할 때마다 업데이트된 UpdatePanel을 나타내는 DOM(문서 개체 모델) 요소를 세 번 연속으로 빠르게 표시했다가 사라지도록 하여 깜박입니다. 이러한 깜박임은 깜박임 횟수를 입력 매개 변수로 사용하는 flashPanels라는 도우미 함수에 의해 수행됩니다.
<script type=”text/javascript”>

var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_pageLoaded(pageLoaded);

var _panels, _count;

function pageLoaded(sender, args)
{
    if (_panels != undefined && _panels.length > 0)
    {
        for (i=0; i < _panels.length; i++)
            _panels[i].dispose();
    }

    var panels = args.get_panelsUpdated();

    if (panels.length > 0)
    {
        _panels = new Array(panels.length);

        for (i=0; i < panels.length; i++) 
            _panels[i] = new Sys.UI.Control(panels[i]);

        flashPanels(3);
    }
}

function flashPanels(count)
{
    _count = (count << 1) + 1;
        
    for (i=0; i < _panels.length; i++) 
        _panels[i].set_visible(false);

    window.setTimeout(toggleVisibility, 50);
}

function toggleVisibility()
{
    for (i=0; i < _panels.length; i++) 
        _panels[i].set_visible(!_panels[i].get_visible());
        
    if (--_count > 0)
        window.setTimeout(toggleVisibility, 50);
}
</script>
다음으로 업데이트된 UpdatePanel을 반복적으로 표시하고 숨겨 깜박임 효과를 만드는 방법을 살펴보겠습니다. 이 코드에서는 DOM 요소와 직접 상호 작용하는 대신 UpdatePanel을 나타내는 DOM 요소를 Sys.UI.Control 개체로 래핑합니다. 그리고 Sys.UI.Control의 set_visible 및 get_visible 메서드를 사용하여 표시하거나 숨깁니다.
_panels[i].set_visible(!_panels[i].get_visible());
Sys.UI.Control은 Microsoft AJAX 라이브러리(MicrosoftAjax.js)에 포함된 JavaScript 클래스입니다. 이 방법으로 표시 여부를 전환하면 브라우저 종류에 관계없이 효과를 구현할 수 있다는 장점이 있습니다. 이는 ASP.NET AJAX를 지원하는 모든 브라우저(현재 사용되는 거의 모든 브라우저)에 동일하게 적용됩니다. 반면 브라우저 DOM과 직접 상호 작용하는 JavaScript 코드의 경우 다른 종류의 브라우저에서 사용하려면 코드를 수정해야 합니다.

UpdatePanel 업데이트 취소
pageLoaded 이벤트는 콘텐츠를 업데이트하기 위해 UpdatePanel을 서버로 다시 보낼 때 PageRequestManager 클래스에 의해 발생하는 몇 가지 이벤트 중 하나입니다. PageRequestManager가 발생시키는 다른 중요한 이벤트로는 비동기 콜백이 발생하기 전에 실행되는 initializeRequest가 있습니다.
최근에 AsyncPostBackTrigger가 UpdatePanel 업데이트를 트리거할 수 있을지 여부를 런타임에 결정할 수 있는가에 대해 질문을 받았습니다. 대답은 "가능하다"입니다. 이를 위해서는 initializeRequest 이벤트를 처리하면 됩니다.
initializeRequest 처리기에 전달되는 두 번째 매개 변수는 initializeRequestEventArgs 형식의 개체입니다. 이 개체에는 업데이트를 트리거한 단추나 기타 요소를 식별하는 get_postBackElement 메서드와 콜백이 발생하기 전에 취소하는 데 사용할 수 있는 set_cancel 메서드가 들어 있습니다. 다음은 set_cancel 메서드의 실제 사용 예입니다.
<script type=”text/javascript”>

var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_initializeRequest(initializeRequest);

function initializeRequest(sender, args)
{
    args.set_cancel(!confirm(‘Are you sure?’));
}
</script>
이 예에서 intializeRequest 처리기는 콜백이 실행되기 전에 사용자에게 업데이트를 계속할지를 묻는 확인 대화 상자를 표시합니다. 이 확인 대화 상자에서 취소를 클릭하면 set_cancel에 True 값이 전달되어 콜백이 바로 중지됩니다. 실제 개발 시에는 업데이트를 계속하기 전에 사용자에게 확인 대화 상자를 표시할 필요가 없겠지만 응용 프로그램의 다른 위치에서 상황에 따라 업데이트를 취소할 수 있도록 하면 유용합니다.
또한 비동기 콜백이 실행된 후 완료되기 전에 취소할 수도 있습니다. PageRequestManager는 이러한 기능을 수행하는 abortPostBack 메서드와 비동기 콜백이 보류 상태인지를 확인하는 get_isInAsyncPostBack 메서드를 제공합니다. 이 두 메서드는 UpdateProgress 컨트롤과 함께 취소 UI를 구현하는 데 사용되는 경우가 많습니다.

여러 UpdatePanel
하나의 페이지에 여러 UpdatePanel이 포함되어 있을 수 있습니다. 기본적으로 페이지에서 UpdatePanel 중 하나가 업데이트되면 페이지의 다른 UpdatePanel도 업데이트됩니다. 이러한 동작이 필요한 경우도 있겠지만 대개 특정 UpdatePanel에 대한 응답으로 다른 모든 UpdatePanel을 업데이트할 필요는 없습니다.
이 경우 페이지에 있는 UpdatePanel 컨트롤의 UpdateMode 속성을 "Conditional"로 설정하면 업데이트할 UpdatePanel 인스턴스와 업데이트 시점을 선택할 수 있습니다. 그리고 UpdatePanel 중 하나가 업데이트되고 서버 측 이벤트 처리기를 호출할 때 업데이트할 다른 창에 대한 UpdatePanel.Update를 호출합니다. 이 경우 렌더링되는 컨트롤의 수가 적어지므로 서버의 부하가 감소되고, 업데이트하지 않는 UpdatePanel이 응답에 항목을 추가하지 않으므로 응답의 데이터 양이 줄어듭니다.

UpdatePanel을 사용하지 않고 업데이트
AJAX는 사용자 환경을 개선할 뿐만 아니라 연결 효율성도 크게 높입니다. 기존에는 ASP.NET 포스트백이 발생하면 보기 상태를 비롯하여 Web form의 모든 데이터가 포스트백을 통해 서버로 전달되었습니다. 보기 상태는 ASP.NET 페이지, 특히 DataGrid 컨트롤과 GridView 컨트롤을 사용하는 페이지의 응답 속도가 느려지는 주된 원인 중 하나입니다. 보기 상태가 너무 많은 페이지는 성능을 저하시키기 때문입니다. 이렇게 보기 상태가 너무 많은 페이지는 ASP.NET 응용 프로그램에서 쉽게 발견할 수 있습니다.
ASP.NET 포스트백을 AJAX 콜백으로 제대로 대체했을 때 기대할 수 있는 이점 중 하나는 AJAX 콜백 시에 필요한 데이터만 전송된다는 점입니다. 다시 말해, 전송에서 보기 상태를 포함하지 않아도 됩니다.
UpdatePanel을 사용하여 페이지에서 깜박임 없이 업데이트를 수행하면 효율성이 높아질 것이라고 기대하게 됩니다. UpdatePanel에는 AJAX가 사용되니까요. 그러나 UpdatePanel이 업데이트될 때 전송 트래픽을 확인해 보면 효율성이 크게 개선되지 않음을 알 수 있습니다. 특히 데이터를 보낼 때 말이죠. 포스트백을 실행하는 동안 일반적으로 서버에 전송되는 보기 상태 데이터를 비롯한 여러 가지 데이터는 UpdatePanel 콜백 시에도 전송됩니다. 사실 UpdatePanel에서 발생하는 비동기 XML-HTTP 요청 시에 업로드되는 데이터는 표준 ASP .NET 포스트백 시에 업로드되는 데이터와 거의 같습니다. 따라서 ASP.NET AJAX에 관한 안타까운 진실은, UpdatePanel은 사용하기 편리하도록 할 뿐이지 전송 효율성과는 관계가 없다는 것입니다.
UpdatePanel의 효율성을 높일 수 있는 방법은 사실상 전무하지만, UpdatePanel을 사용하는 대신 ASP.NET AJAX의 다른 기능을 사용하면 마찬가지로 원활하지만 훨씬 효율적인 방식으로 페이지의 콘텐츠를 업데이트할 수 있습니다. 이 경우 작업이 약간 복잡해지지만 클라이언트와 서버 간에 전송되는 데이터의 양을 크게 줄일 수 있으므로 그만한 가치가 충분하다고 할 수 있습니다.
뿐만 아니라 서버의 부하도 줄일 수 있습니다. UpdatePanel이 서버를 다시 호출하면 콜백 대상 페이지가 전체 수명 주기에 가까운 주기를 반복하게 됩니다. 즉, 페이지가 인스턴스화되면 페이지의 컨트롤이 인스턴스화되고 UpdatePanel에 있는 컨트롤이 일반 렌더링 주기를 경험하게 됩니다. 페이지의 일부만 업데이트하는 데 오버헤드가 너무 많이 발생하게 되는 것입니다.
예를 들어 그림 2의 페이지 조각을 살펴보겠습니다. 이 페이지 조각은 사용자가 우편 번호를 입력하고 단추를 클릭하여 시 및 주 필드를 해당 시와 주로 초기화하는 간단한 UI를 제공합니다(그림 3 참조). 모든 컨트롤은 UpdatePanel에서 호스팅되므로 Button 컨트롤의 포스트백이 비동기 콜백으로 변환되고 콜백에서 서버의 이벤트 처리기(GetCityAndState)가 호출됩니다. GetCityAndState(코드는 생략)는 ZIP Code TextBox에서 우편 번호를 읽어 시 및 주로 변환하고 해당 정보에 따라 시 및 주를 나타내는 TextBox와 DropDownList를 업데이트합니다. 이러한 모든 프로세스는 UpdatePanel 내에서 수행되므로 업데이트가 깜박임 없이 원활하게 이루어집니다.
<asp:UpdatePanel ID=”UpdatePanel1” runat=”server”>
  <ContentTemplate>
    City:<br />
    <asp:TextBox ID=”City” runat=”server” />
    <br /><br />
    State:<br />
    <asp:DropDownList ID=”Region” runat=”server”>
        <asp:ListItem Value=”AL”>Alabama</asp:ListItem>
        <asp:ListItem Value=”AK”>Alaska</asp:ListItem>
        <asp:ListItem Value=”AZ”>Arizona</asp:ListItem>
          ...
        <asp:ListItem Value=”WV”>West Virginia</asp:ListItem>
        <asp:ListItem Value=”WI”>Wisconsin</asp:ListItem>
        <asp:ListItem Value=”WY”>Wyoming</asp:ListItem>
    </asp:DropDownList>
    <br /><br />
    Zip Code:<br />
    <asp:TextBox ID=”ZipCode” runat=”server” />&nbsp;
    <asp:Button ID=”AutofillButton” Text=”Autofill”
      OnClick=”GetCityAndState” runat=”server” />
  </ContentTemplate>
</asp:UpdatePanel>
그림 3 시, 주 및 우편 번호 UI (더 크게 보려면 이미지를 클릭하십시오.)
그런데 문제가 한 가지 있습니다. 이 방법으로 UpdatePanel을 사용하면 사용자 환경이 개선되지만 전달되는 데이터 양에는 거의 변화가 없습니다. 또한 서버의 부하도 거의 줄지 않습니다. UpdatePanel의 컨트롤이 렌더링될 때까지 서버에서 수행되는 처리 프로세스는 완전한 포스트백의 경우와 거의 동일합니다. GetCityAndState와 같은 서버 측 이벤트 처리기가 비동기 콜백에서도 기존의 포스트백에서와 마찬가지로 작동한다는 점이 UpdatePanel 컨트롤의 장점 중 하나이므로 이는 필연적인 결과입니다. 따라서 페이지의 컨트롤은 인스턴스화되어야 하고, 초기화되어야 하며, 보기 상태에 액세스할 수 있어야 합니다.
그림 4는 UpdatePanel 컨트롤을 사용하지 않고 동일한 기능을 구현하는 방법을 보여 줍니다. 여기서는 Autofill 단추가 GetCityAndState라는 ASMX 웹 메서드에 비동기 XML-HTTP 요청을 보내는 JavaScript 코드에 연결되어 있습니다. 이 호출은 ScriptManager 컨트롤의 서비스 참조에 의해 생성되는 JavaScript 프록시(ZipCodeService)를 통해 전달됩니다. GetCityAndState는 우편 번호 문자열을 입력 받아 두 가지 항목(해당 시와 주)이 포함된 문자열 배열을 반환합니다. 완료 함수 onGetCityAndStateCompleted는 배열에서 이러한 항목을 검색하여 시 및 주 필드에 삽입합니다. 외부에서도 같은 결과가 나타나지만 내부와 실행 방법이 다릅니다.
<asp:ScriptManager ID=”ScriptManager1” runat=”server”>
    <Services>
        <asp:ServiceReference Path=”ZipCodeService.asmx” />
    </Services>
    <Scripts>
      <asp:ScriptReference Name=”PreviewScript.js”
Assembly=”Microsoft.Web.Preview” />
    </Scripts>
</asp:ScriptManager>

City:<br />
<asp:TextBox ID=”City” runat=”server” />
<br /><br />
State:<br />
<asp:DropDownList ID=”Region” runat=”server”>
    <asp:ListItem Value=”AL”>Alabama</asp:ListItem>
    <asp:ListItem Value=”AK”>Alaska</asp:ListItem>
    <asp:ListItem Value=”AZ”>Arizona</asp:ListItem>
      ...
    <asp:ListItem Value=”WV”>West Virginia</asp:ListItem>
    <asp:ListItem Value=”WI”>Wisconsin</asp:ListItem>
    <asp:ListItem Value=”WY”>Wyoming</asp:ListItem>
</asp:DropDownList>
<br /><br />
Zip Code:<br />
<asp:TextBox ID=”ZipCode” runat=”server” />&nbsp;
<asp:Button ID=”AutofillButton” Text=”Autofill”
  OnClientClick=”autoFill(); return false;” runat=”server” />

<script type=”text/javascript”>
function autoFill()
{
    var tb = new Sys.Preview.UI.TextBox ($get(‘ZipCode’));
    var zip = tb.get_text();

    if (zip.length == 5)
        ZipCodeService.GetCityAndState (zip,
            onGetCityAndStateCompleted);
}

function onGetCityAndStateCompleted(result)
{
    if (result != null)
    {
        var tb = new Sys.Preview.UI.TextBox ($get(‘City’));
        tb.set_text(result[0]);

        var select =
            new Sys.Preview.UI.Selector ($get(‘Region’));
        select.set_selectedValue(result[1]);
    }
}
</script>
다음은 JavaScript 프록시를 통한 ASMX 웹 메서드 호출 방법을 보여 주는 코드입니다.
[ScriptService]
public class ZipCodeService : System.Web.Services.WebService
{
    [WebMethod]
    public string[] GetCityAndState(string zip)
    {
      ...
    }
}
속해 있는 클래스가 WebService가 아니라 ScriptService라는 점을 제외하면 이는 표준 웹 메서드에 해당한다고 할 수 있습니다. ScriptService는 WebService와 같지만 클라이언트 측 스크립트에서 웹 서비스의 웹 메서드를 호출할 수 있음을 나타내는 부가적인 의미가 있습니다.
ASP.NET AJAX는 일반적인 웹 메서드가 XML-HTTP 요청의 대상이 되도록 허용할 뿐만 아니라 페이지 메서드라는 특수한 웹 메서드도 지원합니다. 페이지 메서드는 웹 페이지에 구현되는 웹 메서드입니다. 즉, ASMX 파일이 아니라 ASPX 파일 또는 코드 숨김 파일에 작성됩니다. 이러한 페이지 메서드를 사용하면 전용 웹 서비스를 작성하지 않고도 XML-HTTP 콜백에 대한 종단점을 제공할 수 있습니다.
페이지 메서드는 공용 정적 메서드여야 하며 웹 메서드와 마찬가지로 WebMethod 특성이 지정되어야 합니다. 웹 메서드와 페이지 메서드에는 전송되는 데이터를 보다 세부적으로 제어할 수 있는 ScriptMethod 특성도 지정될 수 있습니다. 클라이언트에서 페이지 메서드는 특수한 PageMethods 프록시를 통해 JavaScript에서 호출됩니다.
웹 서비스와 달리 페이지 메서드에는 서비스 참조가 필요 없지만, ScriptManager 컨트롤의 EnablePageMethods 속성을 True로 설정하는 등의 방법으로 페이지 메서드를 활성화해야 합니다.
<asp:ScriptManager ID=”ScriptManager1” runat=”server”
  EnablePageMethods=”true” />
내부적으로 페이지 메서드는 웹 메서드와 동일한 수준의 효율성을 제공합니다. 즉, 페이지 메서드가 호출될 때 보기 상태와 기타 입력 데이터가 서버로 전송되지 않습니다. 또한 페이지 메서드는 정적이므로 페이지 개체를 인스턴스화하지 않고도 호출할 수 있습니다. 마지막으로 페이지 메서드를 호출하더라도 기존의 ASP.NET 요청에서 트리거되는 페이지 수명 주기가 실행되지 않습니다.

웹 서비스는 SOAP이나 XML과 다르다.
ASP.NET AJAX의 가장 중요한 기능 중 하나는 브라우저 클라이언트에서 비동기 XML-HTTP 요청을 사용하여 서버의 웹 메서드와 페이지 메서드를 호출하는 기능입니다. 그러나 다른 사람에게 이 기능에 대해 말할 때는 조금 망설이게 됩니다.
웹 서비스라는 용어를 들으면 대부분 SOAP와 XML을 떠올리게 됩니다. 그런데 SOAP와 XML이라는 용어는 모두 효율성이라는 단어와는 거리가 멉니다. JavaScript에서 ASP.NET AJAX를 사용하여 웹 메서드를 호출할 수는 있지만 ASP.NET AJAX에서 SOAP와 XML은 사용하지 않습니다.
그림 5에는 그림 4의 웹 메서드 호출을 실행했을 때 전송되는 항목이 나와 있습니다. HTTP 헤더를 제외하고 이 요청에서 전송되는 유일한 데이터는 사용자가 입력한 우편 번호이며, 응답에서 반환되는 유일한 데이터는 시와 주를 나타내는 문자열 쌍입니다. 이때 SOAP나 XML(또는 보기 상태)은 찾아볼 수 없습니다. 대신 XML보다 훨씬 간단하고 처리하기 쉬운 JSON(JavaScript Object Notation)을 사용하여 입/출력이 인코딩됩니다. 또한 요청과 응답에서는 SOAP 대신 기본적으로 HTTP에 해당하는 간단한 프로토콜을 사용합니다. 이러한 HTTP와 JSON의 조합은 웹 메서드 및 페이지 메서드에 대한 ASP.NET AJAX 호출의 효율성을 기존의 웹 서비스 호출에 비해 크게 높입니다.

요청
 
POST /Ajax/ZipCodeService.asmx/GetCityAndState HTTP/1.1
Accept: */*
Accept-Language: en-us
Referer: http://localhost:1997/Ajax/ZipCodePage.aspx
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; ...)
Host: localhost:1997
Content-Length: 15
Connection: Keep-Alive
Cache-Control: no-cache

{"zip":"98052"}
응답
 
HTTP/1.1 200 OK
Server: ASP.NET Development Server/8.0.0.0
Date: Fri, 29 Dec 2006 21:06:17 GMT
X-AspNet-Version: 2.0.50727
Cache-Control: private, max-age=0
Content-Type: application/json; charset=utf-8
Content-Length: 16
Connection: Close

{"REDMOND", "WA"} 
JSON은 최신의 업계 표준 직렬화 형식으로, ASP.NET AJAX에서 기본 형식으로 사용됩니다. 클라이언트 측에서의 JSON 직렬화 및 역직렬화 지원은 Microsoft AJAX 라이브러리의 Sys.Serialization.JavaScriptSerializer 클래스를 통해 제공됩니다. 또한 서버의 경우 System.Web.Script.Serialization.JavaScriptSerializer 클래스에 의해 지원됩니다.
JSON을 지원하지 않는 형식도 있습니다. 예를 들어 순환 참조가 있는 개체는 JSON으로 처리할 수 없습니다. JSON을 지원하지 않는 복잡한 데이터 형식을 반환해야 하는 경우 ASP.NET AJAX의 ScriptMethod 특성을 사용하여 반환 형식을 XML로 직렬화할 수 있습니다. 이 기법은 다음과 같이 XML 데이터를 반환하는 메서드에도 유용합니다.
[ScriptMethod (ResponseFormat=ResponseFormat.Xml)]
public XmlDocument GetData()
{
  ...
}
또는 일반적으로 JSON을 지원하지 않는 형식을 직렬화/역직렬화할 수 있도록 사용자 지정 JSON 변환기를 만들어 등록할 수도 있습니다. ASP.NET AJAX January Futures CTP에는 이러한 변환기가 세 가지(각각 DataSet, DataTable 및 DataRow용) 포함되어 있습니다.

Jeff에게 질문이나 의견이 있으면 다음 전자 메일 주소로 보내시기 바랍니다: wicked@microsoft.com.


Jeff Prosise는 MSDN Magazine 편집자이자 Programming Microsoft .NET(Microsoft Press, 2002)을 비롯한 여러 책의 저자입니다. 또한 .NET을 전문적으로 다루는 소프트웨어 컨설팅 및 교육 업체인 Wintellect(www.wintellect.com)의 공동 설립자이기도 합니다.



articles
recent replies
recent trackbacks
notice
Admin : New post