Share via


중첩된 데이터 웹 컨트롤(C#)

작성자 : Scott Mitchell

PDF 다운로드

이 자습서에서는 다른 반복기 내에 중첩된 리피터를 사용하는 방법을 알아봅니다. 이 예제에서는 내부 반복기를 선언적 및 프로그래밍 방식으로 채우는 방법을 보여 줍니다.

소개

템플릿에는 정적 HTML 및 데이터 바인딩 구문 외에도 웹 컨트롤 및 사용자 컨트롤이 포함될 수 있습니다. 이러한 웹 컨트롤은 선언적 데이터 바인딩 구문을 통해 해당 속성을 할당하거나 적절한 서버 쪽 이벤트 처리기에서 프로그래밍 방식으로 액세스할 수 있습니다.

템플릿 내에 컨트롤을 포함하면 모양과 사용자 환경을 사용자 지정하고 개선할 수 있습니다. 예를 들어 GridView 컨트롤의 TemplateFields 사용 자습서에서는 직원 고용 날짜를 표시하기 위해 TemplateField에 일정 컨트롤을 추가하여 GridView의 디스플레이를 사용자 지정하는 방법을 알아보았습니다. 편집 및 삽입 인터페이스에 유효성 검사 컨트롤 추가데이터 수정 인터페이스 사용자 지정 자습서에서 유효성 검사 컨트롤, TextBoxes, DropDownLists 및 기타 웹 컨트롤을 추가하여 인터페이스 편집 및 삽입을 사용자 지정하는 방법을 알아보았습니다.

템플릿에는 다른 데이터 웹 컨트롤도 포함될 수 있습니다. 즉, 템플릿 내에 다른 DataList(또는 Repeater 또는 GridView 또는 DetailsView 등)가 포함된 DataList가 있을 수 있습니다. 이러한 인터페이스의 과제는 적절한 데이터를 내부 데이터 웹 컨트롤에 바인딩하는 것입니다. ObjectDataSource를 사용하는 선언적 옵션부터 프로그래밍 방식 옵션에 이르기까지 사용할 수 있는 몇 가지 방법이 있습니다.

이 자습서에서는 다른 반복기 내에 중첩된 리피터를 사용하는 방법을 알아봅니다. 외부 반복기는 데이터베이스의 각 범주에 대한 항목을 포함하며 범주의 이름과 설명을 표시합니다. 각 범주 항목의 내부 반복기는 해당 범주에 속하는 각 제품에 대한 정보를 글머리 기호 목록에 표시합니다(그림 1 참조). 이 예제에서는 내부 반복기를 선언적 및 프로그래밍 방식으로 채우는 방법을 보여 줍니다.

각 범주는 제품과 함께 나열됩니다.

그림 1: 제품과 함께 각 범주가 나열됩니다(전체 크기 이미지를 보려면 클릭).

1단계: 범주 목록 만들기

중첩된 데이터 웹 컨트롤을 사용하는 페이지를 빌드할 때 내부 중첩 컨트롤에 대한 걱정 없이 가장 바깥쪽 데이터 웹 컨트롤을 먼저 디자인, 생성 및 테스트하는 것이 도움이 됩니다. 따라서 각 범주의 이름과 설명을 나열하는 페이지에 반복기를 추가하는 데 필요한 단계를 안내해 보겠습니다.

먼저 폴더에서 NestedControls.aspxDataListRepeaterBasics 페이지를 열고 페이지에 Repeater 컨트롤을 추가하고 해당 ID 속성을 CategoryList로 설정합니다. Repeater의 스마트 태그에서 라는 CategoriesDataSource새 ObjectDataSource를 만들도록 선택합니다.

새 ObjectDataSource CategoriesDataSource의 이름을 지정합니다.

그림 2: 새 ObjectDataSource CategoriesDataSource 의 이름을 지정합니다(전체 크기 이미지를 보려면 클릭).

ObjectDataSource를 구성하여 클래스의 GetCategories 메서드에서 CategoriesBLL 해당 데이터를 가져옵니다.

CategoriesBLL 클래스의 GetCategories 메서드를 사용하도록 ObjectDataSource 구성

그림 3: 클래스의 GetCategories 메서드를 사용하도록 CategoriesBLL ObjectDataSource 구성(전체 크기 이미지를 보려면 클릭)

Repeater 템플릿 콘텐츠를 지정하려면 원본 보기로 이동하여 선언적 구문을 수동으로 입력해야 합니다. 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단계: 중첩된 제품 반복기 추가

범주 목록이 완료되면 다음 작업은 적절한 범주에 CategoryListItemTemplate 속하는 제품에 대한 정보를 표시하는 에 Repeater를 추가하는 것입니다. 이 내부 반복기의 데이터를 검색할 수 있는 방법은 여러 가지가 있으며, 그 중 두 가지는 곧 살펴볼 것입니다. 지금은 리ItemTemplate피터 내에 제품 리피터를 CategoryList 만들어 보겠습니다. 특히 제품 리피터가 각 제품을 글머리 기호 목록에 표시하고 제품 이름 및 가격을 포함한 각 목록 항목이 표시되도록 하겠습니다.

이 반복기를 만들려면 의 에 내부 반복기 선언적 구문 및 템플릿을 CategoryListItemTemplate수동으로 입력해야 합니다. Repeater ItemTemplate의 내에 다음 태그를 CategoryList 추가합니다.

<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 Products를 ProductsByCategoryList Repeater에 바인딩

이 시점에서 브라우저를 통해 페이지를 방문하면 아직 데이터를 리피터에 바인딩하지 않았기 때문에 화면이 그림 4와 동일하게 표시됩니다. 적절한 제품 레코드를 잡고 리피터에 바인딩할 수 있는 몇 가지 방법이 있습니다. 다른 레코드보다 더 효율적입니다. 여기서 기본 과제는 지정된 범주에 적합한 제품을 다시 가져오는 것입니다.

내부 반복기 컨트롤에 바인딩할 데이터는 반복기 의 ObjectDataSource CategoryListItemTemplate를 통해 선언적으로 액세스하거나 ASP.NET 페이지의 코드 숨김 페이지에서 프로그래밍 방식으로 액세스할 수 있습니다. 마찬가지로 이 데이터는 내부 반복기의 속성을 통해 또는 선언적 데이터 바인딩 구문을 통해 또는 반복기의 DataSourceID 이벤트 처리기에서 내부 반복기를 참조하고, 프로그래밍 방식으로 속성을 설정하고, 메서드 DataSource 를 호출 DataBind() 하여 내부 리피 ItemDataBound 터에 CategoryList 선언적으로 바인딩할 수 있습니다. 이러한 각 방법을 살펴보겠습니다.

ObjectDataSource 컨트롤 및ItemDataBound이벤트 처리기를 사용하여 선언적으로 데이터에 액세스

이 자습서 시리즈 전체에서 ObjectDataSource를 광범위하게 사용했으므로 이 예제의 데이터에 액세스하기 위한 가장 자연스러운 선택은 ObjectDataSource를 고수하는 것입니다. 클래스에는 ProductsBLL 지정된 categoryIDGetProductsByCategoryID(categoryID) 속하는 제품에 대한 정보를 반환하는 메서드가 있습니다. 따라서 ObjectDataSource를 Repeater s ItemTemplateCategoryList 추가하고 이 클래스의 메서드에서 해당 데이터에 액세스하도록 구성할 수 있습니다.

아쉽게도 Repeater는 디자인 보기를 통해 템플릿을 편집할 수 없으므로 이 ObjectDataSource 컨트롤에 대한 선언적 구문을 직접 추가해야 합니다. 다음 구문은 이 새 ObjectDataSource(ProductsByCategoryDataSource)를 추가한 후의 ItemTemplate 반복기를 보여 CategoryList 합니다.

<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의 DataSourceID 속성을 ID ObjectDataSource(ProductsByCategoryDataSource)의 로 설정 ProductsByCategoryList 해야 합니다. 또한 ObjectDataSource에는 메서드에 <asp:Parameter>GetProductsByCategoryID(categoryID) 전달될 값을 지정 categoryID 하는 요소가 있습니다. 그러나 이 값을 지정하려면 어떻게 해야 할까요? 이상적으로 다음과 같이 데이터 바인딩 구문을 사용하여 요소의 <asp:Parameter> 속성을 설정할 DefaultValue 수 있습니다.

<asp:Parameter Name="CategoryID" Type="Int32"
     DefaultValue='<%# Eval("CategoryID")' />

아쉽게도 데이터 바인딩 구문은 이벤트가 있는 DataBinding 컨트롤에서만 유효합니다. 클래스에는 Parameter 이러한 이벤트가 없으므로 위의 구문이 잘못되어 런타임 오류가 발생합니다.

이 값을 설정하려면 Repeater 이벤트에 ItemDataBound 대한 CategoryList 이벤트 처리기를 만들어야 합니다. 이벤트가 Repeater에 ItemDataBound 바인딩된 각 항목에 대해 한 번 발생합니다. 따라서 외부 반복기에서 이 이벤트가 발생할 때마다 ObjectDataSource의 CategoryID 매개 변수에 ProductsByCategoryDataSource 현재 CategoryID 값을 할당할 수 있습니다.

다음 코드를 사용하여 Repeater 이벤트에 ItemDataBound 대한 CategoryList 이벤트 처리기를 만듭니다.

protected void CategoryList_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    if (e.Item.ItemType == ListItemType.AlternatingItem ||
        e.Item.ItemType == ListItemType.Item)
    {
        // Reference the CategoriesRow object being bound to this RepeaterItem
        Northwind.CategoriesRow category =
            (Northwind.CategoriesRow)((System.Data.DataRowView)e.Item.DataItem).Row;
        // Reference the ProductsByCategoryDataSource ObjectDataSource
        ObjectDataSource ProductsByCategoryDataSource =
            (ObjectDataSource)e.Item.FindControl("ProductsByCategoryDataSource");
        // Set the CategoryID Parameter value
        ProductsByCategoryDataSource.SelectParameters["CategoryID"].DefaultValue =
            category.CategoryID.ToString();
    }
}

이 이벤트 처리기는 헤더, 바닥글 또는 구분 기호 항목이 아닌 데이터 항목을 처리하는 것으로 시작합니다. 다음으로, 현재 RepeaterItem에 바인딩된 실제 CategoriesRow instance 참조합니다. 마지막으로 에서 ObjectDataSource ItemTemplate 를 참조하고 해당 CategoryID 매개 변수 값을 현재 RepeaterItem의 에 CategoryID 할당합니다.

이 이벤트 처리기를 ProductsByCategoryList 사용하면 각 RepeaterItem 의 반복기가 범주의 해당 제품에 바인딩됩니다 RepeaterItem . 그림 5는 결과 출력의 스크린샷을 보여줍니다.

외부 반복기는 각 범주를 Lists, 내부 반복기는 해당 범주에 대한 제품을 Lists.

그림 5: 각 범주에 Lists 외부 반복기, 해당 범주에 대한 제품 Lists 내부 반복기(전체 크기 이미지를 보려면 클릭)

프로그래밍 방식으로 범주별 제품 데이터 액세스

ObjectDataSource를 사용하여 현재 범주에 대한 제품을 검색하는 대신, 에 전달될 때 적절한 제품 집합을 반환하는 ASP.NET 페이지의 코드 숨김 클래스(또는 App_Code 폴더 또는 별도의 클래스 라이브러리 프로젝트)에 CategoryID메서드를 만들 수 있습니다. ASP.NET 페이지의 코드 숨김 클래스에 이러한 메서드가 있고 이름이 GetProductsInCategory(categoryID)인 경우를 상상해 보세요. 이 메서드를 사용하면 다음 선언적 구문을 사용하여 현재 범주의 제품을 내부 리피터에 바인딩할 수 있습니다.

<asp:Repeater runat="server" ID="ProductsByCategoryList" EnableViewState="False"
      DataSource='<%# GetProductsInCategory((int)(Eval("CategoryID"))) %>'>
  ...
</asp:Repeater>

Repeater의 DataSource 속성은 데이터 바인딩 구문을 사용하여 해당 데이터가 메서드에서 GetProductsInCategory(categoryID) 가져온 것임을 나타냅니다. Eval("CategoryID") 형식Object의 값을 반환하므로 메서드에 전달 GetProductsInCategory(categoryID) 하기 전에 개체를 로 Integer 캐스팅합니다. CategoryID 데이터 바인딩 구문을 통해 여기에 액세스하는 은 CategoryID 테이블의 레코드 Categories 에 바인딩된 외부 반복기(CategoryList)의 입니다. 따라서 데이터베이스 값이 CategoryID 될 수 없다는 것을 알고 있으므로 를 처리하는DBNull지 확인하지 않고 메서드를 맹목적으로 캐스팅 Eval 할 수 NULL 있습니다.

이 방법을 사용하면 메서드를 GetProductsInCategory(categoryID) 만들고 제공된 categoryID가 제공된 적절한 제품 집합을 검색하도록 해야 합니다. 클래스의 GetProductsByCategoryID(categoryID) 메서드에서 반환 ProductsBLL 된 를 ProductsDataTable 반환하기만 하면 이 작업을 수행할 수 있습니다. 페이지의 코드 숨김 클래스에서 메서드를 NestedControls.aspx 만들어 GetProductsInCategory(categoryID) 보겠습니다. 이렇게 하려면 다음 코드를 사용합니다.

protected Northwind.ProductsDataTable GetProductsInCategory(int categoryID)
{
    // Create an instance of the ProductsBLL class
    ProductsBLL productAPI = new ProductsBLL();
    // Return the products in the category
    return productAPI.GetProductsByCategoryID(categoryID);
}

이 메서드는 단순히 메서드의 ProductsBLL instance 만들고 메서드의 결과를 반환합니다GetProductsByCategoryID(categoryID). 메서드는 또는 Protected로 표시 Public 되어야 합니다. 메서드가 로 표시Private되면 ASP.NET 페이지의 선언적 태그에서 액세스할 수 없습니다.

이 새 기술을 사용하도록 변경한 후 잠시 시간을 내어 브라우저를 통해 페이지를 봅니다. ObjectDataSource 및 이벤트 처리기 접근 방식을 사용할 때 출력은 출력과 ItemDataBound 동일해야 합니다(스크린샷을 보려면 그림 5를 다시 참조하세요).

참고

ASP.NET 페이지의 코드 숨김 클래스에서 메서드를 만드는 GetProductsInCategory(categoryID) 것은 바쁜 것처럼 보일 수 있습니다. 결국, 이 메서드는 클래스의 ProductsBLL instance 만들고 메서드의 결과를 반환합니다GetProductsByCategoryID(categoryID). 내부 리피터 DataSource='<%# ProductsBLL.GetProductsByCategoryID((int)(Eval("CategoryID"))) %>'의 데이터 바인딩 구문에서 이 메서드를 직접 호출하는 것은 어떨까요? 이 구문은 클래스의 ProductsBLL 현재 구현에서는 작동하지 않지만(GetProductsByCategoryID(categoryID)메서드가 instance 메서드이므로) 정적 메서드를 포함하도록 수정 ProductsBLL 하거나 클래스에 새 GetProductsByCategoryID(categoryID) instance ProductsBLL 반환하는 정적 Instance() 메서드가 포함되도록 할 수 있습니다.

이러한 수정으로 인해 ASP.NET 페이지의 코드 숨김 클래스에서 메서드가 필요하지 GetProductsInCategory(categoryID) 않지만, 코드 숨김 클래스 메서드를 사용하면 곧 확인할 수 있듯이 검색된 데이터를 유연하게 작업할 수 있습니다.

모든 제품 정보를 한 번에 검색

검사한 두 가지 악의적인 기술은 클래스의 GetProductsByCategoryID(categoryID) 메서드를 호출 ProductsBLL 하여 현재 범주에 대한 해당 제품을 가져옵니다(첫 번째 방법은 ObjectDataSource를 통해, 두 번째는 코드 숨김 클래스의 GetProductsInCategory(categoryID) 메서드를 통해 수행됨). 이 메서드가 호출될 때마다 비즈니스 논리 계층은 데이터 액세스 계층을 호출합니다. 이 계층은 제공된 입력 매개 변수와 일치하는 테이블 CategoryIDProducts 행을 반환하는 SQL 문을 사용하여 데이터베이스를 쿼리합니다.

시스템의 N 개 범주가 지정된 경우 이 접근 방식은 데이터베이스에 대한 N + 1 호출을 통해 데이터베이스 1개 쿼리를 호출하여 모든 범주를 가져옵니다. 그런 다음 N 을 호출하여 각 범주에 특정한 제품을 가져옵니다. 그러나 두 개의 데이터베이스 호출에서 필요한 모든 데이터를 검색하여 모든 범주를 가져오는 호출과 모든 제품을 가져오는 호출을 검색할 수 있습니다. 모든 제품이 있으면 현재 CategoryID 제품과 일치하는 제품만 해당 범주의 내부 리피터에 바인딩되도록 해당 제품을 필터링할 수 있습니다.

이 기능을 제공하려면 ASP.NET 페이지의 코드 숨김 클래스에서 메서드를 약간 수정 GetProductsInCategory(categoryID) 하기만 하면됩니다. 클래스의 ProductsBLLGetProductsByCategoryID(categoryID) 메서드 결과를 맹목적으로 반환하는 대신 먼저 모든 제품에 액세스한 다음(아직 액세스하지 않은 경우) 전달된 CategoryID에 따라 필터링된 제품 보기만 반환할 수 있습니다.

private Northwind.ProductsDataTable allProducts = null;
protected Northwind.ProductsDataTable GetProductsInCategory(int categoryID)
{
    // First, see if we've yet to have accessed all of the product information
    if (allProducts == null)
    {
        ProductsBLL productAPI = new ProductsBLL();
        allProducts = productAPI.GetProducts();
    }
    // Return the filtered view
    allProducts.DefaultView.RowFilter = "CategoryID = " + categoryID;
    return allProducts;
}

페이지 수준 변수 allProducts가 추가되었습니다. 이는 모든 제품에 대한 정보를 보유하며 메서드가 처음 호출될 때 GetProductsInCategory(categoryID) 채워집니다. 개체가 allProducts 만들어지고 채워졌는지 확인한 후 메서드는 DataTable의 결과를 필터링하여 지정된 CategoryID 행과 일치하는 행 CategoryID 만 액세스할 수 있도록 합니다. 이 방법은 데이터베이스에 액세스하는 횟수를 N + 1에서 2로 줄입니다.

이 향상된 기능은 페이지의 렌더링된 태그를 변경하지 않으며 다른 방법보다 적은 수의 레코드를 다시 가져오지도 않습니다. 데이터베이스에 대한 호출 횟수를 줄입니다.

참고

데이터베이스 액세스 수를 줄이면 성능이 확실히 향상될 수 있다는 직관적인 이유가 있을 수 있습니다. 그러나 그렇지 않을 수도 있습니다. 예를 들어 가 인 CategoryIDNULL많은 수의 제품이 있는 경우 메서드 호출 GetProducts 은 표시되지 않는 여러 제품을 반환합니다. 또한 범주의 하위 집합만 표시하는 경우 모든 제품을 반환하는 것은 낭비될 수 있으며, 이는 페이징을 구현한 경우일 수 있습니다.

언제나처럼 두 기술의 성능을 분석할 때 유일한 확실한 측정값은 애플리케이션의 일반적인 사례 시나리오에 맞게 조정된 제어된 테스트를 실행하는 것입니다.

요약

이 자습서에서는 특히 외부 반복기가 글머리 기호 목록의 각 범주에 대한 제품을 나열하는 내부 반복기를 사용하여 각 범주에 대한 항목을 표시하는 방법을 검토하여 한 데이터 웹 컨트롤을 다른 데이터 웹 컨트롤 내에 중첩하는 방법을 알아보았습니다. 중첩된 사용자 인터페이스를 빌드하는 기본 문제는 올바른 데이터에 액세스하고 내부 데이터 웹 컨트롤에 바인딩하는 것입니다. 사용할 수 있는 다양한 기술이 있으며, 그 중 두 가지가 이 자습서에서 검토되었습니다. 검사된 첫 번째 방법은 외부 데이터 웹 컨트롤에서 ItemTemplate 해당 속성을 통해 내부 데이터 웹 컨트롤에 바인딩된 ObjectDataSource를 DataSourceID 사용했습니다. 두 번째 기술은 ASP.NET 페이지의 코드 숨김 클래스에서 메서드를 통해 데이터에 액세스했습니다. 그런 다음 이 메서드는 데이터 바인딩 구문을 통해 내부 데이터 웹 컨트롤의 DataSource 속성에 바인딩할 수 있습니다.

이 자습서에서 검사한 중첩된 사용자 인터페이스는 Repeater 내에 중첩된 반복기를 사용했지만 이러한 기술은 다른 데이터 웹 컨트롤로 확장될 수 있습니다. GridView 내에서 반복기를 중첩하거나 DataList 내의 GridView 등을 중첩할 수 있습니다.

행복한 프로그래밍!

저자 정보

7개의 ASP/ASP.NET 책의 저자이자 4GuysFromRolla.com 창립자인 Scott Mitchell은 1998년부터 Microsoft 웹 기술로 작업해 왔습니다. Scott은 독립 컨설턴트, 트레이너 및 작가로 일합니다. 그의 최신 책은 샘스 티치 유어셀프 ASP.NET 24시간 만에 2.0입니다. 그는 에서mitchell@4GuysFromRolla.com 또는 에서 찾을 http://ScottOnWriting.NET수있는 자신의 블로그를 통해 도달 할 수 있습니다.

특별 감사

이 자습서 시리즈는 많은 유용한 검토자가 검토했습니다. 이 자습서의 수석 검토자는 잭 존스와 리즈 슐록이었습니다. 예정된 MSDN 문서를 검토하는 데 관심이 있으신가요? 그렇다면 에 줄을 놓습니다 mitchell@4GuysFromRolla.com.