뷰: 태그 헬퍼 소개

등록일시: 2016-09-20 08:00,  수정일시: 2016-09-29 16:02
조회수: 518
본 번역문서는 ASP.NET Core MVC 기술을 널리 알리고자 하는 개인적인 취지로 번역되어 제공되는 문서로, 원문을 비롯한 모든 저작권은 마이크로소프트사에 있습니다. 마이크로소프트사의 요청이 있을 경우, 언제라도 게시가 중단될 수 있습니다. 본 번역문서에는 오역이 포함되어 있을 수 있으며 주석도 번역자 개인의 견해일뿐입니다. 마이크로소프트사는 본 문서의 번역된 내용에 대해 일체의 보장을 하지 않습니다. 번역이 완료된 뒤에도 제품이 업그레이드 되거나 기능이 변경됨에 따라 원문도 변경되거나 보완되었을 수 있으므로 참고하시기 바랍니다.
본문에서는 보다 HTML 친화적인 방식으로 풍부한 인텔리센스의 지원과 함께 Razor 뷰에서 서버 측 코드로 HTML 요소의 생성과 렌더링에 관여할 수 있는 태그 헬퍼에 관해서 살펴봅니다.

태그 헬퍼

태그 헬퍼를 사용하면 서버 측 코드를 이용해서 Razor 파일에서 수행되는 HTML 요소의 생성과 렌더링에 관여할 수 있습니다. 예를 들어, 내장 ImageTagHelper를 사용하면 이미지의 이름에 버전 번호를 추가할 수 있습니다. 그 결과, 이미지가 변경될 때마다 서버에서 이미지의 새로운 고유 번호가 생성되므로 클라이언트는 항상 최신 이미지를 얻을 수 있습니다 (캐시된 과거 이미지 대신). 폼 생성, 링크, 자산 로딩 등 보편적인 작업들을 위한 다양한 내장 태그 헬퍼가 기본으로 제공되며, 공개 GitHub 리파지터리를 통해서, 그리고 NuGet 패키지로 더 많은 태그 헬퍼들을 사용할 수 있습니다. 태그 헬퍼는 C#으로 작성되었으며, 요소의 이름이나 어트리뷰트 이름, 또는 부모 태그를 기반으로 HTML 요소에 적용됩니다. 가령, 내장 LabelTagHelperLabelTagHelper의 서버 측 어트리뷰트가 지정될 때 HTML <label> 요소에 적용됩니다. HTML 헬퍼(HTML Helpers)와 비교해 볼 때, 태그 헬퍼를 사용하면 Razor 뷰에서 HTML과 C# 간의 명시적인 전환이 현저하게 줄어듭니다. 이 부분에 관해서는 태그 헬퍼와 HTML 헬퍼 비교하기 절에서 그 차이점을 더 자세히 살펴보도록 하겠습니다.

태그 헬퍼의 장점

HTML 친화적인 개발 경험
일반적으로 태그 헬퍼가 사용된 Razor 마크업은 마치 평범한 HTML인 것처럼 보입니다. 따라서 HTML/CSS/JavaScript에 능숙한 프런트-엔드 디자이너는 C# Razor 구문을 따로 학습하지 않아도 Razor를 편집할 수 있습니다.
HTML 및 Razor 마크업 작성을 위한 풍부한 인텔리센스 환경 지원
기존에 서버 측에서 Razor 뷰의 마크업을 생성하기 위해서 사용되던 HTML 헬퍼 접근방식과 비교해보면 그 장점이 더욱 명확합니다. 이 부분에 관해서는 태그 헬퍼와 HTML 헬퍼 비교하기 절에서 자세하게 살펴보도록 하겠습니다. 그리고 태그 헬퍼에 대한 인텔리센스 지원 절에서는 인텔리센스 지원 환경에 관해서 살펴봅니다. Razor C# 구문을 사용해본 경험을 갖고 있는 개발자들도 C# Razor 마크업을 작성할 때보다 태그 헬퍼를 사용할 때의 생산성이 더 높습니다.
생산성을 향상시켜주고, 서버에서만 사용 가능한 정보를 활용하여 보다 강력하고 신뢰할 수 있으며, 유지 보수가 용이한 코드를 만들 수 있는 방식
전통적으로 지금까지는 이미지를 변경하면 이미지의 이름도 변경해야만 했습니다. 왜냐하면 이미지는 성능 상의 이유로 인해서 최대한 적극적으로 캐시되기 때문에, 이미지 이름을 변경하지 않으면 클라이언트가 낡은 사본을 얻게 될 위험성이 존재하기 때문입니다. 따라서 이미지가 변경된 뒤에는 가급적 이름을 변경해야만 했고, 그 결과 웹 응용 프로그램 내부에 존재하는 해당 이미지에 대한 모든 참조도 갱신해야 했습니다. 이 작업은 대단히 노동집약적일 뿐만 아니라, 의도하지 않은 오류를 만들기도 쉬운 작업입니다 (참조 갱신을 누락하거나 실수로 오타를 입력할 수도 있습니다). 그러나 내장 ImageTagHelper를 사용하면 이 작업을 자동으로 처리할 수 있습니다. ImageTagHelper는 이미지 이름에 버전 번호를 추가해주는 기능을 제공하여 이미지가 변경될 때마다 서버가 자동으로 이미지에 대한 새로운 고유 버전을 생성해줍니다. 따라서 클라이언트는 항상 최신 이미지를 얻을 수 있습니다. 이처럼 ImageTagHelper를 사용하면 별다른 노력 없이도 견고함과 노동력 절감을 기본적으로 얻을 수 있습니다.

대부분의 내장 태그 헬퍼들은 기존 HTML 요소에 적용되며 해당 요소에 대한 서버 측 어트리뷰트를 제공합니다. 가령, Views/Account 폴더에 위치한 많은 뷰들에서 사용되는 <label> 요소들에는 지정된 모델 속성의 이름을 렌더된 HTML로 추출하는 asp-for 어트리뷰트가 적용되어 있습니다. 이를테면 다음의 Razor 마크업은:

<label asp-for="Email"></label>

다음과 같은 HTML을 생성합니다:

<label for="Email">Email</label>

이 마크업에 사용된 asp-for 어트리뷰트는 LabelTagHelperFor 속성으로부터 제공되는 것입니다. 이에 관한 더 자세한 정보는 태그 헬퍼 구현하기 문서에서 확인하시기 바랍니다.

태그 헬퍼 범위 관리하기

태그 헬퍼의 사용 범위는 @addTagHelper 지시문과 @removeTagHelper 지시문, 그리고 "!" 옵트-아웃(Opt-Out) 문자의 조합으로 제어됩니다.

@addTagHelper 지시문으로 태그 헬퍼 활성화하기

인증 미사용 옵션을 선택해서 AuthoringTagHelpers 라는 이름으로 새로운 ASP.NET Core 웹 응용 프로그램을 생성한다고 가정해보겠습니다. 그러면 프로젝트에 다음과 같은 내용의 Views/_ViewImports.cshtml  파일이 추가됩니다:

@using AuthoringTagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

이 코드에 사용된 @addTagHelper 지시문은 해당 뷰에서 태그 헬퍼를 사용할 수 있게 만들어줍니다. 또한 이 뷰 파일은 Views 폴더 및 하위 폴더들에 존재하는 모든 뷰 파일들이 기본적으로 상속받는 Views/_ViewImports.cshtml 이므로, 모든 뷰에서 태그 헬퍼를 사용할 수 있게 됩니다. 예제 코드에서는 와일드카드("*") 구문을 사용해서 지정된 어셈블리(Microsoft.AspNetCore.Mvc.TagHelpers)에 존재하는 모든 태그 헬퍼를 Views 폴더와 그 하위 폴더들에 존재하는 모든 뷰 파일에서 사용할 수 있도록 지정하고 있습니다. @addTagHelper 지시문의 첫 번째 매개변수는 로드 할 태그 헬퍼를 지정합니다 (여기에서는 모든 태그 헬퍼를 의미하는 "*"를 지정했습니다). 그리고 두 번째 매개변수("Microsoft.AspNetCore.Mvc.TagHelpers")는 태그 헬퍼가 존재하는 어셈블리를 지정합니다. 이 Microsoft.AspNetCore.Mvc.TagHelpers 는 내장 ASP.NET Core 태그 헬퍼들이 존재하는 어셈블리입니다.

프로젝트에 존재하는 모든 태그 헬퍼를 노출하려면 다음과 같이 지정하면 됩니다 (프로젝트의 이름과 동일한 AuthoringTagHelpers 라는 이름으로 어셈블리가 만들어지므로):

@using AuthoringTagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper "*, AuthoringTagHelpers"

프로젝트의 기본 네임스페이스에 EmailTagHelper라는 태그 헬퍼가 존재한다면(AuthoringTagHelpers.TagHelpers.EmailTagHelper), 다음과 같이 태그 헬퍼의 정규화된 이름(FQN, Fully Qualified Name)을 지정할 수도 있습니다:

@using AuthoringTagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper "AuthoringTagHelpers.TagHelpers.EmailTagHelper, AuthoringTagHelpers"

정규화된 이름을 이용해서 뷰에 태그 헬퍼를 추가할 경우, 먼저 정규화된 이름(AuthoringTagHelpers.TagHelpers.EmailTagHelper)을 지정한 다음, 어셈블리 이름(AuthoringTagHelpers)을 지정하면 되지만, 대부분의 개발자들은 "*" 와일드카드 구문을 선호하는 편입니다. 와일드카드 구문을 사용할 경우, 정규화된 이름에 접미사로 와일드카드 문자 "*"를 입력할 수 있습니다. 예를 들어서, 다음의 지시문들은 모두 EmailTagHelper를 가져옵니다:

@addTagHelper "AuthoringTagHelpers.TagHelpers.E*, AuthoringTagHelpers"
@addTagHelper "AuthoringTagHelpers.TagHelpers.Email*, AuthoringTagHelpers"

이미 언급했던 것처럼, @addTagHelper 지시문을 Views/_ViewImports.cshtml 파일에 설정하면 Views 폴더와 그 하위 폴더에 존재하는 모든 뷰 파일에서 해당 태그 헬퍼를 사용할 수 있습니다. 만약 특정 뷰에서만 태그 헬퍼를 사용하기 위해서 옵트-인 하고 싶다면, 해당 뷰 파일에서 @addTagHelper 지시문을 사용하면 됩니다.

@removeTagHelper 지시문으로 태그 헬퍼 제거하기

@removeTagHelper 지시문은 @addTagHelper 지시문과 같은 두 개의 매개변수를 갖고 있으며, 기존에 추가된 태그 헬퍼를 제거합니다. 가령, 특정 뷰에서 @removeTagHelper를 설정하면 지정한 태그 헬퍼가 해당 뷰에서 제거됩니다. Views/Folder/_ViewImports.cshtml 파일에서 @removeTagHelper 지시문을 지정하면 지정된 태그 헬퍼가 Folder 폴더에 존재하는 모든 뷰에서 제거됩니다.

_ViewImports.cshtml 파일을 이용한 태그 헬퍼의 범위 제어

뷰 폴더의 하위 폴더 어디에나 새로운 _ViewImports.cshtml 파일을 추가할 수 있으며, 뷰 엔진은 Views/_ViewImports.cshtml 파일에 담긴 지시문들에, 해당 _ViewImports.cshtml 파일에 추가된 지시문들을 추가합니다. _ViewImports.cshtml 파일은 누적되는 형태로 적용되기 때문에, 예를 들어 Home 폴더에 빈 Views/Home/_ViewImports.cshtml 파일을 추가하더라도 그 하위의 뷰에서는 아무런 변화도 일어나지 않습니다. 반면 Views/Home/_ViewImports.cshtml 파일에 추가한 모든 @addTagHelper 지시문들은 (기본 Views/_ViewImports.cshtml 파일에는 설정되지 않은) Home 폴더에 존재하는 뷰들에만 해당 태그 헬퍼들을 노출하게 됩니다.

개별 요소별로 적용 해제하기

태그 헬퍼 옵트-아웃 문자("!")를 이용하면 요소 수준에서 태그 헬퍼를 비활성화 시킬수 있습니다. 예를 들어서, 다음과 같이 태그 헬퍼 옵트-아웃 문자를 지정하면 <span> 태그의 Email 유효성 검사가 비활성화 됩니다:

<!span asp-validation-for="Email" class="text-danger"></!span>

반드시 여는 태그와 닫는 태그 모두에 태그 헬퍼 옵트-아웃 문자를 적용해야 합니다 (Visual Studio 편집기를 사용할 경우, 여는 태그에 옵트-아웃 문자를 추가하면 자동으로 닫는 태그에도 옵트-아웃 문자가 추가됩니다). 이렇게 옵트-아웃 문자를 추가하고 나면, 더 이상 요소와 태그 헬퍼 어트리뷰트가 독특한 글꼴로 표시되지 않음을 확인할 수 있습니다.

@tagHelperPrefix 지시문으로 명시적으로 태그 헬퍼 사용하기

@tagHelperPrefix 지시문을 이용해서 태그 헬퍼 기능을 활성화시키기 위한 태그 접두사 문자열을 지정하고 명시적으로 태그 헬퍼를 사용하도록 설정할 수 있습니다. 아래 그림의 코드에서는 태그 헬퍼 접두사가 th:로 설정되었으며, 그 결과 접두사로 th:를 사용하는 요소들만 태그 헬퍼가 지원되고 있습니다 (태그 헬퍼가 활성화된 요소들은 톡특한 글꼴로 강조되어 있습니다). <label> 요소와 <input> 요소에는 태그 헬퍼 접두사가 지정되어 있고 태그 헬퍼를 지원하는 반면, <span> 요소는 그렇지 않습니다.

또한 @addTagHelper에 적용되는 것과 동일한 계층적 규칙들이 @tagHelperPrefix에도 적용됩니다.

태그 헬퍼에 대한 인텔리센스 지원

Visual Studio에서 새로운 ASP.NET 웹 응용 프로그램을 생성하면 기본적으로 project.json 파일에 "Microsoft.AspNetCore.Razor.Tools" 패키지가 추가됩니다. 이 패키지는 태그 헬퍼 도구들을 추가해주는 패키지입니다.

HTML <label> 요소를 작성한다고 가정해보겠습니다. Visual Studio 편집기에서 <l을 입력하는 순간, 다음 그림과 같이 인텔리센스가 일치하는 요소들의 목록을 출력해줍니다:

이때, HTML 도움말과 함께 태그 헬퍼가 적용되는 요소임을 알려주는 ("@" 기호 좌측 하단에 "<>"가 표시된) 아이콘이 함께 제공됩니다. 반면 fieldset 같은 순수한 HTML 요소들은 단순한 "<>" 아이콘으로 나타납니다.

기본 Visual Studio 색 테마에서 순수한 HTML <label> 태그의 HTML 태그는 갈색 글꼴로, 어트리뷰트는 붉은색 글꼴로, 어트리뷰트 값은 파란색 글꼴로 출력됩니다.

이제 <label까지 입력한 다음, 빈 문자를 하나 더 입력하면 인텔리센스가 사용 가능한 HTML/CSS 어트리뷰트와 태그 헬퍼 적용 어트리뷰트들의 목록을 제시해줍니다:

다음과 같은 상태에서 탭 키를 입력하면 인텔리센스 구문 완성이 선택된 값으로 구문이 완성시켜줍니다:

태그 헬퍼 어트리뷰트가 입력되는 순간, 태그와 어트리뷰트의 글꼴이 바뀝니다. Visual Studio의 기본 "파랑(Blue)" 색 테마나 "광원(Light)" 색 테마를 사용할 경우, 굵은 보라색 글꼴로 변경되고, "어둡게(Dark)" 테마를 사용하고 있다면 굵은 청록색 글꼴로 변경됩니다. 본문의 그림들은 기본 테마를 사용하는 경우를 보여줍니다.

이때, 쌍따옴표("") 내부에서는 Visual Studio의 단어 자동 완성(CompleteWord) 단축키를 입력할 수 있으며 (컨트롤 + 스페이스바의 조합이 default 기본 단축키입니다), 해당 어트리뷰트가 태그 헬퍼 어트리뷰트인 경우, 그 내부에서는 C# 클래스를 편집할 때처럼 C# 구문을 작성하고 있는 것입니다. 인텔리센스는 페이지의 모델에 존재하는 모든 메서드와 속성들의 목록을 제시해줍니다. 이렇게 메서드와 속성들을 사용할 수 있는 이유는 태그 헬퍼 어트리뷰트를 구현하는 속성이 ModelExpression 형식이기 때문입니다. 다음 그림은 Register 뷰를 편집하고 있는 상황을 보여주고 있으며, 따라서 RegisterViewModel의 메서드 및 속성들이 제시되고 있습니다.

인텔리센스는 페이지의 모델에 대한 속성과 메서드들의 목록을 제공해줍니다. 또한, 풍부한 인텔리센스 환경은 CSS 클래스의 선택도 지원해줍니다:

태그 헬퍼와 HTML 헬퍼 비교하기

태그 헬퍼는 Razor 뷰의 HTML 요소와 결합되는 반면, HTML 헬퍼(HTML Helpers)는 Razor 뷰 내부에서 HTML과 함께 여기 저기에 작성되어 메서드의 형태로 호출됩니다. 다음과 같이 "caption"이라는 CSS 클래스가 지정된, HTML 라벨을 생성하는 Razor 마크업을 가정해보겠습니다:

@Html.Label("FirstName", "First Name:", new {@class="caption"})

이 마크업에서 @ 기호는 Razor에게 코드가 시작됨을 알려줍니다. 그리고 처음 두 매개변수들은 ("FirstName" 및 "First Name:") 문자열이므로 인텔리센스가 그다지 도움이 되지 않습니다. 마지막 매개변수로는 다음과 같이 어트리뷰트를 나타내는 익명 개체가 전달됩니다:

new {@class="caption"}

여기서 class는 C#에서 예약된 키워드이기 때문에, @ 기호를 사용해서 C#이 "@class="를 기호로 인식하도록 강제해야 합니다. 프런트-엔드 디자이너에게는 (HTML/CSS/JavaScript 및 다른 클라이언트 기술에 능숙하지만 C#이나 Razor에는 익숙하지 않은) 이 마크업을 구성하는 대부분의 내용들이 생소합니다. 또한, 마크업 전체를 인텔리센스의 도움 없이 작성해야만 합니다.

위와 동일한 마크업을 LabelTagHelper로 작성하면 다음과 같습니다:

태그 헬퍼를 이용해서 작성하는 경우에는 Visual Studio 편집기에서 <l를 입력하는 순간 인텔리센스가 일치하는 요소들의 목록을 제시해줍니다:

게다가 마크업을 작성하는 내내 인텔리센스의 도움을 받을 수 있습니다. 그리고 LabelTagHelper는 기본적으로 asp-for 어트리뷰트의 값("FirstName")을 이용해서 콘텐츠를 "First Name"으로 설정합니다. 즉, 카멜-케이스로 작성된 속성 이름에서 각 대문자마다 그 앞에 빈 문자를 추가하여 구성된 문장으로 변환합니다. (역주: 문맥상 파스칼 케이스가 맞을 것 같습니다.) 이를테면 다음 마크업은:

다음과 같은 HTML을 생성합니다:

<label class="caption" for="FirstName">First Name</label>

반면, 직접 <label>에 콘텐츠를 추가한 경우에는 카멜-케이스를 문장 형태의 콘텐츠로 변환하는 작업이 수행되지 않습니다. 가령 다음 마크업은:

다음과 같은 HTML을 생성합니다:

<label class="caption" for="FirstName">Name First</label>

다음 그림은 Visual Studio 2015에서 기본 ASP.NET 4.5.x MVC 템플릿을 이용해서 만들어진 Views/Account/Register.cshtml Razor 뷰의 폼 부분 코드를 보여주고 있습니다.

이 그림에서는 Visual Studio 편집기가 C# 코드 부분들을 회색 배경으로 표시해주고 있습니다. 즉, 다음과 같은 AntiForgeryToken HTML 헬퍼 부분은 회색 배경으로 나타납니다:

@Html.AntiForgeryToken()

이 Register 뷰에서 대부분의 마크업은 C#입니다. 이제 태그 헬퍼를 사용해서 이와 동일한 뷰를 작성해보면 다음과 같습니다:

HTML 헬퍼를 사용할 때와 비교해 볼 때, 마크업이 보다 깔끔해졌으며 읽거나 편집하고 관리하기도 쉬워졌습니다. 서버에서 알 필요가 있는 부분들에 대해서만 C# 코드가 남아서 최소한으로 줄어들었습니다. 또한 Visual Studio 편집기는 태그 헬퍼가 적용된 마크업들을 특수한 글꼴로 구분해줍니다.

계속해서 이번에는 Email 관련 부분을 살펴보겠습니다:

<div class="form-group">
    <label asp-for="Email" class="col-md-2 control-label"></label>
    <div class="col-md-10">
        <input asp-for="Email" class="form-control" />
        <span asp-validation-for="Email" class="text-danger"></span>
    </div>
</div>

각각의 "asp-" 어트리뷰트들은 "Email"이라는 값을 갖고 있는데, 이 값은 문자열이 아닙니다. 이 문맥에서 "Email"은 RegisterViewModel에 대한 C#의 모델 표현식 속성(Model Expression Property)입니다.

태그 헬퍼 방식으로 등록 폼의 마크업을 작성할 경우에는 거의 모든 마크업에 대해 Visual Studio 편집기의 지원을 받을 수 있는 반면, HTML 헬퍼 방식을 사용하면 대부분의 코드에서 Visual Studio 편집기의 지원을 받을 수 없습니다. Visual Studio 편집기에서 태그 헬퍼를 이용하는 작업에 대한 더 자세한 정보는 태그 헬퍼에 대한 인텔리센스 지원 절을 참고하시기 바랍니다.

태그 헬퍼와 웹 서버 컨트롤 비교하기

  • 태그 핼퍼는 요소와 관련되어 사용될 뿐, 요소 자체를 제어하지는 않으며, 단순히 요소 및 콘텐츠의 렌더링에 관여할 뿐입니다. 반면, ASP.NET 웹 서버 컨트롤은 페이지에서 선언 및 호출됩니다.
  • 웹 서버 컨트롤은 특수한 수명 주기를 갖고 있기 때문에 개발이나 디버깅이 어렵습니다.
  • 웹 서버 컨트롤을 사용하면 클라이언트 컨트롤을 이용해서 클라이언트 문서 개체 모델(DOM, Document Object Model)에 기능을 추가 할 수 있습니다. 반면 태그 헬퍼에는 DOM이 없습니다.
  • 웹 서버 컨트롤은 자동 브라우저 감지 기능을 갖고 있습니다. 태그 헬퍼는 브라우저에 대해서는 관심을 갖지 않습니다.
  • 여러 태그 헬퍼들을 같은 요소에 적용할 수 있는 반면 (태그 헬퍼 간의 충돌 제어하기 참조), 웹 서버 컨트롤은 대부분 섞어서 사용할 수 없습니다.
  • 태그 헬퍼는 자신의 범위로 지정된 태그와 HTML 요소의 콘텐츠를 수정할 수는 있지만, 페이지의 다른 부분을 직접적으로 수정할 수는 없습니다. 웹 서버 컨트롤은 보다 불분명한 범위를 갖고 있으며 페이지의 다른 부분에 영향을 주는 작업을 수행할 수 있는데, 이는 의도하지 않은 부작용을 가능하게 합니다.
  • 웹 서버 컨트롤은 형식 변환기(Type Converters)를 사용해서 문자열을 개체로 변환합니다. 태그 헬퍼를 사용하면 네이티브 C#으로 작업하기 때문에 형식 변환기가 필요 없습니다.
  • 웹 서버 컨트롤은 System.ComponentModel 네임스페이스를 이용해서 구성 요소 및 컨트롤의 런타임 및 디자인 타임 동작을 구현합니다. System.ComponentModel 네임스페이스에는 어트리뷰트 와 형식 변환기, 데이터 소스에 대한 바인딩, 그리고 구성 요소 라이센싱 등을 구현하기 위한 기본 클래스와 인터페이스들이 포함되어 있습니다. 이에 반해 태그 헬퍼는 대부분 TagHelper를 상속받기만 하면 되는데, 이 TagHelper 기본 클래스는 오직 두 개의 메서드, 즉 ProcessProcessAsync만 노출할 뿐입니다.

태그 헬퍼 요소 글꼴 사용자 지정하기

도구(Tools) > 옵션(Options) > 환경(Environment) > 글꼴 및 색(Fonts and Colors)에서 Visual Studio 편집기에 나타나는 태그 헬퍼의 글꼴과 색상을 사용자 지정할 수 있습니다:

추가 자료

COPYRIGHT © 2001-2017 EGOCUBE. ALL RIGHTS RESERVED.
Total Visit Count: 9,262,757, v1.9.0.37589 β