뷰: Razor 구문 참조

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

Razor

Razor는 웹 페이지에 서버 기반의 코드를 끼워 넣기 위한 마크업 구문입니다. Razor 구문은 Razor 마크업과 C#, 그리고 HTML 태그로 구성됩니다. 일반적으로 Razor 구문이 작성된 파일은 .cshtml 이라는 파일 확장자를 갖습니다.

HTML 렌더링

Razor의 기본 언어는 HTML입니다. Razor를 이용해서 렌더된 HTML과 일반적인 HTML 파일로 렌더된 HTML은 완벽하게 같습니다. 예를 들어서, 다음과 같은 마크업이 작성된 Razor 파일은:

<p>Hello World</p>

서버에 의해서 처리되기는 하지만, 변경 없이 <p>Hello World</p> 그대로 렌더됩니다.

Razor 구문

Razor는 C#을 지원하며 @ 기호를 사용해서 HTML에서 C#으로 전환됩니다. Razor는 C# 표현식을 평가하고 그 결과를 HTML 출력에 렌더합니다. Razor는 HTML에서 C#이나 Razor 전용 마크업으로 전환할 수 있습니다. @ 기호 뒤에 Razor 예약 키워드가 나타나면 Razor 전용 마크업으로 전환되고, 아니면 일반적인 C#으로 전환됩니다.

따라서, @ 기호 자체가 포함된 HTML을 작성하려면 @ 기호를 한번 더 추가해서 이스케이프시켜야 합니다. 가령, 다음 Razor 구문은:

<p>@@Username</p>

다음과 같은 HTML을 렌더합니다:

<p>@Username</p>

그러나 이메일 주소를 담고 있는 HTML 어트리뷰트나 콘텐츠에서는 @ 기호가 전환 문자로 인식되지 않습니다.

<a href="mailto:Support@contoso.com">Support@contoso.com</a>

암시적 Razor 표현식

암시적 Razor 표현식에서는 다음과 같이 @ 기호 뒤에 바로 C# 코드가 작성됩니다:

<p>@DateTime.Now</p>
<p>@DateTime.IsLeapYear(2016)</p>

기본적으로 암시적 Razor 표현식에서는 공백 문자를 사용할 수 없지만, 예외적으로 C# await 키워드에서만은 공백 문자가 허용됩니다. 즉, 다음과 같이 C# 구문이 명료하게 마무리되기만 한다면 공백 문자를 섞어서 작성할 수 있습니다:

<p>@await DoSomething("hello", "world")</p>

명시적 Razor 표현식

명시적 Razor 표현식은 @ 기호와 괄호로 구성됩니다. 가령, 일주일 전의 시간을 렌더하고 싶다면 다음과 같이 Razor 구문을 작성하면 됩니다:

<p>Last week this time: @(DateTime.Now - TimeSpan.FromDays(7))</p>

이렇게 명시적으로 표현식을 작성하면, @() 괄호 내부에 존재하는 모든 표현식이 평가되어 출력에 렌더됩니다.

반면, 암시적 표현식에서는 이전 절에서 설명한 것처럼 일반적으로 공백 문자를 사용할 수 없습니다. 가령, 다음 코드는 현재 시간에서 한 주를 뺀 시간을 렌더하고자 하는 본래의 의도와는 달리:

<p>Last week: @DateTime.Now - TimeSpan.FromDays(7)</p>

다음과 같은 HTML을 렌더하게 됩니다:

<p>Last week: 7/7/2016 4:39:52 PM - TimeSpan.FromDays(7)</p>

또한, 명시적 표현식을 사용해서 표현식의 결과와 텍스트를 연결할 수도 있습니다:

@{
    var joe = new Person("Joe", 33);
 }

<p>Age@(joe.Age)</p>

만약 이번 예제에서 명시적 표현식을 사용하지 않았다면, <p>Age@joe.Age</p> 구문이 이메일 주소로 간주되어 <p>Age@joe.Age</p>로 렌더되게 됩니다. 그러나 명시적 표현식으로 작성했기 때문에 의도한 대로 <p>Age33</p>가 렌더됩니다.

표현식 인코딩

문자열로 평가되는 C# 표현식은 HTML 인코딩 처리됩니다. 반면, IHtmlContent로 평가되는 C# 표현식은 IHtmlContent.WriteTo 메서드를 통해서 직접 렌더됩니다. IHtmlContent로 평가되지 않는 C# 표현식은 먼저 문자열로 변환된 다음 (ToString 메서드를 통해서), 렌더되기 전에 인코딩 처리됩니다. 예를 들어서, 다음 Razor 마크업은:

@("<span>Hello World</span>")

다음과 같은 HTML로 렌더됩니다:

&lt;span&gt;Hello World&lt;/span&gt;

따라서, 브라우저에서는 다음과 같이 출력됩니다:

<span>Hello World</span>

그러나 HtmlHelperRaw 메서드 출력은 인코딩되지 않으며 HTML 마크업으로 렌더됩니다.

주의

검증되지 않은 사용자의 입력을 HtmlHelper.Raw 메서드로 출력하는 것은 보안 측면에서 매우 위험합니다. 사용자의 입력에 악의적인 JavaScript 또는 다른 공격들이 포함되어 있을 수도 있기 때문입니다. 직접 사용자의 입력을 검사해서 효율적으로 악의적인 내용을 걸러내기는 매우 어렵기 때문에, 사용자의 입력을 HtmlHelper.Raw 메서드로 렌더하는 일은 지양하는 것이 좋습니다.

다음 Razor 마크업은:

@Html.Raw("<span>Hello World</span>")

다음과 같은 HTML을 렌더합니다:

<span>Hello World</span>

Razor 코드 블럭

Razor 코드 블럭은 @ 기호로 시작하고 {}로 둘러싸입니다. 표현식과는 다르게 코드 블럭 내부에 작성된 C# 코드는 렌더되지 않습니다. Razor 페이지의 모든 코드 블럭과 표현식들은 동일한 범위(Scope)을 공유하며 작성된 순서대로 정의됩니다 (즉, 특정 코드 블럭에서 선언된 변수를 그 이후의 코드 블럭이나 표현식에서 접근할 수 있습니다).

@{
    var output = "Hello World";
}

<p>The rendered result: @output</p>

이 Razor 마크업은 다음과 같이 렌더됩니다:

<p>The rendered result: Hello World</p>

암시적 전환

코드 블럭 내부의 기본 언어는 C#이지만 HTML로 다시 전환할 수도 있습니다. 간단히 코드 블럭 내부에서 HTML 태그를 작성하면 다시 HTML 렌더링으로 전환됩니다:

@{
    var inCSharp = true;
    <p>Now in HTML, was in C# @inCSharp</p>
}

명시적으로 구분된 전환

코드 블럭 내부에서 명시적으로 HTML을 렌더하기 위한 하위 영역을 정의하려면, Razor <text> 태그로 렌더될 문자들을 둘러싸면 됩니다:

@for (var i = 0; i < people.Length; i++)
{
    var person = people[i];
    <text>Name: @person.Name</text>
}

이 방법은 주로 HTML 태그로 감싸여지지 않은 HTML을 렌더하고자 할 때 사용됩니다. 이 경우, HTML 태그나 Razor 태그로 HTML을 감싸지 않으면 Razor 런타임 오류가 발생합니다.

@:를 이용한 명시적 행 단위 전환

코드 블럭 내부에서 특정 라인 한 줄을 HTML로 렌더하려면 @: 구문을 사용합니다:

@for (var i = 0; i < people.Length; i++)
{
    var person = people[i];
    @:Name: @person.Name
}

이 코드에서도 @: 구문을 사용하지 않으면 Razor 런타임 오류가 발생합니다.

제어 구조

제어 구조(Control Structures)는 코드 블럭의 확장입니다. 코드 블럭의 모든 특징들이 (마크업으로의 전환, 인라인 C# 등) 이번 절에서 살펴볼 제어 구조에도 동일하게 적용됩니다.

조건문 @if, else if, else, @switch

@if 관련 구문들은 코드가 실행될 조건을 제어합니다:

@if (value % 2 == 0)
{
    <p>The value was even</p>
}

그리고 else 구문과 else if 구문에는 @ 기호를 붙일 필요가 없습니다:

@if (value % 2 == 0)
{
    <p>The value was even</p>
}
else if (value >= 1337)
{
    <p>The value is large.</p>
}
else
{
    <p>The value was not large and is odd.</p>
}

다음과 같이 switch 구문을 사용할 수도 있습니다:

@switch (value)
{
    case 1:
        <p>The value is 1!</p>
        break;
    case 1337:
        <p>Your number is 1337!</p>
        break;
    default:
        <p>Your number was not 1 or 1337.</p>
        break;
}

루프문 @for, @foreach, @while, @do while

루프 제어 구문을 사용하면 템플릿 형태의 HTML을 반복적으로 렌더할 수 있습니다. 가령, 다음과 같이 배열에 저장된 목록을 렌더하기 위해서:

@{
    var people = new Person[]
    {
        new Person("John", 33),
        new Person("Doe", 41),
    };
}

다음 중 어떤 루프 구문이라도 사용할 수 있습니다:

@for

@for (var i = 0; i < people.Length; i++)
{
    var person = people[i];
    <p>Name: @person.Name</p>
    <p>Age: @person.Age</p>
}

@foreach

@foreach (var person in people)
{
    <p>Name: @person.Name</p>
    <p>Age: @person.Age</p>
}

@while

@{ var i = 0; }
@while (i < people.Length)
{
    var person = people[i];
    <p>Name: @person.Name</p>
    <p>Age: @person.Age</p>

    i++;
}

@do while

@{ var i = 0; }
@do
{
    var person = people[i];
    <p>Name: @person.Name</p>
    <p>Age: @person.Age</p>

    i++;
} while (i < people.Length);

복합문 @using

C#에서 using 구문은 개체의 제거를 보장하기 위한 용도로 사용됩니다. Razor에서도 부가적인 콘텐츠를 담는 HTML 헬퍼를 생성하기 위해서 같은 메커니즘을 사용할 수 있습니다. 예를 들어, 다음과 같이 @using 구문과 HTML 헬퍼를 함께 사용해서 폼 태그를 렌더할 수 있습니다:

@using (Html.BeginForm())
{
    <div>
        email:
        <input type="email" id="Email" name="Email" value="" />
        <button type="submit"> Register </button>
    </div>
}

또는, 태그 헬퍼(Tag Helpers)를 사용해서 위와 동일한 범위 수준의 작업을 수행할 수도 있습니다.

@try, catch, finally

Razor의 예외 처리 방식은 C#과 비슷합니다:

@try
{
    throw new InvalidOperationException("You did something invalid.");
}
catch (Exception ex)
{
    <p>The exception message: @ex.Message</p>
}
finally
{
    <p>The finally statement.</p>
}

@lock

Razor는 @lock 구문을 이용해서 임계 영역(Critical Sections) 보호 기능을 지원해줍니다:

@lock (SomeLock)
{
    // Do critical section work
}

주석

Razor는 C#의 주석과 HTML 주석까지 모두 지원합니다. 다음 마크업은:

@{
    /* C# comment. */
    // Another C# comment.
}
<!-- HTML comment -->

서버에 의해서 다음과 같이 렌더됩니다:

<!-- HTML comment -->

Razor 주석은 페이지가 렌더되기 전에 서버에 의해서 제거됩니다. Razor는 @* *@를 이용해서 주석의 범위를 지정합니다. 다음 코드는 주석으로 처리되므로 서버는 아무런 마크업도 렌더하지 않습니다:

 @*
 @{
     /* C# comment. */
     // Another C# comment.
 }
 <!-- HTML comment -->
*@

지시문

Razor 지시문은 @ 기호 뒤에 예약된 키워드가 나타나는 암시적 표현식으로 작성됩니다. 일반적으로 지시문은 페이지의 구문 해석 방법을 변경하거나, Razor 페이지 내에서 다른 기능을 활성화시키는 등의 역할을 수행합니다.

먼저 Razor가 뷰의 코드를 생성하는 방법을 이해하면, 지시문이 동작하는 방식을 보다 쉽게 파악할 수 있습니다. 결론적으로 말해서 Razor 페이지는 C# 코드를 생성하기 위한 중간 단계일 뿐입니다. 가령, 다음과 같은 Razor 페이지는:

@{
    var output = "Hello World";
}

<div>Output: @output</div>

다음과 비슷한 클래스를 생성하게 됩니다:

public class _Views_Something_cshtml : RazorPage<dynamic>
{
    public override async Task ExecuteAsync()
    {
        var output = "Hello World";

        WriteLiteral("/r/n<div>Output: ");
        Write(output);
        WriteLiteral("</div>");
    }
}

잠시 후, 자동으로 생성되는 뷰의 Razor C# 클래스 살펴보기 절에서 이런 방식으로 생성되는 코드를 직접 살펴볼 수 있는 방법을 알아봅니다.

@using

@using 지시문은 생성되는 Razor C# 클래스에 C#의 using 지시문을 추가합니다:

@using System.IO
@{
    var dir = Directory.GetCurrentDirectory();
}
<p>@dir</p>

@model

@model 지시문을 사용해서 Razor 페이지에 전달되는 모델의 형식을 지정할 수 있습니다. 이 지시문은 다음과 같은 구문을 사용합니다:

@model TypeNameOfModel

예를 들어서, 템플릿을 이용해서 개별 사용자 계정을 사용하는 ASP.NET Core MVC 응용 프로그램을 생성할 경우, Views/Account/Login.cshtml Razor 뷰에는 다음과 같은 모델 지시문이 포함됩니다:

@model LoginViewModel

다시 이번 절의 첫 번째 예제를 살펴보면 RazorPage<dynamic> 형식을 상속받는 클래스가 생성된 것을 알 수 있는데, @model 지시문을 추가하면 바로 이 상속받는 형식을 일부분 제어할 수 있습니다. 가령, 다음과 같이 지시문을 작성하면:

@model LoginViewModel

다음과 같은 클래스가 생성됩니다:

public class _Views_Account_Login_cshtml : RazorPage<LoginViewModel>

Razor 페이지는 페이지에 전달된 모델에 접근할 수 있는 Model 속성을 노출시켜줍니다.

<div>The Login Email: @Model.Email</div>

결론적으로 @model 지시문은 이 속성의 형식을 지정하는 것입니다 (페이지를 위해서 생성된 클래스가 상속받는 RazorPage<T> 클래스의 T 형식을 지정해서). 반면, 명시적으로 @model 지시문을 지정하지 않으면 Model 속성은 dynamic 형식이 됩니다. 모델의 값은 컨트롤러에서 뷰로 전달되는데, 보다 자세한 정보는 강력한 형식의 모델과 @model 키워드를 참고하시기 바랍니다.

@inherits

@inherits 지시문을 사용하면 Razor 페이지가 상속받은 클래스 자체를 완벽하게 제어할 수 있습니다:

@inherits TypeNameOfClassToInheritFrom

가령, 다음과 같은 사용자 지정 Razor 페이지 형식을 구현했다고 가정해보겠습니다:

using Microsoft.AspNetCore.Mvc.Razor;

public abstract class CustomRazorPage<TModel> : RazorPage<TModel>
{
    public string CustomText { get; } = "Hello World.";
}

이 경우, 다음 Razor 페이지는 <div>Custom text: Hello World</div>와 같은 HTML 마크업을 생성하게 됩니다.

@inherits CustomRazorPage<TModel>

<div>Custom text: @CustomText</div>

동일한 페이지 내에서 @model 지시문과 @inherits 지시문을 동시에 함께 사용할 수는 없습니다. 그러나 대신 Razor 페이지가 임포트하는 _ViewImports.cshtml 파일에 @inherits 지시문을 지정할 수는 있습니다. 가령, Razor 뷰가 다음과 같은 _ViewImports.cshtml 파일을 임포트할 경우:

@inherits CustomRazorPage<TModel>

다음과 같은 강력한 형식의 Razor 페이지는, 이메일이 "Rick@contoso.com"으로 설정된 모델의 인스턴스가 전달될 경우:

@inherits CustomRazorPage<TModel>

<div>The Login Email: @Model.Email</div>
<div>Custom text: @CustomText</div>

역주: 이번 예제를 본문의 내용에 더 부합하게 변경하려면 @inherits CustomRazorPage<TModel> 지시문을 @model Person 지시문으로 바꾸는 편이 더 현실적일 것 같습니다.

다음의 HTML 마크업을 생성하게 됩니다:

<div>The Login Email: Rick@contoso.com</div>
<div>Custom text: Hello World</div>

더 자세한 정보는 레이아웃(Layout)을 참고하시기 바랍니다.

@inject

@inject 지시문을 이용해서 서비스 컨테이너에서 가져온 서비스를 Razor 페이지에 주입해서 사용할 수도 있습니다. 보다 자세한 정보는 뷰에 서비스 주입하기를 참고하시기 바랍니다.

@functions

@functions 지시문을 이용하면 함수 수준의 콘텐츠를 Razor 페이지에 추가할 수 있습니다. 구문은 다음과 같습니다:

@functions { // C# Code }

다음 Razor 구문은:

@functions {
    public string GetHello()
    {
        return "Hello";
    }
}

<div>From method: @GetHello()</div>

다음과 같은 HTML 마크업을 생성하게 됩니다:

<div>From method: Hello</div>

그리고 다음과 비슷한 Razor C# 클래스가 만들어집니다:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Razor;

public class _Views_Home_Test_cshtml : RazorPage<dynamic>
{
    // Functions placed between here 
    public string GetHello()
    {
        return "Hello";
    }
    // And here.
#pragma warning disable 1998
    public override async Task ExecuteAsync()
    {
        WriteLiteral("\r\n<div>From method: ");
        Write(GetHello());
        WriteLiteral("</div>\r\n");
    }
#pragma warning restore 1998

@section

@section 지시문은 레이아웃 페이지와 함께 사용되며, 렌더되는 HTML 페이지의 각기 다른 부분에 뷰의 콘텐츠를 렌더하기 위한 용도로 사용됩니다. 보다 자세한 정보는 섹션(Sections)을 참고하시기 바랍니다.

태그 헬퍼

다음 태그 헬퍼(Tag Helpers) 지시문들에 대한 자세한 정보는 각 링크에서 확인하시기 바랍니다.

Razor 예약 키워드

Razor 키워드

  • functions
  • inherits
  • model
  • section
  • helper (ASP.NET Core에서는 지원되지 않습니다.)

Razor 키워드는 @(functions)과 같이 @(Razor Keyword)의 형태로 이스케이프 할 수 있습니다. 전체 예제는 아래를 참고하시기 바랍니다.

C# Razor 키워드

  • case
  • do
  • default
  • for
  • foreach
  • if
  • lock
  • switch
  • try
  • using
  • while

C# Razor 키워드는 @@(@@case)와 같이 @@(@@C# Razor Keyword)의 형태로 두 번 이스케이프시켜야 합니다. 첫 번째 @@ 기호는 Razor 파서를 이스케이프 하고, 두 번째 @@ 기호는 C# 파서를 이스케이프 합니다. 전체 예제는 아래를 참고하시기 바랍니다.

Razor에서 사용되지 않는 예약된 키워드

  • namespace
  • class

다음 예제는 이스케이프 된 Razor의 모든 예약된 키워드들을 보여줍니다:

@@{
    // Razor keywords.

    var @@functions = "functions";
    var @@inherits = "inherits";
    var @@model = "model";
    var @@section = "section";
    var @@helper = "helper";         // Not supported by ASP.NET Core.

    // C# Razor keywords.

    var @@case = "case";
    var @@do = "do";
    var @@default = "default";
    var @@for = "for";
    var @@foreach = "foreach";
    var @@if = "if";
    var @@lock = "lock";
    var @@switch = "switch";
    var @@try = "try";
    var @@using = "using";
    var @@while = "while";

    // Reserved keywords not used.

    var @@namespace = "namespace";
    var @@class = "class";
}

<p>Razor keywords.</p>
<div>@@(functions)</div>
<div>@@(inherits)</div>
<div>@@(model)</div>
<div>@@(section)</div>
<div>@@(helper)</div>

<p>C# Razor keywords.</p>
<div>@@(@@case)</div>
<div>@@(@@do)</div>
<div>@@(@@default)</div>
<div>@@(@@for)</div>
<div>@@(@@foreach)</div>
<div>@@(@@if)</div>
<div>@@(@@lock)</div>
<div>@@(@@switch)</div>
<div>@@(@@try)</div>
<div>@@(@@using)</div>
<div>@@(@@while)</div>

<p>Reserved keywords not used</p>
<div>@@(@@namespace)</div>
<div>@@(@@class)</div>

자동으로 생성되는 뷰의 Razor C# 클래스 살펴보기

먼저 ASP.NET Core MVC 프로젝트에 다음 클래스를 추가합니다:

using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

public class CustomCompilationService : DefaultRoslynCompilationService, ICompilationService
{
    public CustomCompilationService(ApplicationPartManager partManager, 
        IOptions<RazorViewEngineOptions> optionsAccessor, 
        IRazorViewEngineFileProviderAccessor fileProviderAccessor, 
        ILoggerFactory loggerFactory) 
        : base(partManager, optionsAccessor, fileProviderAccessor, loggerFactory)
    {
    }

    CompilationResult ICompilationService.Compile(RelativeFileInfo fileInfo, 
        string compilationContent)
    {
        return base.Compile(fileInfo, compilationContent);
    }
}

MVC에 의해서 추가된 ICompilationService를 이 클래스로 재정의합니다:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddSingleton<ICompilationService, CustomCompilationService>();
}

디버깅 모드에서 CustomCompilationService 클래스의 Compile 메서드에 중단점을 설정하고 compilationContent 변수의 값을 살펴봅니다.