작성자: 스콧 미첼
이 자습서에서는 다른 반복기 내에 중첩된 반복기를 사용하는 방법을 살펴보겠습니다. 이 예제에서는 내부 반복기를 선언적 및 프로그래밍 방식으로 채우는 방법을 보여 줍니다.
소개
템플릿에는 정적 HTML 및 데이터 바인딩 구문 외에도 웹 컨트롤 및 사용자 컨트롤이 포함될 수 있습니다. 이러한 웹 컨트롤은 선언적 데이터 바인딩 구문을 통해 해당 속성을 할당하거나 적절한 서버 쪽 이벤트 처리기에서 프로그래밍 방식으로 액세스할 수 있습니다.
템플릿 내에 컨트롤을 포함하면 모양과 사용자 환경을 사용자 지정하고 개선할 수 있습니다. 예를 들어 GridView 컨트롤 자습서의 TemplateFields 사용 자습서에서는 직원 고용 날짜를 표시하기 위해 TemplateField에 일정 컨트롤을 추가하여 GridView의 표시를 사용자 지정하는 방법을 알아보았습니다. 편집 및 삽입 인터페이스에 유효성 검사 컨트롤 추가 및데이터 수정 인터페이스 사용자 지정 자습서에서는 유효성 검사 컨트롤, TextBoxes, DropDownLists 및 기타 웹 컨트롤을 추가하여 편집 및 삽입 인터페이스를 사용자 지정하는 방법을 알아보았습니다.
템플릿에는 다른 데이터 웹 컨트롤도 포함될 수 있습니다. 즉, 우리는 템플릿 내에 다른 DataList(또는 Repeater, GridView, DetailsView 등)를 포함하는 DataList를 가질 수 있습니다. 이러한 인터페이스의 과제는 적절한 데이터를 내부 데이터 웹 컨트롤에 바인딩하는 것입니다. ObjectDataSource를 사용하는 선언적 옵션부터 프로그래밍 방식 옵션에 이르기까지 사용할 수 있는 몇 가지 방법이 있습니다.
이 자습서에서는 다른 반복기 내에 중첩된 반복기를 사용하는 방법을 살펴보겠습니다. 외부 반복기는 데이터베이스의 각 범주에 대한 항목을 포함하며 범주의 이름과 설명을 표시합니다. 각 범주 항목의 내부 반복기는 해당 범주에 속하는 각 제품에 대한 정보를 글머리 기호 목록에 표시합니다(그림 1 참조). 이 예제에서는 내부 반복기를 선언적 및 프로그래밍 방식으로 채우는 방법을 보여 줍니다.
그림 1: 제품과 함께 각 범주가 나열됩니다(전체 크기 이미지를 보려면 클릭).
1단계: 범주 목록 만들기
중첩된 데이터 웹 컨트롤을 사용하는 페이지를 빌드할 때는 가장 바깥쪽 데이터 웹 컨트롤을 먼저 디자인, 생성 및 테스트하는 데 도움이 되며, 내부 중첩된 컨트롤에 대해서도 걱정할 필요가 없습니다. 따라서 각 범주의 이름과 설명을 나열하는 페이지에 반복기를 추가하는 데 필요한 단계를 안내해 보겠습니다.
먼저 폴더에서 NestedControls.aspx
DataListRepeaterBasics
페이지를 열고 반복기 컨트롤을 페이지에 추가하고 해당 ID
속성을 .로 CategoryList
설정합니다. Repeater의 스마트 태그에서 새 ObjectDataSource를 CategoriesDataSource
만들도록 선택합니다.
그림 2: 새 ObjectDataSource CategoriesDataSource
이름 지정(전체 크기 이미지를 보려면 클릭)
ObjectDataSource가 CategoriesBLL
클래스의 GetCategories
메서드에서 데이터를 가져오도록 구성합니다.
그림 3: Class s CategoriesBLL
메서드를 사용하도록 GetCategories
ObjectDataSource 구성(전체 크기 이미지를 보려면 클릭)
반복기 템플릿 콘텐츠를 지정하려면 원본 뷰로 이동하여 선언적 구문을 수동으로 입력해야 합니다.
ItemTemplate
요소의 범주 이름과 <h4>
단락 요소(<p>
)의 범주 설명을 표시하는 항목을 추가합니다. 또한 각 범주를 가로 규칙(<hr>
)으로 구분해 보겠습니다. 이러한 변경을 수행한 후 페이지에는 다음과 유사한 Repeater 및 ObjectDataSource에 대한 선언적 구문이 포함되어야 합니다.
<asp:Repeater ID="CategoryList" DataSourceID="CategoriesDataSource"
EnableViewState="False" runat="server">
<ItemTemplate>
<h4><%# Eval("CategoryName") %></h4>
<p><%# Eval("Description") %></p>
</ItemTemplate>
<SeparatorTemplate>
<hr />
</SeparatorTemplate>
</asp:Repeater>
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
OldValuesParameterFormatString="original_{0}"
SelectMethod="GetCategories" TypeName="CategoriesBLL">
</asp:ObjectDataSource>
그림 4는 브라우저를 통해 볼 때 진행률을 보여 줍니다.
그림 4: 각 범주의 이름과 설명이 가로 규칙으로 구분되어 나열됩니다(전체 크기 이미지를 보려면 클릭).
2단계: 중첩된 제품 반복 요소를 추가하기
범주 목록이 완료되면 다음 작업은 적절한 범주에 CategoryList
ItemTemplate
속하는 제품에 대한 정보를 표시하는 반복기를 s에 추가하는 것입니다. 이 내부 반복기의 데이터를 검색할 수 있는 방법은 여러 가지가 있으며, 그 중 두 가지는 곧 살펴볼 것입니다. 지금은 CategoryList
내의 Repeater에서 제품 반복기를 생성해 보겠습니다. 특히 제품 반복기에서 제품 이름과 가격을 포함한 각 목록 항목이 포함된 글머리 기호 목록의 각 제품을 표시하도록 하겠습니다.
이 반복기를 만들려면 내부 반복기 선언적 구문과 템플릿 CategoryList
ItemTemplate
을 수동으로 입력해야 합니다. 반복기 내에 다음 태그를 추가합니다 CategoryList
ItemTemplate
.
<asp:Repeater ID="ProductsByCategoryList" EnableViewState="False"
runat="server">
<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li><strong><%# Eval("ProductName") %></strong>
(<%# Eval("UnitPrice", "{0:C}") %>)</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
3단계: Category-Specific 제품을 ProductsByCategoryList Repeater에 바인딩
이 시점에서 브라우저를 통해 페이지를 방문하면 리피터에 데이터를 바인딩하지 않았기 때문에 화면이 그림 4와 동일하게 표시됩니다. 몇 가지 방법으로 적절한 제품 레코드를 잡고 반복기(Repeater)에 바인딩할 수 있으며, 이는 다른 레코드보다 더 효율적입니다. 여기서 주요 과제는 지정된 범주에 적합한 제품을 다시 가져오는 것입니다.
내부 반복기 컨트롤에 바인딩할 데이터는 반복기에서 ObjectDataSource CategoryList
ItemTemplate
를 통해 선언적으로 액세스하거나 ASP.NET 페이지의 코드 숨김 페이지에서 프로그래밍 방식으로 액세스할 수 있습니다. 마찬가지로 이 데이터는 내부 반복기의 DataSourceID
속성이나 선언적 데이터 바인딩 구문을 통해 선언적으로 바인딩하거나, CategoryList
이벤트 처리기에서 내부 반복기를 참조하여 프로그래밍 방식으로 ItemDataBound
속성을 설정하고 DataSource
메서드를 호출하여 바인딩할 수 있습니다. 이러한 각 방법을 살펴보겠습니다.
ObjectDataSource 컨트롤 및ItemDataBound
이벤트 처리기를 사용하여 선언적으로 데이터에 액세스
이 자습서 시리즈 전체에서 ObjectDataSource를 광범위하게 사용했으므로 이 예제의 데이터에 액세스하는 가장 자연스러운 선택은 ObjectDataSource를 고수하는 것입니다. 클래스에는 ProductsBLL
GetProductsByCategoryID(categoryID)
지정된 categoryID
제품에 속하는 제품에 대한 정보를 반환하는 메서드가 있습니다. 따라서 Repeater에 ObjectDataSource를 CategoryList
추가하고 이 클래스의 ItemTemplate
메서드에서 해당 데이터에 액세스하도록 구성할 수 있습니다.
안타깝게도 Repeater는 디자인 보기를 통해 템플릿을 편집할 수 없으므로 이 ObjectDataSource 컨트롤에 대한 선언적 구문을 직접 추가해야 합니다. 다음 구문은 이 새 ObjectDataSource(CategoryList
)를 추가한 후 ItemTemplate
반복기를 보여줍니다(ProductsByCategoryDataSource
).
<h4><%# Eval("CategoryName") %></h4>
<p><%# Eval("Description") %></p>
<asp:Repeater ID="ProductsByCategoryList" EnableViewState="False"
DataSourceID="ProductsByCategoryDataSource" runat="server">
<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li><strong><%# Eval("ProductName") %></strong> -
sold as <%# Eval("QuantityPerUnit") %> at
<%# Eval("UnitPrice", "{0:C}") %></li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
<asp:ObjectDataSource ID="ProductsByCategoryDataSource" runat="server"
SelectMethod="GetProductsByCategoryID" TypeName="ProductsBLL">
<SelectParameters>
<asp:Parameter Name="CategoryID" Type="Int32" />
</SelectParameters>
</asp:ObjectDataSource>
ObjectDataSource 접근 방식을 사용할 때, Repeater의 속성을 ObjectDataSource의 ProductsByCategoryList
(DataSourceID
)로 ID
설정해야 합니다. 또한, ObjectDataSource에는 메서드에 전달될 <asp:Parameter>
값을 지정하는 categoryID
요소가 있습니다. 그러나 이 값을 지정하려면 어떻게 해야 할까요? 이상적으로는 다음과 같이 데이터 바인딩 구문을 사용하여 DefaultValue
요소의 <asp:Parameter>
속성을 간단히 설정할 수 있습니다.
<asp:Parameter Name="CategoryID" Type="Int32"
DefaultValue='<%# Eval("CategoryID")' />
아쉽게도 데이터 바인딩 구문은 이벤트가 있는 DataBinding
컨트롤에서만 유효합니다. 클래스에는 Parameter
이러한 이벤트가 없으므로 위의 구문이 잘못되어 런타임 오류가 발생합니다.
이 값을 설정하려면 Repeater CategoryList
이벤트에 대한 ItemDataBound
이벤트 처리기를 만들어야 합니다. 기억하세요, ItemDataBound
이벤트는 반복자에 연결된 각 항목에 대해 한 번 발생합니다. 따라서 외부 반복기에서 이 이벤트가 발생할 때마다 현재 CategoryID
값을 ObjectDataSource의 ProductsByCategoryDataSource
매개변수에 할당할 수 있습니다.
다음 코드를 사용하여 CategoryList
Repeater의 ItemDataBound
이벤트에 대한 이벤트 처리기를 만듭니다.
Protected Sub CategoryList_ItemDataBound(sender As Object, e As RepeaterItemEventArgs) _
Handles CategoryList.ItemDataBound
If e.Item.ItemType = ListItemType.AlternatingItem _
OrElse e.Item.ItemType = ListItemType.Item Then
' Reference the CategoriesRow object being bound to this RepeaterItem
Dim category As Northwind.CategoriesRow = _
CType(CType(e.Item.DataItem, System.Data.DataRowView).Row, _
Northwind.CategoriesRow)
' Reference the ProductsByCategoryDataSource ObjectDataSource
Dim ProductsByCategoryDataSource As ObjectDataSource = _
CType(e.Item.FindControl("ProductsByCategoryDataSource"), _
ObjectDataSource)
' Set the CategoryID Parameter value
ProductsByCategoryDataSource.SelectParameters("CategoryID").DefaultValue = _
category.CategoryID.ToString()
End If
End Sub
이 이벤트 처리기는 머리글, 바닥글 또는 구분 기호 항목이 아닌 데이터 항목을 처리하는 것으로 시작합니다. 다음으로, 현재CategoriesRow
에 바인딩된 실제 RepeaterItem
인스턴스를 참조합니다. 마지막으로, ObjectDataSource ItemTemplate
를 참조하고, 해당 CategoryID
매개 변수 값을 현재 CategoryID
의 RepeaterItem
에 할당합니다.
이 이벤트 처리기를 사용하면 ProductsByCategoryList
의 각 RepeaterItem
반복기가 RepeaterItem
범주의 해당 제품에 바인딩됩니다. 그림 5는 결과 출력의 스크린샷을 보여줍니다.
그림 5: 외부 반복기는 각 범주를 나열합니다. 내부 하나는 해당 범주에 대한 제품을 나열합니다(전체 크기 이미지를 보려면 클릭).
프로그래밍 방식으로 범주별 제품 데이터 액세스
ASP.NET 페이지의 코드-비하인드 클래스(또는 App_Code
폴더나 별도의 클래스 라이브러리 프로젝트)에서, ObjectDataSource를 사용하여 현재 범주의 제품을 검색하는 대신, CategoryID
가 전달되었을 때 적합한 제품 집합을 반환하는 메서드를 만들 수 있습니다. ASP.NET 페이지의 코드 숨김 클래스에 이러한 메서드가 있고 이름이 지정 GetProductsInCategory(categoryID)
되었다고 상상해 보세요. 이 메서드를 사용하면 다음 선언적 구문을 사용하여 현재 범주의 제품을 내부 반복자에 바인딩할 수 있습니다.
<asp:Repeater runat="server" ID="ProductsByCategoryList" EnableViewState="False"
DataSource='<%# GetProductsInCategory(CType(Eval("CategoryID"), Integer)) %>'>
...
</asp:Repeater>
Repeater 속성 DataSource
은 데이터 바인딩 구문을 사용하여 해당 데이터가 메서드에서 GetProductsInCategory(categoryID)
가져온 것임을 나타냅니다.
Eval("CategoryID")
형식Object
의 값을 반환하므로 개체를 메서드에 전달하기 전에 개체를 메서드 Integer
로 GetProductsInCategory(categoryID)
캐스팅합니다.
CategoryID
여기서 데이터 바인딩 구문을 통해 액세스하는 것은 CategoryID
테이블의 레코드에 연결된 외부 반복기(CategoryList
)인 Categories
입니다. 따라서 CategoryID
는 데이터베이스 NULL
값이 될 수 없다는 것을 알고 있기 때문에, Eval
인지 확인하지 않고도 DBNull
메서드를 무분별하게 캐스팅할 수 있습니다.
이 방법을 사용하면 제공된 경우 메서드를 GetProductsInCategory(categoryID)
만들고 적절한 제품 집합을 categoryID
검색해야 합니다. 우리는 단순히 ProductsDataTable
클래스의 ProductsBLL
메서드가 반환하는 GetProductsByCategoryID(categoryID)
을 반환함으로써 이 작업을 수행할 수 있습니다. 코드 숨김 클래스에서 우리의 GetProductsInCategory(categoryID)
페이지를 위한 NestedControls.aspx
메서드를 만들어 봅시다. 다음 코드를 사용하여 수행합니다.
Protected Function GetProductsInCategory(ByVal categoryID As Integer) _
As Northwind.ProductsDataTable
' Create an instance of the ProductsBLL class
Dim productAPI As ProductsBLL = New ProductsBLL()
' Return the products in the category
Return productAPI.GetProductsByCategoryID(categoryID)
End Function
이 메서드는 단순히 메서드의 인스턴스를 ProductsBLL
만들고 메서드의 결과를 반환합니다 GetProductsByCategoryID(categoryID)
. 메서드는 Public
또는 Protected
로 표시되어야 합니다. 메서드가 Private
로 표시되어 있는 경우, ASP.NET 페이지의 선언적 태그에서는 액세스할 수 없습니다.
이 새 기술을 사용하도록 변경한 후 잠시 시간을 내어 브라우저를 통해 페이지를 봅니다. ObjectDataSource 및 이벤트 처리기 방법을 사용할 때 출력은 출력과 ItemDataBound
동일해야 합니다(스크린샷을 보려면 그림 5를 다시 참조하세요).
비고
ASP.NET 페이지의 코드 숨김 클래스에서 GetProductsInCategory(categoryID)
메서드를 만드는 것이 단순한 반복 작업처럼 보일 수 있습니다. 결국, 이 메서드는 단순히 클래스의 인스턴스를 ProductsBLL
만들고 해당 메서드의 결과를 반환합니다 GetProductsByCategoryID(categoryID)
. 내부 반복기 DataSource='<%# ProductsBLL.GetProductsByCategoryID(CType(Eval("CategoryID"), Integer)) %>'
에서 데이터 바인딩 구문에서 이 메서드를 직접 호출하는 것은 어떨까요? 이 구문은 클래스의 ProductsBLL
현재 구현에서 작동하지 않지만(GetProductsByCategoryID(categoryID)
메서드는 인스턴스 메서드이므로) 정적 메서드를 포함하도록 수정 ProductsBLL
하거나 클래스에 GetProductsByCategoryID(categoryID)
새 Instance()
인스턴스를 반환하는 정적 ProductsBLL
메서드를 포함하도록 할 수 있습니다.
이러한 수정으로 인해 ASP.NET 페이지의 코드 숨김 클래스에서 GetProductsInCategory(categoryID)
메서드가 필요하지 않지만, 곧 확인하겠지만 코드 숨김 클래스 메서드를 사용하면 검색된 데이터를 더욱 유연하게 처리할 수 있습니다.
모든 제품 정보를 한 번에 검색
검토했던 두 가지 이전 기술은 클래스 ProductsBLL
의 메서드 GetProductsByCategoryID(categoryID)
를 호출하여 현재 카테고리의 제품을 가져오는 것입니다 (첫 번째 방법은 ObjectDataSource를 통해, 두 번째 방법은 코드 비하인드 클래스의 GetProductsInCategory(categoryID)
메서드를 통해 수행됨). 이 메서드가 호출될 때마다 비즈니스 논리 계층은 데이터 액세스 계층을 호출합니다. 이 계층은 제공된 입력 매개 변수와 일치하는 테이블 Products
의 CategoryID
행을 반환하는 SQL 문을 사용하여 데이터베이스를 쿼리합니다.
시스템에 N개의 범주가 있는 경우, 이 방식은 데이터베이스 쿼리 한 개로 모든 범주를 가져온 다음, N + 1번의 호출을 통해 각 범주에 특정한 제품을 가져옵니다. 그러나 필요한 모든 데이터를 단 두 번의 데이터베이스 호출로 검색할 수 있습니다. 한 번은 모든 범주를 가져오고, 다른 한 번은 모든 제품을 가져오는 호출입니다. 모든 제품이 있으면 해당 제품을 필터링하여 현재 CategoryID
와 일치하는 제품만 해당 범주의 내부 리피터에 바인딩되도록 할 수 있습니다.
이 기능을 제공하려면 ASP.NET 페이지의 코드 숨김 클래스에서 메서드를 약간 수정 GetProductsInCategory(categoryID)
하기만 하면됩니다. 클래스 메서드의 ProductsBLL
GetProductsByCategoryID(categoryID)
결과를 맹목적으로 반환하는 대신 먼저 모든 제품에 액세스한 다음(아직 액세스하지 않은 경우) 전달된 CategoryID
항목에 따라 필터링된 제품 보기만 반환할 수 있습니다.
Private allProducts As Northwind.ProductsDataTable = Nothing
Protected Function GetProductsInCategory(ByVal categoryID As Integer) _
As Northwind.ProductsDataTable
' First, see if we've yet to have accessed all of the product information
If allProducts Is Nothing Then
Dim productAPI As ProductsBLL = New ProductsBLL()
allProducts = productAPI.GetProducts()
End If
' Return the filtered view
allProducts.DefaultView.RowFilter = "CategoryID = " & categoryID
Return allProducts
End Function
페이지 수준 변수 allProducts
가 추가되었습니다. 이는 모든 제품에 대한 정보를 보유하며 메서드가 처음 호출될 때 GetProductsInCategory(categoryID)
채워집니다. 개체가 allProducts
만들어지고 채워졌는지 확인한 후 메서드는 DataTable의 결과를 필터링하여 지정된 CategoryID
행과 일치하는 행 CategoryID
만 액세스할 수 있도록 합니다. 이 방법은 데이터베이스에 액세스하는 횟수를 N + 1에서 2로 줄입니다.
이 향상된 기능은 페이지의 렌더링된 태그를 변경하지 않으며 다른 방법보다 적은 레코드를 다시 가져오지도 않습니다. 데이터베이스에 대한 호출 횟수를 줄입니다.
비고
데이터베이스 액세스 수를 줄이면 성능이 확실히 향상되는 직관적인 이유가 있을 수 있습니다. 그러나 그렇지 않을 수도 있습니다. 예를 들어, 많은 수의 제품의 CategoryID
이 NULL
인 경우, GetProducts
메서드를 호출하면 표시되지 않는 여러 제품을 반환합니다. 또한 페이징을 구현한 경우 범주의 하위 집합만 표시하는 경우 모든 제품을 반환하는 것은 낭비될 수 있습니다.
언제나처럼 두 가지 기술의 성능을 분석할 때 유일한 확실한 측정값은 애플리케이션의 일반적인 사례 시나리오에 맞게 조정된 제어된 테스트를 실행하는 것입니다.
요약
이 자습서에서는 외부 반복기가 각 범주에 대한 항목을 표시하고, 내부 반복기가 각 범주의 제품을 나열하는 방식으로 한 데이터 웹 컨트롤을 다른 데이터 웹 컨트롤 내에 중첩하는 방법을 살펴보았습니다. 중첩된 사용자 인터페이스를 빌드하는 주요 과제는 올바른 데이터에 액세스하고 내부 데이터 웹 컨트롤에 바인딩하는 것입니다. 사용할 수 있는 다양한 기술이 있으며, 그 중 두 가지는 이 자습서에서 검토했습니다. 검사된 첫 번째 방법은 ItemTemplate
외부 데이터 웹 컨트롤에 내부 데이터 웹 컨트롤의 DataSourceID
속성을 통해 바인딩된 ObjectDataSource를 사용하는 것이었습니다. 두 번째 기술은 ASP.NET 페이지의 코드 숨김 클래스의 메서드를 통해 데이터에 액세스했습니다. 이 메서드는 데이터 바인딩 구문을 통해 내부 데이터 웹 컨트롤의 DataSource
속성에 바인딩할 수 있습니다.
이 자습서에서는 반복기 안에 중첩된 반복기를 사용하는 중첩된 사용자 인터페이스를 다루었지만, 이러한 기술은 다른 데이터 웹 컨트롤에도 확장될 수 있습니다. GridView 내에 Repeater를 중첩하거나 DataList 내의 GridView를 중첩하는 등 여러 조합으로 사용할 수 있습니다.
행복한 프로그래밍!
작성자 정보
7개의 ASP/ASP.NET 책의 저자이자 4GuysFromRolla.com 창립자인 Scott Mitchell은 1998년부터 Microsoft 웹 기술을 연구해 왔습니다. Scott은 독립 컨설턴트, 트레이너 및 작가로 일합니다. 그의 최신 책은 Sams Teach Yourself ASP.NET 2.0 in 24 Hours입니다. 그에게 mitchell@4GuysFromRolla.com으로 연락할 수 있습니다.
특별히 감사드립니다.
이 자습서 시리즈는 많은 유용한 검토자가 검토했습니다. 이 자습서의 수석 검토자는 잭 존스와 리즈 슐록이었습니다. 예정된 MSDN 문서를 검토하는 데 관심이 있으신가요? 그렇다면 mitchell@4GuysFromRolla.com으로 메시지를 보내 주세요.