파트 3: 뷰 추가하기
- 본 번역문서의 원문은 Adding a View www.asp.net 입니다.
본문에서는 뷰 템플릿을 이용해서 클라이언트에 응답할 HTML을 간결하게 은닉화시켜서 생성할 수 있도록 HelloWorldController
클래스를 수정해보겠습니다.
더불어 뷰 템플릿 파일 자체는 Razor 뷰 엔진을 이용해서 작성할 것입니다. Razor 기반의 뷰 템플릿은 .cshtml 이라는 파일 확장자를 가지며 C#을 이용해서 우아한 방식으로 HTML 출력을 생성할 수 있습니다. Razor를 이용하면 뷰 템플릿을 작성하기 위한 글자 수와 키 입력을 최소화시킬 수 있으며 빠르고 자연스러운 코딩이 가능합니다.
지금의 Index
메서드는 단순히 컨트롤러 클래스 자체에 하드코딩 되어 있는 문자열 메시지를 반환해줄 뿐입니다.
따라서 다음 코드와 같이 View
메서드를 호출함으로서 ActionResult
개체를 반환하도록 Index
메서드를 변경합니다:
public ActionResult Index() { return View(); }
이렇게 변경된 Index
메서드는 브라우저에 응답할 HTML을 생성하기 위해서 뷰 템플릿을 사용하게 됩니다.
이처럼 컨트롤러의 메서드는 (보통 액션 메서드라고 부릅니다) 일반적으로 이 Index
메서드처럼 문자열 같은 기본 형식 대신 ActionResult 자체나 ActionResult로부터 파생된 클래스를 반환하는 경우가 대부분입니다.
계속해서 이번에는 마우스 오른쪽 버튼으로 Views\HelloWorld 폴더를 클릭하고 추가(Add)를 선택한 다음, 레이아웃이 있는 MVC 5 뷰 페이지(MVC 5 View Page with Layout (Razor))를 선택합니다.
그러면 항목 이름 지정(Specify Name for Item) 대화 상자가 나타나는데 항목 이름을 Index 로 지정하고 확인(OK) 버튼을 클릭합니다.
다시 레이아웃 페이지 선택(Select a Layout Page) 대화 상자가 나타나면 기본 레이아웃인 _Layout.cshtml 파일을 선택하고 확인(OK) 버튼을 클릭합니다.
잠시 이 대화 상자를 살펴보면 좌측 패인에 Views\Shared 폴더가 선택되어 있는 것을 확인할 수 있습니다. 만약 다른 폴더에 사용자 지정 레이아웃 파일이 존재한다면 해당 레이아웃 파일을 선택할 수도 있습니다. 레이아웃 파일에 관해서는 본 자습서의 뒷 부분에서 다시 자세하게 살펴보도록 하겠습니다.
지금까지의 과정을 정상적으로 마쳤다면 MvcMovie\Views\HelloWorld\Index.cshtml 파일이 만들어졌을 것입니다.
이 파일에 다음의 강조된 마크업을 추가합니다.
@{ Layout = "~/Views/Shared/_Layout.cshtml"; } @{ ViewBag.Title = "Index"; } <h2>Index</h2> <p>Hello from our View Template!</p>
그리고 마우스 오른쪽 버튼으로 Index.cshtml 파일을 클릭하고 브라우저에서 보기(View in Browser)를 선택하면 작업 결과를 확인할 수 있습니다.
만약 만들어진 페이지를 보다 꼼꼼하게 분석해보고 싶다면 마우스 오른쪽 버튼으로 Index.cshtml 파일을 클릭한 다음, 페이지 검사기로 보기(View in Page Inspector)를 선택할 수도 있습니다. 이 기능에 관한 더 자세한 정보는 ASP.NET MVC에서 페이지 검사기 활용하기 문서를 참고하시기 바랍니다.
응용 프로그램을 실행하고 브라우저에서 HelloWorld
컨트롤러로 이동해도 (http://localhost:xxxx/HelloWorld) 동일한 결과를 확인할 수 있습니다.
Index 메서드는 그다지 거창한 작업을 수행하지는 않습니다.
단지 return View()
구문을 실행해서 브라우저에 응답을 렌더할 때 뷰 템플릿을 사용하도록 MVC 프레임워크에게 지시할 뿐입니다.
그리고 사용해야 할 뷰 템플릿 파일명을 명시적으로 지정하지 않았기 때문에 ASP.NET MVC가 기본 템플릿인 \Views\HelloWorld 폴더의 Index.cshtml 뷰 파일을 사용하게 됩니다.
다음 그림은 컨트롤러에 하드코딩 된 문자열이 아닌 뷰에 하드코딩 된 문자열이 출력된 화면을 보여주고 있습니다.
이 결과도 나름대로 괜찮아 보이기는 하지만 브라우저 제목 표시줄에는 여전히 "Index - 내 ASP.NET 응용 프로그램(Index - My ASP.NET Application)"이라는 기본적인 문구가 나타나 있고 페이지 상단에는 "응용 프로그램 이름(Application name)"이라는 큼지막한 링크도 자리잡고 있습니다. 또한 브라우저 창의 현재 크기에 따라 홈(Home), 정보(About), 연락처(Contact), 등록(Register) 그리고 로그인(Log in) 링크를 보려면 우측 상단에 위치한 세 개의 막대 모양 아이콘을 클릭해야만 합니다.
뷰와 레이아웃 페이지 변경하기
먼저 페이지 상단에 위치한 "응용 프로그램 이름(Application name)" 링크부터 변경해보겠습니다. 이 링크는 모든 페이지에 공통적으로 나타납니다. 즉, 실제로는 프로젝트 전체에서 단 한 곳에만 링크가 작성되어 있지만 응용 프로그램에 존재하는 모든 페이지에 나타나게 됩니다. 솔루션 탐색기(Solution Explorer)에서 /Views/Shared 폴더로 이동한 다음, _Layout.cshtml 파일을 엽니다. 이 파일을 레이아웃 페이지라고 부르며 응용 프로그램의 다른 모든 페이지들이 공유하는 공유 폴더(/Views/Shared)에 위치해 있습니다.
레이아웃 템플릿을 사용하면 사이트의 HTML 컨테이너로 사용하고자 하는 레이아웃을 한 곳에서 지정하고 이 레이아웃을 사이트의 여러 페이지에 적용할 수 있습니다.
이 파일을 살펴보면 @RenderBody()
라는 코드가 작성된 라인을 찾아볼 수 있는데, 이 RenderBody
메서드는 레이아웃 페이지 내부에 여러분이 작성한 모든 뷰-전용 페이지들이 나타나게 될 위치를 지정합니다.
가령 정보(About) 링크를 클릭해보면 이 RenderBody
메서드의 위치에 Views\Home\About.cshtml 뷰의 내용이 나타나는 것을 확인할 수 있습니다.
먼저 title 요소의 내용을 변경합니다.
그리고 레이아웃 템플릿에서 ActionLink 링크의 문구(즉 첫번째 매개변수의 값)를 "응용 프로그램 이름(Application name)"에서 "MVC Movie"로 변경하고 컨트롤러도 Home
에서 Movies
로 변경합니다.
여기까지 작업을 마치고 나면 레이아웃 파일은 다음과 같은 모습일 것입니다:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>@ViewBag.Title - Movie App</title> @Styles.Render("~/Content/css") @Scripts.Render("~/bundles/modernizr") </head> <body> <div class="navbar navbar-inverse navbar-fixed-top"> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> @Html.ActionLink("MVC Movie", "Index", "Movies", null, new { @class = "navbar-brand" }) </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav"> <li>@Html.ActionLink("Home", "Index", "Home")</li> <li>@Html.ActionLink("About", "About", "Home")</li> <li>@Html.ActionLink("Contact", "Contact", "Home")</li> </ul> @Html.Partial("_LoginPartial") </div> </div> </div> <div class="container body-content"> @RenderBody() <hr /> <footer> <p>© @DateTime.Now.Year - My ASP.NET Application</p> </footer> </div> @Scripts.Render("~/bundles/jquery") @Scripts.Render("~/bundles/bootstrap") @RenderSection("scripts", required: false) </body> </html>
다시 응용 프로그램을 실행시켜보면 링크의 문구가 "MVC Movie"로 변경된 것을 확인할 수 있습니다. 뿐만 아니라 정보(About) 등의 링크를 클릭해보면 해당 페이지의 링크 문구 역시 "MVC Movie"로 변경된 것을 확인할 수 있습니다. 레이아웃 템플릿 단 한곳에서만 문구를 변경했지만 사이트의 모든 페이지에 새로운 링크 문구가 반영된 것입니다.
본문에서 처음 Views\HelloWorld\Index.cshtml 파일을 생성했을 때 기본적으로 다음과 같은 코드가 작성되어 있던 것을 기억하실 것입니다:
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
이 Razor 코드는 해당 페이지가 사용할 레이아웃 페이지를 명시적으로 지정합니다. 그런데 Views 폴더에 위치해 있는 _ViewStart.cshtml 파일의 내용을 확인해보면 이 파일에도 역시 동일한 내용의 Razor 마크업이 작성되어 있는 것을 알 수 있습니다. Views\_ViewStart.cshtml 파일은 모든 뷰에 적용될 공통 레이아웃이 정의되는 파일입니다. 따라서 Views\HelloWorld\Index.cshtml 파일에서는 해당 코드를 주석으로 처리하거나 아예 제거해버릴 수도 있습니다.
@*@{ Layout = "~/Views/Shared/_Layout.cshtml"; }*@ @{ ViewBag.Title = "Index"; } <h2>Index</h2> <p>Hello from our View Template!</p>
이 Layout
속성을 설정하면 다른 레이아웃 뷰를 지정하거나 아예 null
로 설정해서 레이아웃 파일을 사용하지 않도록 지정할 수도 있습니다.
이번에는 Index 뷰의 제목 표시줄 문구를 변경해보도록 하겠습니다.
다시 MvcMovie\Views\HelloWorld\Index.cshtml 파일을 엽니다.
이 파일에서 변경해야 할 부분은 모두 두 곳으로 브라우저 제목 표시줄에 나타나는 문구와 페이지 본문의 2차 헤더(<h2>
요소) 문구가 그것입니다.
변경되는 부분이 극히 일부분에 불과하기 때문에 어떤 코드가 응용 프로그램의 어떤 부분을 변경하게 되는지 쉽게 파악할 수 있을 것입니다.
@{ ViewBag.Title = "Movie List"; } <h2>My Movie List</h2> <p>Hello from our View Template!</p>
이 코드에서는 출력할 HTML 제목을 지정하기 위해서 ViewBag
개체(이 개체는 Index.cshtml 뷰 템플릿의 속성입니다)의 Title
속성을 설정하고 있습니다.
방금 수정했던 레이아웃 템플릿(Views\Shared\_Layout.cshtml)을 다시 살펴보면 HTML <head>
영역 하위의 <title>
요소에서 이 값을 사용하고 있다는 사실을 확인할 수 있습니다.
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>@ViewBag.Title - Movie App</title> @Styles.Render("~/Content/css") @Scripts.Render("~/bundles/modernizr") </head>
이처럼 ViewBag 접근 방식을 사용하면 손쉽게 뷰 템플릿과 레이아웃 페이지 간에 매개변수들을 전달할 수 있습니다.
응용 프로그램을 다시 실행해보면 브라우저 제목 표시줄과 좌상단 링크의 문구, 그리고 2차 헤더의 문구가 변경된 것을 확인할 수 있습니다.
(변경된 내용들이 브라우저에 반영되지 않는다면 아마도 캐시된 내용 때문일 것입니다.
브라우저에서 Ctrl+F5 키를 눌러서 강제로 서버로부터 받은 응답을 로드해보시기 바랍니다.)
이 브라우저 제목 표시줄 문구는 Index.cshtml 뷰 템플릿에서 설정한 ViewBag.Title
의 값과 레이아웃 파일에서 추가된 " - Movie App" 문구가 결합되어 만들어진 결과입니다.
또한 Index.cshtml 뷰 템플릿의 내용이 _Layout.cshtml 뷰 템플릿의 내용과 병합되어 단일 HTML 응답으로 브라우저에 전송되는 방식도 주의 깊게 살펴보시기 바랍니다. 이와 같이 레이아웃 템플릿을 활용하면 응용 프로그램의 모든 페이지에 공통적으로 적용되는 부분들을 정말 손쉽게 수정할 수 있습니다.
그러나 이번 예제에 사용된 간단한 "데이터"(즉, "Hello from our View Template!"라는 메시지)는 여전히 하드코딩 되어 있는 값에 불과합니다. 결국 이 예제 MVC 응용 프로그램에는 "V"(뷰)와 "C"(컨트롤러)는 존재하지만, 여전히 "M"(모델)은 존재하지 않는 상태나 마찬가지인 셈입니다. 다음 단계에서는 데이터베이스를 생성하고 데이터베이스로부터 모델 데이터를 가져오는 방법을 살펴보도록 하겠습니다.
컨트롤러에서 뷰로 데이터 전달하기
데이터베이스와 모델에 관해서 살펴보기 전에 잠시 컨트롤러에서 뷰로 정보를 전달하는 방법부터 먼저 알아보도록 하겠습니다. 컨트롤러 클래스는 전달된 URL 요청에 응답해서 호출됩니다. 그러면 컨트롤러 클래스에 작성된 코드에 의해서 전달된 브라우저 요청이 처리되어 데이터베이스에서 데이터를 가져온 다음, 최종적으로 어떤 형태의 응답을 브라우저로 전송할 것인지가 결정되어 집니다. 이런 처리가 마무리되고 난 뒤에야 컨트롤러에 의해 브라우저로 전달될 HTML 응답을 생성하고 형식화하기 위해서 뷰 템플릿이 사용됩니다.
컨트롤러는 뷰 템플릿이 브라우저에 응답을 렌더할 때 필요한 모든 데이터와 개체를 제공해야 할 책임을 갖고 있습니다. 가급적 뷰 템플릿은 어떠한 경우에도 업무 로직을 수행하거나 데이터베이스와 직접 연결돼서는 안됩니다. 뷰 템플릿은 컨트롤러로부터 제공 받은 데이터만 이용해서 모든 작업을 수행해야 합니다. 이렇게 "관심사의 분리(Separation of Concerns)"를 지켜야만 깔끔하고 테스트 가능한 유지보수가 용이한 코드를 유지할 수 있습니다.
현재 HelloWorldController
클래스의 Welcome
액션 메서드는 name
및 numTimes
매개변수를 받아서 그 값들을 브라우저에 직접 출력하도록 구현되어 있습니다.
이렇게 컨트롤러가 직접 응답을 문자열로 렌더하는 대신, 뷰 템플릿을 이용하도록 컨트롤러를 변경해보겠습니다.
그리고 뷰 템플릿이 동적 응답을 생성하도록 만들어볼텐데, 결국 얘기는 응답을 생성하기 위해 필요한 간단한 데이터들을 컨트롤러에서 뷰로 전달해줘야 한다는 뜻입니다.
이 작업을 위해서 뷰 템플릿에 필요한 동적 데이터들(매개변수들)을 컨트롤러 내부에서 ViewBag
개체에 설정한 다음, 이를 뷰 템플릿에서 접근해볼 것입니다.
다시 HelloWorldController.cs 파일로 돌아가서 Message
및 NumTimes
매개변수 값을 ViewBag
개체에 설정하도록 Welcome
메서드를 변경합니다.
ViewBag
은 동적 개체로 원하는 것은 무엇이든지 이 개체에 설정할 수 있으며, 여러분이 ViewBag
개체에 무언인가를 설정하기 전까지는 어떠한 속성도 정의되어 있지 않습니다.
또한 ASP.NET MVC의 모델 바인딩 시스템이
자동으로 브라우저 주소 표시줄의 쿼리 문자열에 포함된 명명된 매개변수들(name
과 numTimes
)을 액션 메서드의 매개변수들과 맵핑시켜줍니다.
모든 변경 작업이 마무리된 HelloWorldController.cs 파일은 다음과 같습니다:
using System.Web; using System.Web.Mvc; namespace MvcMovie.Controllers { public class HelloWorldController : Controller { public ActionResult Index() { return View(); } public ActionResult Welcome(string name, int numTimes = 1) { ViewBag.Message = "Hello " + name; ViewBag.NumTimes = numTimes; return View(); } } }
이 작업의 결과로 ViewBag
개체에 담겨있는 데이터들은 자동으로 뷰에 전달되게 됩니다.
계속해서 이번에는 Welcome 뷰 템플릿이 필요합니다.
먼저 빌드(Build) 메뉴에서 솔루션 빌드(Build Solution)를 선택해서 (또는 Ctrl+Shift+B 키를 눌러서) 프로젝트를 컴파일합니다.
그런 다음 Views\HelloWorld 폴더를 마우스 오른쪽 버튼으로 클릭하고 추가(Add)를 선택한 다음, 레이아웃이 있는 MVC 5 뷰 페이지(MVC 5 View Page with Layout (Razor))를 선택합니다.
그러면 항목 이름 지정(Specify Name for Item) 대화 상자가 나타나는데 항목 이름을 Welcome으로 지정하고 확인(OK) 버튼을 클릭합니다. 그리고 다시 레이아웃 페이지 선택(Select a Layout Page) 대화 상자가 나타나면 기본 레이아웃인 _Layout.cshtml 파일을 선택하고 확인(OK) 버튼을 클릭합니다.
그러면 MvcMovie\Views\HelloWorld\Welcome.cshtml 파일이 만들어집니다.
새로 만들어진 Welcome.cshtml 파일의 마크업을 다음과 같이 변경합니다. 이 코드는 쿼리 문자열을 통해서 사용자가 지정한 횟수만큼 지정한 사용자에게 "Hello"를 출력하는 루프문입니다. 작업을 마친 Welcome.cshtml 파일은 다음과 같습니다.
@{ ViewBag.Title = "Welcome"; } <h2>Welcome</h2> <ul> @for (int i = 0; i < ViewBag.NumTimes; i++) { <li>@ViewBag.Message</li> } </ul>
다시 응용 프로그램을 실행하고 브라우저에서 다음 URL로 이동합니다:
http://localhost:xx/HelloWorld/Welcome?name=Scott&numtimes=4
먼저 URL로부터 얻어진 데이터가 모델 바인더에 의해서 컨트롤러로 전달됩니다.
그러면 컨트롤러는 이 데이터를 ViewBag
개체에 정리해서 집어 넣고 이 개체를 다시 뷰로 전달합니다.
마지막으로 뷰는 해당 데이터를 HTML로 출력해서 이를 사용자에게 전달하게 됩니다.
이번 예제에서는 컨트롤러에서 뷰로 데이터를 전달하기 위해서 ViewBag
개체를 사용하고 있습니다.
그러나 본 자습서의 이후 과정에서는 컨트롤러에서 뷰로 데이터를 전달할 때 뷰 모델을 사용하려고 합니다.
일반적으로 ViewBag
개체를 이용한 데이터 전달 방식보다는 뷰 모델 방식이 훨씬 더 선호되는 편입니다.
보다 자세한 정보는 Dynamic V Strongly Typed Views 블로그 포스트를 참고하시기 바랍니다.
이번 예제에 사용된 데이터도 일종의 "M", 즉 모델로 볼 수 있겠지만 데이터베이스라고 보기에는 많이 부족합니다. 다음 단계에서는 이 부분에 관해서 알아보고 뒤이어 영화 정보를 저장할 데이터베이스도 생성해보도록 하겠습니다.
이 기사는 2013년 10월 17일에 최초 작성되었습니다.
- 파트 1: ASP.NET MVC 5 시작하기 2015-04-13 08:00
- 파트 2: 컨트롤러 추가하기 2015-04-20 08:00
- 파트 3: 뷰 추가하기 2015-04-27 08:00
- 파트 4: 모델 추가하기 2015-05-04 08:00
- 파트 5: 연결 문자열 생성 및 SQL Server LocalDB 구성하기 2015-05-11 08:00
- 파트 6: 컨트롤러에서 모델 데이터에 접근하기 2015-05-18 08:00
- 파트 7: Edit 메서드와 Edit 뷰 살펴보기 2015-05-25 08:00
- 파트 8: 검색 기능 구현하기 2015-06-01 08:00
- 파트 9: 새로운 필드 추가하기 2015-06-08 08:00
- 파트 10: 유효성 검사 추가하기 2015-06-15 08:00
- 파트 11: Details 메서드 및 Delete 메서드 살펴보기 2015-06-22 08:00