Dino Esposito
Wintellect

적용 대상
   Microsoft ASP.NET
   Microsoft ASP.NET 2.0


요약: 상속을 통해 Page 클래스 등의 공통 Microsoft ASP.NET 클래스에 기능을 추가할 수 있습니다. 그러면 공통 장소에 기능을 추가하고 이를 모든 페이지에서 사용하도록 할 수 있습니다. 이 기사에서 Dino는 페이지 새로 고침 처리, 긴 프로세스 지원 및 포커스 제어를 Page 클래스에 추가합니다

이 기사의 소스 코드를 다운로드하십시오.

목차

더욱 탄탄한 기초 만들기
브라우저 새로 고침 트래핑
페이지 새로 고침 이벤트 소비
긴 작업을 하는 동안 사용자를 지루하지 않게 하기
컨트롤 포커스
결론


모든 Microsoft ASP.NET 페이지의 출처는 System.Web.UI.Page 클래스로 대표되는 공용 루트 페이지입니다. ASP.NET 런타임은 .aspx 리소스에 대한 요청을 처리하기 위해 동적 클래스를 빌드하며, 이 동적 클래스가 기본 Page 클래스로부터 상속받거나 기본 클래스로부터 상속받는 또 다른 클래스로부터 상속받도록 만듭니다. 코드 숨김 모델을 지원하는 Microsoft Visual Studio .NET 2003 프로젝트 내에서 페이지를 만드는 경우, 동적으로 만들어진 Page 클래스는 기본 페이지에서 상속받는 코드 숨김 클래스로부터 상속받습니다(이 기사에는 영문 페이지 링크가 포함되어 있습니다).


기본 Page 클래스는 일반적인 ASP.NET 페이지 주기(로드-포스트백(postback)-렌더링 주기)를 구현하며 다시 게시 감지, 스크립트 주입, 렌더링, viewstate 관리 등 다수의 미리 정의된 멤버 및 기능을 포함하는 파생 페이지를 제공합니다.


마지막으로 System.Web.UI.Page 클래스는 공통적인 최소의 기능 및 동작 집합을 정의하는 기본 클래스일 뿐입니다. 응용 프로그램에 따라 페이지가 무리없이 더 많은 내용을 목표로 하거나 더 풍부한 프로그래밍 인터페이스를 제공할 수도 있습니다. 확장의 유형에는 두 가지가 있습니다. 하나는 페이지 인프라에 대한 일반 목적의 향상이고 다른 하나는 응용 프로그램 관련 기능입니다. 전자의 예 중 하나는 페이지 메뉴와 해당 항목을 나타내는 속성입니다. 응용 프로그램 관련 페이지는 일반적으로 정적 및 공통 영역과 사용자 지정 가능 영역으로 구성된 논리적 "마스터" 페이지부터 시작합니다. 영역의 내용은 페이지 기준에 따라 다를 수 있으며 일반적으로 템플릿, 자리 표시자 및 사용자 컨트롤로 채워집니다.


Microsoft ASP.NET 2.0에서 마스터 페이지를 도입함에 따라 사용자 지정 및 프로그래밍 가능 속성과 멤버를 통해 응용 프로그램 고유 페이지를 빌드하는 작업이 크게 단순화되었습니다.

좀 더 탄탄하고 복합적인 인프라를 페이지에 사용하고 싶으십니까? F5(새로 고침) 키를 인식할 수 있는 기능과 같은 추가적인 시스템 수준 기능을 모든 페이지에서 제공하도록 하려면 어떻게 해야 할 것 같습니까? 특정 내용을 구현하는 데 필요한 코드를 개별 페이지의 연산 코드와 언제든지 병합할 수 있습니다. 연산 코드는 코드 숨김 클래스에 압축되어 있습니다. 그러나 두세 가지 기능만 구현되어 있는 경우에도 결과 코드의 품질은 악명 높은 스파게티 코드와 거의 흡사해집니다. 따라서 다른 방법을 찾아봐야 합니다.

더욱 탄탄한 기초 만들기

더 좋은 방법은 새로운 기본 Page 클래스를 빌드하여 표준 System.Web.UI.Page 클래스 대신 사용하는 것입니다. 이 기사에서는 몇 가지 기능과 이들의 일반적인 구현을 선택하여 더욱 풍부한 새 Page 클래스로 묶어 보겠습니다. 사용할 기능은 다음과 같습니다.

  • F5(새로 고침) 키 트래핑 기능
  • 사용자에게 중간 피드백 페이지를 보내야 하는 긴 작업을 시작하고 제어하는 기능
  • 페이지를 로드할 때 입력 포커스를 설정하는 기능

ASP.NET 전용 뉴스 그룹 및 커뮤니티 사이트를 자주 방문하며 기사, 책자, 회보를 유심히 읽어 보는 사람이라면 위에서 설명한 각각의 기능을 ASP.NET 1.x 응용 프로그램의 컨텍스트에 개별적으로 구현하는 방법을 이미 알고 계실 것입니다. 여기에서 문제는 이 모든 기능을 고유한 구성 요소 및 단일 진입점을 통해 사용할 수 있도록 하는 것입니다.


사용자 지정 Page 클래스를 정의하면 최소한의 노력으로 새 .aspx 페이지에서 모든 추가 기능 및 서비스를 사용하여 최대한의 효과를 얻을 수 있습니다. Visual Studio .NET 2003으로 만든 코드 숨김 페이지는 다음과 같이 선언됩니다.

public class WebForm1 : System.Web.UI.Page
{
   :
}

웹 양식 클래스가 기본값이 아닌 Page 클래스로부터 상속받도록 하려면 다음과 같이 기본 유형을 변경하기만 하면 됩니다.

public class WebForm1 : Msdn.Page
{
   :
}

Visual Studio .NET 2003 이외의 응용 프로그램에서 페이지를 만들거나 인라인 코드를 사용하는 경우에는 @Page 지시문에서 Inherits 특성을 통해 기본 유형을 설정합니다.

<% @Page Inherits="Msdn.Page" ... %>

여기에서 설명한 대로 ASP.NET 페이지의 기본 유형은 페이지 기준으로 변경하거나 구성 파일의 <pages> 노드를 사용하여 변경할 수 있습니다.

<pages pageBaseType="Msdn.Page" />

<pages> 노드의 pageBaseType 특성은 동적으로 만들어진 모든 Page 클래스의 기본 유형으로 사용할 클래스의 이름 및 어셈블리를 나타냅니다. 기본적으로 이 특성은 machine.config 파일에서 System.Web.UI.Page로 설정되어 있으며 응용 프로그램의 web.config 파일에서 설정을 재정의할 수 있습니다.


이제는 앞에서 나열한 각각의 기능이 실제로 어떻게 구현되고 포괄적인 클래스에 어떻게 캡슐화되는지 살펴 보겠습니다.

브라우저 새로 고침 트래핑

몇 달 전 aspnetPRO Magazine  에 최초로 게시한 기사에서는 사용자가 F5 브라우저 단추를 눌러 현재 페이지를 새로 고칠 때 감지해야 하는 단계를 대략적으로 설명했습니다. 페이지 새로 고침은 F5 키를 누르거나 도구 모음 단추를 클릭하는 등의 특정 사용자 동작에 대한 브라우저의 응답입니다. 페이지 새로 고침 동작은 브라우저가 이벤트 또는 콜백에 대해 외부 알림을 제공하지 않는 일종의 내부 브라우저 작업입니다. 기술적으로 설명하자면 페이지 새로 고침은 마지막 요청의 "간단한" 반복 수행입니다. 다시 말해 브라우저가 수행한 마지막 요청을 캐시하여 사용자가 페이지 새로 고침 키를 누를 때 다시 실행하는 것입니다.


제가 아는 한, 어떤 브라우저도 페이지 새로 고침 이벤트에 대해 알림을 제공하지 않으므로 ASP.NET, 일반적인 ASP 또는 ISAPI DLL 등의 서버측 코드가 새로 고침 요청을 일반적인 제출 또는 포스트백(postback)과 구별할 수 없습니다. ASP.NET에서 페이지 새로 고침을 감지하고 처리하려면 서로 다른 두 개의 동일 요청을 구별할 수 있는 보조 수단을 마련해야 합니다.


브라우저는 마지막으로 보낸 HTTP 페이로드를 다시 보내어 새로 고침을 구현합니다. 추가 서비스에 매개 변수를 추가하고 ASP.NET 페이지가 이를 감지할 수 있어야 복사본과 원본을 구별할 수 있게 됩니다. 아래 그림은 앞으로 빌드할 하위 시스템의 개략도입니다.

그림 1. 새로 고침 요청을 포스트백(postback)/제출 요청과 구분하도록 만드는 설정


세션 컨텍스트에 사용된 각 요청에는 고유한 순차적 티켓 번호가 부여됩니다. ASP.NET 페이지는 응답이 생성되기 직전에 티켓을 생성하여 브라우저에 보낸 숨겨진 사용자 지정 필드에 저장합니다. 표시된 페이지를 포스트백(postback)하도록 하는 새로운 요청을 사용자가 제출하면 숨겨진 필드가 서버에 대한 요청에 자동으로 첨부됩니다.


웹 서버에서 새 HTTP 모듈은 AcquireSessionState 이벤트를 가로채고, 숨겨진 필드에서 현재 티켓을 검색하고, 이를 내부에서 캐시되어 마지막으로 사용한 티켓 ID와 비교합니다. 마지막으로 사용한 티켓은 세션 상태에 저장됩니다. 현재 티켓이 마지막으로 사용한 ID보다 크거나 둘 다 0인 경우에는 해당 요청은 일반적인 제출 또는 포스트백(postback)입니다. 이 이외에는 새로 고침 HTTP 모듈이 평소와 동일하게 작동하며 요청을 변경하지 않고 그대로 전달합니다.


마지막으로 사용한 티켓이 현재 티켓과 같거나 이를 초과하는 경우 해당 요청은 페이지 새로 고침으로 식별됩니다. 이 경우 HTTP 모듈은 요청의 HTTP 컨텍스트에 있는 Items 컬렉션에 새 항목을 만드는 것을 제한합니다. ASP.NET에서 HttpContext 개체는 요청의 컨텍스트를 나타내며 전체 요청 주기에 걸쳐 처음부터 끝까지 함께 합니다. HttpContext 개체의 Items 속성은 HTTP 모듈, 팩토리 처리기, 처리기가 사용자 지정 정보를 실제 페이지 개체에 전달하는 데 사용할 수 있는 카고(cargo) 컬렉션입니다. Items 컬렉션에 저장된 내용은 현재 요청을 처리하는 데 참여하는 모든 구성 요소가 볼 수 있습니다. 정보 주기는 요청 주기와 동일하므로 응답이 생성될 때 모든 데이터가 삭제됩니다. HttpContext.Current 정적 속성을 사용하면 프로세스와 관련된 모든 클래스로부터 진행 중인 요청의 HTTP 컨텍스트에 액세스할 수 있습니다.


새로 고침 HTTP 모듈은 Items 컬렉션에 IsPageRefreshed라는 새 항목을 만듭니다. 항목의 부울 값은 페이지가 정규 제출/포스트백(postback)을 통해 요청되었는지 또는 새로 고침으로서 요청되었는지 여부를 나타냅니다. 아래 목록은 새로 고침 HTTP 모듈의 구현을 보여 줍니다.

using System;
using System.Web;
using System.Web.SessionState;

namespace Msdn
{
  public class RefreshModule : IHttpModule {
     // IHttpModule::Init
   public void Init(HttpApplication app)
   {
      // 파이프라인 이벤트에 등록합니다.
      app.AcquireRequestState += 
             new EventHandler(OnAcquireRequestState);
   }

   // IHttpModule::Dispose
   public void Dispose() {}

   // F5 또는 뒤로/앞으로 동작이 진행 중인지 확인합니다.
   private void OnAcquireRequestState(object sender, EventArgs e) {
      // HTTP 컨텍스트에 액세스합니다. 
      HttpApplication app = (HttpApplication) sender;
      HttpContext ctx = app.Context;

      // F5 동작을 확인합니다.
      RefreshAction.Check(ctx);
         return;
   }
   }
}

RefreshAction 클래스에는 현재 요청이 페이지 새로 고침인지 판단하는 논리가 포함되어 있습니다. 페이지 새로 고침으로 판단될 경우에는 HttpContextItems 컬렉션에 true로 설정된 IsPageRefreshed라는 새 항목이 포함됩니다.

public static void Check(HttpContext ctx)
{
   // 티켓 슬롯을 초기화합니다.
   EnsureRefreshTicket(ctx);

   // 마지막으로 사용한 티켓을 세션으로부터 읽습니다.
   int lastTicket = GetLastRefreshTicket(ctx);

   // 현재 요청의 티켓을 숨겨진 필드로부터 읽습니다.
   int thisTicket = GetCurrentRefreshTicket(ctx);

   // 티켓을 비교합니다.
   if (thisTicket > lastTicket || 
      (thisTicket==lastTicket && thisTicket==0))
   {
      UpdateLastRefreshTicket(ctx, thisTicket);
      ctx.Items[PageRefreshEntry] = false;
   }
   else
      ctx.Items[PageRefreshEntry] = true;
}

숨겨진 필드 및 세션 슬롯의 이름은 RefreshAction 클래스에서 공개 상수로 설정되며 클래스 외부에서 사용할 수 있습니다.


응용 프로그램 페이지가 이러한 메커니즘을 어떻게 활용할까요? 페이지 새로 고침을 감지할 때 이 메커니즘이 실제로 유용하고 도움이 될까요? HTTP 모듈은 요청을 차단하지 않습니다. 단지 요청을 처리하기 위해 최종 ASP.NET 페이지에 정보를 추가할 뿐입니다. 이 추가 정보에는 페이지 새로 고침을 표시하기 위한 부울 값이 포함됩니다.

페이지 새로 고침 이벤트 소비

웹 페이지 사용자가 흔히, 그리고 어느 정도는 무심코 사용하는 몇 가지 동작이 있습니다. 뒤로, 앞으로, 중지 및 새로 고침이 이러한 동작에 포함됩니다. 그러나 이러한 동작은 인터넷 브라우저에서는 일종의 표준 도구 키트를 구성합니다. 이러한 동작을 가로채고 하위 클래스를 만들 경우 일반적으로 받아들여지는 인터넷 사용 방법이 "제한"되는 느낌을 받을 수도 있습니다. 따라서 사용자에게 부정적인 영향을 미칠 수 있습니다.


반대로 사용자가 현재 페이지를 새로 고치거나 이전에 방문한 페이지로 다시 이동하는 경우에는 이전에 처리된 요청을 서버에 제출하게 됩니다. 이 요청은 응용 프로그램 상태의 일관성을 해칠 염려가 있습니다. 이 경우에도 응용 프로그램에 부정적인 영향을 미칠 수 있습니다.


다음 경우를 상상해 보시기 바랍니다.


DataGrid를 통해 데이터를 표시하고, 나타나는 데이터 행을 사용자가 삭제할 수 있도록 하는 단추를 각 행에 제공합니다. 이 방법은 실제로 흔히 사용되지만(현재 응용 프로그램에 이렇게 구현하지 않은 사람이 있다면 손 들어 보시기 바랍니다.) 매우 위험합니다. 사용자가 실수로 다른 단추를 클릭할 가능성이 높으며, 이 경우 데이터 일관성에 문제가 발생할 수 있습니다. 의도적이건 실수이건 삭제 후에 페이지를 새로 고치면 두 번째 행도 삭제될 수 있습니다.


페이지를 새로 고치면 브라우저가 마지막 게시를 반복하기만 하는 것입니다. ASP.NET 런타임의 관점에서 이는 단지 새로운 서비스 요청일 뿐입니다. ASP.NET 런타임은 정규 요청과 실수로 반복된 요청을 구별할 방법이 없습니다. 두서 없이 작업하는 편이고 메모리 내 DataSet에서 위치를 기준으로 레코드를 삭제하는 경우에는 실수로 한 레코드를 여러 번 삭제할 가능성이 큽니다. 또한 마지막 작업이 INSERT로 끝나는 경우에는 페이지를 새로 고칠 때 불필요한 페이지가 추가될 가능성이 더욱 큽니다.


이러한 예는 논란이 되는 몇 가지 디자인 문제가 명백히 있음에도 불구하고 실제로 흔히 발생할 수 있습니다. 그렇다면 페이지 새로 고침을 차단하는 가장 좋은 방법은 무엇일까요?

이 기사의 앞부분에서 논의한 메커니즘은 요청을 미리 처리하고 페이지가 새로 고쳐지고 있는 중인지 확인합니다. 이 정보는 HttpContext 개체를 통해 페이지 처리기로 파이프라인됩니다. 페이지에서 개발자는 다음 코드를 사용하여 이 데이터를 검색할 수 있습니다.

bool isRefresh = (bool) HttpContext.Current.Items["IsPageRefreshed"];

하지만 이보다 더 나은 방법은 좀 더 구체적인 사용자 지정 Page 클래스를 사용하는 경우 이 클래스를 좀 더 사용하기 쉬운 속성인 IsPageRefresh 속성으로 묶는 것입니다.

public bool IsPageRefresh {
  get {
    object o = 
      HttpContext.Current.Items[RefreshAction.PageRefreshEntry];
    if (o == null)
       return false;
    return (bool) o; 
  }
}

Page 클래스가 더욱 탄탄한 새 기본 클래스(이 예제의 경우 Msdn.Page)로부터 상속받도록 하면 새 속성을 사용하여 요청의 실제 출처를 알 수 있습니다. 다음은 페이지를 새로 고칠 때 반복해서는 안 되는 주요 작업을 구현하는 방법의 예입니다.

void AddContactButton_Click(object sender, EventArgs e) {
   if (!IsPageRefresh)
      AddContact(FName.Text, LName.Text);
   BindData();
   TrackRefreshState();
}

페이지를 새로 고치지 않는 경우에만 새 연락처가 추가됩니다. 즉, 사용자가 추가-연락처 누름 단추를 클릭할 때만 연락처가 추가됩니다. 위의 코드 조각에서 좀 생소한 TrackRefreshState 메서드는 어떤 역할을 하는 것일까요?


이 메서드는 티켓 카운터를 업데이트하며 새 페이지 응답에 숨겨진 필드가 최신 티켓과 함께 포함되도록 합니다. 이 예제의 경우 세션 상태에 저장된 값이 1 증가할 때 새 티켓이 생깁니다. 여기에서 세션 상태는 원하는 대로 사용할 수 있으며 ASP.NET 2.0에 있는 것과 같은 좀 더 확장 가능한 공급자 모델로 대체할 수도 있습니다.


그러나 TrackRefreshState(좀 더 친숙한 TrackViewState 메서드를 떠올릴 수 있도록 일부러 고른 이름)에 대해서는 한 가지 알아두어야 할 점이 있습니다. 무엇보다도 이 메서드를 호출하면 현재 요청 티켓이 있는 숨겨진 필드가 페이지 응답에 추가됩니다. 숨겨진 필드(그림 1 참조)가 없으면 새로 고침 메커니즘은 다음 포스트백(postback)이 새로 고침인지 제출인지를 알 수 없습니다. 다시 말해, 포스트백(postback) 이벤트 처리기에서 TrackRefreshState를 호출하면 페이지 새로 고침에 대한 동작만을 추적하겠다는 의사를 시스템에 전달하는 것입니다. 이 방법을 사용하면 세션 주기 동안 발생하는 모든 페이지 새로 고침이 아니라 해를 끼칠 가능성이 있는 페이지 새로 고침만 추적할 수 있습니다.


페이지 새로 고침 기능을 활용하려면 Microsoft Visual Studio .NET 프로젝트에 새 페이지를 추가하고, 코드 숨김 파일을 열고, 페이지의 기본 클래스를 Msdn.Page로 변경합니다. 그런 다음 새로 고치면 안 되는 동작을 실행할 때마다 Msdn.Page 클래스에 있는 새로운 공개 메서드인 TrackRefreshState를 호출합니다. 새 부울 속성인 IsPageRefresh를 사용하여 새로 고침 상태를 확인합니다.

긴 작업을 하는 동안 사용자를 지루하지 않게 하기

웹을 통해 시간이 많이 소요되는 작업을 처리하는 방법에 대해서는 여러 기사 및 회의에서 다양한 해결책을 이미 제공한 바 있습니다. 여기에서 "시간이 소요되는 작업"이라고 함은 Windows Forms 시나리오에서 일반적으로 진행률 표시줄이 필요한 모든 작업을 의미합니다. 웹 페이지에서는 진행 표시줄이 큰 문제를 일으킵니다. 진행률 표시줄을 사용하려면 서버와 빈번히 통신하여 눈금을 업데이트하는 데 필요한 정보를 읽어 와야 합니다. 또한 페이지 전체가 새로 고쳐져서는 안 되므로 포스트백(postback)이나 새로 고침 메타 태그를 통해 수행해서는 안됩니다. 따라서 강력한 동적 HTML 지원이 필요합니다.


긴 작업을 하는 동안 사용자를 지루하지 않게 만드는 간단한 방법은 기다려 달라는 메시지가 적힌 중간 피드백 페이지를 표시하는 것입니다. 작은 애니메이션을 표시할 수 있다면 더 좋습니다. 이 페이지는 분명 해당 상황에 적합하지 않을 수도 있지만 새 페이지를 한참 동안 로드하는데 빈 공간에 모래 시계만 달랑 있는 것보다는 낫습니다.


긴 작업이 완료될 때까지 몇 가지 피드백을 표시하는 간단하면서도 효과적인 방법은 다음 단계와 같이 요약할 수 있습니다.

  • 사용자가 작업을 시작하기 위해 클릭하면 피드백 페이지로 사용자를 리디렉션합니다. 피드백 페이지는 작업을 실제로 수행하는 페이지의 URL을 알고 있어야 합니다. 이 URL은 쿼리 문자열로 전달되거나 세션 상태를 비롯한 액세스 가능한 데이터 저장소에 배치됩니다.
  • 피드백 페이지는 로딩을 시작한 후 작업 페이지로 리디렉션됩니다. 이 경우 리디렉션은 페이지의 onload Javascript 이벤트에 있는 스크립트를 통해 수행됩니다. 브라우저는 피드백 페이지를 로드하고 표시한 다음 작업 페이지를 가리킵니다. 페이지에서 긴 작업이 수행되는 동안 사용자에게는 피드백 페이지가 표시됩니다.
  • 피드백 페이지는 원하는 대로 꾸미거나 UI를 마음껏 사용할 수 있습니다. "잠시 기다려 주십시오." 메시지를 넣거나, 애니메이션 GIF를 표시하거나, 일부 동적 HTML 기능을 사용하여 실제 진행률 표시줄처럼 보이도록 표시할 수 있습니다.

긴 작업을 쉽게 시작할 수 있도록 LengthyAction 클래스를 만들어 보았습니다.

private const string UrlFormatString = "{0}?target={1}";
public static void Start(string feedbackPageUrl, 
  string targetPageUrl)
{
   //피드백 페이지의 URL을 준비합니다.
   string url = String.Format(UrlFormatString,
      feedbackPageUrl, targetPageUrl);

   // 호출을 피드백 페이지로 리디렉션합니다.
   HttpContext.Current.Response.Redirect(url);
}

이 클래스에는 정적 메서드가 Start 하나밖에 없습니다. Start 메서드는 피드백 페이지와 대상 페이지(작업을 수행하는 페이지)의 URL을 가져와서 두 인수를 단일 URL로 결합하고 리디렉션합니다.


피드백 페이지에는 원하는 사용자 인터페이스를 마음대로 넣을 수 있지만 두 가지 중요한 조건이 있습니다. 페이지가 작업 페이지의 이름을 검색할 수 있어야 하며 스크립트를 통해 작업 페이지로 리디렉션할 수 있는 자동 메커니즘을 제공해야 합니다. 이러한 기능이 이미 내장되어 있는 사용자 지정 기본 Page 클래스를 정의했습니다. 그러면서 몇 가지 가정을 해야 했습니다. 특히 이 구현에서는 잘 알려진 특성 이름인 target을 사용하여 쿼리 문자열을 통해 작업 페이지의 이름을 전달한다고 가정했습니다. 대상 페이지의 이름은 TargetURL이라는 공개 속성에 저장됩니다. 또한 피드백 페이지는 GetAutoRedirectScript라는 함수를 제공합니다. 이 함수의 목표는 스크립트를 통한 리디렉션을 구현하는 데 필요한 스크립트 코드를 반환하는 것입니다.

public string GetAutoRedirectScript() {
   return String.Format("location.href='{0}';", TargetUrl);
}

작업을 최대한 단순화하기 위해 FeedbackBasePage 클래스는 Body라는 일반 HTML 컨트롤도 찾습니다. 이는 다음 태그에서 얻을 수 있는 내용과 정확히 동일합니다.

<body runat="server" id="Body">  

페이지의 본문 태그를 쉽게 프로그래밍할 수 있는 방법이 있으면 FeedbackBasePage 클래스가 이를 찾아서 onload 특성을 추가합니다. 그렇지 않으면 onload 특성을 수동으로 추가해야 합니다. 이러한 특성은 피드백 페이지가 작동하는 데 필요합니다.

HtmlGenericControl body = FindControl(BodyId) as HtmlGenericControl;
if (body != null)
   body.Attributes["onload"] = GetAutoRedirectScript();

브라우저에 사용되는 최종 태그 코드는 다음과 같습니다.

<body onload="location.href='lengthyop.aspx'">

이 기사에서 논의한 클래스를 사용하여 긴 작업을 이행하는 데 필요한 단계를 검토해 보겠습니다.

먼저 필요한 어셈블리를 참조한 다음 작업을 트리거하는 클릭 단추에 대한 이벤트 처리기 작성을 다음과 같이 시작합니다.

void ButtonLengthyOp_Click(object sender, EventArgs e) {
   LengthyAction.Start("feedback.aspx", "work.aspx");
}

그런 다음 프로젝트에 피드백 페이지를 추가합니다. 이 페이지는 <body> 태그를 위처럼 수정하고 기본 클래스를 FeedbackBasePage로 변경한 정규 웹 양식 페이지입니다. 클릭하여 프로세스를 시작한 후, 결과가 준비되기 전에 피드백 사용자 인터페이스가 표시됩니다. 아래 그림을 참조하십시오.


그림 2. 긴 작업의 시퀀스


이 예제에서는 특히 긴 작업에서 매우 흔한 경우인 일종의 페이지 간 포스트백(postback)을 사용했습니다. 그러나 이는 뷰 상태, 그리고 일반적으로 작업 페이지가 작업을 완료하는 데 필요한 매개 변수를 전송하는 데 문제가 있습니다. 작업 페이지의 쿼리 문자열을 사용하여 serialize된 개체 버전을 연결하거나 모든 내용을 ASP.NET 캐시 또는 Session 개체에 저장할 수 있습니다. 이 경우에는 작업이 여러 HTTP 요청으로 확산되고 각각의 항목 집합이 다르기 때문에 HTTP 컨텍스트를 사용할 수 없습니다.


피드백 페이지의 URL은 호출에 대한 세부 정보를 포함하고 있으며 다음과 유사합니다.

feedback.aspx?target=work.aspx?param1=123&param2=hello

이 세부 정보를 숨기려면 사용자 지정 HTTP 처리기를 정의하고 이를 원하는 가짜 URL에 바인딩하면 됩니다. HTTP 처리기는 캐시 또는 세션 상태로부터 피드백 및 작업 페이지의 이름을 포함한 필요한 정보를 검색합니다.

컨트롤 포커스

ASP.NET 2.0의 뛰어난 새 기능을 사용하면 페이지를 처음 표시할 때 어떤 입력 컨트롤에 포커스를 둘 것인지 지정할 수 있습니다. 이는 사용자가 데이터를 입력하기 위해 입력란을 클릭하지 않아도 되게 하는 편리한 기능입니다.


HTML 구성 요소에 입력 포커스를 할당하려면 간단한 Javascript 코드가 필요합니다. 분명히 말씀 드리겠습니다. 이 작업은 <body> 태그의 onload 특성에 인라인 코드를 추가하는 정도의 쉬운 작업입니다. 그러나 SetFocus 메서드를 Page 클래스에 넣어 서버에서 포커스를 지정할 컨트롤의 이름을 결정할 수 있다면 크게 발전한 것이라고 할 수 있습니다. 사실 ASP.NET 2.0에서는 다음 코드를 사용할 수 있습니다.

void Page_Load(object sender, System.EventArgs e) {
   SetFocus("TheFirstName");
}

페이지가 표시되면 TheFirstName이라는 입력 컨트롤에 포커스가 지정됩니다. 이 방법은 쉽고 효과적이지만 ASP.NET 1.x에서는 코드를 어떻게 작성해야 할까요?

이 기능을 구현하는 팁 역시 널리 알려져 있으며 구글에서 쉽게 검색할 수 있습니다. 문제는 이를 기본 Page 클래스에 통합하여 계속 다시 사용할 수 있도록 하는 것입니다.

다음 선언으로 Msdn.Page 기본 클래스를 확장해 보겠습니다.

private string m_focusedControl;
public void SetFocus(string ctlId) {
   m_focusedControl = ctlId;
}

 

SetFocus 메서드는 컨트롤의 ID를 수집하여 내부 멤버에 저장합니다. 페이지의 PreRender 이벤트에서는 Javascript 코드를 빌드하고 주입하는 또 다른 도우미 함수를 호출합니다.

private void AddSetFocusScript()
{
   if (m_focusedControl == "")
      return;

   // 함수를 선언하는 스크립트를 추가합니다.
   StringBuilder sb = new StringBuilder("");
   sb.Append("<script language=javascript>");
   sb.Append("function ");
   sb.Append(SetFocusFunctionName);
   sb.Append("(ctl) {");
   sb.Append("  if (document.forms[0][ctl] != null)");
   sb.Append("  {document.forms[0][ctl].focus();}");
   sb.Append("}");

   // 함수를 호출하는 스크립트를 추가합니다.
   sb.Append(SetFocusFunctionName);
   sb.Append("('");
   sb.Append(m_focusedControl);
   sb.Append("');<");
   sb.Append("/");   // 잘못 이해하지 않도록 이렇게 줄을 바꿉니다.
   sb.Append("script>");

   // 스크립트를 등록합니다. 이름은 대소문자를 구분합니다.
   if (!IsStartupScriptRegistered(SetFocusScriptName)) 
      RegisterStartupScript(SetFocusScriptName, sb.ToString());
}

Javascript 코드는 동적 문자열과 같이 구성되며 StringBuilder 개체에 누적됩니다. 다음 단계는 해당 문자열을 페이지 출력에 추가하는 것입니다. ASP.NET에서 클라이언트 스크립트 코드를 페이지에 추가하려면 특정 페이지 수준 컬렉션을 통해 등록해야 합니다. 따라서 Page 클래스에서는 몇 가지의 RegisterXxx 메서드가 사용 가능합니다. 각 RegisterXxx 메서드는 Javascript 코드의 블록을 다른 컬렉션에 추가하여 최종 페이지 태그 내에서 서로 다른 위치에 주입되도록 합니다. 예를 들어 RegisterStartupScript는 폼의 닫는 태그 직전에 코드를 주입합니다. 대신 RegisterClientScriptBlock는 폼의 여는 태그 바로 뒤에 스크립트 코드를 내보냅니다. <script> 요소의 두 태그를 스크립트에 모두 포함시키는 것이 중요합니다. 각 스크립트 블록은 키로 식별되므로 복수의 서버 컨트롤이 출력 스트림을 두 번 이상 내보내지 않고도 동일한 스크립트 블록을 사용할 수 있습니다.


다음 Javascript 코드 블록은 페이지에서 폼의 닫는 태그 바로 뒤에 삽입됩니다. 따라서 시작할 때 초기화 직후에 실행됩니다.

<form>
:
<script language=javascript>
function __setFocus(ctl) {  
   if (document.forms[0][ctl] != null) {
       document.forms[0][ctl].focus();
   }
}
__setFocus('TheFirstName');
</script>
</form>

 

Msdn.Page 클래스에서 SetFocus 공개 메서드를 사용하면 페이지 코드의 어느 지점에서나 브라우저에 페이지가 표시될 때 입력 포커스를 할당할 컨트롤을 결정할 수 있습니다. 더욱 중요한 것은 이러한 결정을 런타임 조건 및 포스트백(postback) 이벤트의 기준으로 삼을 수 있다는 점입니다.

결론

ASP.NET 같은 개체 지향 기술의 주요 장점 중 하나는 상속을 광범위하게 사용할 수 있다는 것입니다. 이제는 기존 컨트롤의 공개 인터페이스를 상속하고 향상시킴으로써 최소한의 노력으로 새로운 사용자 지정 서버 컨트롤을 만들 수 있습니다. 파생 클래스에서는 가상 메서드를 재정의할 수 있으므로 구성 요소의 내부 동작을 변경할 수 있습니다. 이러한 개체 지향 프로그래밍(OOP) 원칙을 컨트롤에 적용하는 것은 매우 자연스럽고 일반적이지만 ASP.NET 페이지에서 사용하는 클래스의 경우에는 다르다고 할 수 있습니다.


그럼에도 불구하고 요청된 각 .ASPX 페이지의 실행 파일을 표시하도록 빌드하는 데 페이지 상속이 널리 사용되고 있습니다. 코드 숨김 페이지는 기본 System.Web.UI.Page 클래스로부터 상속받는 페이지일 뿐입니다. 사용 중인 응용 프로그램 관련 페이지에 더욱 탄탄한 기초를 제공하는 중간 단계의 Page 클래스를 정의해 보는 것은 어떻겠습니까?


이 기사의 내용이 바로 그것입니다. 많은 개발자들이 어느정도 성공적으로 구현하는 인기있는 세 가지 기능을 사용해 보았습니다. 새로 고침 키 트래핑, 긴 작업 제어, 컨트롤에 입력 포커스 할당이 이 세 가지 기능입니다. 그리고 이 기능을 포괄적인 단일 Page 클래스의 컨텍스트 안에 묶었습니다.


코드 숨김 및 코드 인라인 응용 프로그램의 기본 Page 클래스 대신 사용된 새 Page 클래스, 즉 Msdn.Page 클래스는 개발자에게 더욱 기초적이며 쉽게 재사용할 수 있는 기능을 제공합니다. 기본 Page 클래스를 더욱 탄탄하게 하는 것은 ASP.NET 응용 프로그램의 향상된 플랫폼을 빌드하기 위한 중요한 단계입니다. 주요 응용 프로그램의 모든 페이지는 사용자 지정 클래스를 사용하여 빌드해야 하며 Page 클래스를 신중하게 선택해야 합니다.

관련 서적

자료출처

http://www.microsoft.com/korea/msdn/library/ko-kr/dev/aspdotnet/BedrockAspNet.aspx

+ Recent posts