Karl Seguin

2005년 9월

적용 대상:
   AJAX(Asynchronous JavaScript And XML)
   Microsoft AJAX.NET
   Microsoft ASP.NET

요약: AJAX(Asynchronous JavaScript And XML)를 사용하면 Microsoft ASP.NET 응용 프로그램의 동적 성능을 높이고 응답 능력을 개선할 수 있습니다.

이 기사의 코드 샘플인 AjaxASPNETCS.msi in C#을 다운로드하십시오.

이 기사의 코드 샘플인 AjaxASPNETVB.msi in Visual Basic을 다운로드하십시오.

목차

소개
AJAX란?
ASP.NET용 AJAX
AJAX 활용
AJAX와 사용자
결론

소개

웹 프로그래밍이 시작된 이래로 웹 응용 프로그램과 데스크톱 응용 프로그램 사이에는 많은 장단점이 존재해 왔습니다. 예를 들어, 웹 응용 프로그램이 데스크톱 응용 프로그램처럼 다양한 종류의 사용자 인터페이스를 제공하지 않는다는 것은 주지의 사실입니다. 반면, 웹 응용 프로그램은 플랫폼에 구애 받지 않으며 개발 메커니즘이 보다 간단합니다. 웹 개발자에게 끊임없이 문제를 제기해 온 한 가지 영역은 응답 성능이 보다 뛰어난 응용 프로그램을 제공하는 작업으로, 겉으로는 간단한 일처럼 보입니다.

일반적으로 사용자의 입력에 대한 응답은 웹 서버로 새 요청을 전송해야만 받을 수 있습니다. 하지만 경우에 따라 개발자들은 JavaScript를 사용하여 클라이언트 측에서 모든 응답을 로드하고 더 나은 사용자 환경을 제공할 수도 있습니다. 이러한 기법에 대한 일반적인 예로는 선택된 국가의 시/도 목록을 동적으로 로드하는 것이 있습니다. 하지만 아쉽게도 대개의 경우, JavaScript로 모든 항목을 다시 게시하거나 로드하는 방식은 정확하지 않을 수 있습니다. 다시 게시하는 과정에 UI 연결 끊김이 너무 많이 발생하거나, 데이터의 양이 클라이언트 측에서 관리할 수 없는 수준에 이르러 JavaScript를 읽을 수 없게 되는 경우가 있기 때문입니다. AJAX는 원활한 응답 성능과 부드러운 느낌을 유지하면서 서버 기반 응용 프로그램을 활용할 수 있는 새로운 중간 대체 기술을 제공합니다.

AJAX란?

AJAX(Asynchronous JavaScript And XML)는 하나의 기술이라기 보다는 여러 기술을 하나로 묶은 것입니다. AJAX는 SOAP 및 XML 같은 통신 기술을 사용하여 비동기 요청/응답을 서버와 주고 받으며, JavaScript, DOM, HTML 및 CSS 같은 프레젠테이션 기술을 사용하여 응답을 처리합니다. 대부분의 브라우저에서 필요한 기술을 지원하므로 오늘날 응용 프로그램에서는 AJAX를 적절히 사용할 수 있습니다. AJAX에 대한 자세한 정의를 보려면 AJAX Wikipedia 항목 (영문)을 참조하십시오.

그렇다면 AJAX란 대체 무엇일까요? AJAX를 사용하면 브라우저를 새로 고칠 필요 없이, JavaScript를 호출하여 서버 측 메서드를 실행할 수 있습니다. 한마디로, 사용자 모르게 백그라운드에서 발생하는 작은 요청/응답으로 생각하면 될 것입니다. 그래도 AJAX에 대해 잘 모르겠으면 Google의 유명한 서비스인 Google SuggestsGoogle Maps (영문) 같은 예를 살펴보십시오. AJAX에 대해 생소하다면 이 두 응용 프로그램의 응답 성능에 크게 놀라실 것입니다.

ASP.NET용 AJAX

현재 AJAX를 작동시키기 위해 많은 작업이 진행되고 있습니다. 아마도 몇 시간 또는 몇 일에 걸쳐 AJAX의 본질을 이해하려 하기보다는, 당장 AJAX를 사용하는 응용 프로그램을 만들어 기존의 요구를 해결하고 싶을 것입니다. 사실 AJAX가 내부적으로 작동하는 방법은 여기서 설명할 수 있는 범위를 벗어납니다. AJAX를 곧바로 활용할 수 있는 개발자용 도구에는 여러 가지가 있지만 여기서는 Michael Schwarz가 작성한 무료 공개 소스인 Ajax.NET을 살펴보겠습니다. Ajax.NET은 모든 구현 세부 사항을 처리하며 .NET을 인식할 뿐 아니라 확장 가능한 특성을 가지고 있습니다. Microsoft ASP.NET 2.0에는 Client Callback 기능 (영문)을 통해 비동기 콜백의 고유한 특징이 도입되었으며, 최근 발표 (영문)에 따르면 "Atlas"라는 코드 이름으로 AJAX의 구현이 진행되고 있다고 합니다.

용어가 혼동될 수도 있지만 여기서 AJAX는 클라이언트에서 서버 측 함수를 비동기적으로 호출하는 전체 프레임워크를 말하며, Ajax.NET은 AJAX 프레임워크를 활용하는 솔루션을 구축하는 데 도움이 되는 특정 구현을 의미합니다.

ASP.NET 2.0의 Client Callback 기능에 대한 자세한 내용을 보려면 Bertrand Le Roy의 블로그 (영문)를 방문해 보십시오.

AJAX 활용

이 기사의 나머지 부분에서는 Ajax.NET을 통해 AJAX의 기능을 활용하는 세 가지 예를 소개하겠습니다. 이 가이드에는 Microsoft C# 및 Microsoft Visual Basic .NET 코드가 포함되어 있으며, 두 가지가 모두 제공되거나 하나만 제공되기도 합니다. 코드 구성이 간단하기 때문에 C# 개발자가 Visual Basic .NET 전용 코드를 쉽게 따라할 수 있는 것은 물론, 그 반대의 경우도 마찬가지입니다. 이 기사에는 다운로드할 수 있는 샘플 C# 및 Visual Basic .NET 프로젝트가 포함되어 있으며, 올바르게 작동하는 실행 코드를 제공합니다. 예를 살펴보기 전에 Ajax.NET의 설정 및 작업과 관련된 기본 사항을 설명하고 넘어가겠습니다.

Ajax.NET

AJAX.NET 설명서 (영문)웹 사이트 (영문)는 개발자가 작업을 계획하고 실행하는 데 매우 유용합니다. 이 기술의 몇 가지 구체적인 활용 예를 살펴보기 전에 알아 두어야 할 주요 단계를 간략하게 검토해 보겠습니다.

AJAX.NET 프로젝트 웹 사이트 (영문)에서 AJAX 파일을 다운로드하고 압축을 푼 다음 새 ASP.NET 프로젝트(Visual Basic .NET 또는 C# 중 선택)를 만들고 AJAX.dll 파일에 대한 참조를 추가 (영문)합니다. 그런 다음 <system.web> 요소에 있는 web.config 파일에 다음 코드를 추가하는 구성 단계만 수행하면 됩니다.

<configuration>    
 <system.web>  
  <httpHandlers>
   <!-- Register the ajax handler -->
   <add verb="POST,GET" path="ajax/*.ashx" 
        type="Ajax.PageHandlerFactory, Ajax" />
  </httpHandlers>  
  ...
  ... 
 </system.web>
</configuration>

JavaScript를 통해 서버 측 함수를 사용할 수 있도록 하려면 두 가지 작업을 수행해야 합니다. 우선 해당 함수를 Ajax.AjaxMethodAttribute로 표시해야 합니다. 그런 다음 페이지 로드 이벤트 내에서 Ajax.Utility.RegisterTypeForAjax를 호출하여 이 함수를 포함하는 클래스를 등록해야 합니다. 코드에 두 줄만 추가하면 되는 간단한 작업이므로 염려할 필요는 없습니다. 다음 예를 참조하십시오.
//C#

public class Sample : System.Web.UI.Page
{
 private void Page_Load(object sender, System.EventArgs e)
 {
  //필요한 서버 측 함수가 포함된
  //클래스를 등록합니다.
  Ajax.Utility.RegisterTypeForAjax(typeof(Sample));
 }
 [Ajax.AjaxMethod()]
 public string GetMessageOfTheDay()
 {
  return "Experience is the mother of wisdom";
 }
}
'VB.NET
Public Class Sample
 Inherits System.Web.UI.Page

 Private Sub Page_Load(sender AsObject, e As EventArgs) 
                                         Handles MyBase.Load
  '필요한 서버 측 함수가 포함된
  '클래스를 등록합니다.
  Ajax.Utility.RegisterTypeForAjax(GetType(Sample))
 End Sub
 <Ajax.AjaxMethod()> _
 Public Function GetMessageOfTheDay() As String
  Return "Experience is the mother of wisdom"
 End Function
End Class(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.)

위의 예에서는 먼저 Ajax.NET이 Sample 클래스에서 Ajax 관련 메서드를 찾습니다. 여기서는 같은 페이지의 동일한 클래스이지만, 대상 클래스는 다른 .NET 클래스이거나 등록된 여러 클래스일 수도 있습니다. 그런 다음 Ajax.NET이 특정 클래스에서 AjaxMethodAttribute로 표시된 모든 메서드를 찾습니다. Sample 클래스의 경우 GetMessageOfTheDay라는 하나의 메서드가 있습니다.

이 작업을 수행하고 나면 JavaScript에서 이를 사용하는 일만 남게 됩니다. Ajax.NET은 등록된 클래스(이 예에서는 Sample)와 동일한 이름의 JavaScript 변수를 자동으로 생성하며, 이 변수를 통해 AjaxMethod로 표시된 함수(이 예에서는 GetMessageOfTheDay)를 호출할 수 있습니다. 이는 다음과 같이 나타낼 수 있습니다.

<script language="javascript">
 Sample.GetMessageOfTheDay(GetMessageOfTheDay_CallBack);
 function GetMessageOfTheDay_CallBack(response)
 {
  alert(response.value);
 }
</script>

JavaScript에서 GetMessageOfTheDay를 실행하고 응답을 전달하려면 JavaScript 콜백 함수와 함께 서버 측 매개 변수(이 경우에는 없음)와 동일한 매개 변수가 필요합니다. GetMessageOfTheDay를 호출하더라도 다른 JavaScript 코드가 실행되지 않거나, 사용자가 페이지에서 계속 작업을 수행하지 못하게 되는 것은 아니므로 여기서 우리는 AJAX의 비동기적 특성을 확인할 수 있습니다. 서버 측 처리가 완료되면 Ajax.NET은 콜백 함수인 GetMessageOfTheDay_CallBack을 호출하고 서버 측의 반환 값에 해당하는 응답을 전달합니다.

서버 측 코드와 JavaScript 코드 간의 매핑을 혼동하는 경우가 있을 수 있습니다. 그림 1에서는 서버 측 코드와 JavaScript 코드 간의 매핑과 함께 두 코드의 개요를 보여 줍니다.

그림 1. 서버 측 코드 및 JavaScript 코드 간 매핑

.NET 형식에 대한 지원 및 콜백 응답의 다양성(하나 이상의 값) 등을 비롯하여 Ajax.NET에 대한 흥미거리는 이 밖에도 다양합니다. 다음 예제는 일부 기능에 중점을 두고 있으며, AJAX를 통해 성공적인 응용 프로그램을 손쉽게 만드는 방법을 보여 줍니다.

샘플 1: 연결된 드롭다운 목록

이 기사의 도입 부분에서 두 DropDownList를 서로 연결하는 데 사용되는 두 가지 일반적인 방법에 대해 간략히 언급한 바 있습니다. 바로 선택된 인덱스가 변경될 때 페이지를 다시 게시하거나, 가능한 모든 데이터를 JavaScript 배열로 로드하여 동적으로 표시하는 것입니다. 여기서는 이러한 두 솔루션의 대안으로 AJAX를 활용하는 방법을 살펴 보겠습니다.

우선 데이터 인터페이스를 살펴보고 여기서 예제를 도출하겠습니다. 데이터 액세스 계층에서는 두 개의 메서드를 노출하는데, 첫 번째 메서드는 시스템에서 지원하는 국가 목록을 검색하고, 두 번째는 국가 ID를 가져오고 시/도 목록을 반환합니다. 이는 순수한 데이터 액세스이므로, 여기서는 메서드 서명만 확인하겠습니다.

//C#
public static DataTable GetShippingCountries();
public static DataView GetCountryStates(int countryId);
'VB.NET
Public Shared Function GetShippingCountries() As DataTable
Public Shared Function GetCountryStates(ByVal countryId As Integer)
                                                        As DataView

이제 반대 계층으로 넘어가 간단한 Web Form을 만들어 보겠습니다.

<asp:DropDownList ID="countries" Runat="server" /> 
<asp:DropDownList ID="states" Runat="server" />
<asp:Button ID="submit" Runat="server" Text="Submit" />

Page_Load 이벤트 역시 간단하며 앞의 Web Form만큼이나 일반적입니다. 여기서는 데이터 액세스 계층을 사용하여 사용 가능한 국가를 검색하고 countries DropDownList에 바인딩합니다.

//C#
if (!Page.IsPostBack)
{
 countries.DataSource = DAL.GetShippingCountries();
 countries.DataTextField = "Country";
 countries.DataValueField = "Id";
 countries.DataBind();
 countries.Items.Insert(0, new ListItem("Please Select", "0"));
}

일반적인 코드는 여기까지입니다. 이제 JavaScript에서 호출할 서버 측 함수를 만들겠습니다.

'VB.NET
<Ajax.AjaxMethod()> _
Public Function GetStates (ByVal countryId As Integer) As DataView
 Return DAL.GetCountryStates(countryId)
End Function

이는 일반적인 다른 함수와 같습니다. 즉, 가져올 국가 ID를 예상하고 DAL로 요청을 전달합니다. 유일한 차이점은 메서드를 AjaxMethodAttribute로 표시했다는 점입니다. 마지막으로 수행해야 할 서버 측 단계는 RegisterTypeForAjax를 호출하여 위의 메서드(이 경우에는 코드 숨김)를 포함하는 클래스를 Ajax.NET에 등록하는 것입니다.

//C#
Ajax.Utility.RegisterTypeForAjax(typeof(Sample));
'VB.NET
Ajax.Utility.RegisterTypeForAjax(GetType(Sample))

작업이 거의 완료되었습니다. 이제 남은 일은 JavaScript에서 GetStates 메서드를 호출하고 응답을 처리하는 작업입니다. 여기서는 사용자가 국가 목록에서 새 항목을 선택할 때 GetStates를 논리적으로 호출해야 합니다. 이를 위해 JavaScript onChange 이벤트에 연결합니다. 그러면 Web Form 코드가 약간 변경됩니다.

<asp:DropDownList onChange="LoadStates(this)" 
                  ID="countries" Runat="server" />

JavaScript LoadStates 함수는 Ajax.NET에 의해 만들어진 프록시를 통해 비동기 요청을 수행하게 됩니다. 기본적으로 만들어진 프록시 Ajax.NET의 형태는 <RegisteredTypeName>.<ServerSideMethodName>이며, 여기서는 Sample.GetStates입니다. 또한 국가 ID 매개 변수와 함께 Ajax.NET에서 서버 측 함수가 완료되면 호출해야 하는 콜백 함수를 전달해야 합니다.

//JavaScript
function LoadStates(countries)
{
 var countryId = countries.options[countries.selectedIndex].value;
 Sample.GetStates(countryId, LoadStates_CallBack);
}

마지막 단계는 LoadStates_CallBack 함수에서 응답을 처리하는 것입니다. Ajax.NET의 가장 생산적인 기능은 다양한 .NET 형식을 지원하는 것이며, 이 점에 대해서는 이미 여러 번 언급했습니다. 서버 측 함수에서 DataView를 반환한 사실을 떠올려 보십시오. JavaScript에서 DataViews의 어떤 부분을 인식하고 있습니까? 아무 것도 인식하지 못합니다. 하지만 JavaScript는 객체 지향 언어이며, Ajax.NET은 .NET DataView와 유사한 개체를 만들 뿐 아니라 함수에서 반환한 값을 JavaScript의 해당 부분으로 매핑할 수 있습니다. JavaScript DataView는 실제 DataView의 복제본에 불과하며, 아직까지는 행을 반복하고 열 값에 액세스하는 기능 이외에 추가 기능(예: RowFilter 또는 Sort 속성 설정 기능)은 지원하지 않습니다.

function LoadStates_CallBack(response)
{
 //서버 측 코드에서 예외가 발생하는 경우
 if (response.error != null)
 {
  //더 나은 방법도 있겠으나 여기서는 오류를 보고만 합니다.
  alert(response.error); 
  return;
 }

 var states = response.value;  
 //예상하지 않은 응답인 경우
 if (states == null || typeof(states) != "object")
 {
  return;
 }
 //시/도 드롭다운을 가져옵니다.
 var statesList = document.getElementById("states");
 statesList.options.length = 0; //시/도 드롭다운을 다시 설정합니다.

 //여기서 length는 JavaScript의 Length와는 다릅니다.
 for (var i = 0; i < states.length; ++i)
 {
  //행의 열을 명명된 속성처럼 사용할 수 있습니다.
  statesList.options[statesList.options.length] = 
         new Option(states[i].State, states[i].Id);
 }
}

간단한 오류 검사가 끝나면 앞의 JavaScript는 시/도 드롭다운 목록을 가져오고 응답 값을 반복하며 드롭다운 목록에 옵션을 동적으로 추가합니다. 이 코드는 명확하고 간단하며 C# 및 Visual Basic .NET과도 매우 유사합니다. 서버 측 변수를 기반으로 JavaScript 배열을 만들고 이를 함께 연결하는 개발자인 저로서도 아직 이 코드가 실제로 작동한다고 믿기가 어렵습니다.

여기서 아직 분명하지 않은 한 가지 중요한 문제가 있습니다. DropDownList가 JavaScript에서 동적으로 만들어졌으므로 목록의 각 항목은 ViewState의 일부가 아닐 뿐 아니라 관리되지도 않습니다. 따라서 단추의 OnClick 이벤트 처리기를 통해 몇 가지 추가 작업을 수행해야 합니다.

'VB.NET
Private Sub submit_Click(sender As Object, e As EventArgs)
  Dim selectedStateId As String = Request.Form(states.UniqueID)

  '실제 코드에서는 사용자 입력에 대한 유효성 검사가 필요합니다...
  states.DataSource = 
     DAL.GetCountryStates(Convert.ToInt32(countries.SelectedIndex))
  states.DataTextField = "State"
  states.DataValueField = "Id"
  states.DataBind()
  states.SelectedIndex = 
    states.Items.IndexOf(states.Items.FindByValue(selectedStateId))
End Sub

먼저, 여기서는 states.SelectedValue 속성을 사용할 수 없으며 Request.Form을 사용해야 합니다. 두 번째로, 사용자에게 목록을 다시 표시하려면 동일한 데이터 액세스 메서드를 다시 사용하여 시/도 DropDownList를 바인딩해야 합니다. 마지막으로 선택한 값을 프로그래밍 방식으로 설정해야 합니다.

샘플 2: 문서 락커(Locker)

다음 예제에서는 좀 더 복잡한 기능을 살펴보고 AJAX를 사용하여 이를 향상시켜 보겠습니다. 이 예제는 간단한 문서 관리 시스템에 대한 것입니다. 우선, 다른 훌륭한 문서 관리 시스템처럼 동시성 관리 기능을 제공해야 합니다. 즉, 같은 문서를 편집하려는 두 사용자를 처리하는 방법이 필요합니다. 이를 위해 한 사용자가 이미 편집 중인 문서를 다른 사용자가 편집하지 못하도록 하는 일종의 잠금 메커니즘을 만들겠습니다. AJAX를 사용하여 보다 친숙한 사용자 환경을 구성해볼 것입니다. 먼저, 사용자가 편집을 시도하지만 이미 편집 중이기 때문에 그럴 수 없는 문서의 대기열을 만들고, 문서를 편집할 수 있게 되면 자동으로 사용자에게 알립니다. 그런 다음 사용자가 브라우저를 닫거나 다른 곳으로 이동하면 문서의 잠금이 해제되도록 합니다. 이 마지막 기능은 문서가 영원히 잠겨 있지 않도록 하는 데 도움이 됩니다. 이 가이드의 목적에 따라 AJAX 구현과 명확하게 관련되지 않은 기능은 생략하겠습니다. 하지만 다운로드 가능한 프로젝트에는 모든 기능이 포함되어 있습니다.

우선 사용자가 문서를 편집하려 하면 문서에 대한 단독 잠금을 확보하고, 사용자가 문서를 편집할 수 없으면 문서를 사용자의 대기열에 추가한 다음 사용자를 기본 페이지로 보냅니다. 여기에는 AJAX에 해당되는 사항은 없지만 예제에 필요한 컨텍스트를 제공하기 위해 코드를 살펴보겠습니다. 편집에 사용되는 PageOnLoad 이벤트에 다음 코드가 추가됩니다.

//C#
if (!Page.IsPostBack)
{
 //실제 코드에서는 사용자 입력에 대한 유효성 검사가 필요합니다...
 Document document = GetDocument(Request.QueryString["id"]);
 //문서가 있지만 편집할 수 없습니다!
 if (!Locker.AcquireLock(document))
 {
  //사용자의 문서 목록에 추가하고 대기합니다.
  User.CurrentUser.AddDocumentToQueue(document.DocumentId);
  Response.Redirect("DocumentList.aspx");
 }
 //문서가 있으며 편집할 수 있습니다.
 //...
}

여기서 핵심적인 역할을 하는 줄은 문서를 현재 사용자의 대기열에 추가하는 부분, 즉 세션에 추가하는 위치입니다. 다음으로 대기열에 있는 문서를 사용할 수 있게 되면 이를 사용자에게 알리는 데 필요한 사용자 컨트롤을 만듭니다. 사용자 컨트롤은 모든 페이지에 배치할 수 있습니다. 이 사용자 컨트롤에는 단일 AJAX 메서드와 함께 AJAX를 사용하여 클래스를 등록하는 데 필요한 코드가 포함되어 있습니다.

'VB.NET
Private Sub Page_Load(s As Object, e As EventArgs) 
                                   Handles MyBase.Load
 Ajax.Utility.RegisterTypeForAjax(GetType(UnlockNotifier))
End Sub

'대기 중인 문서를 반복하여 사용할 수 있는지 확인합니다.
<Ajax.AjaxMethod()> _
Public Function GetUnlockedDocuments() As DocumentCollection
 '사용자가 보유한 대기 중인 문서 ID를 모두 가져옵니다.
 Dim queuedDocument As ArrayList = User.CurrentUser.DocumentQueue
 Dim unlocked As DocumentCollection = New DocumentCollection

 For Each documentId As Integer In queuedDocumentIds
 '대기 중인 문서가 더 이상 잠겨 있지 않은 경우
  If Not Locker.IsLocked(documentId) Then
   unlocked.Add(Document.GetDocumentById(documentId))
  End If
 Next

 Return unlockedDocuments
End Function

이제 필요한 부분은 요청을 만들고 응답을 처리하는 JavaScript뿐입니다. 잠금이 해제된 문서 정보가 있는 경우, 응답을 기반으로 동적으로 빌드할 테이블 내에 이 정보를 배치합니다. 먼저 HTML 부분부터 시작합니다.

<div id="notifyBox" style="display:none;">
 <b>이제 대기열 내의 다음 문서를 편집할 수 있습니다.</b>
 <table cellpadding="5" cellspacing="0" 
       border="0" style="border:1px solid #EEE;" 
       id="notifyTable">
  </table>
</div>

사용 가능한 문서가 없으면(또는 사용자의 대기열에 문서가 없는 경우) DIV 태그를 사용하여 모든 항목을 숨기고, TABLE 태그를 사용하여 결과를 표시합니다. 여기서는 대기 중인 문서가 있는지 확인하기 위해 폴링 시스템을 사용하겠습니다. 즉, 지정된 간격에 따라 서버 측 메서드를 계속 호출하고 결과를 표시하는 것입니다. 첫 번째 호출은 페이지가 로드될 때만 발생하며, 이후의 호출은 X초마다 발생하게 됩니다.

<script language="javascript">
window.setTimeout("PollQueue();", 2000);
//2초마다 대기 중인 문서가 해제되었는지 확인합니다.
//사용자 수가 많은 실제 시스템에서 2초의 시간은
//서버에 너무 많은 부하를 줄 수 있습니다. 따라서 사용자의 대기열에 문서가 있는지
//확인하기 전에 몇 가지 성능 테스트를
//수행해야 합니다.
function PollQueue()
{
 //UnlockNotifier는 Ajax.NET을 사용하여 등록한 형식입니다.
 //GetUnlockedDocuments는 AjaxMethod 특성으로 표시된 해당 형식 내의
 //메서드입니다.
 UnlockNotifier.GetUnlockedDocuments(PollQueue_CallBack);
 //2초마다 자동으로 호출됩니다.
 window.setTimeout("PollQueue();", 2000);
}
</script>

이제 응답을 처리하는 작업만 남았습니다. 이 코드는 이전 예제의 코드와 유사합니다. 먼저 오류를 확인하며 응답을 가져오고 사용 가능한 문서를 반복한 다음, HTML을 동적으로 빌드합니다. 여기서는 테이블에 행과 열을 추가합니다.

function PollQueue_CallBack(response)
{
  var notifyBox = document.getElementById("notifyBox");
  var notifyTable = document.getElementById("notifyTable");
  //테이블 알림 상자를 찾을 수 없는 경우
  if (notifyBox == null || notifyTable == null)
  {
    return;
  }
  //서버 측 코드에서 예외가 발생하는 경우
  if (response.error != null)
  { 
    notifyBox.style.display = "none"; 
    alert(response.error); //더 나은 방법도 있겠으나 여기서는 오류를 보고만 합니다.
    return;
  }  
    
  var documents = response.value;
  //예상하지 않은 응답인 경우
  if (documents == null || typeof(documents) != "object")
  {
    notifyBox.style.display = "none";
    return;  
  }  
  for (var i = 0; i < notifyTable.rows.length; ++i)
  {
   notifyTable.deleteRow(i);
  }
  for(var i = 0; i < documents.length; ++i)
  {    
    var row = notifyTable.insertRow(0);
    row.className = "Row" + i%2;
    var cell = row.insertCell(0);
    cell.innerHTML = documents[i].Title;
    cell = row.insertCell(1);
    var date = documents[i].Created;
    cell.innerHTML = date.getDay() + "/" + date.getMonth() 
                     + "/" + date.getYear();
    cell = row.insertCell(2);
    cell.innerHTML = "<a href='DocumentEdit.aspx?id=" 
                     + documents[i].DocumentId + "'>edit</a>";
  } 
  notifyBox.style.display = "block"; 
}

마지막으로 살펴볼 개선 사항은 사용자가 브라우저를 닫고 다른 링크로 이동하거나 뒤로 단추를 누른 경우 자동으로 문서의 잠금을 해제하는 부분입니다. 일반적으로 이 작업은 JavaScript OnBeforeUnLoad 이벤트 또는 OnUnload 이벤트에 연결하고, 작은 새 팝업 페이지를 열어 몇 가지 정리 작업을 수행한 다음 닫는 방식으로 수행합니다. 팝업 사용이 적절할 수도 있지만 어떤 경우에는 이를 적절히 처리하지 못해 팝업이 차단되어 문서가 영원히 잠기게 될 수 있습니다. 이 문제를 해결하기 위해서도 여전히 두 JavaScript 이벤트를 사용하지만, 팝업을 실행하는 대신 AJAX를 통해 서버 측 메서드를 실행합니다. 여기서는 잠금이 설정된 문서를 편집하는 데 사용되는 페이지에 몇 가지 간단한 JavaScript를 추가합니다.

<script language="javascript">
//사용자가 브라우저를 닫거나 뒤로 단추를 누르면
//문서의 잠금을 해제합니다.
window.onbeforeunload = ReleaseLock;
function ReleaseLock() {
 Locker.ReleaseDocument(<%=DocumentID%>);
}
</script>

여기서 DocumentId는 코드 숨김에 정의되어 설정된 변수입니다. 또는 세션에 DocumentId를 저장한 다음 서버 측 ReleaseDocument에서 액세스할 수도 있습니다. ReleaseDocument는 기본적으로 잠긴 문서 목록에서 문서를 제거합니다.

샘플 3: 포럼 주제 검색

마지막으로 살펴볼 예제는 기존 응용 프로그램의 수정입니다. 개인적으로 이 개념은 Josh Ledgard (영문)MSDN 포럼 (영문)에서 사용되는 기능으로 소개했을 때 처음 들었습니다. 이 기능의 목적은 게시 수가 중복되지 않도록 하는 것은 물론 질문이 있는 사용자를 도와주려는 것입니다. 기본적으로 사용자가 포럼에서 새 질문을 하면서 주제와 질문을 입력할 때, 이전에 이미 한 질문이며 답변이 있는지 검색하는 일은 좀처럼 없었습니다. 여기서 AJAX에 대해 생각해 봅시다. 사용자가 주제를 입력하고 필드를 이동하고 나면, 우리는 주제에 따라 포럼을 비동기적으로 검색하고 사용자에게 조심스럽게 결과를 제시할 것입니다. 결과는 유용할 때도 있고 그렇지 않을 때도 있을 것입니다.

이를 위해 여기서는 asp.NETPRO 독자가 선정한 최고의 포럼 응용 프로그램인 CommunityServer를 수정하겠습니다. 다운로드 가능한 샘플에는 이 섹션(또는 포럼)의 코드가 포함되어 있지 않지만 http://communityserver.org/ (영문)에서 CommunityServer에 대한 자세한 정보를 확인하고 다음 코드 조각을 여기에 적용할 수 있습니다.

CommunityServer를 설치하고 Ajax.NET을 구성했으면(web.config에 참조 및 처리기 추가), 원하는 기능을 얻기 위해 몇 가지만 변경하면 됩니다. 먼저, CommunityServerForums 프로젝트의 CreateEditPost.cs 파일로 이동합니다. 이 파일은 사용자가 새 게시를 추가하는 페이지의 코드 숨김으로 생각하면 됩니다. 여기에 AJAX 사용 함수를 추가하겠습니다.

//C#
[Ajax.AjaxMethod()]
public static ArrayList Search(string search)
{
 SearchQuery query = new SearchQuery();
 query.PageIndex = 0; //처음 10개의 결과를 얻습니다.
 query.PageSize = 10;
 query.UserID = Users.GetUser().UserID;
 query.SearchTerms = search;
 return new ForumSearch().GetSearchResults(query).Posts;
}

CommunityServer에 이미 빌드된 검색 기능을 활용할 수 있으며 함수를 래핑하기만 하면 됩니다. 언제나처럼 형식은 Ajax.NET으로 등록해야 합니다. 이를 동일한 파일의 InitializeSkin 함수에서 수행하겠습니다(Page_Load로 간주).

//C#
Ajax.Utility.RegisterTypeForAjax(typeof(CreateEditPost));

JavaScript로 이동하기 전에 마지막 서버 측 변경을 수행해야 합니다. 여기서 반환되는 ArrayList에 포함된 ForumPost처럼 Ajax.NET에 반환되는 사용자 지정 클래스는 Serializable 특성으로 표시되어야 합니다. 이제 CommunityServerForums 프로젝트의 Components/ForumPost.cs 파일로 이동하여 특성을 추가하기만 하면 됩니다.

//C#
[Serializable]
public class ForumPost : Post
{
 ...
}

프레젠테이션 부분에서는 CommunityServerWeb 프로젝트의 Themes/default/Skins/View-EditCreatePost.cs만 수정하면 됩니다. 우선 제목 텍스트 상자의 onBlur 이벤트에 연결하겠습니다.

<asp:textbox onBlur="Search(this.value);"
             id="PostSubject" runat="server" ... />

그런 다음 서버 측 Search를 호출하도록 JavaScript Search 메서드를 작성합니다.

var oldValue = '';
function Search(value)
{ 
  //방금 검색한 항목은 다시 검색하지 않습니다.
  //사용자가 앞뒤로 이동하는 경우
  if (value != oldValue)
  {
   CreateEditPost.Search(value, Search_CallBack);
   oldValue = value;
  }
}

마지막으로 응답을 처리하는 작업만 남았습니다. 결과가 좀 더 깔끔하게 표시하는 방법은 이전 예제에서 살펴 보았으므로 여기서는 동적인 HTML을 만들고 가상의 DIV에 고정하기만 하겠습니다.

function Search_CallBack(response)
{
 //결과가 없는 경우 검색 기능이 자동으로 리디렉션되므로
 //response.error를 사용할 수 없습니다.

 var results = response.value;
 //결과를 얻지 못한 경우
 if (results == null)
 {
  return;
 }

 //결과를 출력하는 데 사용되는 div
 var someDiv = document.getElementById("someDiv");  
 var html = "";
 for (var i = 0; i < results.length; ++i)
 {
  var result = results[i];
  html += "<a target=_blank href='" + result.PostID
  html += "/ShowPost.aspx'>";   
  html += result.Subject;
  html += "</a><br />"
 }
 someDiv.innerHTML = html;
}

CommunityServer 응용 프로그램 파일 세 개와 구성에 필요한 web.config를 약간 수정하여 유용한 기능을 추가할 수 있었습니다. 하지만 기존 응용 프로그램에 AJAX 사용 기능을 추가할 때는 신중해야 합니다. 실제 검색을 수행하는 ForumSearch 클래스는 여기서 소개한 사용 유형에 맞게 디자인되지 않았을 수 있습니다. 여기서 소개한 코드로 인해 추가 검색이 수행될 수 있으며, 그 영향은 매우 클 수 있습니다.

AJAX와 사용자

AJAX를 응용 프로그램에 적용하는 방법 및 경우를 비롯하여 응용 프로그램이 이미 존재하는지 여부는 상황에 따라 크게 달라집니다. 지금까지 Ajax.NET으로 AJAX를 사용하는 솔루션을 만드는 방법이 얼마나 쉬운지 살펴보았지만 아직 고려해야 할 부분이 남아 있습니다. 중요하게 고려해야 할 사항은 응용 프로그램의 전체 아키텍처와 관리 용이성에 미치는 영향입니다. AJAX는 프레젠테이션, 프레젠테이션 논리 및 비즈니스 계층 같은 시스템 계층 사이의 경계를 희미하게 만들 수 있습니다. 이는 AJAX 자체가 문제인 것이 아니라 AJAX를 사용하는 방법에 문제가 있는 것입니다. 계층 구분이 흐릿해지는 것이 얼마나 쉬운지를 이해하고 작업에 신중을 기해야 좋은 결과를 얻을 수 있습니다.

여기서 AJAX를 사용하는 응용 프로그램은 관리하기가 더 힘들지 않은가 하는 질문을 할 수 있을 것입니다. 이에 대한 대답은 JavaScript를 이미 얼마나 사용하고 있는지, 그리고 JavaScript를 구성하고 관리하는 데 얼마나 능숙한지에 따라 달라집니다. 많은 개발자들이 JavaScript 자체의 문제가 아닌 도구 지원 및 개발자의 지식 수준으로 인해 JavaScript를 작성, 테스트 및 디버깅하는 데 어려움을 느끼고 있습니다. 현재 JavaScript를 사용하여 연결된 드롭다운 목록을 구현하고 있고 AJAX로 바꾼 경우, 아마 코드를 관리하기가 쉬울 것입니다. 그 이유는 Ajax.NET에서 .NET 형식 및 배열을 지원하기 때문입니다. 하지만 반대의 경우라면 응용 프로그램에 완전히 새로운 언어(JavaScript)를 도입해야 할 뿐 아니라, 단추 클릭 이벤트에서처럼 ViewState와 관련이 없는 데이터를 처리해야 할 것입니다.

또 다른 고려 사항은 웹 사이트의 유용성에 미치는 AJAX의 영향입니다. AJAX의 궁극적인 이점은 응답 성능이 뛰어난 인터페이스를 만드는 것이지만 개발자는 두 가지를 명심해야 합니다. 먼저, 가장 중요한 사실은 AJAX가 JavaScript와 종속 관계를 이루고 있다는 점입니다. 다들 아시다시피, 어떤 사용자들은 JavaScript를 사용할 수 없으며 일부 표준(예: 캐나다 정부의 Common Look and Feel[캐나다 508])에서는 웹 사이트가 JavaScript와 관계없이 작동하도록 요구하고 있습니다. 따라서 AJAX 기능이 제대로 작동하는 것으로 가정해서는 안 되며, AJAX 기능을 사용할 수 없는 경우 응용 프로그램에 보다 일반적인 웹 처리 방식을 사용해야 합니다. 두 번째로, AJAX 응용 프로그램은 성능이 뛰어나더라도 사용자가 일반적으로 응용 프로그램을 사용하는 방식과 다를 수 있습니다. 예를 들어, AJAX를 통해 다양한 작업을 수행하는 페이지에는 뒤로 단추, 즐겨찾기 메뉴는 물론 사용자가 있을 것으로 생각하는 다른 브라우저 기능이 없을 수 있습니다.

결론

AJAX는 과도하게 포장된 신기술이 아니라 웹 응용 프로그램을 빌드할 때 일상적으로 발생하는 문제에 대해 대안을 제시할 수 있는 구체적인 프레임워크입니다. ASP.NET 개발자는 Ajax.NET을 사용하여 AJAX를 손쉽게 활용할 수 있습니다. 다운로드 가능한 프로젝트와 함께 여기서 살펴본 세 가지 예제는 AJAX와 Ajax.NET의 사용 방법을 이해하는 데 도움이 될 뿐 아니라 사용자의 생각을 실행해 볼 수 있는 간단한 학습 기회로 활용할 수도 있습니다. AJAX는 세련되고 멋진 응용 프로그램을 만드는 수준을 뛰어넘어 고객 만족도와 경쟁력을 향상시킬 수 있습니다. Atlas에 대해 논의되고 있는 몇 가지 상위 개념을 적용하면 향후 출시되는 제품을 크게 개선할 수 있을 것입니다. 제가 경험한 최상의 AJAX 구현은 매우 간단하며 적절한 느낌입니다. 여러분의 구현에서도 사용자가 동일한 긍정적인 경험을 얻을 수 있으리라 생각합니다. 하지만 문제를 고려해 볼 때 AJAX는 유일한 솔루션이 아니며 최고의 솔루션은 더욱 아닐 수 있습니다. 자, 이제 ASP.NET 커뮤니티가 최고임을 증명해 봅시다.

+ Recent posts