뷰: 뷰 개요

등록일시: 2016-08-26 08:00,  수정일시: 2016-10-14 13:27
조회수: 6,893
이 문서는 ASP.NET Core MVC 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.
본문에서는 ASP.NET Core MVC의 뷰(Views)에 관한 기본적인 정보들을 몇 가지 간단하게 살펴봅니다.

ASP.NET Core MVC의 컨트롤러는 뷰(Views)를 이용해서 서식화된 결과를 반환할 수 있습니다.

MVC(Model-View-Controller) 패턴에서 뷰(View)는 사용자와 응용 프로그램 간의 상호작용에 대한 프리젠테이션 세부 내역을 캡슐화하는 역할을 수행합니다. 뷰는 내부에 클라이언트로 전송될 콘텐츠를 생성하는 코드를 포함하고 있는 HTML 템플릿입니다. ASP.NET Core MVC의 뷰는 최소한의 코드와 절차로 HTML과 상호작용하는 코드를 작성할 수 있는 Razor 구문을 사용합니다.

기본적으로 ASP.NET Core MVC의 뷰는 응용 프로그램의 Views 폴더에 저장되며 .cshtml 이라는 파일 확장자를 갖습니다. 일반적으로 각 컨트롤러들은 해당 컨트롤러의 액션들이 사용하는 뷰들이 저장되는 자신만의 폴더를 갖고 있습니다.

이런 액션 전용 뷰 외에도, 부분 뷰(Partial Views) 레이아웃(Layouts), 그리고 그 밖의 특수한 뷰 파일들을 이용해서 불필요한 반복을 줄이고 응용 프로그램에서 사용되는 뷰들의 재사용성을 높일 수 있습니다.

뷰의 장점

뷰는 사용자 인터페이스 수준의 마크업과 업무 로직을 따로 분리해서 캡슐화시킴으로써 MVC 응용 프로그램 내부의 관심사의 분리를 제공해줍니다. ASP.NET Core MVC의 뷰는 HTML 마크업과 서버 측 로직을 번거로움 없이 전환해가면서 작업할 수 있는 Razor 구문을 사용합니다. 일반적으로 응용 프로그램의 사용자 인터페이스에 존재하는 반복적인 부분들은 레이아웃이나 공유 지시문, 또는 부분 뷰를 사용해서 뷰들 간에 손쉽게 재사용이 가능합니다.

뷰 생성하기

특정 컨트롤러와 관련된 뷰들은 Views/[ControllerName] 폴더에 만들어집니다. 반면 컨트롤러들 간에 공유되는 뷰들은 /Views/Shared 폴더에 위치해야 합니다. 뷰 파일의 이름은 관련된 컨트롤러 액션의 이름과 같고, 뒤에 .cshtml 이라는 파일 확장자가 추가됩니다. 예를 들어서, Home 컨트롤러의 About 액션에서 사용할 뷰를 생성하려면 /Views/Home 폴더에 About.cshtml 이라는 이름으로 파일을 생성하면 됩니다.

다음은 예제 뷰 파일입니다 (About.cshtml):

@{
    ViewData["Title"] = "About";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<p>Use this area to provide additional information.</p>

뷰에서 Razor 코드는 @ 기호로 표시됩니다. 이 예제 뷰에서 볼 수 있는 것처럼, ViewData["Title"] 요소에 "About"이라는 문자열을 할당하고 있는 구문처럼, C# 구문은 중괄호({ })로 둘러싸인 Razor 코드 블럭 내부에서 실행됩니다. 그리고 <h2> 요소와 <h3> 요소에서 볼 수 있는 것처럼, 간단히 접두사로 @ 기호를 추가해서 값을 참조하는 것만으로도 Razor를 이용해서 HTML 내부에 값을 출력할 수 있습니다.

이 예제 뷰는 자신이 담당하는 영역을 출력하는 작업에만 집중하고 있습니다. 페이지의 레이아웃이나 그 밖에 뷰의 다른 공통적인 부분들은 다른 곳에서 지정됩니다. 레이아웃과 뷰 로직 공유에 관해서 더 자세한 내용을 살펴보시기 바랍니다.

컨트롤러에서 뷰를 지정하는 방법

일반적으로 뷰는 액션에서 ViewResult 개체로 반환됩니다. 액션 메서드에서 직접 ViewResult 개체를 생성하고 반환할 수도 있지만, 컨트롤러가 Controller 클래스를 상속받는 경우, 거의 대부분 다음 예제에서 볼 수 있는 것처럼 간단히 View 헬퍼 메서드를 사용하는 것이 일반적입니다:

HomeController.cs

public IActionResult About()
{
    ViewData["Message"] = "Your application description page.";

    return View();
}

이 코드에 사용된 View 헬퍼 메서드는 응용 프로그램 개발자가 손쉽게 뷰를 반환할 수 있도록 몇 가지 버전의 오버로드를 지원해줍니다. 필요한 경우 선택적으로 반환할 뷰를 지정할 수도 있고, 뷰에 모델 개체를 전달할 수도 있습니다.

이 액션 메서드가 반환되면 이전 절에서 살펴봤던 About.cshtml 뷰가 렌더됩니다:

뷰 검색

액션에서 뷰가 반환되면 뷰 검색(View Discovery) 작업이 수행되는데, 이는 사용할 뷰 파일을 결정하는 과정입니다. 특정 뷰 파일이 명시적으로 지정하지 않았다면, 런타임은 먼저 컨트롤러 관련 뷰들을 찾아본 다음, Shared 폴더에서 이름이 일치하는 뷰 파일을 찾습니다.

만약 액션에서 return View(); 처럼 매개변수 없이 View 메서드를 반환됐다면, 해당 액션의 이름이 뷰의 이름으로 사용됩니다. 가령, 이 헬퍼 메서드가 "Index"라는 이름의 액션 메서드에서 호출된 경우, 뷰 이름으로 "Index"를 전달한 것과 동일하게 간주됩니다. 물론, 뷰 이름을 메서드에 명시적으로 전달할 수도 있습니다 (return View("SomeView");). 두 가지 경우 모두, 뷰 검색 작업에서는 다음과 같은 위치에서 일치하는 뷰 파일을 검색하게 됩니다:

  1. Views/<ControllerName>/<ViewName>.cshtml
  2. Views/Shared/<ViewName>.cshtml

가급적 액션에서 매개변수 없이 View()를 반환하는 규약을 준수하는 것을 권장하는데, 이 방식이 더 유연하고 코드 리팩터링도 쉽기 때문입니다.

단지 뷰 이름만 전달하는 대신 뷰 파일의 경로까지 함께 전달할 수도 있습니다. 이 경우에는 반드시 파일 경로의 일부로 .cshtml 파일 확장자까지 지정해야 합니다. 그리고 경로는 응용 프로그램 루트로부터 상대적이어야 합니다 (필요에 따라 "/" 또는 "~/"로 시작할 수 있습니다). 예를 들어서, return View("Views/Home/About.cshtml"); 같은 형태입니다.

노트

부분 뷰(Partial Views)뷰 구성 요소(View Components) 역시 비슷한 (그러나 완전히 같지는 않은) 검색 메카니즘을 사용합니다.

노트

IViewLocationExpander 인터페이스를 구현하면 응용 프로그램 내에서 뷰가 위치하는 장소에 대한 기본 규약을 사용자 지정할 수 있습니다.

기반 파일 시스템에 따라서는 뷰 이름이 대소문자를 구분할 수도 있습니다. 운영 체제들 간의 호환성을 위해서 가급적 컨트롤러 및 액션의 이름과, 관련된 뷰 폴더 및 파일명의 대소문자를 통일하는 것이 좋습니다.

뷰에 데이터 전달하기

몇 가지 메커니즘을 이용해서 뷰에 데이터를 전달할 수 있습니다. 가장 강력한 방법은 뷰에서 특정 모델 형식을 지정한 다음 (일반적으로 업무 도메인 모델 형식과 구분하기 위해서, 이런 용도의 모델을 뷰 모델이라고 부릅니다), 액션에서 해당 형식의 인스턴스를 뷰로 전달하는 것입니다. 대부분의 경우, 모델이나 뷰 모델을 이용해서 뷰에 데이터를 전달하는 것을 권장합니다. 이 방식을 사용하면 뷰에서 강력한 형식 검사의 이점을 얻을 수 있습니다. @model 지시문을 사용해서 뷰의 모델을 지정할 수 있습니다:

@model WebApplication1.ViewModels.Address
<h2>Contact</h2>
<address>
    @Model.Street<br />
    @Model.City, @Model.State @Model.PostalCode<br />
    <abbr title="Phone">P:</abbr>
    425.555.0100
</address>

이렇게 뷰에 모델을 지정하고 나면, 예제에서 볼 수 있는 것처럼 @Model을 이용해서 뷰에 전달된 인스턴스에 강력한 형식으로 접근할 수 있습니다. 모델 형식의 인스턴스를 뷰에 전달하기 위해서는 다음과 같이 컨트롤러에서 해당 인스턴스를 View 메서드에 매개변수로 전달해야 합니다:

public IActionResult Contact()
{
    ViewData["Message"] = "Your contact page.";

    var viewModel = new Address()
    {
        Name = "Microsoft",
        Street = "One Microsoft Way",
        City = "Redmond",
        State = "WA",
        PostalCode = "98052-6399"
    };
    return View(viewModel);
}

뷰에 모델로 제공될 수 있는 형식에는 어떠한 제한도 없습니다. 다만, 업무 로직은 응용 프로그램의 다른 곳에서 캡슐화할 수 있도록, 동작이 거의 혹은 전혀 존재하지 않는 POCO(Plain Old CLR Object) 뷰 모델을 전달하는 것이 바람직합니다. 다음은 이번 절의 예제에서 사용된 Address 뷰 모델을 POCO 방식으로 구현한 사례입니다:

namespace WebApplication1.ViewModels
{
    public class Address
    {
        public string Name { get; set; }
        public string Street { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string PostalCode { get; set; }
    }
}

노트

같은 클래스를 동시에 업무 모델 형식과 출력 모델 형식으로 사용하지 못할 이유는 전혀 없습니다. 그러나 이 두 유형의 클래스들을 분리해서 사용하면 도메인 모델이나 영속화 모델에 독립적으로 뷰를 변경할 때 유리할 뿐만 아니라, 다소나마 보안 상의 이점도 제공할 수 있습니다 (모델 바인딩(Model Binding)을 사용하는 응용 프로그램에 사용자가 전송하는 모델의 경우).

느슨한 형식의 데이터

강력한 형식의 뷰를 포함한 모든 뷰에서 느슨한 형식의 데이터 컬렉션에 접근할 수 있습니다. 컨트롤러와 뷰 양쪽 모두에서 같은 이름으로 제공되는 ViewData 속성 및 ViewBag 속성을 이용하면 동일한 컬렉션을 참조할 수 있습니다. ViewBag 속성은, 컬렉션을 이용해서 동적 뷰를 제공해주는 ViewData를 감싸는 래퍼입니다. 이 둘은 개별적인 컬렉션이 아닙니다.

ViewDatastring 형식의 키를 이용해서 접근할 수 있는 사전 개체로, 개체를 저장하거나 조회할 수 있으며, 저장된 개체를 가져와서 사용하려면 먼저 특정 형식으로 형 변환해야 합니다. 컨트롤러에서 뷰로 데이터를 전달할 때 뿐만 아니라, 뷰에서 뷰로 (혹은 부분 뷰나 레이아웃 간에) 데이터를 전달하고자 할 경우에도 ViewData를 활용할 수 있습니다. 문자열 데이터는 형 변환 없이도 바로 저장하고 사용할 수 있습니다.

먼저 액션에서 ViewData에 약간의 데이터를 설정하면:

public IActionResult SomeAction()
{
    ViewData["Greeting"] = "Hello";
    ViewData["Address"]  = new Address()
    {
        Name = "Steve",
        Street = "123 Main St",
        City = "Hudson",
        State = "OH",
        PostalCode = "44236"
    };

    return View();
}

다음과 같이 뷰에서 설정된 데이터를 사용할 수 있습니다:

@{
    // Requires cast
    var address = ViewData["Address"] as Address;
}

@ViewData["Greeting"] World!

<address>
    @address.Name<br />
    @address.Street<br />
    @address.City, @address.State @address.PostalCode
</address>

ViewBag 개체는 ViewData에 저장된 개체들에 대한 동적 접근을 제공해줍니다. 이 개체를 사용하면 형 변환이 필요 없기 때문에 작업이 조금 더 편리합니다. 다음은 뷰에서 강력한 형식의 address 인스턴스 대신 ViewBag을 사용해서 위와 동일한 작업을 수행하는 예제입니다:

@ViewBag.Greeting World!

<address>
    @ViewBag.Address.Name<br />
    @ViewBag.Address.Street<br />
    @ViewBag.Address.City, @ViewBag.Address.State @ViewBag.Address.PostalCode
</address>

노트

두 속성 모두 동일한 기반 ViewData 컬렉션을 참조하므로, 별다른 문제가 없다면 값을 읽거나 쓸 때 ViewDataViewBag을 적절히 섞어서 사용해도 무방합니다.

동적 뷰

모델 형식을 선언하지 않은 뷰에 모델의 인스턴스가 전달되는 경우에도 해당 인스턴스를 동적으로 참조할 수 있습니다. 가령, @model을 선언하지 않은 뷰에 Address의 인스턴스가 전달되면 다음과 같이 동적으로 인스턴스 속성에 접근 가능합니다:

<address>
    @Model.Street<br />
    @Model.City, @Model.State @Model.PostalCode<br />
    <abbr title="Phone">P:</abbr>
    425.555.0100
</address>

이 기능은 약간의 유연성은 제공해줄 수 있으나, 컴파일 보호나 인텔리센스 같은 이점들을 누릴 수 없게 됩니다. 또한, 접근한 속성이 존재하지 않으면 런타임 시 페이지 오류가 발생합니다.

뷰의 다른 기능들

태그 헬퍼(Tag Helpers)를 사용하면 뷰에서 사용자 지정 코드나 헬퍼를 사용하지 않고서도 손쉽게 기존 HTML 태그에 서버 측 동작을 추가할 수 있습니다. 태그 헬퍼는 HTML 요소에 어트리뷰트 형태로 적용되며 이를 인식하지 못하는 편집기에서는 단지 무시될 뿐이므로, 다양한 도구를 사용해서 뷰의 마크업을 편집하거나 렌더할 수 있습니다. 태그 헬퍼는 다양한 용도를 갖고 있지만, 특히 폼과 관련된 작업을 보다 손쉽게 만들어줍니다.

다양한 내장 HTML 헬퍼(HTML Helpers)를 이용해서 사용자 지정 HTML 마크업을 생성할 수 있으며, 보다 복잡한 UI 로직은 (자체적인 데이터 요구사항까지도) 뷰 구성 요소(View Components)로 캡슐화시킬 수 있습니다. 뷰 구성 요소는 컨트롤러나 뷰가 제공해주는 것과 동등한 관심사의 분리를 제공해주며, 공통 UI 요소에서 사용될 데이터를 처리하기 위해서 필요한 액션 및 뷰에 대한 요구를 줄여줍니다.

마지막으로 ASP.NET Core의 다른 많은 부분들처럼 뷰도 의존성 주입(Dependency Injection)을 지원하므로, 뷰에 서비스를 주입할 수도 있습니다.