다음을 통해 공유


자습서: ASP.NET MVC 애플리케이션에서 Entity Framework를 사용하여 정렬, 필터링 및 페이징 추가

이전 자습서에서는 엔터티에 대한 기본 CRUD 작업에 대한 웹 페이지 집합을 Student 구현했습니다. 이 자습서에서는 학생 인덱스 페이지에 정렬, 필터링 및 페이징 기능을 추가합니다. 간단한 그룹화 페이지도 만듭니다.

다음 이미지는 완료되면 페이지가 어떻게 표시되는지 보여줍니다. 열 제목은 해당 열로 정렬하기 위해 사용자가 클릭할 수 있는 링크입니다. 열 제목을 반복해서 클릭하면 오름차순 및 내림차순으로 정렬 순서가 토글됩니다.

Students_Index_page_with_paging

이 자습서에서는 다음과 같은 작업을 수행합니다.

  • 열 정렬 링크 추가
  • 검색 상자 추가
  • 페이징 추가
  • 정보 페이지 만들기

사전 요구 사항

학생 인덱스 페이지에 정렬을 추가하려면 컨트롤러의 메서드를 IndexStudent 변경하고 인덱스 보기에 Student 코드를 추가합니다.

Index 메서드에 정렬 기능 추가

  • Controllers\StudentController.cs에서 메서드를 Index 다음 코드로 바꿉니다.

    public ActionResult Index(string sortOrder)
    {
       ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
       ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";
       var students = from s in db.Students
                      select s;
       switch (sortOrder)
       {
          case "name_desc":
             students = students.OrderByDescending(s => s.LastName);
             break;
          case "Date":
             students = students.OrderBy(s => s.EnrollmentDate);
             break;
          case "date_desc":
             students = students.OrderByDescending(s => s.EnrollmentDate);
             break;
          default:
             students = students.OrderBy(s => s.LastName);
             break;
       }
       return View(students.ToList());
    }
    

이 코드는 URL의 쿼리 문자열에서 sortOrder 매개 변수를 받습니다. 쿼리 문자열 값은 ASP.NET MVC에서 작업 메서드에 대한 매개 변수로 제공됩니다. 매개 변수는 "Name" 또는 "Date"인 문자열이며, 필요에 따라 밑줄과 "desc" 문자열을 사용하여 내림차순을 지정합니다. 기본 정렬 순서는 오름차순입니다.

인덱스 페이지를 처음 요청하면 쿼리 문자열은 없습니다. 학생은 문에서 가을 통과 사례 switch 에 의해 설정된 기본값인 에 의해 오름차순LastName으로 표시됩니다. 사용자가 열 제목 하이퍼링크를 클릭하면 쿼리 문자열에 해당 sortOrder 값이 제공됩니다.

뷰에서 적절한 쿼리 문자열 값으로 열 머리글 하이퍼링크를 구성할 수 있도록 두 ViewBag 변수가 사용됩니다.

ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";

이것은 3개로 구성된 문입니다. 첫 번째는 매개 변수가 null이거나 비어 ViewBag.NameSortParm 있는 경우 sortOrder 를 "name_desc"로 설정하도록 지정하고, 그렇지 않으면 빈 문자열로 설정해야 합니다. 이러한 두 문을 사용하면 뷰에서 다음과 같이 열 제목 하이퍼링크를 설정할 수 있습니다.

현재 정렬 순서 성 하이퍼링크 날짜 하이퍼링크
성 오름차순 descending ascending
성 내림차순 ascending ascending
날짜 오름차순 ascending descending
날짜 내림차순 ascending ascending

메서드는 LINQ to Entities 사용하여 정렬 기준 열을 지정합니다. 코드는 문 앞에 변수를 IQueryable<T>switch 만들고, 문에서 switch 수정하고, 문 다음에 메서드를 ToListswitch 호출합니다. IQueryable 변수를 작성하고 수정하면 데이터베이스에 쿼리가 보내지지 않습니다. 와 같은 ToList메서드를 호출하여 개체를 IQueryable 컬렉션으로 변환할 때까지 쿼리가 실행되지 않습니다. 따라서 이 코드는 문까지 실행되지 않는 단일 쿼리를 return View 생성합니다.

각 정렬 순서에 대해 서로 다른 LINQ 문을 작성하는 대신 LINQ 문을 동적으로 만들 수 있습니다. 동적 LINQ에 대한 자세한 내용은 동적 LINQ를 참조하세요.

  1. Views\Student\Index.cshtml에서 제목 행의 <tr><th> 요소를 강조 표시된 코드로 바꿉니다.

    <p>
        @Html.ActionLink("Create New", "Create")
    </p>
    <table class="table">
        <tr>
            <th>
                @Html.ActionLink("Last Name", "Index", new { sortOrder = ViewBag.NameSortParm })
            </th>
            <th>First Name
            </th>
            <th>
                @Html.ActionLink("Enrollment Date", "Index", new { sortOrder = ViewBag.DateSortParm })
            </th>
            <th></th>
        </tr>
    
    @foreach (var item in Model) {
    

    이 코드는 속성의 ViewBag 정보를 사용하여 적절한 쿼리 문자열 값으로 하이퍼링크를 설정합니다.

  2. 페이지를 실행하고 등록 날짜 열 머리글을 클릭하여 정렬이 작동하는지 확인합니다.

    제목을 클릭하면 학생이 내림차순으로 표시됩니다.

학생 인덱스 페이지에 필터링을 추가하려면 보기에 텍스트 상자와 제출 단추를 추가하고 메서드를 Index 변경합니다. 텍스트 상자를 사용하면 이름 및 성 필드에서 검색할 문자열을 입력할 수 있습니다.

인덱스 메서드에 필터링 기능 추가

  • Controllers\StudentController.cs에서 메서드를 Index 다음 코드로 바꿉니다(변경 내용이 강조 표시됨).

    public ViewResult Index(string sortOrder, string searchString)
    {
        ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
        ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";
        var students = from s in db.Students
                       select s;
        if (!String.IsNullOrEmpty(searchString))
        {
            students = students.Where(s => s.LastName.Contains(searchString)
                                   || s.FirstMidName.Contains(searchString));
        }
        switch (sortOrder)
        {
            case "name_desc":
                students = students.OrderByDescending(s => s.LastName);
                break;
            case "Date":
                students = students.OrderBy(s => s.EnrollmentDate);
                break;
            case "date_desc":
                students = students.OrderByDescending(s => s.EnrollmentDate);
                break;
            default:
                students = students.OrderBy(s => s.LastName);
                break;
        }
    
        return View(students.ToList());
    }
    

코드는 메서드에 searchString 매개 변수를 Index 추가합니다. 검색 문자열 값은 Index 뷰에 추가할 텍스트 상자에서 가져옵니다. 또한 이름 또는 성에 검색 문자열이 포함된 학생만 선택하는 절을 LINQ 문에 추가 where 합니다. 절을 Where 추가하는 문은 검색할 값이 있는 경우에만 실행됩니다.

참고

대부분의 경우 Entity Framework 엔터티 집합에서 또는 메모리 내 컬렉션의 확장 메서드로 동일한 메서드를 호출할 수 있습니다. 결과는 일반적으로 동일하지만 경우에 따라 다를 수 있습니다.

예를 들어 메서드의 Contains .NET Framework 구현은 빈 문자열을 전달할 때 모든 행을 반환하지만 SQL Server Compact 4.0에 대한 Entity Framework 공급자는 빈 문자열에 대해 0개의 행을 반환합니다. 따라서 예제의 코드(문 안에 if 문을 배치Where)를 사용하면 모든 버전의 SQL Server 동일한 결과를 얻을 수 있습니다. 또한 메서드의 Contains .NET Framework 구현은 기본적으로 대/소문자를 구분하는 비교를 수행하지만 Entity Framework SQL Server 공급자는 기본적으로 대/소문자를 구분하지 않는 비교를 수행합니다. 따라서 테스트를 명시적으로 대/소문자를 구분하지 않도록 메서드를 호출 ToUpper 하면 나중에 리포지토리를 사용하도록 코드를 변경할 때 결과가 변경되지 않습니다. 그러면 개체 대신 IQueryable 컬렉션이 반환 IEnumerable 됩니다. (IEnumerable 컬렉션에서 Contains 메서드를 호출하면 .NET Framework 구현을 가져오고 IQueryable 개체에서 호출하면 데이터베이스 공급자 구현을 가져옵니다.)

Null 처리는 다른 데이터베이스 공급자 또는 컬렉션을 사용하는 경우와 개체를 IQueryable 사용하는 IEnumerable 경우에도 다를 수 있습니다. 예를 들어 일부 시나리오 Where 에서는 와 같은 table.Column != 0 조건이 값으로 있는 null 열을 반환하지 않을 수 있습니다. 기본적으로 EF는 메모리에서 작동하는 것처럼 데이터베이스에서 null 값이 동일하게 작동하도록 추가 SQL 연산자를 생성하지만 EF6에서 UseDatabaseNullSemantics 플래그를 설정하거나 EF Core에서 UseRelationalNulls 메서드를 호출하여 이 동작을 구성할 수 있습니다.

학생 인덱스 보기에 검색 상자 추가

  1. Views\Student\Index.cshtml에서 열린 태그 바로 앞에 table 강조 표시된 코드를 추가하여 캡션, 텍스트 상자 및 검색 단추를 만듭니다.

    <p>
        @Html.ActionLink("Create New", "Create")
    </p>
    
    @using (Html.BeginForm())
    {
        <p>
            Find by name: @Html.TextBox("SearchString")  
            <input type="submit" value="Search" /></p>
    }
    
    <table>
        <tr>
    
  2. 페이지를 실행하고 검색 문자열을 입력한 다음 검색 을 클릭하여 필터링이 작동하는지 확인합니다.

    URL에 "an" 검색 문자열이 포함되어 있지 않습니다. 즉, 이 페이지에 책갈피를 지정하면 책갈피를 사용할 때 필터링된 목록이 표시되지 않습니다. 전체 목록을 정렬하므로 열 정렬 링크에도 적용됩니다. 자습서의 뒷부분에서 필터 조건에 쿼리 문자열을 사용하도록 검색 단추를 변경합니다.

페이징 추가

학생 인덱스 페이지에 페이징을 추가하려면 먼저 PagedList.Mvc NuGet 패키지를 설치합니다. 그런 다음 메서드를 추가로 변경하고 보기에 Index 페이징 링크를 Index 추가합니다. PagedList.Mvc 는 ASP.NET MVC에 대한 많은 좋은 페이징 및 정렬 패키지 중 하나이며, 여기에서 사용하는 것은 다른 옵션에 대한 권장 사항이 아니라 예제로만 사용됩니다.

PagedList.MVC NuGet 패키지 설치

NuGet PagedList.Mvc 패키지는 PagedList 패키지를 종속성으로 자동으로 설치합니다. PagedList 패키지는 및 IEnumerable 컬렉션에 PagedList 대한 컬렉션 형식 및 확장 메서드를 IQueryable 설치합니다. 확장 메서드는 또는 IEnumerable에서 컬렉션 IQueryablePagedList 단일 데이터 페이지를 만들고 컬렉션은 페이징을 PagedList 용이하게 하는 여러 속성과 메서드를 제공합니다. PagedList.Mvc 패키지는 페이징 단추를 표시하는 페이징 도우미를 설치합니다.

  1. 도구 메뉴에서 NuGet 패키지 관리자를 선택한 다음 패키지 관리자 콘솔을 선택합니다.

  2. 패키지 관리자 콘솔 창에서 패키지 원본nuget.org 기본 프로젝트가ContosoUniversity인지 확인하고 다음 명령을 입력합니다.

    Install-Package PagedList.Mvc
    
  3. 프로젝트를 빌드합니다.

참고

PageList 패키지는 더 이상 유지 관리되지 않습니다. 따라서 현재 프로젝트의 경우 X.PagedList 패키지를 사용하는 것이 좋습니다. 기본 차이점은 X.PagedList가 이식 가능한 어셈블리라는 점입니다. 즉, 패키지는 플랫폼 간이며 웹 프로젝트뿐만 아니라 다른 .NET 프로젝트에도 사용할 수 있습니다. 새 패키지는 버전 8.4 이후 .NET 6으로 이식되었기 때문에 호환성 문제가 발생하지 않아야 합니다.

인덱스 메서드에 페이징 기능 추가

  1. Controllers\StudentController.cs에서 네임스페이 usingPagedList 스에 대한 문을 추가합니다.

    using PagedList;
    
  2. Index 메서드를 다음 코드로 바꿉니다.

    public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
    {
       ViewBag.CurrentSort = sortOrder;
       ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
       ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";
    
       if (searchString != null)
       {
          page = 1;
       }
       else
       {
          searchString = currentFilter;
       }
    
       ViewBag.CurrentFilter = searchString;
    
       var students = from s in db.Students
                      select s;
       if (!String.IsNullOrEmpty(searchString))
       {
          students = students.Where(s => s.LastName.Contains(searchString)
                                 || s.FirstMidName.Contains(searchString));
       }
       switch (sortOrder)
       {
          case "name_desc":
             students = students.OrderByDescending(s => s.LastName);
             break;
          case "Date":
             students = students.OrderBy(s => s.EnrollmentDate);
             break;
          case "date_desc":
             students = students.OrderByDescending(s => s.EnrollmentDate);
             break;
          default:  // Name ascending 
             students = students.OrderBy(s => s.LastName);
             break;
       }
    
       int pageSize = 3;
       int pageNumber = (page ?? 1);
       return View(students.ToPagedList(pageNumber, pageSize));
    }
    

    이 코드는 page 매개 변수, 현재 정렬 순서 매개 변수 및 현재 필터 매개 변수를 메서드 서명에 추가합니다.

    public ActionResult Index(string sortOrder, string currentFilter, string searchString, int? page)
    

    페이지가 처음 표시되거나 사용자가 페이징 또는 정렬 링크를 클릭하지 않은 경우 모든 매개 변수가 null입니다. 페이징 링크를 클릭하면 변수에 page 표시할 페이지 번호가 포함됩니다.

    ViewBag 페이징하는 동안 정렬 순서를 동일하게 유지하려면 페이징 링크에 포함되어야 하므로 속성은 현재 정렬 순서로 보기를 제공합니다.

    ViewBag.CurrentSort = sortOrder;
    

    또 다른 속성인 ViewBag.CurrentFilter는 현재 필터 문자열을 사용하여 뷰를 제공합니다. 이 값은 페이징 중 필터 설정을 유지하기 위해 페이징 링크에 포함되어야 하며 페이지를 다시 표시할 때 텍스트 상자에 복원되어야 합니다. 페이징 중에 검색 문자열이 변경되면 새 필터로 인해 다른 데이터가 표시될 수 있으므로 페이지는 1로 재설정되어야 합니다. 텍스트 상자에 값을 입력하고 제출 단추를 누르면 검색 문자열이 변경됩니다. 이 경우 매개 변수는 searchString null이 아닙니다.

    if (searchString != null)
    {
        page = 1;
    }
    else
    {
        searchString = currentFilter;
    }
    

    메서드 ToPagedList 의 끝에서 students 개체의 확장 메서드는 학생 IQueryable 쿼리를 페이징을 지원하는 컬렉션 형식의 학생 단일 페이지로 변환합니다. 그러면 학생의 단일 페이지가 보기로 전달됩니다.

    int pageSize = 3;
    int pageNumber = (page ?? 1);
    return View(students.ToPagedList(pageNumber, pageSize));
    

    ToPagedList 메서드는 페이지 번호를 사용합니다. 두 물음표는 null 병합 연산자를 나타냅니다. Null 병합 연산자는 nullable 형식의 기본값을 정의합니다. (page ?? 1) 식은 값이 있는 경우 page 값을 반환하고 page가 Null이면 1일 반환합니다.

  1. Views\Student\Index.cshtml에서 기존 코드를 다음 코드로 바꿉니다. 변경 내용은 강조 표시되어 있습니다.

    @model PagedList.IPagedList<ContosoUniversity.Models.Student>
    @using PagedList.Mvc;
    <link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
    
    @{
        ViewBag.Title = "Students";
    }
    
    <h2>Students</h2>
    
    <p>
        @Html.ActionLink("Create New", "Create")
    </p>
    @using (Html.BeginForm("Index", "Student", FormMethod.Get))
    {
        <p>
            Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)
            <input type="submit" value="Search" />
        </p>
    }
    <table class="table">
        <tr>
            <th>
                @Html.ActionLink("Last Name", "Index", new { sortOrder = ViewBag.NameSortParm, currentFilter=ViewBag.CurrentFilter })
            </th>
            <th>
                First Name
            </th>
            <th>
                @Html.ActionLink("Enrollment Date", "Index", new { sortOrder = ViewBag.DateSortParm, currentFilter=ViewBag.CurrentFilter })
            </th>
            <th></th>
        </tr>
    
    @foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.LastName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FirstMidName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                @Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
                @Html.ActionLink("Details", "Details", new { id=item.ID }) |
                @Html.ActionLink("Delete", "Delete", new { id=item.ID })
            </td>
        </tr>
    }
    
    </table>
    <br />
    Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount
    
    @Html.PagedListPager(Model, page => Url.Action("Index", 
        new { page, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter }))
    

    페이지 맨 위에 @model 문은 뷰가 List 개체 대신 PagedList 개체를 가져오는 것을 지정합니다.

    에 대한 PagedList.Mvc 문은 using 페이징 단추에 대한 MVC 도우미에 대한 액세스 권한을 제공합니다.

    코드는 FormMethod.Get을 지정할 수 있는 BeginForm의 오버로드를 사용합니다.

    @using (Html.BeginForm("Index", "Student", FormMethod.Get))
    {
        <p>
            Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)  
            <input type="submit" value="Search" />
        </p>
    }
    

    기본 BeginForm 은 POST를 사용하여 양식 데이터를 제출합니다. 즉, 매개 변수는 URL이 아닌 HTTP 메시지 본문에 쿼리 문자열로 전달됩니다. HTTP GET을 지정하면 폼 데이터가 URL에 쿼리 문자열로 전달되고 이를 통해 사용자는 URL을 책갈피로 지정할 수 있습니다. HTTP GET 사용에 대한 W3C 지침은 작업이 업데이트되지 않는 경우 GET을 사용하는 것이 좋습니다.

    텍스트 상자는 현재 검색 문자열로 초기화되므로 새 페이지를 클릭하면 현재 검색 문자열을 볼 수 있습니다.

    Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)
    

    열 머리글 링크는 쿼리 문자열을 사용하여 현재 검색 문자열을 컨트롤러에 전달하므로 사용자가 필터 결과 내에서 정렬할 수 있습니다.

    @Html.ActionLink("Last Name", "Index", new { sortOrder=ViewBag.NameSortParm, currentFilter=ViewBag.CurrentFilter })
    

    현재 페이지와 총 페이지 수가 표시됩니다.

    Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount
    

    표시할 페이지가 없으면 "페이지 0/0"이 표시됩니다. (이 경우 는 1 Model.PageCount 이고 는 0이므로 페이지 번호가 페이지 수보다 큽니다Model.PageNumber.)

    페이징 단추는 도우미가 PagedListPager 표시합니다.

    @Html.PagedListPager( Model, page => Url.Action("Index", new { page }) )
    

    도우미는 PagedListPager URL 및 스타일 지정을 포함하여 사용자 지정할 수 있는 다양한 옵션을 제공합니다. 자세한 내용은 GitHub 사이트의 TroyGoode/PagedList 를 참조하세요.

  2. 페이지를 실행합니다.

    다른 정렬 순서의 페이징 링크를 클릭하여 페이징이 작동하는지 확인합니다. 그런 다음, 검색 문자열을 입력하고 페이징을 다시 시도하여 정렬 및 필터링을 통해 페이징이 제대로 작동하는지 확인합니다.

정보 페이지 만들기

Contoso University 웹 사이트의 정보 페이지에는 각 등록 날짜에 등록된 학생 수가 표시됩니다. 여기에는 그룹화와 그룹에 대한 간단한 계산이 필요합니다. 이 작업을 수행하기 위해 다음을 수행합니다.

  • 뷰에 전달해야 하는 데이터에 대해 뷰 모델 클래스를 만듭니다.
  • 컨트롤러에서 About 메서드를 수정합니다 Home .
  • 보기를 수정합니다 About .

보기 모델 만들기

프로젝트 폴더에 ViewModels 폴더를 만듭니다. 해당 폴더에서 클래스 파일 EnrollmentDateGroup.cs를 추가하고 템플릿 코드를 다음 코드로 바꿉니다.

using System;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.ViewModels
{
    public class EnrollmentDateGroup
    {
        [DataType(DataType.Date)]
        public DateTime? EnrollmentDate { get; set; }

        public int StudentCount { get; set; }
    }
}

홈 컨트롤러 수정

  1. HomeController.cs에서 파일 맨 위에 다음 using 문을 추가합니다.

    using ContosoUniversity.DAL;
    using ContosoUniversity.ViewModels;
    
  2. 클래스의 여는 중괄호 바로 뒤에 데이터베이스 컨텍스트에 대한 클래스 변수를 추가합니다.

    public class HomeController : Controller
    {
        private SchoolContext db = new SchoolContext();
    
  3. About 메서드를 다음 코드로 바꿉니다.

    public ActionResult About()
    {
        IQueryable<EnrollmentDateGroup> data = from student in db.Students
                   group student by student.EnrollmentDate into dateGroup
                   select new EnrollmentDateGroup()
                   {
                       EnrollmentDate = dateGroup.Key,
                       StudentCount = dateGroup.Count()
                   };
        return View(data.ToList());
    }
    

    LINQ 문은 등록 날짜별로 학생 엔터티를 그룹화하고 각 그룹의 엔터티 수를 계산하며 결과를 EnrollmentDateGroup 뷰 모델 개체의 컬렉션에 저장합니다.

  4. 메서드를 추가합니다.Dispose

    protected override void Dispose(bool disposing)
    {
        db.Dispose();
        base.Dispose(disposing);
    }
    

정보 뷰 수정

  1. Views\Home\About.cshtml 파일의 코드를 다음 코드로 바꿉니다.

    @model IEnumerable<ContosoUniversity.ViewModels.EnrollmentDateGroup>
               
    @{
        ViewBag.Title = "Student Body Statistics";
    }
    
    <h2>Student Body Statistics</h2>
    
    <table>
        <tr>
            <th>
                Enrollment Date
            </th>
            <th>
                Students
            </th>
        </tr>
    
    @foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                @item.StudentCount
            </td>
        </tr>
    }
    </table>
    
  2. 앱을 실행하고 정보 링크를 클릭합니다.

    각 등록 날짜에 대한 학생 수가 테이블에 표시됩니다.

    About_page

코드 가져오기

완료된 프로젝트 다운로드

추가 리소스

다른 Entity Framework 리소스에 대한 링크는 ASP.NET 데이터 액세스 - 권장 리소스에서 찾을 수 있습니다.

다음 단계

이 자습서에서는 다음과 같은 작업을 수행합니다.

  • 열 정렬 링크 추가
  • 검색 상자 추가
  • 페이징 추가
  • 정보 페이지 만들기

다음 문서로 이동하여 연결 복원력 및 명령 가로채기를 사용하는 방법을 알아봅니다.