Fritz Onion
DevelopMentor
적용 대상 :
Microsoft ASP.NET
요약 : 3차원 이상인 계층적 성격의 데이터 원본에 ASP.NET 데이터 바인딩을 수행하는 기술에 대해 알아봅니다.
HierarchicalDataBindingSample.msi를 다운로드하십시오.
목차
소개
데이터 바인딩
계층적 데이터
계층적 데이터베이스 데이터에 바인딩
XML 데이터에 바인딩
중첩된 컨트롤 액세스
DataGrid 및 DataList 계층적 바인딩
제한 및 효율성
결론
소개
ASP.NET은 데이터를 서버 쪽 컨트롤에 바인딩하기 위한 편리한 아키텍처를 제공하며 이러한 컨트롤은 설계된 표시 형식에 상관없이 클라이언트에 렌더링됩니다. ASP.NET에서 데이터 바인딩 예제 대부분은 데이터베이스에 대한 쿼리의 결과로 얻어진 플랫 데이터 소스에 대한 바인딩 예제입니다. 대부분의 응용 프로그램에서 이 유형의 데이터 바인딩이 가장 일반적이기는 하지만 간단한 2차원 공간에 데이터가 적합하지 않고 표준 데이터 바인딩 기술로 충분하지 않은 경우가 있습니다.
이 기사는 3차원 이상인 계층적 성격의 데이터 소스에 데이터 바인딩을 수행하는 기술에 대해 설명합니다.
데이터 바인딩
ASP.NET의 데이터 바인딩은 서버의 데이터를 서버 쪽 컨트롤로 바인딩하는 프로세스로 이때 해당 데이터를 특정 형태로 클라이언트에 렌더링합니다. 데이터 바인딩에는 서버 쪽 컨트롤이 DataSource
속성과 DataBind()
메서드를 지원하고 컨트롤이 바인딩되는 데이터 소스가 IEnumerable
인터페이스를 구현해야 한다는 유일한 제약 조건이 따릅니다.
참고 이 제약 조건에는 DataSet
및 DataTable
이라는 주목할 만한 예외가 있습니다. 둘 다 직접 바인딩될 수 있으므로 기본 테이블의 기본 DataView
에 바인딩하며 DataView
는 IEnumerable
을 구현합니다. DataSet
와 DataTable
은 흔히 데이터 바인딩에서 데이터 원본으로 사용되므로 이러한 특징은 사용의 편리함을 위한 것입니다.
데이터를 컨트롤에 바인딩하려면 데이터 소스를 컨트롤의 DataSource
속성에 할당하고 해당 DataBind()
메서드를 호출합니다.
예를 들어 ArrayList
에 Item
클래스의 전체 인스턴스를 반환하는 다음 데이터 소스가 있다고 가정합니다.
public class Item
{
private string _name;
public Item(string name) { _name = name; }
public string Name { get { return _name; } }
}
public class TestDataSource
{
public static ArrayList GetData()
{
ArrayList items = new ArrayList();
for (int i=0; i<10; i++)
{
Item item = new Item("item" + i.ToString());
items.Add(item);
}
return items;
}
}
ArrayList
가 IEnumerable
을 구현하므로 TestDataSource
클래스의 GetData()
메서드 결과는 바인딩을 위한 유효한 데이터 소스입니다. 데이터를 바인딩할 서버 쪽 컨트롤로 Repeater
를 사용하며 이때 열거할 수 있는 데이터 소스의 각 항목을 렌더링하는 방법을 설명하는 ItemTemplate
을 제공해야 합니다. 아래 샘플은 해당 텍스트가 바인딩 대상인 Item
클래스 인스턴스의 Name
속성으로 설정된 CheckBox
컨트롤을 렌더링합니다.
<asp:Repeater Runat="server" ID="_itemsRepeater"
EnableViewState="false">
<ItemTemplate>
<asp:CheckBox Runat="server"
Text='<%# DataBinder.Eval(Container.DataItem, "Name") %>'
/>
<br/>
</ItemTemplate>
</asp:Repeater>
이제 마지막으로 Repeater
에 대해 실제 데이터 바인딩을 수행합니다. 이 작업은 일반적으로 다음과 같이 Page
파생 클래스의 Load
처리기에서 수행됩니다.
private void Page_Load(object sender, EventArgs e)
{
_itemsRepeater.DataSource = TestDataSource.GetData();
_itemsRepeater.DataBind();
}
다음은 이 페이지의 샘플 렌더링을 보여 줍니다.
그림 1. 데이터 바인딩 페이지
계층적 데이터
위에서 살펴본 첫 번째 데이터 소스 샘플은 데이터 수준이 하나뿐인 플랫입니다. 이제 다음과 같이 데이터 소스의 각 항목에 하위 항목 컬렉션을 추가한다고 가정합니다.
public class Item
{
string _name;
ArrayList _subItems = new ArrayList();
public Item(string name) { _name = name; }
public string Name { get { return _name; } }
public ArrayList SubItems { get { return _subItems; } }
}
이어서 다음과 같이 인위적인 데이터 소스 채우기 메서드인 GetData
에서 각 항목에 5개의 하위 항목을 추가합니다.
public class TestDataSource
{
public static ArrayList GetData()
{
ArrayList items = new ArrayList();
for (int i=0; i<10; i++)
{
Item item = new Item("item" + i.ToString());
for (int j=0; j<5; j++)
{
Item subItem = new Item("subitem" + j.ToString());
item.SubItems.Add(subItem);
}
items.Add(item);
}
return items;
}
}
이제 데이터 구조는 최상위 항목 컬렉션 아래에 하나의 수준이 있는 계층입니다. 이전에 수행했던 데이터 바인딩이 계속 제대로 작동하지만 렌더링 시에 하위 항목을 무시하여 첫 번째 데이터 수준만 표시합니다. 모든 데이터를 적절히 표시하기 위해 이 지점에서 각 항목의 하위 항목에서 중첩된 데이터 바인딩을 수행해야 합니다. 논리적으로 기존 Repeater
의 ItemTemplate
에 데이터 바인딩된 다른 컨트롤을 배치하여 최상위 Repeater
에 의해 나열된 각 Item
의 SubItems
컬렉션에 바인딩해야 한다는 의미입니다. 중첩된 Repeater
를 추가하여 .aspx 파일에서 선언적으로 이 작업을 수행할 수 있습니다. 중첩된 Repeater
의 DataSource
속성에 바인딩되는 Item
의 SubItems
컬렉션을 올바르게 매핑하는 부분이 유일하게 까다로운 작업입니다. 이 작업을 수행하려면 다음과 같이 중첩된 Repeater
의 DataSource
속성을 데이터 바인딩 식으로 선언적으로 설정하여 결과적으로 SubItems
컬렉션이 되게 합니다.
<asp:Repeater Runat="server" ID="_itemsRepeater"
EnableViewState="false">
<ItemTemplate>
<asp:CheckBox Runat="server"
Text='<%# DataBinder.Eval(Container.DataItem, "Name")
%>'
/>
<asp:Repeater Runat="server" ID="_subitemsRepeater"
EnableViewState="false"
DataSource=
'<%# DataBinder.Eval(Container.DataItem, "SubItems") %>'>
<ItemTemplate>
<br/>???
<asp:CheckBox Runat="server"
Text=
'<%# DataBinder.Eval(Container.DataItem, "Name") %>'
/>
</ItemTemplate>
</asp:Repeater>
<br/>
</ItemTemplate>
</asp:Repeater>
데이터 소스가 이미 최상위 Repeater
에 바인딩되었으므로 코드 숨김 클래스를 변경할 필요가 없습니다. 최상위 컬렉션에서 Item
당 한 번씩 데이터 바인딩이 중첩됩니다. 이렇게 데이터 바인딩된 중첩 컨트롤 쌍을 읽을 때는 가장 근접한 컨트롤이 데이터 바인딩 식(<%# %>
)의 범위를 지정한다는 점에 유의해야 합니다. 이 예제에서 처음 두 개의 데이터 바인딩 식은 최상위 Repeater
의 외부 데이터 바인딩으로 범위가 지정되며 최상위 컬렉션의 현재 항목을 확인합니다. 세 번째 데이터 바인딩 식은 내부 Repeater
로 범위가 지정되며 바인딩된 현재 Item
의 SubItems
컬렉션에 있는 요소를 확인합니다. 다음은 이 페이지의 렌더링을 보여 줍니다.
그림 2. Repeater 샘플에 대한 데이터 바인딩을 포함한 페이지의 렌더링
이 중첩된 데이터 바인딩은 한 수준으로만 제한되지 않고 임의의 수준으로 확장될 수 있습니다. 데이터 바인딩된 컨트롤의 중첩이 데이터 소스의 컬렉션 중첩과 일치하고 데이터 소스 형태가 규칙적이면 바인딩이 작동합니다. 예를 들어 데이터 수준을 하나 더 포함하도록 데이터 소스를 확장하여 기존 SubItems
컬렉션의 각 Item
에 고유한 SubItems
컬렉션을 제공할 수 있습니다.
public class TestDataSource
{
public static ArrayList GetData()
{
ArrayList items = new ArrayList();
for (int i=0; i<10; i++)
{
Item item = new Item("item" + i.ToString());
for (int j=0; j<5; j++)
{
Item subItem = new Item("subitem" + j.ToString());
item.SubItems.Add(subItem);
for (int k=0; k<4; k++)
{
Item subsubItem =
new Item("subsubitem" + k.ToString());
subItem.SubItems.Add(subsubItem);
}
}
items.Add(item);
}
return items;
}
}
이번에도 DataSource
속성이 두 번째 수준 Repeater
에 의해 현재 열거되는 항목의 SubItems
속성에 바인딩되는 중첩 Repeater
를 하나 더 추가하기만 하면 중첩된 새 데이터를 표시할 수 있습니다.
<asp:Repeater Runat="server" ID="_itemsRepeater"
EnableViewState="false">
<ItemTemplate>
<asp:CheckBox Runat="server"
Text='<%# DataBinder.Eval(Container.DataItem, "Name") %>'
/>
<asp:Repeater Runat="server" ID="_subitemsRepeater"
EnableViewState="false"
DataSource=
'<%# DataBinder.Eval(Container.DataItem, "SubItems") %>'
>
<ItemTemplate>
<br/>???
<asp:CheckBox Runat="server"
Text=
'<%# DataBinder.Eval(Container.DataItem, "Name")
%>'/>
<asp:Repeater Runat="server" EnableViewState="false"
DataSource=
'<%# DataBinder.Eval(Container.DataItem, "SubItems")
%>'>
<ItemTemplate>
<br/>??????
<asp:CheckBox Runat="server"
Text=
'<%# DataBinder.Eval(Container.DataItem, "Name") %>'
/>
</ItemTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
<br />
</ItemTemplate>
</asp:Repeater>
다음은 이 페이지의 렌더링 일부를 보여 줍니다.
그림 3. 중첩된 추가 Repeater를 보여 주는 페이지
계층적 데이터베이스 데이터에 바인딩
계층적 데이터 바인딩을 수행하는 방법에 대한 기본 사항을 이해했으므로 좀 더 실제적인 응용 방법을 살펴볼 차례입니다. 데이터 바인딩을 수행하면 대부분 데이터베이스 쿼리 결과에 대한 바인딩도 수행되므로 이번에는 데이터베이스에서 검색한 계층적 데이터를 사용합니다. 계층적 데이터는 일반적으로 테이블 간의 일대다 관계를 사용하여 관계형 데이터베이스에 저장됩니다. 예를 들어 기본 SQL Server 및 Microsoft Access 설치에서 사용할 수 있는 샘플 Northwind 데이터베이스에는 Customers
테이블과 Orders
테이블 간의 일대다 관계가 존재합니다. 마찬가지로 Orders
테이블과 Order Details
테이블 간에 일대다 관계가 존재합니다. 다음 그림에서는 이러한 관계를 보여 줍니다.
그림 4. 테이블 관계
이 데이터를 쿼리할 수 있는 여러 가지 방법이 있지만 세 테이블 모두의 내용을 DataSet
로 가져오고 DataSet
에서 관계를 정의하는 기능을 사용하여 데이트를 계층적으로 추출하면 가장 간단하게 데이터베이스 왕복 수를 1회로 줄일 수 있습니다. 다음 Load
처리기는 이 작업을 수행한 다음 결과 DataSet
를 ID가 _customerRepeater
인 Repeater에 바인딩합니다.
private void Page_Load(object sender, EventArgs e)
{
string strConn =
"server=.;trusted_connection=yes;database=northwind";
string strSql = "SELECT CustomerID, CompanyName FROM " +
" Customers; " +
"SELECT OrderID, CustomerID, " +
" EmployeeID FROM Orders;" +
"SELECT OrderID, Products.ProductID," +
"ProductName, Products.UnitPrice FROM" +
" [Order Details], Products WHERE " +
" [Order Details].ProductID = " +
"Products.ProductID";
SqlConnection conn = new SqlConnection(strConn);
SqlDataAdapter da = new SqlDataAdapter(strSql, conn);
da.TableMappings.Add("Customers1", "Orders");
da.TableMappings.Add("Customers2", "OrderDetails");
_ds = new DataSet();
da.Fill(_ds, "Customers");
_ds.Relations.Add("Customer_Order",
_ds.Tables["Customers"].Columns["CustomerID"],
_ds.Tables["Orders"].Columns["CustomerID"]);
_ds.Relations[0].Nested = true;
_ds.Relations.Add("Order_OrderDetail",
_ds.Tables["Orders"].Columns["OrderID"],
_ds.Tables["OrderDetails"].Columns["OrderID"]);
_ds.Relations[1].Nested = true;
_customerRepeater.DataSource = _ds.Tables["Customers"];
_customerRepeater.DataBind();
}
데이터가 DataSet
에 로드되면 설정된 관계를 사용하여 데이터를 계층적으로 탐색할 수 있습니다. 예를 들어 Customers
테이블에 있는 행이 DataSet
에 입력된 경우 "Customer_Order
" 문자열로 GetChildRows()
를 호출하여 해당 고객과 관련된 Orders
테이블에서 행 컬렉션을 검색할 수 있습니다. 마찬가지로 Orders
테이블의 행에서 "Order_OrderDetail
" 문자열로 GetChildRows
를 호출하여 주어진 주문과 관련된 모든 Order Detail
항목을 검색하면 해당 주문과 관련된 모든 Order Detail
항목을 찾을 수 있습니다. DataRowView
클래스의 CreateChildView
메서드를 사용하면 더 효과적입니다. 이 메서드는 특정 관계의 모든 행을 표시하는 DataView
를 반환합니다.
이제 데이터를 바인딩할 준비가 되었으므로 알맞은 수준으로 중첩되어 데이터 바인딩된 컨트롤을 추가해 데이터를 렌더링해야 합니다. 사용자 지정 데이터 구조를 사용하는 이전 예제와 거의 마찬가지로 여기서 바인딩할 데이터도 두 수준으로 중첩된 데이터입니다. 즉, 각 하위 수준의 데이터를 렌더링하려면 두 개의 중첩된 컨트롤이 필요합니다. 좀더 구체적으로 설명하자면 DataSet
의 Customers
테이블에 바인딩하기 위한 하나의 최상위 Repeater
, 각 고객과 연관된 모든 Orders
에 바인딩하기 위한 하나의 중첩된 Repeater
, 각 주문과 연관된 모든 Order Detail
항목에 바인딩하기 위한 또 다른 중첩된 Repeater
가 필요합니다. 중첩된 두 Repeater
의 DataSource
는 부모 행에서 적절한 관계 이름으로 CreateChildView
를 호출한 결과가 됩니다. Repeater
선언의 단일 식에서 DataView
를 만드는 대신 관계 이름과 부모 행을 가져오고 DataView
를 반환하는 코드 숨김 클래스에서 함수를 정의하는 것이 편리합니다.
protected DataView GetChildRelation(object dataItem,
string relation)
{
DataRowView drv = dataItem as DataRowView;
if (drv != null)
return drv.CreateChildView(relation);
else
return null;
}
해당 함수와 데이터 소스가 적절히 배치된 경우 .aspx 파일에서 구분선과 공백을 사용하는 간단한 레이아웃의 Repeater
컨트롤 선언을 작성할 수 있습니다.
<asp:Repeater Runat="server" ID="_customerRepeater"
EnableViewState="false">
<ItemTemplate>
Customer:
<%# DataBinder.Eval(Container.DataItem, "CustomerID") %>
<%# DataBinder.Eval(Container.DataItem,"CompanyName") %>
<br />
<asp:Repeater runat="server" EnableViewState="false"
DataSource=
'<%# GetChildRelation(Container.DataItem,
"Customer_Order")%>'
>
<itemTemplate>
Orderid:<b>
<%#DataBinder.Eval(Container.DataItem, "OrderID")%>
</b><br/>
<asp:Repeater runat="server" EnableViewState="false"
DataSource=
'<%# GetChildRelation(Container.DataItem,
"Order_OrderDetail")%>'
>
<itemTemplate>
<b><%# DataBinder.Eval(Container.DataItem,
"ProductName") %></b>
$<%# DataBinder.Eval(Container.DataItem,
"UnitPrice") %> <br/>
</itemTemplate>
</asp:Repeater>
</itemTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
XML 데이터에 바인딩
오늘날 대부분의 시스템에서 사용되는 대표적인 계층적 데이터 형식이 XML이라는 점에서 계층적 데이터를 설명할 때 XML을 빼 놓을 수 없습니다. ASP.NET에는 서버 컨트롤을 XML 데이터에 바인딩하기 위한 몇 가지 옵션이 있습니다. 그 중 하나는 XML 데이터를 DataSet
로 읽어 들인 다음 앞에서 설명한 기술을 사용하는 것이고, 또 하나는 .NET의 XML API를 사용하여 데이터를 직접 로드한 다음 로드한 데이터에서 열거할 수 있는 클래스에 대해 바인딩하는 것입니다. 가장 흥미로운 마지막 옵션은 XML 문서에 XSL 변환을 적용하여 스스로 렌더링하는 특수 Xml
웹 컨트롤을 사용하는 것입니다.
XmlDocument
클래스는 .NET에서 XML DOM의 구현을 제공하며 데이터 바인딩을 지원하는 컨트롤에 대해 바인딩하기 위해 직접 사용할 수 있습니다. XmlDocument
에서 DOM을 탐색하는 데 사용되는 기본 클래스는 문서의 요소를 나타내는 XmlNode
입니다. 다행히 XmlNode
클래스는 IEnumerable
을 구현하여 자식 위의 열거자를 반환하므로 임의의 XmlNode
를 데이터 바인딩의 데이터 소스로 사용할 수 있습니다. 또한 문서가 실제로 자식을 가진 단일 노드에 불과하여 XmlDocument
가 XmlNode
에서도 파생되므로 탐색이 매우 용이합니다. 예를 들어 다음 XML 문서가 'publishers.xml'에 저장되었다고 가정합니다.
<publishers>
<publisher name="New Moon Books" city="Boston"
state="MA" country="USA">
<author name="Albert Ringer ">
<title name="Is Anger the Enemy?" />
<title name="Life Without Fear" />
</author>
<author name="John White ">
<title name="Prolonged Data Deprivation " />
</author>
<author name="Charlene Locksley ">
<title name="Emotional Security: A New Algorithm" />
</author>
<author name="Marjorie Green ">
<title name="You Can Combat Computer Stress!" />
</author>
</publisher>
<publisher name="Binnet and Hardley" city="Washington"
state="DC" country="USA">
<author name="Sylvia Panteley ">
<title name="Onions, Leeks, and Garlic" />
</author>
<author name="Burt Gringlesby ">
<title name="Sushi, Anyone?" />
</author>
<author name="Innes del Castillo ">
<title name="Silicon Valley Gastronomic Treats" />
</author>
<author name="Michel DeFrance ">
<title name="The Gourmet Microwave" />
</author>
<author name="Livia Karsen ">
<title name="Computer Phobic AND Non-Phobic" />
</author>
</publisher>
<!-- ... -->
</publishers>
다음과 같이 페이지의 Load
처리기에서 이 파일을 XmlDocument
클래스로 로드하고 최상위 publishers
요소를 Repeater에 바인딩할 수 있습니다.
private void Page_Load(object sender, EventArgs e)
{
XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath("~/Publishers.xml"));
_rep1.DataSource = doc.FirstChild;
_rep1.DataBind();
}
이제 XML 문서에서 데이터를 추출하여 클라이언트로 렌더링하기 위해 필요한 중첩 Repeater
를 작성하는 방법을 알아보아야 합니다. 앞의 두 예제를 바탕으로 이 데이터를 거의 같은 방식으로 모델링할 수 있습니다. 문서에 세 개 수준의 데이터(게시자, 작성자 및 제목)가 있을 경우 게시자 Repeater
안에 작성자 Repeater
가 중첩되고 작성자 Repeater
안에 제목 Repeater
가 중첩된 세 개의 Repeater
컨트롤을 정의합니다. 이 배열은 다음과 같습니다.
<asp:Repeater id="_rep1" runat="server"
EnableViewState="false">
<itemTemplate>
Publisher: <%# ((XmlNode)Container.DataItem).
Attributes["name"].Value %><br/>
<asp:Repeater runat="server" EnableViewState="false"
DataSource='<%# Container.DataItem %>' >
<itemTemplate>
??Author: <%#
((XmlNode)Container.DataItem)
.Attributes["name"].Value
%><br/>
<asp:Repeater runat="server" EnableViewState="false"
DataSource='<%# Container.DataItem %>' >
<itemTemplate>
????<i>
<%# ((XmlNode)Container.DataItem).
Attributes["name"].Value %>
</i><br />
</itemTemplate>
</asp:Repeater>
</itemTemplate>
</asp:Repeater>
<hr />
</itemTemplate>
</asp:Repeater>
위 샘플은 다음과 같이 렌더링됩니다.
그림 5. 데이터 바인딩 테스트
XML 데이터에 바인딩하는 프로세스는 앞의 두 예제와 전혀 다릅니다. 먼저 선언적 DataSource
식이 매우 간단하다는 점에 주의하십시오(Container.DataItem
). 이는 데이터 바인드의 각 수준에 있는 데이터 소스가 단순히 자식 위의 열거자를 구현하는 XmlNode
이기 때문입니다. 또한 현재 데이터 항목에서 데이터를 추출하기 위해 Container.DataItem
을 XmlNode
로 캐스팅하여 해당 특성을 추출해야 한다는 점에 주의하십시오. 사용하기에 편리한 DataBinder.Eval()
메서드는 XML 소스가 아니라 데이터베이스 소스에서 작동하므로 이 경우에는 대개 소용이 없습니다.
일반적으로 데이터 바인딩 컨트롤을 사용하여 임의의 XML 데이터를 바인딩하는 것은 다소 번거로운 작업입니다. 앞의 예제에서는 데이터베이스 테이블 집합에서 추출한 데이터를 사용하기 때문에 매우 규칙적이고 구조가 잘 짜여져 데이터 구조와 일치하는 중첩된 컨트롤 집합을 정의할 수 있습니다. 데이터가 불규칙하거나 계층적이지 않을 경우에는 이 작업이 어려워집니다. 예를 들어 다음 XML 문서를 가정합니다.
<animals>
<animal>
<name>Dog</name>
<sound>woof</sound>
<hasHair>true</hasHair>
</animal>
<animal>
<name>Cat</name>
<sound>meow</sound>
<hasHair>true</hasHair>
</animal>
<animal>
<name>Pig</name>
<sound>oink</sound>
<hasHair>false</hasHair>
</animal>
</animals>
앞의 예제와 동일한 기술을 사용할 경우 최상위 Repeater 한 개를 정의하여 다른 중첩된 Repeater와 함께 각 animal 요소를 열거해 animal의 각 하위 요소를 표시할 수 있습니다.
<asp:Repeater ID="_animalRep" Runat="server"
EnableViewState="false">
<ItemTemplate>
<asp:Repeater Runat="server" EnableViewState="false"
DataSource='<%# Container.DataItem %>' >
<ItemTemplate>
<%# ((XmlNode)Container.DataItem).InnerText %><br
/>
</ItemTemplate>
</asp:Repeater>
<hr />
</ItemTemplate>
</asp:Repeater>
그러나 여기서는 요소 이름을 전혀 사용하지 않고 각 자식 노드의 내용만 렌더링하므로 자연스러운 방법이 아닙니다. 요소가 이름일 경우에는 특정 방법으로 렌더링하고 요소가 소리일 경우에는 다른 방법으로 렌더링하도록 Repeater에 쉽게 지시할 수 있는 방법은 없습니다. 대신 많은 조건식을 작성하여 원하는 방법으로 XML을 렌더링합니다.
이때 ASP.NET의 데이터 바인딩 컨트롤은 임의의 XML 문서에 바인딩되지 않는다는 점을 기억해야 합니다. 대신 기존 XML 변환 언어 XSL을 사용하여 XML을 렌더링하는 것이 훨씬 더 편리합니다. ASP.NET을 사용하면 XML 컨트롤을 통해 이 작업을 편리하게 수행할 수 있으며 페이지 일부분에 대해서도 마찬가지입니다. ASP.NET은 XML 문서와 XSL 변환을 입력으로 가져오며 변환을 문서에 적용하여 렌더링합니다. animal XML 문서의 경우 다음과 같은 animal.xsl을 작성할 수 있습니다.
<xsl:transform
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="animal">
<hr />
<xsl:apply-templates />
</xsl:template>
<xsl:template match="name">
<i><xsl:value-of select="." /></i><br/>
</xsl:template>
<xsl:template match="sound">
<b><xsl:value-of select="." />!!!</b><br/>
</xsl:template>
<xsl:template match="hasHair">
Has hair? <xsl:value-of select="." /><br/>
</xsl:template>
</xsl:transform>
그런 다음 페이지에서 다음과 같이 XML 컨트롤에 대한 입력을 지정할 수 있습니다.
<asp:Xml Runat="server"
DocumentSource="animals.xml"
TransformSource="animals.xsl" />
그러면 다음과 같이 렌더링됩니다.
그림 6. animals.xsl의 렌더링
중첩된 컨트롤 액세스
지금까지의 예제에서는 사용자의 데이터를 수집하는 작업 없이 데이터 표시에만 초점을 맞추었습니다. 계층적으로 바인딩되어 여러 수준으로 중첩된 컨트롤에서 데이터를 검색할 경우 동적으로 만들어진 컨트롤 계층을 탐색하고 이러한 컨트롤 상태를 검색해야 하므로 상당히 번거로울 수 있습니다. 데이터 바인딩된 컨트롤에 포함된 컨트롤에 변경 알림 처리기를 추가하는 옵션을 사용하는 것이 더 편리합니다. 알림 처리기가 발생하면 컨트롤과 연결된 데이터를 추출할 수 있습니다.
이 기술을 보여 주기 위해 Repeater
를 사용자 지정 데이터에 바인딩하고 각 항목과 하위 항목에 대한 확인란을 렌더링하는 첫 번째 예제를 사용할 수 있습니다. 사용자가 확인란 중 하나를 선택하고 페이지를 제출할 경우 선택되었다는 사실이 페이지 아래쪽에서 Label
에 간단하게 인쇄됩니다. 이 경우의 .aspx 파일은 다음과 같습니다.
<asp:Repeater Runat="server" ID="_itemsRepeater"
EnableViewState="False">
<ItemTemplate>
<asp:CheckBox Runat="server"
Text='<%# DataBinder.Eval(Container.DataItem,
"Name") %>'
OnCheckedChanged="OnCheckedItem" />
<asp:Repeater Runat="server" ID="_subitemsRepeater"
EnableViewState="False"
DataSource='<%# DataBinder.Eval(Container.DataItem,
"SubItems") %>'>
<ItemTemplate>
<br/>???
<asp:CheckBox Runat="server"
Text='<%# DataBinder.Eval(Container.DataItem,
"Name") %>'
OnCheckedChanged="OnCheckedItem" />
<asp:Repeater Runat="server" EnableViewState="False"
DataSource='<%#
DataBinder.Eval(Container.DataItem,
"SubItems") %>'>
<ItemTemplate>
<br/>??????
<asp:CheckBox Runat="server"
Text='<%# DataBinder.Eval(Container.DataItem,
"Name") %>'
OnCheckedChanged="OnCheckedItem"/>
</ItemTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
<br />
</ItemTemplate>
</asp:Repeater>
<asp:Button Runat="server" Text="Submit" />
<asp:Label EnableViewState="False" Runat="server"
ID="_message" />
이제 클라이언트에서 페이지를 게시할 때마다 선택한 상태에서 선택하지 않은 상태로 변경되거나 그 반대로 변경된 각 항목에 대해 OnCheckedItem
처리기가 한 번씩 호출됩니다. 이벤트 처리기에 대한 발신자 매개 변수를 확인하면 클라이언트에서 변경한 컨트롤을 확인할 수 있습니다. 확인란 상태가 변경되었음을 나타내는 메시지를 페이지에 기록하는 처기리 샘플 구현은 다음과 같습니다.
protected void OnCheckedItem(object sender, EventArgs e)
{
CheckBox cb = sender as CheckBox;
if (cb.Checked)
_message.Text += string.Format("{0} was checked<br
/>",
cb.Text);
else
_message.Text += string.Format("{0} was
unchecked<br/>",
cb.Text);
}
DataGrid 및 DataList 계층적 바인딩
지금까지의 예제에서는 간단한 설명을 위해 Repeater
컨트롤에 초점을 맞추었습니다. 그러나 DataList
및 DataGrid
컨트롤을 둘 다 사용하여 계층적 데이터 바인딩을 수행할 수 있습니다. 실제로 데이터의 바인딩 세부 사항은 사용되는 컨트롤에 상관없이 동일합니다. 예를 들어 중첩된 DataList
와 함께 Repeater
를 사용함으로써 이러한 세부 사항을 혼합 및 일치시킬 수 있습니다. 이 기사의 코드 샘플에는 DataList
및 DataGrid
클래스에 Repeater
를 사용하는 것을 보여 주는 샘플이 포함되어 있습니다.
DataList
클래스는 테이블의 각 셀이 결과 집합의 행 렌더링이 되는 테이블을 렌더링합니다. DataList
를 사용하여 계층적으로 바인딩하면 최상위 컨트롤의 셀이 중첩된 컨트롤에 의해 렌더링되는 전체 테이블을 포함하는 중첩된 테이블 렌더링이 됩니다. 아래 샘플은 세 개 수준의 계층적 데이터 바인딩을 사용하는 Northwind 데이터로 채워진 DataSet
를 DataList
에 렌더링합니다. 이 경우 최상위 셀 하나만 표시됩니다.
그림 7. Northwind 데이터로 채워진 DataSet
DataGrid
클래스는 테이블의 각 행이 결과 집합의 행 렌더링이 되는 테이블을 렌더링합니다. DataGrid
를 사용하여 계층적으로 바인딩해도 중첩된 테이블 렌더링이 되지만 DataList
와 달리 사용자가 직접 해당 열을 템플릿 열로 만들고 중첩된 DataGrid
를 템플릿 열 정의의 일부로 추가하여 중첩된 테이블이 포함되는 셀을 결정합니다. 아래 샘플은 세 개 수준의 계층적 데이터 바인딩을 사용하는 Northwind 데이터로 채워진 DataSet
를 DataGrid
에 렌더링합니다. 이 경우 최상위 행 하나만 표시됩니다.
그림 8. Northwind 데이터로 채워진 DataSet
제한 및 효율성
ASP.NET의 데이터 바인딩 메커니즘은 플랫 데이터 소스를 바인딩하기 위해 설계되었으며 이 메커니즘을 사용하여 계층적 바인딩을 수행할 수는 있지만 본래 계층적 데이터를 렌더링하는 최선의 방법이 아닐 수 있다는 점에 주의해야 합니다. 데이터 소스의 형태는 규칙적이어야 합니다. 예를 들어 어떤 위치에서는 수준이 두 개이고 다른 위치에서는 네 개나 다섯 개인 데이터 소스에는 바인딩할 수 없습니다. XSL은 계층적 형태가 불규칙한 데이터를 렌더링하는 데 적합하기 때문에 데이터를 XML로 변환하고 XSL 변환을 ASP XML 컨트롤과 함께 사용하는 것이 최선의 옵션입니다.
이 기사의 모든 샘플에서는 데이터 바인딩된 각 컨트롤의 EnableViewState
플래그가 false로 설정되어 있습니다. ViewState
는 같은 페이지에 대한 여러 POST 요청 간의 컨트롤 대신 상태를 저장하는 데 사용됩니다. 기본적으로 ASP.NET의 모든 서버 쪽 컨트롤은 POST 요청 간의 모든 상태를 보유하므로 모든 컨트롤의 상태를 보존할 수 있어 편리합니다. 그러나 페이지의 렌더링 크기가 현저히 증가할 수도 있으므로 상태 보존 기능을 사용하지 않을 경우 이러한 컨트롤에 대해 ViewState
를 해제해야 합니다. 기본적으로 데이터 바인딩된 각 컨트롤과 모든 자식 컨트롤의 전체 내용이 ViewState
에 저장되므로 이 기술을 사용할 경우 ViewState
가 지나치게 증가하기 쉽습니다. 페이지에 대한 첫 번째 GET 요청인지 아니면 동일한 페이지에 대한 후속 POST인지 여부에 상관없이 데이터 바인딩된 컨트롤이 각 요청과 함께 데이터 소스에 다시 바인딩되므로 대부분의 경우 이 ViewState
의 내용은 전혀 사용되지 않습니다. 따라서 특별한 이유가 없을 경우 데이터 바인딩된 모든 컨트롤에서 EnableViewState
를 flase로 설정하는 것이 바람직하며 특히 이 문서에 설명된 계층적 데이터 바인딩 기술을 사용하는 경우에 더욱 그렇습니다. Repeater
예제에 있는 중첩된 CheckBox
컨트롤의 경우와 마찬가지로 데이터 바인딩된 컨트롤의 ItemTemplate
안에 중첩된 서버 쪽 컨트롤에는 ViewState
를 사용하는 것이 좋습니다. 단, 데이터 바인딩된 실제 컨트롤 자체에서는 사용하지 않아야 합니다. 단적인 예로 DataSet
에 바인딩되는 샘플의 모든 Repeater
컨트롤에 뷰 상태를 사용하면 ViewState
필드가 250,000자까지 증가합니다. 페이지로 렌더링되어 표시되는 문자 수가 100,000자 단위인 것과 대조적입니다.
결론
이 기사에서는 템플릿이 지정되고 데이터 바인딩된 컨트롤을 중첩하여 데이터 소스를 동적으로 할당하여 계층적 데이터에 바인딩하는 기술을 보여 줍니다. 이 기술은 데이터베이스의 테이블 관계에 존재하는 데이터와 같이 규칙적이고 구조화된 계층적 데이터를 렌더링하는 데 매우 유용합니다. 이 기술을 사용하여 다른 계층적 데이터 소스를 렌더링할 수도 있지만 데이터가 모든 면에서 규칙적이지 않을 경우에는 번거로운 작업입니다. 이 경우에는 대개 XML 데이터 소스와 함께 XSL을 사용하는 것이 더 간단하며 렌더링을 세부적으로 제어할 수 있습니다.