Fritz Onion

Get the sample code for this article.

코드 다운로드 위치: ExtremeASPNET2007_01.exe


목차

AJAX를 사용한 웹 서비스 호출
작동 원리
Serialization
결론


처음 등장할 때부터 ASP.NET은 서버측 기술을 기반으로 했습니다. 유효성 검사 제어나 최근에 등장한 웹 파트 인프라에서와 같이 ASP.NET이 클라이언트측 JavaScript를 생성하는 경우도 있지만, 이는 단순히 서버측 속성을 클라이언트측 동작으로 변환한 것에 불과합니다. 따라서 개발자는 다음 POST 요청을 수신하기 전까지는 클라이언트와의 상호 작용을 고려할 필요가 없었습니다. JavaScript의 경우에는 보다 대화형에 가까운 페이지를 작성해야 했는데, 이러한 문제는 ASP.NET 2.0 스크립트 콜백 기능을 활용하여 DHTML에서 자체적으로 해결되었습니다. 그러나 작년에 이러한 기능이 완전히 바뀌었습니다.

2005년 9월에 열린 Microsoft Professional Developer's Conference에서 Microsoft는 새로운 ASP.NET 추가 기능을 발표했습니다. 코드명 "Atlas"로 소개된 이 추가 기능은 오로지 클라이언트측 JavaScript, DHTML 및 XMLHttpRequest 개체 활용에만 중점을 두고 개발된 것이었습니다. 즉, 개발자가 보다 대화형에 가까운 AJAX 지원 웹 응용 프로그램을 만들 수 있도록 하는 것을 목표로 한 것이었습니다. Microsoft® AJAX Library 및 ASP.NET 2.0 AJAX Extensions라는 공식 타이틀로 이름이 바뀐 이 프레임워크는 클라이언트측 데이터 바인딩에서 DHTML 애니메이션 및 동작, 그리고 UpdatePanel을 사용한 클라이언트 POST 백의 정교한 차단에 이르기까지 여러 가지 유용한 기능을 제공합니다. 이러한 기능은 대부분 구문 분석 및 클라이언트측 JavaScript 호출을 통한 상호 작용이 용이한 형태로 서버에서 비동기식으로 데이터를 검색하는 기능을 기반으로 합니다. 이 달의 칼럼은 ASP.NET 2.0 AJAX Extensions 지원 페이지의 클라이언트측 JavaScript에서 서버측 웹 서비스를 호출하는 이 새롭고 유용한 기능을 주제로 설명합니다.

AJAX를 사용한 웹 서비스 호출

wsel.exe 유틸리티를 사용하거나 Visual Studio®의 웹 참조 추가 기능을 이용하여 Microsoft .NET Framework에서 웹 서비스를 사용한 경험이 있다면 어렵지 않게 .NET 형식을 사용하여 웹 서비스를 호출하는 작업을 수행할 수 있을 것입니다. 사실 .NET 프록시를 통해 웹 서비스 메서드를 호출하는 것은 다른 클래스에 대한 메서드를 호출하는 것과 다르지 않습니다. .NET 프록시는 사용자가 전달한 매개 변수를 기준으로 XML을 준비하고 수신한 XML 응답을 proxy 메서드에 지정된 .NET 형식으로 세밀하게 변환합니다. .NET Framework를 이용하여 웹 서비스 끝점을 손쉽게 사용할 수 있게 됨으로써 개발자가 선택할 수 있는 폭이 넓어졌으며, 이것이 바로 오늘날 새롭게 탄생한 서비스 지향 응용 프로그램의 핵심입니다.

ASP.NET 2.0 AJAX Extensions를 사용하면 브라우저에서 실행되는 클라이언트측 JavaScript의 웹 서비스에 대해서도 이와 같이 원활하게 프록시를 생성할 수 있습니다. 또한 서버에서 호스트하는 .asmx 파일을 작성하고 클라이언트측 JavaScript 클래스를 통해 해당 서비스의 메서드를 호출할 수 있습니다. 예를 들어 그림 1에는 임의 데이터를 사용하여 가상 주식 시세 검색 기능을 구현하는 간단한 .asmx 서비스가 나와 있습니다.

이 서비스에는 표준 .asmx 웹 서비스 특성 외에 JavaScript 클라이언트에서도 해당 서비스를 사용할 수 있도록 하는 ScriptService 특성이 있습니다. 이 .asmx 파일을 ASP.NET AJAX 지원 웹 응용 프로그램에 배포하면 .aspx 파일의 ScriptManager 컨트롤에 ServiceReference를 추가하여 JavaScript에서 서비스의 메서드를 호출할 수 있습니다. ScriptManager 컨트롤은 Visual Studio에서 ASP.NET AJAX 지원 웹 사이트 템플릿을 사용하여 웹 사이트를 만들 때 기본.aspx 페이지에 자동으로 추가됩니다.

<asp:ScriptManager ID="_scriptManager" runat="server">
  <Services>
    <asp:ServiceReference Path="StockQuoteService.asmx" />
  </Services>
</asp:ScriptManager>

이제 클라이언트측 JavaScript 런타임에서 MsdnMagazine.StockQuoteService 클래스를 사용하여 서비스의 모든 메서드를 호출할 수 있습니다. 호출을 비동기 방식의 메커니즘을 기반으로 이루어지기 때문에 동기식 메서드는 사용할 수 없습니다. 대신 각 프록시 메서드에 표준 입력 매개 변수 외에 추가 매개 변수가 하나 사용됩니다. 이 매개 변수는 메서드가 완료되었을 때 비동기로 호출되는 다른 클라언트측 JavaScript 함수에 대한 참조입니다. 그림 2의 예제 페이지에서는 클라이언트측 JavaScript를 사용하여 주식 시세 웹 서비스 호출 결과를 페이지의 레이블(span)에 인쇄합니다.

클라이언트측 웹 서비스 호출에 문제가 발생한 경우 클라이언트에 알려야 하므로 일반적으로 오류, 중단 또는 시간 초과 발생 시에 호출되는 다른 메서드를 전달하는 것이 좋습니다. 예를 들어 앞에서 나왔던 OnLookup을 다음과 같이 변경하여 문제를 표시하는 OnError 메서드를 추가할 수 있습니다.

function OnLookup()
{           
  var stb = document.getElementById("_symbolTextBox");  
  MsdnMagazine.StockQuoteService.GetStockQuote(
    stb.value, OnLookupComplete, OnError);
}
    
function OnError(result)
{
  alert("Error: " + result.get_message());
}

이렇게 하면 웹 서비스 호출에 실패한 경우 경고 상자를 통해 클라이언트에 알리게 됩니다. 또한 클라이언트에서 만들어지는 웹 서비스 호출에 userContext 매개 변수를 포함할 수도 있습니다. 이 매개 변수는 Web 메서드에 마지막 매개 변수로 전달되는 임의 문자열로, success 및 failure 메서드에 추가 매개 변수로 전파됩니다. 이 경우 다음과 같이 OnLookupComplete 메서드에 표시할 수 있도록 userContext로 요청된 실제 주식 기호를 전달해야 합니다.

function OnLookup()
{           
  var stb = document.getElementById("_symbolTextBox");  
  MsdnMagazine.StockQuoteService.GetStockQuote(
    stb.value, OnLookupComplete, OnError, stb.value);
}

function OnLookupComplete(result, userContext)
{
  // userContext contains symbol passed into method
  var res = document.getElementById("_resultLabel");
  res.innerHTML = userContext + " : <b>" + result + "</b>";
}

웹 서비스에 대한 서로 다른 호출을 여러 개 만들고 각 호출에 동일한 오류 및/또는 완료 메서드를 사용하는 경우 기본 오류 및 콜백 성공 메서드를 전역으로 설정할 수 있습니다. 이렇게 하면 호출을 만들 때마다 콜백 메서드 쌍을 지정할 필요가 없습니다. 단, 원하는 경우 전역으로 정의된 메서드를 각 메서드별로 재설정할 수 있습니다. 다음은 기본 콜백 성공 메서드와 콜백 실패 메서드를 호출별로 설정하는 대신 전역으로 설정하는 OnLookup 메서드 샘플입니다.

// Set default callbacks for stock quote service
MsdnMagazine.StockQuoteService.set_defaultSucceededCallback(
                 OnLookupComplete);
MsdnMagazine.StockQuoteService.set_defaultFailedCallback(
                 OnError);
function OnLookup()
{           
  MsdnMagazine.StockQuoteService.GetStockQuote(stb.value);
}

웹 서비스 메서드를 호출하기 위해 .asmx 파일을 새로 작성하는 대신 페이지 클래스에 직접 웹 서비스 메서드를 포함할 수도 있습니다. 호출할 메서드에 대해 완전한 웹 서비스 끝점을 만들 수 없는 경우에는 서버측 메서드를 페이지에 추가(페이지에서 직접 추가하거나 코드 숨김 파일에 추가)하고 WebMethod 특성을 연결하여 클라이언트측 JavaScript에서 호출 가능한 웹 메서드를 페이지에서 제공할 수 있습니다. 그러면 클라이언트측 개체 PageMethods를 통해 해당 메서드를 호출할 수 있습니다. 그림 3에는 별도의 웹 서비스로 나누는 대신 전체가 단일 페이지에 포함되도록 다시 작성된 주식 시세 서비스 샘플이 나와 있습니다.

이러한 클라이언트측 프록시는 ASP.NET .asmx 끝점, Windows Communication Foundation .svc 끝점 또는 페이지에 직접 포함된 웹 메서드에서만 생성할 수 있으며 임의 웹 서비스를 호출하는 일반적인 메커니즘은 없습니다. 사실 보안상의 이유로 기본 XmlHTTPRequest 개체에는 요청이 페이지가 로드된 도메인으로 제한되기 때문에 클라이언트측 프록시에서 지원되는지 여부에 관계없이 이러한 방식으로 임의 웹 서비스를 호출할 수 없습니다. 외부 웹 서비스를 호출해야 하는 경우 wsdl.exe를 사용하거나 Visual Studio의 웹 참조 추가 명령을 통해 생성한 .NET 프록시 클래스를 호출하는 응용 프로그램에서 외부 웹 서비스에 대한 브리지 .asmx 끝점을 설정하는 것이 최선의 방법입니다.

Back to top

작동 원리

표준 .asmx 웹 서비스를 가져와 브라우저에서 클라이언트측 JavaScript를 통해 거의 변경되지 않은 상태로 액세스할 수 있다는 사실이 처음에는 놀랍게 느껴질 수 있습니다. 이러한 기능은 모든 ASP.NET AJAX 지원 웹 사이트의 구성 파일에 추가된 새로운 .asmx HTTP 처리기가 등록되면서 가능해졌습니다.

<httpHandlers>
  <remove verb="*" path="*.asmx"/>
  <add verb="*" path="*.asmx" 
       type="Microsoft.Web.Services.ScriptHandlerFactory" 
       validate="false"/>
</httpHandlers>

새로 등록된 이 처리기는 .asmx 끝점에 대한 표준 웹 서비스 요청이 전달되면 표준 웹 서비스 처리기(System.Web.Services.Protocols.WebServiceHandlerFactory)를 호출합니다. 그러나 요청의 URL 뒷부분에 /js 옵션이 있거나 mn= 변수를 사용한 쿼리 문자열(예: ?mn=GetStockQuote)이 포함되어 있으면 처리기는 웹 서비스에 사용할 클라이언트측 프록시를 생성하는 JavaScript를 반환(/js 옵션을 사용한 경우)하거나 웹 서비스 파생 클래스에 정의된 해당 메서드를 호출하고 JSON(JavaScript Object Notation)으로 인코딩된 문자열로 응답을 패키징(?mn= 옵션을 사용한 경우)합니다.

페이지에 ScriptManager 컨트롤의 ServiceReference 요소를 통한 클라이언트측 .asmx 서비스 참조가 있으면 .asmx 파일을 참조하는 스크립트 요소가 후행 /js 옵션과 함께 추가되어 클라이언트에 프록시가 생성됩니다. 예를 들어 앞서 필자가 작성한 주식 시세 페이지는 다음과 같은 스크립트 요소가 추가된 상태로 렌더링됩니다.

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

물론 이 프록시와 상호 작용하는 데 필요한 클라이언트측 기능이 포함된 Microsoft AJAX 라이브러리에도 스크립트 참조가 추가됩니다. 이 끝점을 직접 탐색하려 하면 다음과 같은 JavaScript (일부 생략)가 나타납니다.

Type.registerNamespace('MsdnMagazine');
MsdnMagazine.StockQuoteService=function() {
  this._timeout = 0;
  this._userContext = null;
  this._succeeded = null;
  this._failed = null;
}
MsdnMagazine.StockQuoteService.prototype={
GetStockQuote:Sys.Net._WebMethod._createProxyMethod(this,
     "GetStockQuote", 
     "MsdnMagazine.StockQuoteService.GetStockQuote",
     "symbol"), ...
}

이 JavaScript는 ScriptManager 컨트롤이 포함된 모든 페이지에 추가되어 있는 Microsoft AJAX 라이브러리의 기능(네임스페이스, WebMethod 클래스 등)을 사용합니다. 이 JavaScript에 의해 생성된 프록시 메서드는 ?mn=GetStockQuote라는 쿼리 문자열을 사용하여 .asmx 끝점을 호출하도록 초기화되므로 클라이언트에서 MsdnMagazine.StockQuoteService.GetStockQuote를 호출할 때마다 동일한 .asmx 끝점에 대한 비동기 웹 요청으로 바뀝니다. 이렇게 클라이언트측 프록시 생성 기능과 서버측의 JavaScript로 시작되는 웹 서비스 호출 지원 기능이 결합됨으로써 직관적인 방식으로 .asmx 웹 서비스에 클라이언트측 호출을 포함할 수 있게 되었습니다.

Back to top

Serialization

AJAX 기반 웹 서비스는 기본적으로 JSON으로 serialize됩니다. 실행 시에 마지막 섹션의 페이지 추적 내용에는 웹 서비스 요청 및 응답의 본문이 다음과 같이 표시됩니다.

Request: {"symbol":"ABC"}
Response: 51

이는 기존에 .asmx 웹 서비스를 호출할 때 표시되었던 익숙한 표준 XML 형식이 아닙니다. .asmx 끝점이 XML로 serialize되도록 작성되었기 때문에 ASP.NET 2.0 AJAX Extensions에 JSON Serializer가 추가된 것입니다. JSON Serializer는 두 가지가 있습니다. 그 중 하나는 클라이언트용으로 JavaScript에 구현되어 있고 다른 하나는 서버용으로 .NET에 구현되어 있습니다. 이 서버용은 특히 AJAX 클라이언트가 .asmx 끝점을 호출하는 경우에 많이 사용됩니다. 서버측 Serializer는 Microsoft.Web.Script.Serialization.JavaScriptSerializer 클래스를 통해 사용할 수 있으며 클라이언트측 Serializer는 Sys.Serialization.JavaScriptSerializer를 통해 사용할 수 있습니다. XML 대신 JSON을 Serialization 형식으로 사용하는 데 따른 가장 큰 이점은 JSON 문자열을 평가하는 방법으로 간단히 JavaScript의 개체를 deserialize할 수 있다는 점입니다. 따라서 오류 검사가 제거되어 클라이언트 serializer 클래스의 deserialize 메서드가 매우 짧아집니다.

Sys.Serialization.JavaScriptSerializer.deserialize=
    function(){eval('('+data+')');}

반면 JavaScriptSerializer의 serialize 메서드는 상대적으로 사용 빈도가 높습니다. JSON 형식을 사용하는 데 따른 또 다른 이점으로는 XML 형식에 비해 상대적으로 간략하게 표현할 수 있다는 점입니다.

표준 웹 서비스에 XmlSerializer를 사용하여 다른 형식을 XML로 serialize하는 경우와 마찬가지로 JavaScriptSerializer를 사용하여 거의 모든 .NET 형식을 JSON으로 serialize할 수 있습니다. JavaScriptSerializer 클래스의 Serialize 메서드만 호출하면 손쉽게 사용해 볼 수 있습니다. 그림 4에는 그림 5의 복잡한 Person 형식을 serialize하는 샘플 콘솔 응용 프로그램이 나와 있습니다. 이 응용 프로그램의 경우 GAC(전역 어셈블리 캐시)에 ASP.NET AJAX Extensions과 함께 설치된 Microsoft.Web.Extensions.dll에 대한 참조를 포함해야 합니다.

이 콘솔 응용 프로그램의 출력은 JSON 형식의 Person 클래스 또는 다음이 됩니다.

{"Married":true,"Age":33,"FirstName":"Bob","LastName":"Smith"}

XmlSerializer와 마찬가지로 JavaScriptSerializer는 공개적으로 액세스할 수 있는 특정 형식의 데이터만 serialize하며, 순환 참조 확인 기능은 지원되지 않습니다. 그러나 DataSet를 비롯하여 표준 .asmx 웹 서비스로 serialize할 수 있는 모든 형식은 이 serializer에서도 문제 없이 serialize할 수 있습니다. 이 serializer를 사용하면 .asmx 파일에 정의된 SOAP 기반 웹 서비스와 마찬가지로 손쉽게 처리할 수 있으므로 복잡한 웹 서비스도 쉽게 작성할 수 있습니다.

그림 6의 예제는 앞에서 정의된 Person 개체의 특성을 적절히 수정하기 위한 Marry 메서드를 구현하는 MarriageService라는 웹 서비스를 보여 줍니다. 해당 ASP.NET 페이지는 이번 호의 코드 다운로드 파일에 포함되어 있습니다.

원한다면 클라이언트측 스크립트에 XML 형식을 사용할 수도 있습니다. 웹 서비스를 정의할 때 사용되는 WebMethod 특성 외에 Microsoft.Web.Script.Services 네임스페이스에 ScriptMethod라는 특성이 새로 추가되었습니다. 이 특성에는 Json(기본값) 또는 Xml로 설정할 수 있는 ResponseFormat 속성이 있습니다.

namespace PS
{
    [ScriptService] 
    [WebService(Namespace = "http://pluralsight.com/ws")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class StockQuoteService : WebService
    {
        [WebMethod]
        public int GetStockQuote(string symbol)
        {
            return (new Random()).Next(0, 120);
        }
    }
}
serialize된 응답은 다음과 같습니다.
<?xml version="1.0" encoding="utf-8"?><int>74</int>

클라이언트측 JavaScript에서 XML 응답을 처리하기 위해 이 메서드를 호출하는 시점은 개발자가 결정할 수 있습니다. 이는 XML 응답을 변환할 계획이거나 이미 MSXML을 사용하고 있는 경우에 유용합니다.

Back to top

결론

ASP.NET 2.0 AJAX Extensions가 클라이언트측 코드에서 특정 ASP.NET 2.0 서비스를 실행하는 미리 작성된 ProfileService 및 AuthenicationService 서비스를 제공한다는 점은 중요한 장점입니다. 이 두 가지 클라이언트측 프록시 클래스를 사용하면 개별 클라이언트의 프로필 값을 설정 및 검색하고 기본 구성원 자격 공급자를 통해 인증을 수행하고 전적으로 클라이언트 스크립트에서 인증 쿠키를 허용할 수 있습니다.

ASP.NET 2.0 AJAX Extensions에 대한 대부분의 논의나 예제가 응답성이 뛰어난 사용자 인터페이스를 구현하는 화려한 컨트롤에 중점을 두고 있지만 가장 인상적이고 유용한 기능은 클라이언트측 JavaScript에서 웹 서비스를 직접 호출하는 기능입니다. 정식 .NET Framework/JSON Serializer를 사용하는 경우 익숙한 .asmx 웹 서비스에 직접 통합될 뿐만 아니라 일괄 처리, 자동 생성된 외부 웹 서비스 브리지 등 광범위하고 심도 있는 웹 서비스 지원 기능이 제공되기 때문입니다..

+ Recent posts