ASP.NET Core의 보기 구성 요소View components in ASP.NET Core

작성자: Rick AndersonBy Rick Anderson

예제 코드 살펴보기 및 다운로드(다운로드 방법)View or download sample code (how to download)

뷰 구성 요소View components

뷰 구성 요소는 부분 보기와 유사하지만 훨씬 강력합니다.View components are similar to partial views, but they're much more powerful. 뷰 구성 요소는 모델 바인딩을 사용하지 않으며 호출할 때 제공되는 데이터에만 의존합니다.View components don't use model binding, and only depend on the data provided when calling into it. 이 아티클은 ASP.NET Core MVC를 사용하여 작성되었지만 뷰 구성 요소는 Razor 페이지로 작업합니다.This article was written using ASP.NET Core MVC, but view components also work with Razor Pages.

뷰 구성 요소:A view component:

  • 전체 응답보다는 청크를 렌더링합니다.Renders a chunk rather than a whole response.
  • 컨트롤러 및 뷰 간에 확인할 수 있는 동일한 개념 분리 및 테스트 가능성 이점을 포함합니다.Includes the same separation-of-concerns and testability benefits found between a controller and view.
  • 매개 변수 및 비즈니스 논리를 포함할 수 있습니다.Can have parameters and business logic.
  • 일반적으로 레이아웃 페이지에서 호출됩니다.Is typically invoked from a layout page.

뷰 구성 요소는 다음과 같이 부분 뷰에 대해 너무 복잡한 재사용 가능한 렌더링 논리를 포함하는 모든 곳에서 사용할 수 있습니다.View components are intended anywhere you have reusable rendering logic that's too complex for a partial view, such as:

  • 동적 탐색 메뉴Dynamic navigation menus
  • 태그 클라우드(여기서는 데이터베이스를 쿼리)Tag cloud (where it queries the database)
  • 로그인 패널Login panel
  • 쇼핑 카트Shopping cart
  • 최근에 게시된 문서Recently published articles
  • 일반적인 블로그에서 추가 기사 콘텐츠Sidebar content on a typical blog
  • 모든 페이지에 렌더링되고 사용자의 로그인 상태에 따라 로그아웃 또는 로그인하는 링크를 표시하는 로그인 패널A login panel that would be rendered on every page and show either the links to log out or log in, depending on the log in state of the user

뷰 구성 요소는 클래스(일반적으로 ViewComponent에서 파생됨)와 반환되는 결과(일반적으로 뷰)의 두 부분으로 구성됩니다.A view component consists of two parts: the class (typically derived from ViewComponent) and the result it returns (typically a view). 컨트롤러와 마찬가지로, 뷰 구성 요소는 POCO일 수 있지만 대부분의 개발자는 ViewComponent에서 파생되어 사용 가능한 메서드와 속성을 활용하려고 합니다.Like controllers, a view component can be a POCO, but most developers will want to take advantage of the methods and properties available by deriving from ViewComponent.

뷰 구성 요소 만들기Creating a view component

이 섹션에서는 뷰 구성 요소를 만들기 위한 전반적인 요구 사항이 포함되어 있습니다.This section contains the high-level requirements to create a view component. 이 문서의 뒷부분에서는 각 단계를 자세히 검토하고 뷰 구성 요소를 만듭니다.Later in the article, we'll examine each step in detail and create a view component.

뷰 구성 요소 클래스The view component class

다음 방법으로 뷰 구성 요소 클래스를 만들 수 있습니다.A view component class can be created by any of the following:

  • ViewComponent에서 파생Deriving from ViewComponent
  • [ViewComponent] 특성으로 클래스 데코레이팅 또는 [ViewComponent] 특성을 사용하여 클래스에서 파생Decorating a class with the [ViewComponent] attribute, or deriving from a class with the [ViewComponent] attribute
  • 이름이 ViewComponent 접미사로 끝나는 클래스 만들기Creating a class where the name ends with the suffix ViewComponent

컨트롤러와 마찬가지로, 뷰 구성 요소는 공용이고 비중첩 및 비추상 클래스여야 합니다.Like controllers, view components must be public, non-nested, and non-abstract classes. 뷰 구성 요소 이름은 "ViewComponent" 접미사가 제거된 클래스 이름입니다.The view component name is the class name with the "ViewComponent" suffix removed. 또한 ViewComponentAttribute.Name 속성을 사용하여 명시적으로 지정할 수도 있습니다.It can also be explicitly specified using the ViewComponentAttribute.Name property.

뷰 구성 요소 클래스:A view component class:

  • 생성자 종속성 주입을 완전히 지원합니다.Fully supports constructor dependency injection

  • 컨트롤러 수명 주기를 따르지 않습니다. 즉, 뷰 구성 요소에 필터를 사용할 수 없습니다.Doesn't take part in the controller lifecycle, which means you can't use filters in a view component

뷰 구성 요소 메서드View component methods

뷰 구성 요소는 해당 논리를 IViewComponentResult를 반환하는 InvokeAsync 메서드에 정의합니다.A view component defines its logic in an InvokeAsync method that returns an IViewComponentResult. 매개 변수는 모델 바인딩이 아닌 뷰 구성 요소 호출에서 직접 가져옵니다.Parameters come directly from invocation of the view component, not from model binding. 뷰 구성 요소는 요청을 직접 처리하지 않습니다.A view component never directly handles a request. 일반적으로 뷰 구성 요소는 모델을 초기화하고 View 메서드를 호출하여 뷰에 전달합니다.Typically, a view component initializes a model and passes it to a view by calling the View method. 요약하자면, 뷰 구성 요소 메서드는 다음과 같습니다.In summary, view component methods:

  • IViewComponentResult를 반환하는 InvokeAsync 메서드를 정의합니다.Define an InvokeAsync method that returns an IViewComponentResult
  • 일반적으로 모델을 초기화하고 ViewComponent View 메서드를 호출하여 뷰에 전달합니다.Typically initializes a model and passes it to a view by calling the ViewComponent View method
  • 매개 변수는 HTTP가 아닌 호출 메서드에서 가져오며 모델 바인딩이 없습니다.Parameters come from the calling method, not HTTP, there's no model binding
  • HTTP 끝점으로 직접 연결할 수 없으며 코드(일반적으로 뷰에서)에서 호출됩니다.Are not reachable directly as an HTTP endpoint, they're invoked from your code (usually in a view). 뷰 구성 요소는 요청을 처리하지 않습니다.A view component never handles a request
  • 현재 HTTP 요청의 세부 정보가 아닌 서명에 오버로드됩니다.Are overloaded on the signature rather than any details from the current HTTP request

뷰 검색 경로View search path

런타임은 다음 경로에서 뷰를 검색합니다.The runtime searches for the view in the following paths:

  • Views/<controller_name>/Components/<view_component_name>/<view_name>Views/<controller_name>/Components/<view_component_name>/<view_name>
  • Views/Shared/Components/<view_component_name>/<view_name>Views/Shared/Components/<view_component_name>/<view_name>

뷰 구성 요소에 대한 기본 뷰 이름은 Default이며 이것은 일반적으로 뷰 파일의 이름이 Default.cshtml로 지정됨을 의미합니다.The default view name for a view component is Default, which means your view file will typically be named Default.cshtml. 뷰 구성 요소 결과를 만들거나 View 메서드를 호출할 때 다른 뷰 이름을 지정할 수 있습니다.You can specify a different view name when creating the view component result or when calling the View method.

뷰 파일 이름을 Default.cshtml로 지정하고 Views/Shared/Components/<view_component_name>/<view_name> 경로를 사용하는 것이 좋습니다.We recommend you name the view file Default.cshtml and use the Views/Shared/Components/<view_component_name>/<view_name> path. 이 샘플에 사용된 PriorityList 뷰 구성 요소는 뷰 구성 요소 보기에 Views/Shared/Components/PriorityList/Default.cshtml을 사용합니다.The PriorityList view component used in this sample uses Views/Shared/Components/PriorityList/Default.cshtml for the view component view.

뷰 구성 요소 호출Invoking a view component

뷰 구성 요소를 사용하려면 뷰 내에서 다음을 호출합니다.To use the view component, call the following inside a view:

@Component.InvokeAsync("Name of view component", <anonymous type containing parameters>)

매개 변수가 InvokeAsync 메서드에 전달됩니다.The parameters will be passed to the InvokeAsync method. 문서에서 개발된 PriorityList 뷰 구성 요소가 Views/Todo/Index.cshtml 뷰 파일에서 호출됩니다.The PriorityList view component developed in the article is invoked from the Views/Todo/Index.cshtml view file. 다음에서 InvokeAsync 메서드는 두 매개 변수를 사용하여 호출됩니다.In the following, the InvokeAsync method is called with two parameters:

@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })

태그 도우미로 뷰 구성 요소 호출Invoking a view component as a Tag Helper

ASP.NET Core 1.1 이상에서는 뷰 구성 요소를 태그 도우미로 호출할 수 있습니다.For ASP.NET Core 1.1 and higher, you can invoke a view component as a Tag Helper:

<vc:priority-list max-priority="2" is-done="false">
</vc:priority-list>

태그 도우미에 대한 파스칼식 클래스 및 메서드 매개 변수가 kebab 소문자로 번역됩니다.Pascal-cased class and method parameters for Tag Helpers are translated into their lower kebab case. 뷰 구성 요소를 호출하는 태그 도우미는 <vc></vc> 요소를 사용합니다.The Tag Helper to invoke a view component uses the <vc></vc> element. 뷰 구성 요소는 다음과 같이 지정됩니다.The view component is specified as follows:

<vc:[view-component-name]
  parameter1="parameter1 value"
  parameter2="parameter2 value">
</vc:[view-component-name]>

참고: 태그 도우미로 뷰 구성 요소를 사용하려면 @addTagHelper 지시문을 사용하여 뷰 구성 요소가 포함된 어셈블리를 등록해야 합니다.Note: In order to use a View Component as a Tag Helper, you must register the assembly containing the View Component using the @addTagHelper directive. 예를 들어 뷰 구성 요소가 "MyWebApp"이라는 어셈블리에 있으면 다음 지시문을 _ViewImports.cshtml 파일에 추가합니다.For example, if your View Component is in an assembly called "MyWebApp", add the following directive to the _ViewImports.cshtml file:

@addTagHelper *, MyWebApp

뷰 구성 요소를 참조하는 모든 파일에 태그 도우미로 뷰 구성 요소를 등록할 수 있습니다.You can register a View Component as a Tag Helper to any file that references the View Component. 태그 도우미를 등록하는 방법에 대한 자세한 내용은 태그 도우미 범위 관리를 참조하세요.See Managing Tag Helper Scope for more information on how to register Tag Helpers.

이 자습서에 사용된 InvokeAsync 메서드:The InvokeAsync method used in this tutorial:

@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })

태그 도우미 태그에서:In Tag Helper markup:

<vc:priority-list max-priority="2" is-done="false">
</vc:priority-list>

위의 샘플에서 PriorityList 뷰 구성 요소는 priority-list가 됩니다.In the sample above, the PriorityList view component becomes priority-list. 뷰 구성 요소에 대한 매개 변수는 kebab 소문자의 특성으로 전달됩니다.The parameters to the view component are passed as attributes in lower kebab case.

컨트롤러에서 뷰 구성 요소 직접 호출Invoking a view component directly from a controller

일반적으로 뷰 구성 요소는 뷰에서 호출되지만 컨트롤러 메서드에서 직접 호출할 수 있습니다.View components are typically invoked from a view, but you can invoke them directly from a controller method. 뷰 구성 요소는 컨트롤러와 같은 끝점을 정의하지 않지만 ViewComponentResult의 내용을 반환하는 컨트롤러 동작을 쉽게 구현할 수 있습니다.While view components don't define endpoints like controllers, you can easily implement a controller action that returns the content of a ViewComponentResult.

이 예제에서는 뷰 구성 요소가 컨트롤러에서 직접 호출됩니다.In this example, the view component is called directly from the controller:

public IActionResult IndexVC()
{
    return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
}

연습: 간단한 뷰 구성 요소 만들기Walkthrough: Creating a simple view component

시작 코드를 다운로드, 빌드 및 테스트합니다.Download, build and test the starter code. Todo 항목의 목록을 표시하는 Todo 컨트롤러가 포함된 간단한 프로젝트입니다.It's a simple project with a Todo controller that displays a list of Todo items.

ToDo 목록

ViewComponent 클래스 추가Add a ViewComponent class

ViewComponents 폴더를 만들고 다음 PriorityListViewComponent 클래스를 추가합니다.Create a ViewComponents folder and add the following PriorityListViewComponent class:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents
{
    public class PriorityListViewComponent : ViewComponent
    {
        private readonly ToDoContext db;

        public PriorityListViewComponent(ToDoContext context)
        {
            db = context;
        }

        public async Task<IViewComponentResult> InvokeAsync(
        int maxPriority, bool isDone)
        {
            var items = await GetItemsAsync(maxPriority, isDone);
            return View(items);
        }
        private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
        {
            return db.ToDo.Where(x => x.IsDone == isDone &&
                                 x.Priority <= maxPriority).ToListAsync();
        }
    }
}

코드에 대한 참고 사항:Notes on the code:

  • 뷰 구성 요소 클래스는 프로젝트의 모든 폴더에 포함될 수 있습니다.View component classes can be contained in any folder in the project.

  • 클래스 이름 PriorityListViewComponentViewComponent 접미사로 끝나기 때문에 런타임은 뷰에서 클래스 구성 요소를 참조할 때 "PriorityList" 문자열을 사용합니다.Because the class name PriorityListViewComponent ends with the suffix ViewComponent, the runtime will use the string "PriorityList" when referencing the class component from a view. 나중에 보다 자세히 설명하겠습니다.I'll explain that in more detail later.

  • [ViewComponent] 특성은 뷰 구성 요소를 참조하는 데 사용된 이름을 변경할 수 있습니다.The [ViewComponent] attribute can change the name used to reference a view component. 예를 들어 클래스 XYZ의 이름을 지정하고 ViewComponent 특성을 적용할 수 있습니다.For example, we could've named the class XYZ and applied the ViewComponent attribute:

    [ViewComponent(Name = "PriorityList")]
       public class XYZ : ViewComponent
    
  • 위의 [ViewComponent] 특성은 구성 요소와 연관된 뷰를 찾을 때 PriorityList 이름을 사용하고, 뷰에서 클래스 구성 요소를 참조할 때 "PriorityList" 문자열을 사용하도록 뷰 구성 요소 선택기에 지시합니다.The [ViewComponent] attribute above tells the view component selector to use the name PriorityList when looking for the views associated with the component, and to use the string "PriorityList" when referencing the class component from a view. 나중에 보다 자세히 설명하겠습니다.I'll explain that in more detail later.

  • 이 구성 요소에서는 종속성 주입을 사용하여 데이터 컨텍스트를 사용할 수 있도록 합니다.The component uses dependency injection to make the data context available.

  • InvokeAsync는 뷰에서 호출할 수 있는 메서드를 노출하며 임의 수의 인수를 사용할 수 있습니다.InvokeAsync exposes a method which can be called from a view, and it can take an arbitrary number of arguments.

  • InvokeAsync 메서드는 isDonemaxPriority 매개변수를 충족하는 ToDo 항목 집합을 반환합니다.The InvokeAsync method returns the set of ToDo items that satisfy the isDone and maxPriority parameters.

뷰 구성 요소 Razor 뷰 만들기Create the view component Razor view

  • Views/Shared/Components 폴더를 만듭니다.Create the Views/Shared/Components folder. 이 폴더의 이름은 Components지정되어야 합니다.This folder must be named Components.

  • Views/Shared/Components/PriorityList 폴더를 만듭니다.Create the Views/Shared/Components/PriorityList folder. 이 폴더 이름은 뷰 구성 요소 클래스의 이름 또는 클래스 이름에서 접미사를 뺀 이름과 일치해야 합니다(규칙을 준수하고 클래스 이름에 ViewComponent 접미사를 사용한 경우).This folder name must match the name of the view component class, or the name of the class minus the suffix (if we followed convention and used the ViewComponent suffix in the class name). ViewComponent 특성을 사용한 경우 클래스 이름은 특성 지정과 일치해야 합니다.If you used the ViewComponent attribute, the class name would need to match the attribute designation.

  • Views/Shared/Components/PriorityList/Default.cshtml Razor 뷰를 만듭니다. [!code-cshtml]Create a Views/Shared/Components/PriorityList/Default.cshtml Razor view: [!code-cshtml]

    Razor 뷰는 TodoItem 목록을 가져와 표시합니다.The Razor view takes a list of TodoItem and displays them. 뷰 구성 요소 InvokeAsync 메서드가 뷰의 이름을 전달하지 않은 경우(샘플에서처럼) 규칙에 따라 뷰 이름으로 Default가 사용됩니다.If the view component InvokeAsync method doesn't pass the name of the view (as in our sample), Default is used for the view name by convention. 자습서의 뒷부분에서 뷰 이름을 전달하는 방법을 보여 줍니다.Later in the tutorial, I'll show you how to pass the name of the view. 특정 컨트롤러에 대한 기본 스타일 지정을 재정의하려면 컨트롤러 관련 뷰 폴더에 뷰를 추가합니다(예를 들어 Views/Todo/Components/PriorityList/Default.cshtml).To override the default styling for a specific controller, add a view to the controller-specific view folder (for example Views/Todo/Components/PriorityList/Default.cshtml).

    뷰 구성 요소가 컨트롤러에 관한 것이면 컨트롤러 관련 폴더에 추가할 수 있습니다(Views/Todo/Components/PriorityList/Default.cshtml).If the view component is controller-specific, you can add it to the controller-specific folder (Views/Todo/Components/PriorityList/Default.cshtml).

  • 우선 순위 목록 구성 요소에 대한 호출을 포함하는 divViews/Todo/index.cshtml 파일 맨 아래에 추가합니다.Add a div containing a call to the priority list component to the bottom of the Views/Todo/index.cshtml file:

    </table>
    <div>
        @await Component.InvokeAsync("PriorityList", new { maxPriority = 2, isDone = false })
    </div>
    

@await Component.InvokeAsync 태그는 호출하는 뷰 구성 요소에 대한 구문을 보여 줍니다.The markup @await Component.InvokeAsync shows the syntax for calling view components. 첫 번째 인수는 호출하려는 구성 요소의 이름입니다.The first argument is the name of the component we want to invoke or call. 후속 매개 변수는 구성 요소에 전달됩니다.Subsequent parameters are passed to the component. InvokeAsync는 임의 개수의 인수를 사용할 수 있습니다.InvokeAsync can take an arbitrary number of arguments.

앱을 테스트합니다.Test the app. 다음 이미지는 ToDo 목록 및 우선 순위 항목을 보여 줍니다.The following image shows the ToDo list and the priority items:

todo 목록 및 우선 순위 항목

또한 컨트롤러에서 직접 뷰 구성 요소를 호출할 수도 있습니다.You can also call the view component directly from the controller:

public IActionResult IndexVC()
{
    return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
}

IndexVC 작업에서 우선 순위 항목

뷰 이름 지정Specifying a view name

복잡한 뷰 구성 요소에는 조건에 따라 기본값이 아닌 뷰를 지정해야 할 수 있습니다.A complex view component might need to specify a non-default view under some conditions. 다음 코드에서 InvokeAsync 메서드에서 "PVC" 뷰를 지정하는 방법을 보여 줍니다.The following code shows how to specify the "PVC" view from the InvokeAsync method. PriorityListViewComponent 클래스에서 InvokeAsync 메서드를 업데이트합니다.Update the InvokeAsync method in the PriorityListViewComponent class.

public async Task<IViewComponentResult> InvokeAsync(
    int maxPriority, bool isDone)
{
    string MyView = "Default";
    // If asking for all completed tasks, render with the "PVC" view.
    if (maxPriority > 3 && isDone == true)
    {
        MyView = "PVC";
    }
    var items = await GetItemsAsync(maxPriority, isDone);
    return View(MyView, items);
}

Views/Shared/Components/PriorityList/Default.cshtml 파일을 Views/Shared/Components/PriorityList/PVC.cshtml이라는 뷰에 복사합니다.Copy the Views/Shared/Components/PriorityList/Default.cshtml file to a view named Views/Shared/Components/PriorityList/PVC.cshtml. PVC 뷰가 사용되었다는 것을 나타내는 제목을 추가합니다.Add a heading to indicate the PVC view is being used.

@model IEnumerable<ViewComponentSample.Models.TodoItem>

<h2> PVC Named Priority Component View</h2>
<h4>@ViewBag.PriorityMessage</h4>
<ul>
    @foreach (var todo in Model)
    {
        <li>@todo.Name</li>
    }
</ul>

Views/TodoList/Index.cshtml을 업데이트합니다.Update Views/TodoList/Index.cshtml:

@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })

앱을 실행하고 PVC 뷰를 확인합니다.Run the app and verify PVC view.

우선 순위 뷰 구성 요소

PVC 뷰가 렌더링되지 않는 경우 우선 순위가 4 이상인 뷰 구성 요소를 호출하고 있는지 확인합니다.If the PVC view isn't rendered, verify you are calling the view component with a priority of 4 or higher.

뷰 경로 검사Examine the view path

  • 우선 순위 뷰가 반환되지 않도록 우선 순위 매개 변수를 3 이하로 변경합니다.Change the priority parameter to three or less so the priority view isn't returned.

  • Views/Todo/Components/PriorityList/Default.cshtml의 이름을 1Default.cshtml로 임시로 변경합니다.Temporarily rename the Views/Todo/Components/PriorityList/Default.cshtml to 1Default.cshtml.

  • 앱을 테스트하면 다음과 같은 오류가 발생합니다.Test the app, you'll get the following error:

    An unhandled exception occurred while processing the request.
    InvalidOperationException: The view 'Components/PriorityList/Default' wasn't found. The following locations were searched:
    /Views/ToDo/Components/PriorityList/Default.cshtml
    /Views/Shared/Components/PriorityList/Default.cshtml
    EnsureSuccessful
    
  • Views/Todo/Components/PriorityList/1Default.cshtmlViews/Shared/Components/PriorityList/Default.cshtml에 복사합니다.Copy Views/Todo/Components/PriorityList/1Default.cshtml to Views/Shared/Components/PriorityList/Default.cshtml.

  • Shared Todo 뷰 구성 요소 보기에 일부 태그를 추가하여 뷰가 Shared 폴더에 있음을 나타냅니다.Add some markup to the Shared Todo view component view to indicate the view is from the Shared folder.

  • Shared 구성 요소 뷰를 테스트합니다.Test the Shared component view.

공유 구성 요소 뷰가 있는 ToDo 출력

매직 문자열 방지Avoiding magic strings

컴파일 시간 안전성을 원하는 경우 하드 코드된 뷰 구성 요소 이름을 클래스 이름으로 바꿀 수 있습니다.If you want compile time safety, you can replace the hard-coded view component name with the class name. "ViewComponent" 접미사 없이 뷰 구성 요소를 만듭니다.Create the view component without the "ViewComponent" suffix:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents
{
    public class PriorityList : ViewComponent
    {
        private readonly ToDoContext db;

        public PriorityList(ToDoContext context)
        {
            db = context;
        }

        public async Task<IViewComponentResult> InvokeAsync(
        int maxPriority, bool isDone)
        {
            var items = await GetItemsAsync(maxPriority, isDone);
            return View(items);
        }
        private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
        {
            return db.ToDo.Where(x => x.IsDone == isDone &&
                                 x.Priority <= maxPriority).ToListAsync();
        }
    }
}

using 문을 Razor 뷰 파일에 추가하고 nameof 연산자를 사용합니다.Add a using statement to your Razor view file, and use the nameof operator:

@using ViewComponentSample.Models
@using ViewComponentSample.ViewComponents
@model IEnumerable<TodoItem>

<h2>ToDo nameof</h2>
<!-- Markup removed for brevity.  -->

<div>

    @await Component.InvokeAsync(nameof(PriorityList), new { maxPriority = 4, isDone = true })
</div>

추가 자료Additional resources