파트 3: ASP.NET Core MVC - 뷰 추가하기

등록일시: 2016-06-06 08:00,  수정일시: 2016-09-02 08:17
조회수: 9,334
이 문서는 ASP.NET Core MVC 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.
이번 파트에서는 Razor 뷰 템플릿 파일을 이용한 HTML 응답을 생성해보고, 컨트롤러에서 뷰로 데이터를 전달하는 방법과 레이아웃에 관해서 간단히 알아봅니다.

이번 파트에서는 Razor 뷰 템플릿 파일을 이용하도록 HelloWorldController 클래스를 개선하여, 클라이언트에 응답할 HTML의 생성 과정을 간결하게 은닉화시켜 보겠습니다.

본문에서는 Razor 엔진을 이용해서 뷰 템플릿 파일을 만들어봅니다. Razor 기반의 뷰 템플릿은 .cshtml 이라는 파일 확장자를 갖고 있으며, C#을 사용해서 우아한 방식으로 HTML 출력을 생성할 수 있습니다. Razor를 이용하면 뷰 템플릿을 작성하기 위한 글자 수와 키 입력을 최소화시킬 수 있고 빠르고 자연스러운 코딩이 가능합니다.

지난 파트에서 작성한 Index 메서드는 컨트롤러 클래스에 하드코딩 되어 있는 메시지 문자열을 반환합니다. Index 메서드를 다음 코드에서 볼 수 있는 것처럼 View 개체를 반환하도록 변경합니다:

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

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

  • 마우스 오른쪽 버튼으로 Views 폴더를 클릭하고 추가(Add) > 새 폴더(New Folder)를 선택한 다음, 폴더명을 HelloWorld 라고 지정합니다.
  • 다시 Views/HelloWorld  폴더를 마우스 오른쪽 버튼으로 클릭하고 추가(Add) > 새 항목(New Item)을 선택합니다.
  • 그런 다음, 새 항목 추가 - MvcMovie(Add New Item - MvcMovie) 대화 상자가 나타나면
    • 우측 상단에 위치한 검색 상자에 view 라고 입력합니다.
    • MVC 뷰 페이지(MVC View Page)를 선택합니다.
    • 이름(Name) 입력란은 기본값인 Index.cshtml 을 그대로 놔둡니다.
    • 추가(Add) 버튼을 누릅니다.
  • (역주: 2016년 6월 현재, Update 2가 설치된 Visual Studio 2015에서는 이 대화 상자의 구성이 조금 변경되었습니다. 그리고 당연한 얘기겠지만 Visual Studio 2015 한글판에서는 "view" 대신 "뷰"를 입력해야만 "MVC 뷰 페이지" 항목이 검색됩니다.)

그리고 Views/HelloWorld/Index.cshtml Razor 뷰 파일의 내용을 다음 코드로 대체합니다:

@{
    ViewData["Title"] = "Index";
}

<h2>Index</h2>

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

작업을 마쳤으면 이제 http://localhost:xxxx/HelloWorld로 이동해봅니다. 이 HelloWorldController 컨트롤러의 Index 메서드는 그다지 대단한 작업을 수행하지는 않습니다. 단지 return View(); 구문을 실행함으로써 브라우저로 전송할 응답을 뷰 템플릿 파일을 이용해서 렌더하도록 지시할 뿐입니다. 그리고 사용해야 할 뷰 템플릿 파일의 이름을 명시적으로 지정하지 않았기 때문에, MVC는 기본적으로 /Views/HelloWorld  폴더에 위치한 Index.cshtml  뷰 파일을 사용하게 됩니다. 다음 그림은 뷰에 하드코딩 되어 있는 "Hello from our View Template!" 문자열이 출력된 모습을 보여줍니다.

브라우저 창의 크기에 따라 (가령 모바일 장치에서 접근할 경우), Home, About, Contact, Register 그리고 Log in 링크를 보려면 우측 상단의 Bootstrap 탐색 버튼을 눌러야 할 수도 있습니다.

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

이번에는 메뉴 링크들을 (MvcMovie, Home, About) 하나씩 눌러서 살펴보시기 바랍니다. 그러면 모든 페이지들이 동일한 메뉴 레이아웃을 갖고 있음을 알 수 있습니다. 이 메뉴 레이아웃은 Views/Shared/_Layout.cshtml  파일에 구현되어 있는데, 그러면 이제 이 파일을 살펴보겠습니다.

레이아웃 템플릿을 이용하면 사이트의 HTML 컨테이너로 사용하고자 하는 레이아웃을 한 곳에서 지정하고 이 레이아웃을 사이트의 여러 페이지에 적용할 수 있습니다. 페이지에서 @RenderBody()가 위치해 있는 줄을 찾아보시기 바랍니다. 이 RenderBody 메서드는 여러분이 작성한 모든 뷰-전용 페이지들이 레이아웃 페이지 내부에 "감싸여서" 나타나게 될 위치를 지정합니다. 예를 들어서, About 링크를 눌러보면 이 RenderBody 메서드의 위치에 Views/Home/About.cshtml 뷰의 내용이 렌더되는 것을 알 수 있습니다.

이번에는 레이아웃 페이지에서 title 요소의 내용을 변경합니다. 그리고 다음 코드에 강조되어 있는 것처럼 레이아웃 템플릿에서 앵커의 문구를 "MVC Movie"로 변경하고 컨트롤러도 Home에서 Movies로 변경합니다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Movie App</title>

    <environment names="Development">
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
        <link rel="stylesheet" href="~/css/site.css" />
    </environment>
    <environment names="Staging,Production">
        <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/css/bootstrap.min.css"
              asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
              asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
        <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
    </environment>
</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="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a asp-controller="Movies" asp-action="Index" class="navbar-brand">Mvc Movie</a>
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a asp-controller="Home" asp-action="Index">Home</a></li>
                    <li><a asp-controller="Home" asp-action="About">About</a></li>
                    <li><a asp-controller="Home" asp-action="Contact">Contact</a></li>
                </ul>
                @await Html.PartialAsync("_LoginPartial")
            </div>
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; 2016 - MvcMovie</p>
        </footer>
    </div>

    <environment names="Development">
        <script src="~/lib/jquery/dist/jquery.js"></script>
        <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
        <script src="~/js/site.js" asp-append-version="true"></script>
    </environment>
    <environment names="Staging,Production">
        <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
                asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
                asp-fallback-test="window.jQuery">
        </script>
        <script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/bootstrap.min.js"
                asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
                asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal">
        </script>
        <script src="~/js/site.min.js" asp-append-version="true"></script>
    </environment>

    @RenderSection("scripts", required: false)
</body>
</html>

경고

아직 Movies 컨트롤러를 구현하지 않았기 때문에 이 링크를 클릭하면 404 (Not found) 오류가 발생하게 됩니다.

변경사항을 저장한 다음, About 링크 등을 클릭해봅니다. 그리고 각 페이지가 Mvc Movie 링크를 출력하고 있는지 확인해보시기 바랍니다. 레이아웃 템플릿 페이지 한 곳만 변경했지만 사이트의 모든 페이지에 새로운 링크 문구와 새로운 제목이 반영된 것을 알 수 있습니다.

계속해서 Views/_ViewStart.cshtml 파일을 살펴보겠습니다:

@{
    Layout = "_Layout";
}

Views/_ViewStart.cshtml  파일이 모든 뷰에 Views/Shared/_Layout.cshtml  파일을 적용해주는 역할을 합니다. 이 Layout 속성을 이용해서 다른 레이아웃 뷰를 지정하거나, 아예 null을 설정해서 레이아웃 파일을 사용하지 않게 지정할 수도 있습니다.

이번에는 Index 뷰의 제목 문구를 변경해보겠습니다.

다시 Views/HelloWorld/Index.cshtml  파일을 엽니다. 이 파일에서 변경해야 할 부분은 모두 두 곳입니다:

  • 브라우저의 제목 표시줄에 나타날 문구
  • 본문의 보조 제목 문구 (<h2> 요소)

변경해야 할 부분이 극히 일부분에 불과하기 때문에 어떤 코드가 응용 프로그램의 어떤 부분을 변경하는지 쉽게 파악할 수 있을 것입니다.

@{
    ViewData["Title"] = "Movie List";
}

<h2>My Movie List</h2>

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

이 코드에서 ViewData["Title"] = "Movie List"; 줄은 ViewDataDictionary 개체의 Title 속성을 "Movie List"로 설정하고 있습니다. 그리고 이 Title 속성은 레이아웃 페이지의 <title> HTML 요소에서 다음과 같이 사용됩니다:

<title>@ViewData["Title"] - Movie App</title>

변경사항을 저장하고 페이지를 새로 고침합니다. 그러면 브라우저의 제목 표시줄과 기본 제목의 문구, 그리고 보조 제목의 문구가 변경된 것을 확인할 수 있습니다. (변경사항이 브라우저에 반영되지 않는다면 아마도 캐시 때문일 것입니다. 브라우저에서 Ctrl+F5 키를 눌러서 강제로 서버로부터 받은 응답을 로드합니다.) 브라우저 제목 표시줄 문구는 Index.cshtml 뷰 템플릿에서 설정한 ViewData["Title"]의 값과 레이아웃 파일에 추가된 " - Movie App" 문구가 결합되어 만들어진 결과입니다.

그리고 Index.cshtml  뷰 템플릿의 내용과 Views/Shared/_Layout.cshtml  뷰 템플릿의 내용이 병합되어 단일 HTML 응답으로 브라우저에 전송되는 방식을 주의 깊게 살펴보시기 바랍니다. 이처럼 레이아웃 템플릿을 활용하면 응용 프로그램의 모든 페이지에 공통적으로 적용되는 부분들을 손쉽게 수정할 수 있습니다. 더 많은 정보는 레이아웃 문서를 참고하시기 바랍니다.

그러나 이 예제에 사용된 간단한 "데이터"는 (즉, "Hello from our View Template!"라는 메시지는) 하드코딩 되어 있는 값에 불과합니다. 결국 예제 MVC 응용 프로그램에는 "V"(뷰)와 "C"(컨트롤러)는 존재하지만, 아직 "M"(모델)은 존재하지 않는 상태인 셈입니다. 다음 파트에서는 데이터베이스를 생성하고 데이터베이스에서 모델 데이터를 가져오는 방법을 살펴봅니다.

컨트롤러에서 뷰로 데이터 전달하기

데이터베이스와 모델에 관해서 살펴보기 전에 먼저 컨트롤러에서 뷰로 정보를 전달하는 방법부터 알아보도록 하겠습니다. 컨트롤러 클래스는 전달된 URL 요청에 의해서 호출됩니다. 그러면 컨트롤러 클래스에 작성된 코드에 의해서 전달받은 브라우저 요청이 처리되어 데이터베이스로부터 데이터를 가져온 다음, 최종적으로 어떤 형태의 응답을 브라우저로 전송할 것인지가 결정됩니다. 이런 처리가 마무리되고 난 뒤에야 브라우저로 전달될 HTML 응답을 생성하고 형식화하기 위해서 컨트롤러에 의해 뷰 템플릿이 사용됩니다.

컨트롤러는 뷰 템플릿이 브라우저에 응답을 렌더할 때 필요한 모든 데이터와 개체를 제공해야 할 책임을 갖고 있습니다. 뷰 템플릿은 어떠한 경우에도 업무 로직을 수행하거나 데이터베이스와 직접 연결돼서는 안됩니다. 뷰 템플릿은 컨트롤러로부터 제공 받은 데이터만을 이용해서 모든 작업을 수행해야 합니다. 이렇게 "관심의 분리(Separation of Concerns)" 원칙을 지켜야만 깔끔하고 테스트 가능한 유지보수가 용이한 코드를 유지할 수 있습니다.

현재 HelloWorldController 클래스의 Welcome 액션 메서드는 name 매개변수와 ID 매개변수를 받아서 그 값을 브라우저에 직접 출력하도록 구현되어 있습니다. 이렇게 컨트롤러가 직접 응답을 문자열로 렌더하는 대신, 뷰 템플릿을 이용하도록 컨트롤러를 변경해보겠습니다. 그리고 뷰 템플릿이 동적 응답을 생성하도록 만들어볼텐데, 이 말은 응답을 생성하기 위해서 필요한 간단한 데이터를 컨트롤러에서 뷰로 전달해줘야 한다는 뜻입니다. 이 작업을 위해서 뷰 템플릿에 필요한 동적 데이터(매개변수들)를 컨트롤러 내부에서 ViewData 사전에 설정한 다음, 이를 뷰 템플릿에서 접근해 볼 것입니다.

다시 HelloWorldController.cs 파일로 돌아간 다음, Welcome 메서드를 변경해서 ViewData 사전에 Message 속성과 NumTimes 속성 값을 설정하도록 만듭니다. ViewData 사전은 동적 개체로 원하는 것은 무엇이든지 설정할 수 있으며, 여러분이 ViewData 개체에 무언가 설정하기 전까지는 아무런 속성도 정의되어 있지 않습니다. 또한 MVC 모델 바인딩 시스템이 자동으로 브라우저 주소 표시줄의 쿼리 문자열에 포함된 명명된 매개변수들(namenumTimes)을 액션 메서드의 매개변수에 매핑시켜줍니다. 지금까지 설명한 모든 작업이 마무리된 HelloWorldController.cs 파일은 다음과 같습니다:

using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;

namespace MvcMovie.Controllers
{
    public class HelloWorldController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        public IActionResult Welcome(string name, int numTimes = 1)
        {
            ViewData["Message"] = "Hello " + name;
            ViewData["NumTimes"] = numTimes;

            return View();
        }
    }
}

이제 뷰에 전달될 데이터들이 ViewData 사전에 담겨지게 됩니다. 그러면 이제 Welcome 뷰 템플릿을 만들어 볼 차례입니다.

  • 마우스 오른쪽 버튼으로 Views/HelloWorld 폴더를 클릭하고 추가(Add) > 새 항목(New Item)을 선택합니다.
  • 그런 다음, 새 항목 추가 - MvcMovie(Add New Item - MvcMovie) 대화 상자가 나타나면
    • 우측 상단에 위치한 검색 상자에 view 라고 입력합니다.
    • MVC 뷰 페이지(MVC View Page)를 선택합니다.
    • 이름(Name) 입력란에 Welcome.cshtml 을 입력합니다.
    • 추가(Add) 버튼을 누릅니다.

그리고 Welcome.cshtml 뷰 템플릿에 NumTimes 속성에 지정된 횟수만큼 "Hello" 메시지를 출력하는 루프문을 추가해보겠습니다. 다음과 같이 Views/HelloWorld/Welcome.cshtml  파일의 내용을 변경합니다:

@{
    ViewData["Title"] = "About";
}

<h2>Welcome</h2>

<ul>
    @for (int i = 0; i < (int)ViewData["NumTimes"]; i++)
    {
        <li>@ViewData["Message"]</li>
    }
</ul>

이제 변경사항을 저장하고 다음 URL로 이동해봅니다:

http://localhost:xxxx/HelloWorld/Welcome?name=Rick&numtimes=4

그러면 먼저 URL로부터 얻어진 데이터가 모델 바인더에 의해서 컨트롤러로 전달됩니다. 컨트롤러는 이 데이터를 ViewData 사전에 정리해서 담고 이 개체를 다시 뷰로 전달합니다. 마지막으로 뷰는 해당 데이터를 HTML로 렌더해서 이를 브라우저에 전달하게 됩니다.

이번 예제에서는 컨트롤러에서 뷰로 데이터를 전달하기 위해서 ViewData 사전을 사용하고 있습니다. 그러나 본 자습서의 이후 과정에서는 컨트롤러에서 뷰로 데이터를 전달할 때 뷰 모델을 사용하려고 합니다. 일반적으로 ViewData 사전을 이용한 데이터 전달 방식보다는 뷰 모델 방식이 훨씬 더 선호되는 편입니다. 보다 자세한 정보는 Dynamic V Strongly Typed Views 포스트를 참고하시기 바랍니다.

물론 뷰 모델도 일종의 "M", 즉 모델로 볼 수는 있지만, 사실 데이터베이스와는 무관합니다. 다음 파트에서는 모델에 관해서 살펴보고 영화 정보를 저장할 데이터베이스도 생성해보겠습니다.