[maso:저 자:유경상 ]풍성한 ASP.NET 개발환경의 이해 | | | [ASP.NET] |
ASP.NET 정식 버전이 등장한지 1년여가 지났다. ASP.NET은 기존 버전이라 할 수 있는 ASP에 비해 매우 다른 접근 방식을 취하고 있다. 기존 ASP 4.0은 HTML 태그 사이사이에 ASP 코드가 삽입되어 있는 형태로서 CGI나 ISAPI에 비해 개발 생산성을 높여 준다는 장점을 등에 업고 성공했다. 시간이 지나감에 따라 ASP 코드와 HTML이 뒤범벅돼 있는 페이지가 양산되기 시작했고 이는 웹 사이트의 유지 보수와 가독성을 떨어뜨리는 요인으로 작용했다. ASP.NET은 새로운 모델을 제시했다. 소위 웹폼(WebForm) 모델은 웹 페이지를 VB 6.0과 같은 폼 디자인 형태로 개발을 가능하게 해줄 뿐더러 이벤트에 기반한 페이지 제어 모델을 제공한다. 웹폼 모델의 핵심은 ASP.NET의 System.Web.UI.Page 클래스와 다양한 웹 컨트롤이라고 할 수 있다.
웹 컨트롤은 ASP.NET을 처음 배우는 개발자에게 웹 프로그래밍이 쉽다는 느낌을 주지만 어느 정도 경험이 쌓이면 한계로 다가온다. 처음에는 다양한 기능을 제공하는 웹 컨트롤이 도움을 주는 듯 느끼지만 다양한 웹 컨트롤을 사용하다 보면 웹 컨트롤의 제약사항에 빠지기도 하고 무분별한 웹 컨트롤의 사용으로 인해 웹 사이트의 성능이 크게 떨어지는 것을 경험하곤 한다. 웹 컨트롤은 어떻게 사용하는가에 따라 ASP.NET 개발자에게 축복으로 다가오기도 하고 재앙으로 다가오기도 한다는 점을 명심하자.
대표 선수, 데이터그리드
ASP.NET에서 기본적으로 제공하는 웹 컨트롤 중 가장 다양하고 막강한 기능을 가진 컨트롤을 뽑으라면 단연 데이터그리드(DataGrid) 컨트롤을 꼽을 것이다. 데이터 바인딩은 물론이요, 페이징, 정렬, 편집, 추가, 삭제 등의 기능을 갖고 있다. ASP.NET을 처음 접하는 개발자는 데이터그리드의 기능에 놀라며 그 현란함에 감탄하기 마련이다. 그러나 막상 데이터그리드를 실제 사이트에 사용하려고 하면 다양한 문제에 부딪히곤 한다. 데이터그리드의 막강한 기능들은 그 대가가 있다. 첫째로 데이터그리드는 매우 큰 ViewState를 생산한다. 이는 웹 페이지가 생성하는 HTML의 크기가 커짐을 의미하며 이는 곧 웹 페이지의 처리 속도 저하를 가져온다. 그래서 ViewState를 disable시키면, 데이터그리드의 막강한 기능들인 페이징, 정렬 등의 기능은 사용할 수 없게 되어 버린다. 데이터그리드의 편집/추가/삭제 기능은 사실상 많이 사용되지 않기 때문에 차치하더라도 페이징과 정렬을 사용할 수 없게 되면 상당한 양의 코딩을 개발자가 추가로 해야 한다는 부담이 생기곤 한다.
데이터그리드 구조
왜 ViewState를 disable하면 데이터그리드의 기능들이 작동하지 않을까? 이 질문에 대답하기 위해서는 데이터그리드의 구조에 대해 이해할 필요가 있다. 데이터그리드의 구조가 다양한 기능으로 인해 매우 복잡한 것처럼 보이지만 알고 보면 그다지 복잡하다고 볼 수만은 없다. 텍스트 박스나 리스트 박스와 같은 단순 컨트롤의 경우 구조라고 할 것도 없이 간단하지만 데이터그리드는 여러 컨트롤로 구성된 합성 컨트롤(Composite Control)로서 컨트롤이 계층 구조를 이루고 있다. 데이터그리드의 컨트롤 계층 구조를 알아보는 가장 쉬운 방법은 페이지의 트레이스(Trace) 속성을 True로 설정하는 것이다. Trace가 활성화되면 <화면 1>과 같은 결과를 얻을 수 있으며 구조를 파악하는 데 도움이 된다. 데이터그리드의 일반적인 컨트롤 계층 구조는 <그림 1>과 같다.
<화면 1> ASP.NET 트레이스 기능으로 살펴본 데이터그리드 컨트롤 계층 구조
<그림 1>에서 주의할 사항은 이 그림이 컨트롤의 계층 구조를 나타내고 있다는 점이다. 컨트롤이라 함은 System.Web.UI.Control 클래스에서 파생된 웹 컨트롤을 의미하므로 <그림 1>의 구조는 데이터그리드를 구성하는 웹 컨트롤의 구조라는 것이다. 컨트롤이 아닌 클래스의 관점에서 보면 데이터그리드는 <그림 1>에 나타난 웹 컨트롤 클래스 외에도 DataGridColumn, BoundColumn 등의 컬럼 관련 클래스들, 데이터그리드의 외양에 많은 영향을 미치는 스타일 관련 클래스들과 관련이 있다. 컬럼 관련 클래스에 대해서는 조금 후에 언급하기로 하고 컨트롤 계층 구조에 대해 좀더 상세히 살펴보자.
데이터그리드 컨트롤의 자식 컨트롤은 DataGridTable 하나뿐이다(자손이 아닌 자식임에 유의). DataGridTable 컨트롤은 HTML
태그에 대응되는 컨트롤로서 직접 데이터를 표시하거나 다른 컨트롤의 부모 역할을 한다. DataGridItem이 페이저라면 TableCell은 자식 컨트롤로서 다수의 LinkButton 컨트롤과 Literal 컨트롤을 갖으며 DataGridItem이 헤더이고 정렬이 enable되어 있다면 TableCell은 몇 개의 LinkButton 컨트롤을 자식 컨트롤로 보유하게 된다. TableCell이 어떤 자식 컨트롤을 갖는가는 DataGridItem이 어떤 종류인가에 영향을 받지만 해당 컬럼(DataGridColumn)이 어떤 컬럼인가 그리고 현재 DataGridItem이 편집 상태에 있는가에도 영향을 받는다. 대개의 경우 컬럼은 데이터바인딩을 수행하는 단순한 BoundColumn이다. BoundColumn인 경우 TableCell 컨트롤은 편집 상태가 아닌 경우에는 자식 컨트롤을 갖지 않고 TableCell 컨트롤의 Text 속성에 데이터 값이 표현된다. 편집 상태라면 TableCell은 TextBox 컨트롤을 자식 컨트롤로 갖으며 이 컨트롤의 Text 속성에 데이터가 표시된다. 비슷하게 TableCell의 컬럼이 ButtonColumn 타입이라면 LinkButton 컨트롤 혹은 Button 컨트롤이 TableCell의 자식이 되며 데이터 역시 이 자식 컨트롤에 표시된다. 이외에도 TableCell의 컬럼이 TemplateColumn이라면 템플릿이 지정하는 서버 컨트롤이 TableCell의 자식으로 설정되며, 데이터바인딩 역시 템플릿이 지정하는 데이터바인딩 식에 의해 표현된다. |
DataGridColumn 클래스
DataGridColumn 클래스는 데이터그리드의 컬럼 정보를 갖는 클래스로서 중요한 역할을 수행한다. 이 클래스는 추상(abstract) 클래스로서 인스턴스를 가질 수 없다. 따라서 실제 데이터그리드의 컬럼은 DataGridColumn에서 파생된 BoundColumn, ButtonColumn, EditCommandColumn, HyperLinkColumn, TemplateColumn 클래스 중 하나이다. 이들 클래스는 모두 웹 컨트롤이 아니다. 즉, System.Web.UI.Control 클래스에서 파생된 것이 아니라는 것이다. 하지만 이들 클래스들은 각 TableCell 컨트롤이 어떤 자식 컨트롤을 가질 것인가를 결정한다.
데이터그리드는 Columns 컬렉션에 컬럼들의 리스트를 보유하게 된다. Columns 컬렉션에 컬럼을 추가하는 것은 디자인 타임이나 런타임에 가능하다. 디자인 타임에 설정되는 컬럼들은 aspx 파일내의 ASP.NET 태그를 통해 수행될 수 있으며, 이 태그들은 Columns 컬렉션에 DataGridColumn에서 파생된 컬럼 객체를 추가하는 코드로 변환된다. AutoGenerateColumn 속성 값이 True라면 데이터바인딩이 수행될 때 데이터소스의 컬럼 정보를 통해 BoundColumn 클래스의 인스턴스가 생성된다. 주의할 점은 AutoGenerateColumn 속성에 의해 생성된 BoundColumn 클래스는 Columns 컬렉션을 통해 액세스할 수 없다는 점이다.
DataGridColumn 클래스의 중요한 메쏘드는 InitializeCell 메쏘드이다. 이 메쏘드는 protected virtual 메쏘드로서 데이터그리드가 DataGridItem의 인스턴스를 만든 후, 데이터그리드에 설정된 각 컬럼(AutoGenerateColumn에 의해 생성된 컬럼에 대해서도)에 대해 호출한다. InitializeCell 메쏘드는 매개변수로 초기화할 TableCell 객체, 컬럼의 인덱스, DataGridItem의 타입을 매개변수로 취하며 이들에 의해 적절히 TableCell 객체를 초기화한다. 이 메쏘드에서 TableCell의 자식 컨트롤을 생성하기도 하며, 보다 중요한 것은 데이터바인딩을 수행할 DataBind 이벤트의 이벤트 핸들러를 설정한다는 점이다.
<리스트 1> BoundColumn.InitiaizeCell 메쏘드
public virtual void InitializeCell(TableCell cell, int columnIndex, ListItemType itemType)
{
Control childControl;
Control bindingControl;
TextBox textBox;
base.InitializeCell(cell, columnIndex, itemType);
childControl = null;
bindingControl = null;
switch (itemType) {
case ListItemType.Item:
case ListItemType.AlternatingItem:
case ListItemType.SelectedItem:
if (this.DataField.Length == 0)
break;
bindingControl = cell;
break;
case ListItemType.EditItem:
if (this.ReadOnly)
goto case ListItemType.SelectedItem;
textBox = new TextBox();
childControl = textBox;
if (this.boundField.Length != 0)
bindingControl = textBox;
break;
}
if (childControl != null)
cell.Controls.Add(childControl);
if (bindingControl != null)
bindingControl.DataBinding += new EventHandler(this, OnDataBindColumn);
}
<리스트 1>은 BoundColumn 클래스의 InitiailzeCell 메쏘드이다. 이 메쏘드를 살펴보면 현재 TableCell이 속한 DataGridItem의 ItemType에 의거하여 TableCell을 초기화하는 작업을 담고 있다. DataGridItem이 EditItem이라면 자식 컨트롤로서 TextBox를 생성한다는 점과 데이터 바인딩 이벤트를 이 클래스의 OnDataBindColum 메쏘드로 설정한다는 점을 유심히 살펴보자. 데이터 바인딩이 일어나면 TableCell의 데이터 바인딩 메쏘드인 DataBind가 호출될 것이며 TableCell의 DataBind 메쏘드는 DataBind 이벤트를 발생하고 자식 컨트롤의 DataBind를 호출될 것이다. 따라서 자식 컨트롤의 존재 여부와 관계없이 데이터 바인딩은 BoundColumn 클래스의 OnDataBindColumn 메쏘드 내부에서 이뤄진다는 말이다.
TableCell 컨트롤이나 그 자식 컨트롤은 데이터소스가 어떤 것인지 그리고 데이터소스 내에서 어떤 컬럼에 바인딩될 것인지 전혀 알지 못하므로 데이터 바인딩을 수행할 수 있는 객체는 BoundColumn 객체가 되는 것은 매우 자연스럽다고 할 수 있다. BoundColumn은 바인딩할 데이터 필드(DataField 속성)의 이름을 알고 있으며 DataGridItem 컨트롤의 DataItem 속성은 바인딩할 데이터의 행(row)을 나타내고 있으므로(DataBind 이벤트의 sender 매개변수를 통해 DataGriItem 컨트롤이 전달될 것이다) 데이터 바인딩을 수행할 수 있는 것이다.
DataGridColumn 클래스와 그 자식 클래스는 TableCell과 밀접한 관계를 가지고 있으며 데이터 바인딩에서도 중요한 역할을 수행함을 알 수 있을 것이다. 필요에 따라 DataGridColumn 클래스를 상속하여 커스텀 컬럼을 만들 수도 있다. 몇몇 필수적인 메쏘드와 프로퍼티를 구현하기만 하면 원하는 HTML을 렌더링하도록 구성이 가능할 것이다. 이에 대한 상세한 내용은 이 글의 범위에서 벗어나므로 생략하기로 한다.
그 외의 클래스들
지금까지 설명한 컨트롤과 클래스들 외에도 데이터그리드와 연관된 클래스들은 데이터그리드 클래스의 베이스 클래스인 BaseDataList 클래스, 데이터그리드의 각종 스타일에 대한 TableItemStyle 클래스, 페이징에 대한 추상화를 제공하는 PagedDataSource 클래스, DataGridItem과 DataGridColumn에 대한 컬렉션을 제공하는 DataGridItemCollection 클래스, DataGridColumnCollection 클래스 등의 클래스가 있다. 이들 클래스들은 데이터그리드가 작동하는데 직간접적으로 관여한다. 특히 BaseDataList 클래스는 데이터리스트 컨트롤과 데이터그리드 컨트롤에 대한 베이스 클래스로서 데이터바인딩을 지원하는 리스트 컨트롤의 공통적인 인터페이스와 구현을 제공한다.
데이터그리드 컨트롤에 대한 분석을 하고자 한다면 시작점은 BaseDataList가 돼야 할 것이다. PagedDataSource 클래스는 데이터그리드의 데이터소스 속성에 설정된 데이터 소스에 대한 페이징 뷰를 제공한다. 예를 들어 데이터소스가 30개의 레코드를 갖고 PageSize가 10인 상태에서 AllowPagaing이 True라면 PagedDataSource는 현재 페이지의 10개 레코드만을 반환하고 AllowPagaing이 False라면 현재 페이지에 관계 없이 전체 30개의 레코드를 반환한다. PagedDataSource 클래스의 이러한 데이터소스 추상화는 데이터그리드 컨트롤이 AllowPaging 속성에 관계없이 단일 코드 플로우(flow)를 갖도록 해준다. 그 외의 클래스는 데이터그리드의 외양을 제어하고 컬렉션을 구현하는 데 이용된다.
데이터그리드 제어 흐름
데이터그리드의 구조를 살펴봤으니 이제 제어의 흐름을 살펴보자. 데이터그리드도 역시 일반적인 웹 컨트롤의 일종이므로 웹 컨트롤의 일반 제어 흐름을 따른다. 웹 컨트롤의 일반적인 제어 흐름과 데이터그리드의 구체적인 작업은 <표 1>과 같다. 데이터그리드의 전체 제어 흐름을 설명하기에는 그 분량이 너무 많으므로 핵심적인 몇몇 사항과 주의해야 할 사항에 대해서만 언급하기로 한다.
<표 1> 웹 컨트롤의 일반적인 제어 흐름과 데이터그리드의 행동 | ||
일반적인 제어 흐름 | 컨트롤 생성 관련 초기화 | 일반적인 수행 내용 |
Initialize | ViewState에 기록된 속성 값으로 속성들이 초기화됨. | 초기화. 속성들은 초기 값 가짐. <asp:DataGrid> 태그의 속성 값들 설정됨. |
Load ViewState | Post된 폼 데이터를 처리 IPostBackEventHandler 인터페이스를 구현하는 컨트롤만이 해당됨. | IsPostBack이 True인 경우, ViewState로부터 컨트롤 계층 구조를 만듦(<그림 1> 참조). IsPostBack이 False라면 아무런 작업 없음. |
Process Post Data | 하고, 그에 알맞게 속성을 업데이트. | N/A Post된 데이터는 모두 자식 컨트롤로 라우팅됨. 데이터그리드는 IPostBackDataHandler 인터페이스를 구현하지 않음 |
Load | Load 이벤트 처리. | N/A |
Raise Change Event | Post된 폼 데이터에 의해 변경사항을 이벤트로 알림. (ex) Change 이벤트 류 | 자식 컨트롤의 변경 이벤트가 이 단계에서 발생함. |
Raise PostBack Event(Click Event) | Post 이벤트 류의 처리. (ex) LinkButton의 Click 이벤트 IPostBackDataHandler 인터페이스를 구현하는 컨트롤만이 해당됨. | 데이터그리드는 IPostBackEventHandler 이벤트를 구현하지 않음. 자식 컨트롤이 이벤트를 버블링(Bubbling)함. 버블링의 결과로 PageIndexChanged, SortCommand, EditCommand, SelectedIndexChanged 등의 이벤트 발생함. |
PreRender | PreRender 이벤트 처리 | 헤더, 풋터, 페이저, 데이터 아이템 등에 대해 스타일 등을 적용. |
Save ViewState | 상태 저장 | 아이템 갯수, 컬럼 정보, 스타일 정보 등을 ViewState에 기록. 표시된 데이터는 데이터그리드가 저장하지 않고 자식 컨트롤에게 위임함. |
Rendering | HTML 등의 태그 생성 | HTML 태그 생성 |
Disposing | 리소스 해제 | N/A. 자식 컨트롤에게 위임 |
Unload | Unload 이벤트 | N/A |
<화면 2> 테스트에 사용된 웹 페이지
첫 번째 테스트는 ViewState를 enable시켜 놓고 테스트했으며 두 번째 테스트는 ViewState를 disable시켜 놓았다. 테스트 결과는 <표 2>와 같다. 서버가 초당 처리하는 페이지 갯수는 ViewState를 disable했을 때 약 70% 정도 더 많이 처리했으며 응답 속도는 80% 정도 더 빨랐다. 이로써 데이터그리드 컨트롤의 ViewState가 enable되어 있을 때의 문제점이 명확해졌다. 비록 데이터베이스를 액세스하는 부분이 병목되어 이러한 ViewState의 오버헤드가 잘 나타나지 않기도 하지만 말이다. 그렇다고 무조건 ViewState를 disable할 수도 없다. ViewState가 disable되면 페이징, 정렬 등의 기능을 사용할 수 없기 때문이다.
<표 2> ViewState 활성화에 따른 성능 측정 결과 | ||
구분 | ViewState Enabled | ViewState Disabled |
Content Length | 7832바이트 | 3554 바이트 |
Request Per Second | 95.69RPS | 161.94RPS |
Response Time | 84.81msec | 46.69msec |
데이터그리드의 확장
이번 연재에서는 웹 컨트롤의 대표 주자(?) 격인 데이터그리드 컨트롤의 구조와 작동 방식 등을 살펴봤다. 그리고 데이터그리드의 어떤 부분이 문제인가를 살펴보고 관련된 테스트도 수행해 보았다. 이제 남은 것은 지금까지 살펴본 내용을 바탕으로 문제점을 해결하는 것이다.
다음 연재에서는 ViewState를 disable한 상태에서도 페이징과 정렬 등의 기능을 사용할 수 있도록 데이터그리드 컨트롤을 확장해 볼 것이다. 이 컨트롤은 DataGridEx라 부를 것이며, 기존 웹 컨트롤을 바탕으로 이를 확장하는 일반적인 방법을 먼저 살펴본 후에 DataGridEx를 구현해 볼 것이다. 이번 연재에서 살펴봤던 문제점을 제거하기 위한 방법을 살펴보고 이 방법에 대한 장단점 역시 살펴볼 것이다. 미리 예습을 해보고 싶은 독자가 있다면 참고자료에 나타낸 MSDN 라이브러리의 항목이나 관련 서적을 살펴보길 바란다. 다음 연재를 기대하는 독자가 있기를 바라며...
정리 | 박은정 whoami@korea.cnet.com
'ASP.NET' 카테고리의 다른 글
multipart/form-data 타입을 이용한 파일 전송지원 클래스 작성 (0) | 2008.02.12 |
---|---|
세션 하이재킹 시도를 저지 (0) | 2008.02.07 |
responseText 속성을 이용한 단순 문자열 다루기 (0) | 2007.12.27 |
ASP.NET의 캐시 구성 개체 만들기 (1) | 2007.10.17 |
ASP에서 ASP+로 마이그레이션 하기 (1) | 2007.10.17 |