뷰: 뷰 구성 요소
- 본 번역문서의 원문은 View Components docs.asp.net 입니다.
- 본 번역문서는 MVC : View - 뷰 컴포넌트(View Component) www.taeyo.net 에서도 함께 제공됩니다.
주의
이 페이지의 문서는 버전 1.0.0-rc1을 기준으로 작성되었으며, 아직 버전 1.0.0으로 갱신되지 않았습니다.
뷰 구성 요소 소개
ASP.NET Core MVC에서 새로 도입된 뷰 구성 요소(View Components)는 부분 뷰와 매우 비슷하지만, 훨씬 더 강력한 기능입니다. 뷰 구성 요소는 모델 바인딩을 사용하지 않으며, 뷰 구성 요소를 호출하면서 제공되는 데이터만 사용합니다. 뷰 구성 요소는:
- 전체 응답이 아닌 청크(Chunk)를 렌더합니다.
- 컨트롤러와 뷰에서 제공되는 것과 같은 관심사의 분리 및 테스트 용이성의 장점을 제공합니다.
- 매개변수를 전달 받을 수 있고, 업무 로직을 포함할 수 있습니다.
- 대부분 레이아웃 페이지에서 호출됩니다.
뷰 구성 요소는 다음과 같이 부분 뷰로 구현하기에는 너무 복잡하지만, 재사용 가능한 렌더링 로직이 필요한 상황 어디에서나 사용할 수 있습니다:
- 동적 탐색 메뉴
- 태그 클라우드 (데이터베이스 질의가 필요합니다.)
- 로그인 패널
- 쇼핑카트
- 최근 배포된 글
- 일반적인 블로그의 사이드바 콘텐츠
- 모든 페이지에 렌더되어 사용자의 로그인 상태에 따라 로그인 링크나 로그아웃 링크를 제공해주는 로그인 패널
뷰 구성 요소는 클래스(일반적으로 ViewComponent
를 상속받습니다)와 그 클래스가 반환하는 결과 (일반적으로 뷰), 이렇게 두 가지 부분으로 구성됩니다.
뷰 구성 요소도 컨트롤러처럼 POCO로 구현할 수는 있지만, 대부분 개발자들은 ViewComponent
클래스를 상속받아서 제공되는 메서드와 속성들을 활용하는 방식을 선호합니다.
뷰 구성 요소 구현하기
이번 절에서는 뷰 구성 요소를 구현하기 위한 전반적인 요구 사항들을 살펴봅니다. 본문의 이후 절에서는 각 단계들을 보다 자세히 살펴보면서 뷰 구성 요소를 직접 구현해 볼 것입니다.
뷰 구성 요소 클래스
뷰 구성 요소 클래스는 다음 중 한 가지 방법으로 생성할 수 있습니다:
- ViewComponent 클래스를 상속받습니다.
-
클래스에
[ViewComponent]
어트리뷰트를 적용하거나,[ViewComponent]
어트리뷰트가 적용된 클래스를 상속받습니다. - 클래스 이름의 접미사로 ViewComponent 가 지정된 클래스를 생성합니다.
뷰 구성 요소는 컨트롤러와 마찬가지로 public이고, 중첩되지 않은, 비 추상 클래스로 만들어져야 합니다. 뷰 구성 요소의 이름은 클래스 이름에서 "ViewComponent" 접미사를 제외한 부분입니다. 또는 ViewComponentAttribute.Name 속성을 설정해서 뷰 구성 요소의 이름을 명시적으로 지정할 수도 있습니다.
뷰 구성 요소 클래스는:
뷰 구성 요소 메서드
뷰 구성 요소는 IViewComponentResult 형식을 반환하는 InvokeAsync
메서드에 로직을 정의합니다.
또한 모델 바인딩을 사용하지 않고 뷰 구성 요소를 호출할 때 직접 매개변수를 전달받습니다.
뷰 구성 요소는 요청을 직접 처리하지 않습니다.
뷰 구성 요소는 일반적으로 모델을 초기화한 다음, View 메서드를 호출해서 모델을 뷰로 전달합니다.
뷰 구성 요소의 메서드는:
-
IViewComponentResult
형식을 반환하는 InvokeAsync 메서드를 정의합니다. - 일반적으로 모델을 초기화한 다음, ViewComponent 클래스의 View 메서드를 호출해서 이를 뷰로 전달합니다.
- HTTP가 아닌 메서드 호출을 통해서 매개변수를 전달받으며, 모델 바인딩을 사용하지 않습니다.
- HTTP 끝점의 형태로 직접 호출할 수 없으며, 코드를 통해서 실행됩니다 (일반적으로 뷰 내에서). 뷰 구성 요소는 요청을 처리하지 않습니다.
- 현재 HTTP 요청의 세부 정보가 아닌, 시그니처에 따라 재정의됩니다.
뷰 검색 경로
런타임은 다음과 같은 경로에서 뷰를 검색합니다:
- Views/<controller_name>/Components/<view_component_name>/<view_name>
- Views/Shared/Components/<view_component_name>/<view_name>
뷰 구성 요소의 기본 뷰 이름은 Default 로, 뷰 파일의 이름은 Default.cshtml 이 되는 경우가 많습니다.
물론, 뷰 구성 요소 결과를 생성하거나 View
메서드를 호출할 때, 다른 뷰 이름을 지정할 수도 있습니다.
뷰 파일의 이름으로 Default.cshtml 을 사용하고, 경로로는 Views/Shared/Components/<view_component_name>/<view_name> 을 사용하는 것을 권장합니다.
가령, 본문의 PriorityList
예제에서는 뷰 구성 요소로 Views/Shared/Components/PriorityList/Default.cshtml 을 사용합니다.
뷰 구성 요소 호출하기
뷰 구성 요소를 사용하려면 뷰에서 @Component.InvokeAsync("Name of view component", <anonymous type containing parameters>)
를 호출하면 됩니다.
이때 익명 개체에 지정된 매개변수들은 InvokeAsync
메서드의 매개변수로 전달됩니다.
예를 들어, 본문에서 살펴볼 예제에서는 Views/Todo/Index.cshtml 뷰 파일에서 PriorityList
뷰 구성 요소가 호출됩니다.
가령 다음 코드의 경우, 두 개의 매개변수와 함께 InvokeAsync
메서드가 호출됩니다:
@await Component.InvokeAsync("PriorityList", new { maxPriority = 2, isDone = false })
컨트롤러에서 직접 뷰 구성 요소 호출하기
뷰 구성 요소는 뷰에서 호출되는 경우가 거의 대부분이지만, 컨트롤러 메서드에서 직접 호출할 수도 있습니다. 컨트롤러와 달리 뷰 구성 요소는 끝점을 정의할 수 없지만, ViewComponentResult의 콘텐츠를 반환하는 컨트롤러 액션은 간단하게 구현할 수 있습니다.
본문의 예제 뷰 구성 요소는 다음과 같이 컨트롤러에서 직접 호출될 수 있습니다:
public IActionResult IndexVC()
{
return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
}
따라하기: 간단한 뷰 구성 요소 만들기
먼저, 기본 코드를 다운로드 받아서 빌드하고 테스트해봅니다.
이 프로젝트는 Todo 항목들의 목록을 출력해주는 Todo
컨트롤러를 포함하고 있는 간단한 프로젝트입니다.
ViewComponent 클래스 추가하기
먼저 ViewComponents 폴더를 생성한 다음, 아래와 같은 PriorityListViewComponent
클래스를 추가합니다.
using Microsoft.AspNet.Mvc;
using Microsoft.Data.Entity;
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();
}
}
}
코드의 주요 부분들을 살펴보면:
-
뷰 구성 요소 클래스는 프로젝트 내의 어떤 폴더에도 위치할 수 있습니다.
-
PriorityListViewComponent
라는 클래스 이름은 ViewComponent라는 접미사로 끝나므로, 뷰에서 클래스 구성 요소를 참조할 때 런타임에서 "PriorityList"라는 문자열을 사용하게 됩니다. 잠시 후에 이에 관해서 더 자세히 살펴보도록 하겠습니다. -
[ViewComponent]
어트리뷰트를 적용하면 뷰 구성 요소를 참조할 때 사용되는 이름을 변경할 수 있습니다. 예를 들어서, 다음과 같이XYZ
라는 이름으로 클래스를 만들고ViewComponent
어트리뷰트를 적용할 수 있습니다:[ViewComponent(Name = "PriorityList")] public class XYZ : ViewComponent
-
이 코드에 적용된
[ViewComponent]
어트리뷰트는PriorityList
라는 이름을 사용해서 구성 요소와 관련된 뷰를 검색하도록 뷰 구성 요소 선택기에게 지시하고, 뷰에서 클래스 구성 요소를 참조할 때 "PriorityList"라는 문자열을 사용하도록 만듭니다. 잠시 후에 이에 관해서 더 자세히 살펴보도록 하겠습니다. -
이 구성 요소는 의존성 주입을 통해서 데이터 컨텍스트를 사용하고 있습니다.
-
뷰에서 호출할 수 있는
InvokeAsync
메서드를 노출하고 있으며, 이 메서드는 임의의 숫자 값과 완료 여부를 인자로 받을 수 있습니다. -
본문의 예제에서는 기본적으로
InvokeAsync
메서드를 호출해서, 완료되지 않았으며 우선순위가maxPriority
값과 같거나 작은ToDo
항목들의 집합을 반환합니다.
뷰 구성 요소 Razor 뷰 생성하기
- Views/Shared/Components 폴더를 생성합니다. 이 폴더의 이름은 반드시 Components 여야만 합니다.
-
Views/Shared/Components/PriorityList 폴더를 생성합니다.
이 폴더의 이름은 반드시 뷰 구성 요소 클래스의 이름이나 클래스 이름에서 접미사를 제외한 이름과 (규약에 따라서 클래스 이름에 ViewComponent 접미사를 지정한 경우) 일치해야만 합니다.
만약
ViewComponent
어트리뷰틀 적용했다면, 폴더의 이름과 어트리뷰트에 지정한 이름이 일치해야만 합니다. - Views/Shared/Components/PriorityList/Default.cshtml Razor 뷰를 생성합니다.
@model IEnumerable<ViewComponentSample.Models.TodoItem>
<h3>Priority Items</h3>
<ul>
@foreach (var todo in Model)
{
<li>@todo.Name</li>
}
</ul>
이 Razor 뷰는 TodoItem
의 목록을 전달받고 이를 출력합니다.
뷰 구성 요소의 InvokeAsync
메서드가 뷰의 이름을 전달하지 않으면 (본문의 예제처럼), 규약에 따라 뷰 이름으로 Default 가 사용됩니다.
본문에서는 잠시 후에 뷰의 이름을 명시적으로 전달하는 방법도 살펴볼 것입니다.
특정 컨트롤러에 대한 기본적인 뷰 구성 요소의 마크업 형태를 재정의하려면 컨트롤러 전용 뷰 폴더에 뷰 파일을 추가하면 됩니다
(Views/Todo/Components/PriorityList/Default.cshtml).
뷰 구성 요소가 컨트롤러 전용인 경우에도 컨트롤러 전용 폴더에 뷰를 추가하면 됩니다 (Views/Todo/Components/PriorityList/Default.cshtml).
-
Views/Todo/index.cshtml 파일의 하단에 우선순위 목록 구성 요소에 대한 호출을 담을
div
요소를 추가합니다:
}
</table>
<div>
@await Component.InvokeAsync("PriorityList", new { maxPriority = 2, isDone = false })
</div>
이 코드에서 @Component.InvokeAsync
마크업은 뷰 구성 요소를 호출하는 구문의 사례를 보여줍니다.
첫 번째 인자는 실행하거나 호출하고자 하는 구성 요소의 이름입니다.
이어지는 매개변수들은 구성 요소로 전달됩니다.
이 코드의 InvokeAsync
메서드는 인자로 임의의 숫자와 완료 여부를 전달받을 수 있습니다.
다음 그림은 우선순위의 항목들을 보여줍니다:
다음과 같이 컨트롤러에서 직접 뷰 구성 요소를 호출할 수도 있습니다:
public IActionResult IndexVC()
{
return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
}
뷰 이름 지정하기
복잡한 뷰 구성 요소에서는 일부 조건에 따라서 비-기본 뷰를 지정해야 할 수도 있습니다.
다음 코드는 InvokeAsync
메서드에서 "PVC" 뷰를 지정하는 방법을 보여줍니다.
PriorityListViewComponent
클래스의 InvokeAsync
메서드를 변경합니다.
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 이라는 뷰 이름으로 복사합니다. 그리고 PVC 뷰가 사용되고 있음을 확인할 수 있도록 머리말을 추가합니다.
@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 파일을 수정합니다.
</table>
<div>
@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })
</div>
응용 프로그램을 실행하고 PVC 뷰가 사용되는 것을 확인합니다.
만약 PVC 뷰가 렌더되지 않는다면, 뷰 구성 요소를 호출할 때 [ViewComponent]
인자 값으로 4 이상을 지정했는지 확인하시기 바랍니다.
뷰 경로 살펴보기
-
maxPriority
매개변수를 3 이하로 지정해서 PVC 뷰가 반환되지 않도록 조정합니다. -
그리고 임시로 Views/Todo/Components/PriorityList/Default.cshtml 파일의 이름을 Temp.cshtml 로 변경합니다.
-
이제 응용 프로그램을 테스트 해보면, 다음과 같은 오류가 발생할 것입니다:
An unhandled exception occurred while processing the request. InvalidOperationException: The view 'Components/PriorityList/Default' was not found. The following locations were searched: /Views/ToDo/Components/PriorityList/Default.cshtml /Views/Shared/Components/PriorityList/Default.cshtml. Microsoft.AspNetCore.Mvc.ViewEngines.ViewEngineResult.EnsureSuccessful()
- Views/Shared/Components/PriorityList/Default.cshtml 파일을 Views/Todo/Components/PriorityList/Default.cshtml 로 복사합니다.
- Todo 뷰 구성 요소의 뷰에 간단한 마크업을 추가해서 해당 뷰가 Todo 폴더에 위치한 뷰임을 확인할 수 있도록 만듭니다.
- 다시 비-공유 구성 요소 뷰를 테스트 해봅니다.
마법 문자열 사용하지 않기
컴파일 시점 안정성을 확보하기 위해서, 하드 코딩된 뷰 구성 요소 이름을 클래스 이름으로 대체할 수 있습니다. 먼저 "ViewComponent" 접미사 없이 뷰 구성 요소를 생성합니다:
using Microsoft.AspNet.Mvc;
using Microsoft.Data.Entity;
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();
}
}
}
그런 다음, Razor 뷰 파일에 using
구문을 추가하고 nameof
연산자를 사용합니다:
@using ViewComponentSample.Models
@using ViewComponentSample.ViewComponents
@model IEnumerable<TodoItem>
<h2>ToDo nameof</h2>
<!-- Markup removed for brevity. -->
}
</table>
<div>
@await Component.InvokeAsync(nameof(PriorityList), new { maxPriority = 4, isDone = true })
</div>
- 뷰: 뷰 개요 2016-08-26 08:00
- 뷰: Razor 구문 참조 2016-08-31 08:00
- 뷰: 레이아웃 2016-09-05 08:00
- 뷰: 뷰에 서비스 주입하기 2016-09-09 08:00
- 뷰: 태그 헬퍼 소개 2016-09-20 08:00
- 뷰: 태그 헬퍼 구현하기 2016-09-26 08:00
- 뷰: 태그 헬퍼로 폼 관련 작업하기 2016-10-03 08:00
- 뷰: 부분 뷰 2016-10-10 08:00
- 뷰: 뷰 구성 요소 2016-10-17 08:00