파트 3: 뷰 추가하기

등록일시: 2012-06-13 09:48,  수정일시: 2015-04-22 17:03
조회수: 4,715
이 문서는 ASP.NET MVC 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.

이번에는 HelloWorldController 클래스를 변경해서, 클라이언트에 응답할 HTML의 생성 과정을 간결하게 감춰주는 뷰 템플릿을 적용해 보겠습니다.

그리고, 뷰 템플릿 파일은 ASP.NET MVC 3에서 처음 도입된 Razor 뷰 엔진을 사용해서 작성해 볼 것입니다. Razor 기반의 뷰 템플릿은 .cshtml 이라는 파일 확장자를 갖고 있으며, C#을 이용한 우아한 HTML 출력 생성 방식을 제공해줍니다. Razor를 이용하면 뷰 템플릿 작성을 위해 필요한 글자수와 키입력을 최소화할 수 있으며, 빠르고 자연스러운 코딩이 가능해집니다.

먼저, HelloWorldController 클래스의 Index 메서드에 대한 뷰 템플릿부터 만들어 보겠습니다. 현재 Index 메서드는 그저 단순히 컨트롤러 클래스 자체에 하드코딩 되어 있는 문자열 메시지를 반환만 해줄 뿐입니다. 따라서, 다음과 코드와 같이 View 메서드를 호출하여 ActionResult 개체를 반환하도록 Index 메서드의 코드를 변경합니다:

public ActionResult Index() {
    return View();
}

이렇게 변경된 Index 메서드는 브라우저에 응답할 HTML을 생성하기 위해서 뷰 템플릿을 사용하게 됩니다. 일반적으로 컨트롤러의 메서드(액션 메서드)는, 이 Index 메서드처럼 문자열 같은 기본 형식 대신, ActionResultActionResult로부터 파생된 클래스를 반환하는 것이 보통입니다.

이제 프로젝트에 Index 메서드에 대한 뷰 템플릿을 추가하겠습니다. Index 메서드의 내부를 마우스 오른쪽 버튼으로 클릭하고 Add View를 선택합니다.

그러면, Add View 대화 상자가 나타나는데, 기본값을 그대로 남겨두고 Add 버튼을 클릭합니다:

그러면, MvcMovie\Views\HelloWorld 폴더와 MvcMovie\Views\HelloWorld\Index.cshtml 파일이 만들어집니다. 이 폴더와 파일을 Solution Explorer에서 확인해보십시요:

다음 그림에는 생성된 Index.cshtml 파일의 내용이 나타나 있습니다:

HelloWorldIndex

다음의 HTML을 <h2> 태그 아래에 추가합니다.

<p>Hello from our View Template!</p>

여기까지 작업을 마치고 나면 MvcMovie\Views\HelloWorld\Index.cshtml 파일의 내용은 다음과 비슷한 모습이 될 것입니다.

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>Hello from our View Template!</p>

만들어진 페이지를 살펴보고 보다 상세하게 분석해보고 싶다면, Solution Explorer에서 Index.cshtml 파일을 마우스 오른쪽 버튼으로 클릭한 다음, View in Page Inspector를 선택합니다.

이 새로운 도구에 관한 보다 자세한 정보는 Page Inspector tutorial을 참고하시기 바랍니다.

물론, 간단히 응용 프로그램을 실행한 다음, 브라우저에서 HelloWorld 컨트롤러로 이동해도 무방합니다. 이 컨트롤러의 Index 메서드는 그다지 많은 작업을 수행하지는 않습니다. 이 메서드는 단지 return View() 구문만 실행하는데, 이 구문은 브라우저에 응답을 렌더할 때, 뷰 템플릿을 사용하도록 지시합니다. 그리고, 사용해야 할 뷰 템플릿의 파일명을 명시적으로 지정하지 않았으므로, ASP.NET MVC가 자동으로 \Views\HelloWorld 폴더의 Index.cshtml 뷰 파일을 사용하게 됩니다. 다음 그림은 컨트롤러가 아닌, 뷰에 하드코딩 된 문자열이 출력된 화면을 보여주고 있습니다.

그리 나빠보이지는 않지만, 브라우저의 타이틀바에는 "Index My ASP.NET A..."라는 텍스트가 나타나 있고, 페이지 상단에는 "your logo here."라는 보기 싫은 커다란 링크가 자리잡고 있습니다. 그리고, "your logo here." 링크의 아래쪽에는 등록과 로그인과 관련된 링크가 있고, 다시 그 아래쪽에는 Home과 About, 그리고 Contact 페이지에 대한 링크가 자리잡고 있습니다. 자, 그러면 지금부터 이 부분들을 조금 변경해보도록 하겠습니다.

뷰와 레이아웃 페이지 변경하기

먼저, 페이지 상단의 "your logo here." 링크 텍스트부터 바꿔보겠습니다. 이 텍스트는 모든 페이지에 공통으로 나타나는 문구로, 실제로는 프로젝트 전체에서 단 한 곳에만 작성되어 있지만, 응용 프로그램에 존재하는 모든 페이지에 나타납니다. Solution Explorer에서 /Views/Shared 폴더에 위치한 _Layout.cshtml 파일을 열어봅니다. 이 페이지를 레이아웃 페이지라고 부르며, 응용 프로그램의 다른 모든 페이지들이 공유하는 기본적인 "구조"를 담고 있습니다.

_LayoutCshtml

이와 같은 레이아웃 템플릿을 사용하면, 사이트의 HTML 컨테이너로 사용될 레이아웃을 한 곳에서 지정한 다음, 사이트의 모든 페이지에 해당 레이아웃을 적용할 수 있습니다. 이 파일의 내용 중, @RenderBody() 라인을 살펴보시기 바랍니다. 이 RenderBody 메서드는 레이아웃 페이지 상에서 여러분이 작성한 뷰-전용 페이지들이 나타나게 될 위치를 지정합니다. 가령, About 링크를 클릭해보면, 이 RenderBody 메서드의 위치에 Views\Home\About.cshtml 뷰의 내용이 나타나는 것을 확인할 수 있습니다.

먼저, 레이아웃 템플릿에서 링크의 텍스트를 "your logo here."에서 다음과 같이 "MVC Movie"로 변경합니다.

<div class="float-left">
    <p class="site-title">@Html.ActionLink("MVC Movie", "Index", "Home")</p>
</div>

그리고, title 요소도 그 내용을 다음의 마크업으로 변경합니다:

<title>@ViewBag.Title - Movie App</title>

이제 다시 응용 프로그램을 실행시켜보면, 로고 링크의 텍스트가 "MVC Movie"로 변경된 것을 확인할 수 있을 것입니다. 또한, About 링크를 클릭해보면, 이 페이지의 로고 링크 텍스트도 역시 "MVC Movie"로 변경된 것을 확인할 수 있습니다. 비록, 레이아웃 템플릿에서 단 한 번의 변경 작업만 수행했지만, 사이트의 모든 페이지에 새로운 링크 텍스트가 반영된 것입니다.

작업이 마무리 된, 완전한 _Layout.cshtml 파일의 내용은 다음과 같습니다:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>@ViewBag.Title - Movie App</title>
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
        <link href="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Content/css")" rel="stylesheet" type="text/css" />
        <link href="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Content/themes/base/css")" rel="stylesheet" type="text/css" />
        <script src="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Scripts/js")"></script>
        <meta name="viewport" content="width=device-width" />
    </head>
    <body>
        <header>
            <div class="content-wrapper">
            <div class="float-left">
                <p class="site-title">@Html.ActionLink("MVC Movie", "Index", "Home")</p>
            </div>
                <div class="float-right">
                    <section id="login">
                        @Html.Partial("_LoginPartial")
                    </section>
                    <nav>
                        <ul id="menu">
                            <li>@Html.ActionLink("Home", "Index", "Home")</li>
                            <li>@Html.ActionLink("About", "About", "Home")</li>
                            <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
                        </ul>
                    </nav>
                </div>
            </div>
        </header>
        <div id="body">
            @RenderSection("featured", required: false)
            <section class="content-wrapper main-content clear-fix">
                @RenderBody()
            </section>
        </div>
        <footer>
            <div class="content-wrapper">
                <div class="float-left">
                    <p>&copy; @DateTime.Now.Year - My ASP.NET MVC Application</p>
                </div>
                <div class="float-right">
                    <ul id="social">
                        <li><a href="http://facebook.com" class="facebook">Facebook</a></li>
                        <li><a href="http://twitter.com" class="twitter">Twitter</a></li>
                    </ul>
                </div>
            </div>
        </footer>
    </body>
</html>

그러면, 이번에는 Index 뷰의 브라우저 타이틀을 변경해보도록 하겠습니다.

다시 MvcMovie\Views\HelloWorld\Index.cshtml 파일을 엽니다. 이 파일에서 변경하게 될 부분은 모두 두 곳으로, 브라우저 타이틀에 나타나는 텍스트와 페이지 상의 2차 헤더 제목(<h2> 요소) 텍스트가 바로 그것입니다. 변경되는 부분이 극히 일부분이기 때문에, 어떤 코드가 응용 프로그램의 어떤 부분을 변경하는지 쉽게 이해할 수 있을 것입니다.

@{ 
    ViewBag.Title = "Movie List";
}

<h2>My Movie List</h2>

<p>Hello from our View Template!</p>

변경된 Index.cshtml 뷰 템플릿에서는 HTML의 타이틀 출력을 지정하기 위해서, ViewBag 개체의 Title 속성을 설정하고 있습니다. 앞에서 살펴봤던 레이아웃 템플릿의 내용을 다시 살펴보면, HTML <head> 영역 하위의 <title> 요소에서 이 값이 사용되고 있다는 사실을 확인할 수 있을 것입니다. 이처럼 ViewBag 접근 방식을 사용하면 뷰 템플릿과 레이아웃 페이지 간에 매개변수를 손쉽게 전달할 수 있습니다.

응용 프로그램을 다시 시작한 다음, 브라우저에서 http://localhost:xxxx/HelloWorld로 이동합니다. 브라우저의 타이틀과 로고 링크의 텍스트, 그리고 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 액션 메서드는 namenumTimes이라는 이름의 매개변수를 받은 다음, 그 값들을 브라우저로 직접 출력하도록 구현되어 있습니다. 이 메서드를 문자열 대신, 뷰 템플릿을 사용하여 응답을 렌더하도록 변경해 보겠습니다. 그리고, 이 뷰 템플릿을 통해서 동적 응답을 생성해보려고 하는데, 결국 이 말은 응답을 생성하기 위해 필요한 데이터를 컨트롤러에서 뷰로 전달해줘야 한다는 뜻입니다. 이를 위해서 뷰 템플릿에 필요한 동적 데이터를 (매개변수들을) 컨트롤러 내부에서 ViewBag 개체에 설정한 다음, 이를 뷰 템플릿에서 접근해볼 것입니다.

다시 HelloWorldController.cs 파일로 돌아가서, MessageNumTimes의 값을 ViewBag 개체에 설정하도록 Welcome 메서드를 변경합니다. ViewBag은 동적 개체로, 여러분이 원하는 것은 무엇이든지 이 개체에 설정할 수 있습니다. 여러분이 ViewBag 개체에 무언가를 설정하기 전까지는, 이 개체에 어떠한 속성도 정의되어 있지 않습니다. 그리고, ASP.NET MVC의 모델 바인딩 시스템은 자동으로 브라우저 주소 표시줄의 쿼리스트링에 포함된 명명된 매개변수들을 (namenumTimes) 액션 메서드의 매개변수들과 맵핑시켜줍니다. 변경 작업이 마무리된 완전한 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 뷰 템플릿이 필요합니다. 다만, 먼저 Debug 메뉴에서 Build MvcMovie를 선택해서 프로젝트를 컴파일합니다.

BuildHelloWorld

그런 다음, Welcome 메서드의 내부를 마우스 오른쪽 버튼으로 클릭하고, Add View를 선택합니다.

다음 그림은 Add View 대화 상자에 설정된 내용들을 보여주고 있습니다:

마지막으로 Add를 클릭한 다음, 새로 만들어진 Welcome.cshtml 파일의 <h2> 요소 아래에 다음과 같이 코드를 추가합니다. 이 코드는 지정한 횟수만큼 지정한 사용자에게 "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:xxxx/HelloWorld/Welcome?name=Scott&numtimes=4

그러면, URL로부터 얻어진 데이터가 모델 바인더에 의해서 컨트롤러로 전달됩니다. 그리고, 컨트롤러는 이 데이터를 ViewBag 개체에 정리해서 집어 넣은 다음, 이 개체를 다시 뷰로 전달합니다. 마지막으로 뷰는 해당 데이터를 HTML로 출력하고, 이를 사용자에게 전달하게 됩니다.

물론 이번 예제에 사용된 데이터도 일종의 "M", 즉 모델로 볼 수는 있겠지만, 아직도 데이터베이스가 사용되지는 않았습니다. 다음에는 바로 이 문제에 관해 살펴보고 영화 정보를 저장할 데이터베이스도 생성해보도록 하겠습니다.