ASP.NET Core의 Razor Pages 소개

작성자: Rick Anderson, Dave BrockKirk Larkin

참고 항목

이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 ASP.NET Core 8.0 버전을 참조 하세요.

Razor Pages를 사용하면 컨트롤러 및 뷰를 사용하는 것보다 더 쉽고 생산적으로 코딩 페이지에 초점을 맞춘 시나리오를 만들 수 있습니다.

모델-뷰-컨트롤러 방법을 사용하는 자습서를 검색할 경우 ASP.NET Core MVC 시작을 참조하세요.

이 문서에서는 Razor Pages에 대해 소개합니다. 단계별 자습서가 아닙니다. 섹션 일부 내용이 너무 고급이라면 Razor Pages 시작을 참조하세요. ASP.NET Core의 개요는 ASP.NET Core 소개를 참조하세요.

필수 조건

Razor Pages 프로젝트 만들기

Razor Pages 프로젝트를 만드는 방법에 대한 자세한 내용은 Razor Pages 시작을 참조하세요.

Razor Pages

RazorPages는 다음에서 사용하도록 설정됩니다.Program.cs

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

위의 코드에서

기본적인 페이지를 살펴보겠습니다.

@page

<h1>Hello, world!</h1>
<h2>The time on the server is @DateTime.Now</h2>

앞의 코드는 컨트롤러 및 뷰가 포함된 ASP.NET Core 앱에서 사용되는 Razor 뷰 파일과 매우 유사합니다. 차이점은 @page 지시문입니다. @page는 파일을 MVC 작업으로 만듭니다. 즉, 컨트롤러를 거치지 않고 이 파일이 요청을 직접 처리합니다. @page는 페이지의 첫 번째 Razor 지시문이어야 합니다. @page는 다른 Razor 구문의 동작에 영향을 줍니다. Razor Pages 파일 이름에는 .cshtml 접미사가 있습니다.

다음 두 파일은 PageModel 클래스를 사용하는 비슷한 페이지를 보여줍니다. 파일:Pages/Index2.cshtml

@page
@using RazorPagesIntro.Pages
@model Index2Model

<h2>Separate page model</h2>
<p>
    @Model.Message
</p>

Pages/Index2.cshtml.cs 페이지 모델:

using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;

namespace RazorPagesIntro.Pages
{
    public class Index2Model : PageModel
    {
        public string Message { get; private set; } = "PageModel in C#";

        public void OnGet()
        {
            Message += $" Server time is { DateTime.Now }";
        }
    }
}

규칙에 PageModel 따라 클래스 파일의 이름은 Razor 추가된 페이지 파일과 .cs 동일합니다. 예를 들어 위의 Razor Page는 Pages/Index2.cshtml입니다. 그리고 PageModel 클래스가 포함된 파일의 이름은 Pages/Index2.cshtml.cs입니다.

페이지에 대한 URL 경로 연결은 파일 시스템 상의 페이지 위치에 따라 결정됩니다. 다음 표는 Razor Page 경로 및 그와 일치하는 URL을 보여 줍니다.

파일 이름 및 경로 일치하는 URL
/Pages/Index.cshtml / 또는 /Index
/Pages/Contact.cshtml /Contact
/Pages/Store/Contact.cshtml /Store/Contact
/Pages/Store/Index.cshtml /Store 또는 /Store/Index

참고:

  • 런타임은 기본적으로 Pages 폴더에서 Razor Pages 파일을 검색합니다.
  • URL에 페이지가 지정되어 있지 않을 경우 Index가 기본 페이지입니다.

기본 양식 작성

Razor Pages는 앱을 만들 때 웹 브라우저에서 사용되는 일반적인 패턴을 손쉽게 구현할 수 있도록 설계되었습니다. 모델 바인딩, 태그 도우미 및 HTML 도우미는 Razor 페이지 클래스에 정의된 속성을 통해 작동합니다. Contact 모델에 대한 기본적인 "연락처" 양식을 구현하는 페이지를 생각해보겠습니다.

이 문서의 DbContext 샘플의 경우 Program.cs 파일에서 초기화됩니다.

메모리 내 데이터베이스에는 Microsoft.EntityFrameworkCore.InMemory NuGet 패키지가 필요합니다.

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<CustomerDbContext>(options =>
    options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

데이터 모델:

using System.ComponentModel.DataAnnotations;

namespace RazorPagesContacts.Models
{
    public class Customer
    {
        public int Id { get; set; }

        [Required, StringLength(10)]
        public string? Name { get; set; }
    }
}

db 컨텍스트:

using Microsoft.EntityFrameworkCore;

namespace RazorPagesContacts.Data
{
    public class CustomerDbContext : DbContext
    {
        public CustomerDbContext (DbContextOptions<CustomerDbContext> options)
            : base(options)
        {
        }

        public DbSet<RazorPagesContacts.Models.Customer> Customer => Set<RazorPagesContacts.Models.Customer>();
    }
}

Pages/Customers/Create.cshtml 뷰 파일:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer!.Name" />
    <input type="submit" />
</form>

Pages/Customers/Create.cshtml.cs 페이지 모델:

public class CreateModel : PageModel
{
    private readonly Data.CustomerDbContext _context;

    public CreateModel(Data.CustomerDbContext context)
    {
        _context = context;
    }

    public IActionResult OnGet()
    {
        return Page();
    }

    [BindProperty]
    public Customer? Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        if (Customer != null) _context.Customer.Add(Customer);
        await _context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

일반적으로 PageModel 클래스를 <PageName>Model이라고 하고 이 클래스는 페이지와 동일한 네임스페이스에 있습니다.

PageModel 클래스를 사용하면 페이지의 논리를 페이지의 표현으로부터 분리할 수 있습니다. 이 클래스는 페이지로 전송된 요청에 대한 페이지 처리기와 페이지를 렌더링하기 위해 사용되는 데이터를 정의합니다. 이러한 분리를 통해 다음을 수행할 수 있습니다.

페이지에는 POST 요청에서 실행되는 OnPostAsync처리기 메서드가 있습니다(사용자가 폼을 게시할 때). 모든 HTTP 동사에 대한 처리기 메서드를 추가할 수 있습니다. 가장 일반적인 처리기는 다음과 같습니다.

  • OnGet: 페이지에 필요한 상태를 초기화합니다. 위의 코드에서 OnGet 메서드는 CreateModel.cshtmlRazor Page를 표시합니다.
  • OnPost: 양식 제출을 처리합니다.

Async 명명 접미사는 선택 사항이지만, 규칙에 따라 비동기 함수에 자주 사용됩니다. 위의 코드는 Razor Pages의 일반적인 코드입니다.

컨트롤러 및 뷰를 사용하는 ASP.NET 앱에 대해 잘 알고 있는 경우:

  • 앞 예제의 OnPostAsync 코드는 일반적인 컨트롤러 코드와 비슷합니다.
  • 모델 바인딩, 유효성 검사 및 작업 결과와 같은 MVC의 기본적인 기능들이 대부분 컨트롤러 및 Razor Pages와 동일하게 작동합니다.

위의 OnPostAsync 메서드는 다음과 같습니다.

[BindProperty]
public Customer? Customer { get; set; }

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    if (Customer != null) _context.Customer.Add(Customer);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

OnPostAsync의 기본 흐름:

유효성 검사 오류를 확인합니다.

  • 오류가 없는 경우 데이터를 저장하고 리디렉션합니다.
  • 오류가 있을 경우 유효성 검사 메시지가 포함된 페이지를 다시 표시합니다. 대부분의 경우 유효성 검사 오류는 클라이언트에서 검색되고 서버로 제출되지 않습니다.

Pages/Customers/Create.cshtml 뷰 파일:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer!.Name" />
    <input type="submit" />
</form>

Pages/Customers/Create.cshtml에서 렌더링된 HTML:

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input type="text" data-val="true"
           data-val-length="The field Name must be a string with a maximum length of 10."
           data-val-length-max="10" data-val-required="The Name field is required."
           id="Customer_Name" maxlength="10" name="Customer.Name" value="" />
    <input type="submit" />
    <input name="__RequestVerificationToken" type="hidden"
           value="<Antiforgery token here>" />
</form>

이전 코드에서 양식 게시:

  • 유효한 데이터 사용:

    • OnPostAsync 처리기 메서드는 RedirectToPage 도우미 메서드를 호출합니다. RedirectToPageRedirectToPageResult 인스턴스를 반환합니다. RedirectToPage:

      • 작업 결과입니다.
      • RedirectToAction 또는 RedirectToRoute(컨트롤러 및 뷰에서 사용됨)와 유사합니다.
      • 페이지에 맞게 사용자 지정됩니다. 위의 예제에서 이 메서드는 루트 인덱스 페이지(/Index)로 리디렉션합니다. RedirectToPage페이지에 대한 URL 생성 섹션에서 자세히 설명합니다.
  • 서버에 전달되는 유효성 검사 오류 포함:

    • OnPostAsync 처리기 메서드는 Page 도우미 메서드를 호출합니다. PagePageResult 인스턴스를 반환합니다. Page를 반환하는 것은 컨트롤의 액션에서 View를 반환하는 것과 비슷합니다. PageResult는 처리기 메서드의 기본 반환 형식입니다. void를 반환하는 처리기 메서드는 페이지를 렌더링합니다.
    • 앞의 예제에서 값이 없는 양식을 게시하면 false를 반환하는 ModelState.IsValid를 반환합니다. 이 샘플에서는 클라이언트에 유효성 검사 오류가 표시되지 않습니다. 유효성 검사 오류 처리는 이 문서의 뒷부분에 설명되어 있습니다.
    [BindProperty]
    public Customer? Customer { get; set; }
    
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }
    
        if (Customer != null) _context.Customer.Add(Customer);
        await _context.SaveChangesAsync();
    
        return RedirectToPage("./Index");
    }
    
  • 클라이언트 쪽 유효성 검사에서 검색된 유효성 검사 오류 포함:

    • 데이터가 서버에 게시되지 않습니다.
    • 클라이언트 쪽 유효성 검사는 이 문서의 뒷부분에 설명되어 있습니다.

Customer 속성은 [BindProperty] 특성을 이용하여 모델 바인딩에 옵트인(opt in)합니다.

[BindProperty]
public Customer? Customer { get; set; }

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    if (Customer != null) _context.Customer.Add(Customer);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

클라이언트에서 변경하면 안 되는 속성을 포함하는 모델에서는 [BindProperty]를 사용하지 않아야 합니다. 자세한 내용은 초과 게시를 참조하세요.

Razor Pages는 기본적으로 비 GET 동사에 대해서만 속성을 바인딩합니다. 속성에 바인딩하면 HTTP 데이터를 모델 형식으로 변환하는 코드를 작성할 필요가 없습니다. 바인딩은 동일한 속성을 사용하여 폼 필드(<input asp-for="Customer.Name">)를 렌더링하고 입력을 허용하는 방식으로 코드를 줄입니다.

Warning

보안상의 이유로 페이지 모델 속성에 GET 요청 데이터를 바인딩하기 위해 옵트인해야 합니다. 속성에 매핑하기 전에 사용자 입력을 확인합니다. 쿼리 문자열이나 경로 값을 사용하는 시나리오를 지정할 때 GET 바인딩을 옵트인하면 유용합니다.

GET 요청에 속성을 바인딩하려면 [BindProperty] 특성의 SupportsGet 속성을 true로 설정합니다.

[BindProperty(SupportsGet = true)]

자세한 내용은 ASP.NET Core Community 스탠드업을 참조하세요. 토론 가져오기(YouTube)에서 바인딩합니다.

Pages/Customers/Create.cshtml 보기 파일 검토:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer!.Name" />
    <input type="submit" />
</form>
  • 앞의 코드에서 입력 태그 도우미<input asp-for="Customer.Name" />은 HTML <input> 요소를 Customer.Name 모델 식에 바인딩합니다.
  • @addTagHelper는 태그 도우미를 사용할 수 있도록 설정합니다.

홈페이지

Index.cshtml은 홈페이지입니다.

@page
@model RazorPagesContacts.Pages.Customers.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>Contacts home page</h1>
<form method="post">
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
        @if (Model.Customers != null)
        {
            foreach (var contact in Model.Customers)
            {
                <tr>
                    <td> @contact.Id </td>
                    <td>@contact.Name</td>
                    <td>
                        <!-- <snippet_Edit> -->
                        <a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
                        <!-- </snippet_Edit> -->
                        <!-- <snippet_Delete> -->
                        <button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>
                        <!-- </snippet_Delete> -->
                    </td>
                </tr>
            }
        }
        </tbody>
    </table>
    <a asp-page="Create">Create New</a>
</form>

연결된 PageModel클래스(Index.cshtml.cs):

public class IndexModel : PageModel
{
    private readonly Data.CustomerDbContext _context;
    public IndexModel(Data.CustomerDbContext context)
    {
        _context = context;
    }

    public IList<Customer>? Customers { get; set; }

    public async Task OnGetAsync()
    {
        Customers = await _context.Customer.ToListAsync();
    }

    public async Task<IActionResult> OnPostDeleteAsync(int id)
    {
        var contact = await _context.Customer.FindAsync(id);

        if (contact != null)
        {
            _context.Customer.Remove(contact);
            await _context.SaveChangesAsync();
        }

        return RedirectToPage();
    }
}

파일에는 Index.cshtml 다음 태그가 포함됩니다.

<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |

<a /a>앵커 태그 도우미는 Edit 페이지에 대한 링크를 생성하기 위해 asp-route-{value} 특성을 사용합니다. 링크에는 연락처 ID와 함께 경로 데이터가 포함됩니다. 예: https://localhost:5001/Edit/1. 태그 도우미를 사용하면 Razor 파일에서 HTML 요소를 만들고 렌더링하는 데 서버 쪽 코드를 사용할 수 있습니다.

파일에는 Index.cshtml 각 고객 연락처에 대한 삭제 단추를 만드는 태그가 포함되어 있습니다.

<button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>

렌더링된 HTML:

<button type="submit" formaction="/Customers?id=1&amp;handler=delete">delete</button>

삭제 단추가 HTML로 렌더링되는 경우 해당 formaction에는 다음에 대한 매개 변수가 포함됩니다.

  • asp-route-id 특성으로 지정된 고객 연락처 ID.
  • asp-page-handler 특성으로 지정된 handler.

단추를 선택하면 양식의 POST 요청이 서버로 전송됩니다. 규약에 따라 처리기 메서드의 이름은 handler 매개 변수의 값을 기반으로 OnPost[handler]Async 체계에 의해 선택됩니다.

이번 예제에서는 handlerdelete이므로 POST 요청을 처리하기 위해 OnPostDeleteAsync 처리기 메서드가 사용됩니다. asp-page-handlerremove 같은 다른 값으로 설정되면 OnPostRemoveAsync라는 이름의 처리기 메서드가 선택됩니다.

public async Task<IActionResult> OnPostDeleteAsync(int id)
{
    var contact = await _context.Customer.FindAsync(id);

    if (contact != null)
    {
        _context.Customer.Remove(contact);
        await _context.SaveChangesAsync();
    }

    return RedirectToPage();
}

OnPostDeleteAsync 메서드는 다음 작업을 수행합니다.

  • 쿼리 문자열에서 id를 가져옵니다.
  • FindAsync를 사용하여 데이터베이스에서 고객 연락처를 쿼리합니다.
  • 고객 연락처가 발견되면 제거하고 데이터베이스를 업데이트합니다.
  • RedirectToPage를 호출하여 루트 인덱스 페이지(/Index)를 리디렉션합니다.

Edit.cshtml 파일

@page "{id:int}"
@model RazorPagesContacts.Pages.Customers.EditModel

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

<h1>Edit</h1>

<h4>Customer</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Customer!.Id" />
            <div class="form-group">
                <label asp-for="Customer!.Name" class="control-label"></label>
                <input asp-for="Customer!.Name" class="form-control" />
                <span asp-validation-for="Customer!.Name" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

첫 번째 줄에는 @page "{id:int}" 지시문이 작성되어 있습니다. 라우팅 제약 조건인 "{id:int}"int 경로 데이터가 포함된 페이지에 대한 요청을 허용하도록 페이지에 지시합니다. 페이지에 대한 요청에 int로 변환될 수 있는 경로 데이터가 없으면 런타임은 HTTP 404(찾을 수 없음) 오류를 반환합니다. ID를 옵션으로 설정하려면 경로 제약 조건에 ?를 추가합니다.

@page "{id:int?}"

파일:Edit.cshtml.cs

public class EditModel : PageModel
{
    private readonly RazorPagesContacts.Data.CustomerDbContext _context;

    public EditModel(RazorPagesContacts.Data.CustomerDbContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Customer? Customer { get; set; }

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        Customer = await _context.Customer.FirstOrDefaultAsync(m => m.Id == id);
        
        if (Customer == null)
        {
            return NotFound();
        }
        return Page();
    }

    // To protect from overposting attacks, enable the specific properties you want to bind to.
    // For more details, see https://aka.ms/RazorPagesCRUD.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        if (Customer != null)
        {
            _context.Attach(Customer).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!CustomerExists(Customer.Id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }
        }

        return RedirectToPage("./Index");
    }

    private bool CustomerExists(int id)
    {
        return _context.Customer.Any(e => e.Id == id);
    }
}

유효성 검사

유효성 검사 규칙:

  • 모델 클래스에서 선언적으로 지정됩니다.
  • 앱의 모든 위치에서 적용됩니다.

System.ComponentModel.DataAnnotations 네임스페이스는 클래스 또는 속성에 선언적으로 적용되는 유효성 검사 특성의 기본 제공 세트를 제공합니다. 또한 DataAnnotations는 서식 지정을 돕고 아무런 유효성 검사도 제공하지 않는 [DataType] 같은 서식 지정 특성도 포함하고 있습니다.

Customer 모델 고려:

using System.ComponentModel.DataAnnotations;

namespace RazorPagesContacts.Models
{
    public class Customer
    {
        public int Id { get; set; }

        [Required, StringLength(10)]
        public string? Name { get; set; }
    }
}

다음 Create.cshtml 보기 파일 사용:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Validation: customer name:</p>

<form method="post">
    <div asp-validation-summary="ModelOnly"></div>
    <span asp-validation-for="Customer!.Name"></span>
    Name:
    <input asp-for="Customer!.Name" />
    <input type="submit" />
</form>

<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>

앞의 코드가 하는 역할은 다음과 같습니다.

  • jQuery 및 jQuery 유효성 검사 스크립트를 포함합니다.

  • <div /><span />태그 도우미를 사용하여 다음을 사용하도록 설정합니다.

    • 클라이언트 쪽 유효성 검사.
    • 유효성 검사 오류 렌더링.
  • 다음과 같은 HTML을 생성합니다.

    <p>Enter a customer name:</p>
    
    <form method="post">
        Name:
        <input type="text" data-val="true"
               data-val-length="The field Name must be a string with a maximum length of 10."
               data-val-length-max="10" data-val-required="The Name field is required."
               id="Customer_Name" maxlength="10" name="Customer.Name" value="" />
        <input type="submit" />
        <input name="__RequestVerificationToken" type="hidden"
               value="<Antiforgery token here>" />
    </form>
    
    <script src="/lib/jquery/dist/jquery.js"></script>
    <script src="/lib/jquery-validation/dist/jquery.validate.js"></script>
    <script src="/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
    

이름 값 없이 만들기 양식을 게시하면 양식에 “이름 필드는 필수입니다.”라는 오류 메시지가 표시됩니다. 클라이언트에서 JavaScript를 사용하는 경우 서버에 게시하지 않고 브라우저에 오류를 표시합니다.

[StringLength(10)] 특성은 렌더링된 HTML에서 data-val-length-max="10"을 생성합니다. data-val-length-max는 브라우저에서 지정된 최대 길이를 초과하여 입력하지 못하도록 합니다. Fiddler와 같은 도구를 사용하여 게시물을 편집하고 재생하는 경우:

  • 이름이 10글자보다 긴 경우.
  • “필드 이름은 최대 길이가 10글자인 문자열이어야 합니다.”라는 오류 메시지가 반환됩니다.

다음 Movie 모델을 보세요.

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }

        [StringLength(60, MinimumLength = 3)]
        [Required]
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }

        [Range(1, 100)]
        [DataType(DataType.Currency)]
        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }

        [RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$")]
        [Required]
        [StringLength(30)]
        public string Genre { get; set; }

        [RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
        [StringLength(5)]
        [Required]
        public string Rating { get; set; }
    }
}

이 유효성 검사 특성은 적용되는 모델 속성에 시행하려는 동작을 지정합니다.

  • RequiredMinimumLength 특성은 속성에 값이 있어야 하지만 사용자가 이 유효성 검사를 만족하기 위해 공백을 입력하는 것을 막을 수 없다는 것을 나타냅니다.

  • RegularExpression 특성은 입력할 수 있는 문자를 제한하는 데 사용됩니다. 이전 코드에서 “Genre”는 다음 조건을 충족해야 합니다.

    • 문자만 사용해야 합니다.
    • 첫 번째 문자는 대문자여야 합니다 공백, 숫자 및 특수 문자는 허용되지 않습니다.
  • RegularExpression “Rating”은 다음 조건을 충족해야 합니다.

    • 첫 번째 문자는 대문자여야 합니다.
    • 이어지는 자리에서는 특수 문자와 숫자가 허용됩니다. “PG-13”은 등급에서는 유효하지만 “Genre”에서는 허용되지 않습니다.
  • Range 특성은 값을 지정된 범위 내로 제한합니다.

  • StringLength 특성은 문자열 속성의 최대 길이와, 필요에 따라 최소 길이를 설정합니다.

  • 값 형식(예: decimal, int, float, DateTime)은 기본적으로 필수적이며 [Required] 특성이 필요하지 않습니다.

Movie 모델에 대한 만들기 페이지에 잘못된 값이 포함된 오류가 표시됩니다.

Movie view form with multiple jQuery client-side validation errors

자세한 내용은 다음을 참조하세요.

CSS 격리

CSS 스타일을 개별 페이지, 뷰 및 구성 요소로 격리하여 다음을 줄이거나 방지합니다.

  • 유지 관리하기 어려울 수 있는 전역 스타일에 대한 종속성.
  • 중첩된 콘텐츠의 스타일 충돌.

페이지 또는 보기에 대한 범위가 지정되는 CSS 파일을 추가하려면 .cshtml 파일 이름과 일치하는 도우미 .cshtml.css 파일에 CSS 스타일을 배치합니다. 다음 예제에서 Index.cshtml.css 파일은 Index.cshtml 페이지 또는 보기에만 적용되는 CSS 스타일을 제공합니다.

Pages/Index.cshtml.css(Razor 페이지) 또는 Views/Index.cshtml.css(MVC):

h1 {
    color: red;
}

CSS 격리는 빌드 시간에 발생합니다. 프레임워크는 앱의 페이지 또는 보기에서 렌더링된 태그와 일치하도록 CSS 선택기를 다시 작성합니다. 다시 작성된 CSS 스타일은 번들로 묶여 정적 자산, {APP ASSEMBLY}.styles.css로 생성됩니다. 자리 표시자 {APP ASSEMBLY}는 프로젝트의 어셈블리 이름입니다. 번들로 묶인 CSS 스타일에 대한 링크는 앱의 레이아웃에 배치됩니다.

앱의 Pages/Shared/_Layout.cshtml(Razor Pages) 또는 Views/Shared/_Layout.cshtml(MVC)의 <head> 콘텐츠에서 번들로 묶인 CSS 스타일에 대한 링크의 존재를 추가하거나 확인합니다.

<link rel="stylesheet" href="~/{APP ASSEMBLY}.styles.css" />

다음 예제에서 앱의 어셈블리 이름은 WebApp입니다.

<link rel="stylesheet" href="WebApp.styles.css" />

범위가 지정된 CSS 파일에 정의된 스타일은 일치하는 파일의 렌더링된 출력에만 적용됩니다. 위의 예제에서 앱의 다른 위치에 정의된 모든 h1 CSS 선언이 Index의 제목 스타일과 충돌하지 않습니다. CSS 스타일 계단식 배열 및 상속 규칙은 범위가 지정된 CSS 파일에 계속 적용됩니다. 예를 들어 Index.cshtml 파일의 <h1> 요소에 직접 적용된 스타일은 Index.cshtml.css에서 범위가 지정된 CSS 파일의 스타일을 재정의합니다.

참고 항목

묶음이 발생할 때 CSS 스타일 격리를 보장하기 위해 Razor 코드 블록에서 CSS 가져오기는 지원되지 않습니다.

CSS 격리는 HTML 요소에만 적용됩니다. 태그 도우미에는 CSS 격리가 지원되지 않습니다.

번들로 묶인 CSS 파일 내에서 각 페이지, 보기 또는 Razor 구성 요소는 b-{STRING} 형식의 범위 식별자와 연결됩니다. 여기서 {STRING} 자리 표시자는 프레임워크에서 생성된 10자 문자열입니다. 다음 예제에서는 Razor Pages 앱의 Index 페이지에서 위의 <h1> 요소에 대한 스타일을 제공합니다.

/* /Pages/Index.cshtml.rz.scp.css */
h1[b-3xxtam6d07] {
    color: red;
}

번들로 묶인 파일의 CSS 스타일이 적용되는 Index 페이지에서 범위 식별자는 HTML 특성으로 추가됩니다.

<h1 b-3xxtam6d07>

식별자는 앱에 고유합니다. 빌드 시간에 {STATIC WEB ASSETS BASE PATH}/Project.lib.scp.css 규칙을 사용하여 프로젝트 번들을 만듭니다. 여기서 {STATIC WEB ASSETS BASE PATH} 자리 표시자는 정적 웹 자산 기본 경로입니다.

NuGet 패키지 또는 Razor 클래스 라이브러리 같은 다른 프로젝트를 활용하는 경우 묶은 파일은 다음과 같습니다.

  • CSS 가져오기를 사용하여 스타일을 참조합니다.
  • 스타일을 사용하는 앱의 정적 웹 자산으로 게시되지 않습니다.

CSS 전처리기 지원

CSS 전처리기는 변수, 중첩, 모듈, 믹스인 및 상속과 같은 기능을 활용하여 CSS 개발을 개선하는 데 유용합니다. CSS 격리는 Sass 또는 Less 같은 CSS 전처리기를 기본적으로 지원하지 않지만 프레임워크가 빌드 프로세스 중 CSS 선택기를 다시 작성하기 전에 전처리기 컴파일이 수행되는 한 CSS 전처리기를 원활하게 통합할 수 있습니다. 예를 들어 Visual Studio를 사용하여 Visual Studio 작업 실행기 탐색기에서 빌드 전 작업으로 기존 전처리기 컴파일을 구성합니다.

AspNetCore.SassCompiler와 같은 많은 타사 NuGet 패키지는 CSS 격리가 발생하기 전 빌드 프로세스 시작 부분에서 SASS/SCSS 파일을 컴파일할 수 있으며, 추가 구성이 필요하지 않습니다.

CSS 격리 구성

CSS 격리는 기존 도구 또는 워크플로에 대한 의존성이 있는 경우와 같은 일부 고급 시나리오에 대한 구성을 허용합니다.

범위 식별자 형식 사용자 지정

이 섹션에서 {Pages|Views} 자리 표시자는 Razor Pages 앱에 대해 Pages 또는 MVC 앱에 대해 Views입니다.

기본적으로 범위 식별자는 b-{STRING} 형식을 사용합니다. 여기서 {STRING} 자리 표시자는 프레임워크에서 생성된 10자 문자열입니다. 범위 식별자 형식을 사용자 지정하려면 프로젝트 파일을 원하는 패턴으로 업데이트합니다.

<ItemGroup>
  <None Update="{Pages|Views}/Index.cshtml.css" CssScope="custom-scope-identifier" />
</ItemGroup>

위의 예제에서 Index.cshtml.css에 대해 생성된 CSS는 범위 식별자를 b-{STRING}에서 custom-scope-identifier로 변경합니다.

범위가 지정된 CSS 파일을 사용하여 상속을 수행하려면 범위 식별자를 사용하세요. 다음 프로젝트 파일 예제에서 BaseView.cshtml.css 파일은 보기에 걸쳐 일반 스타일을 포함합니다. DerivedView.cshtml.css 파일은 이러한 스타일을 상속합니다.

<ItemGroup>
  <None Update="{Pages|Views}/BaseView.cshtml.css" CssScope="custom-scope-identifier" />
  <None Update="{Pages|Views}/DerivedView.cshtml.css" CssScope="custom-scope-identifier" />
</ItemGroup>

여러 파일에 걸쳐 범위 식별자를 공유하려면 와일드카드(*) 연산자를 사용합니다.

<ItemGroup>
  <None Update="{Pages|Views}/*.cshtml.css" CssScope="custom-scope-identifier" />
</ItemGroup>

정적 웹 자산에 대한 기본 경로 변경

범위가 지정된 CSS 파일은 앱의 루트에 생성됩니다. 프로젝트 파일에서 StaticWebAssetBasePath 속성을 사용하여 기본 경로를 변경합니다. 다음 예제에서는 _content 경로에 범위가 지정된 CSS 파일과 앱의 나머지 자산을 배치합니다.

<PropertyGroup>
  <StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath>
</PropertyGroup>

자동 묶음 사용 안 함

프레임워크가 런타임에 범위가 지정된 파일을 게시하고 로드하는 방법을 옵트아웃하려면 DisableScopedCssBundling 속성을 사용합니다. 이 속성을 사용하면 다른 도구나 프로세스가 obj 디렉터리에서 격리된 CSS 파일을 가져와 런타임에 게시하고 로드합니다.

<PropertyGroup>
  <DisableScopedCssBundling>true</DisableScopedCssBundling>
</PropertyGroup>

RCL(Razor 클래스 라이브러리) 지원

RCL(Razor 클래스 라이브러리)이 격리된 스타일을 제공하는 경우 <link> 태그의 href 특성은 {STATIC WEB ASSET BASE PATH}/{PACKAGE ID}.bundle.scp.css를 가리키며, 여기서 자리 표시자는 다음과 같습니다.

  • {STATIC WEB ASSET BASE PATH}: 정적 웹 자산 기본 경로입니다.
  • {PACKAGE ID}: 라이브러리의 패키지 식별자입니다. 패키지 식별자가 프로젝트 파일에 지정되지 않은 경우 패키지 식별자는 기본적으로 프로젝트의 어셈블리 이름으로 설정됩니다.

다음 예제에서

  • 정적 웹 자산 기본 경로가 _content/ClassLib입니다.
  • 클래스 라이브러리의 어셈블리 이름이 ClassLib입니다.

Pages/Shared/_Layout.cshtml(Razor 페이지) 또는 Views/Shared/_Layout.cshtml(MVC):

<link href="_content/ClassLib/ClassLib.bundle.scp.css" rel="stylesheet">

RCL에 대한 자세한 내용은 다음 문서를 참조하세요.

Blazor CSS 격리에 대한 자세한 내용은 ASP.NET Core Blazor CSS 격리를 참조하세요.

OnGet 처리기 대체를 사용하여 HEAD 요청 처리

HEAD 요청을 사용하면 특정 리소스의 헤더를 검색할 수 있습니다. GET 요청과는 달리 HEAD 요청은 응답 본문을 반환하지 않습니다.

일반적으로 HEAD 요청에 대한 OnHead 처리기를 만들고 호출합니다.

public void OnHead()
{
    HttpContext.Response.Headers.Add("Head Test", "Handled by OnHead!");
}

정의된 OnHead 처리기가 없다면 Razor Pages가 OnGet 처리기 호출로 대체합니다.

XSRF/CSRF 및 Razor Pages

위조 방지 유효성 검사를 통해 Razor Pages를 보호합니다. FormTagHelper는 위조 방지 토큰을 HTML 양식 요소에 삽입합니다.

Razor Pages에서 레이아웃, 부분 뷰, 템플릿 및 태그 도우미 사용하기

Pages는 Razor 뷰 엔진의 모든 기능과 함께 작동합니다. 레이아웃, 부분, 템플릿, 태그 도우미 _ViewStart.cshtml_ViewImports.cshtml 기존 Razor 보기와 동일한 방식으로 작동합니다.

이 기능들 중 일부를 활용하여 페이지를 개선해 보겠습니다.

레이아웃 페이지를에 추가합니다Pages/Shared/_Layout.cshtml.

<!DOCTYPE html>
<html>
<head>
    <title>RP Sample</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
</head>
<body>
    <a asp-page="/Index">Home</a>
    <a asp-page="/Customers/Create">Create</a>
    <a asp-page="/Customers/Index">Customers</a> <br />

    @RenderBody()
    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</body>
</html>

레이아웃은 다음과 같은 역할을 수행합니다.

  • 각 페이지의 레이아웃을 제어합니다(페이지가 명시적으로 레이아웃을 사용하지 않는 경우 제외).
  • JavaScript 및 스타일시트 같은 HTML 구조를 가져옵니다.
  • Razor 페이지의 내용은 @RenderBody()가 호출되는 위치에서 렌더링됩니다.

자세한 내용은 레이아웃 페이지를 참조하세요.

Layout 속성은 다음에서 설정됩니다.Pages/_ViewStart.cshtml

@{
    Layout = "_Layout";
}

레이아웃은 Pages/Shared 폴더에 위치합니다. 페이지는 현재 페이지와 동일한 폴더에서부터 시작하여 계층적으로 다른 뷰(레이아웃, 템플릿, 부분 뷰)들을 검색합니다. Pages/Shared 폴더의 레이아웃은 Pages 폴더 하위의 모든 Razor 페이지에서 사용할 수 있습니다.

레이아웃 파일은 Pages/Shared 폴더에 위치해야 합니다.

레이아웃 파일은 Views/Shared 폴더에 두지 않는 것이 좋습니다. Views/Shared는 MVC 뷰 패턴입니다. Razor Pages는 경로 규칙이 아닌 폴더 계층 구조를 사용해야 합니다.

Razor Page의 뷰 검색에는 Pages 폴더가 포함됩니다. MVC 컨트롤러 및 기존 Razor 뷰에서 사용한 레이아웃, 템플릿 및 부분 뷰는 ‘정상적으로 작동’합니다.

Pages/_ViewImports.cshtml 파일을 추가합니다.

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@namespace는 잠시 뒤에 설명합니다. @addTagHelper 지시문은 기본 제공 태그 도우미Pages 폴더의 모든 페이지에 도입합니다.

페이지에서 설정된 @namespace 지시문:

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

<h2>Name space</h2>
<p>
    @Model.Message
</p>

@namespace 지시문은 페이지에 대한 네임스페이스를 설정합니다. 이렇게 하면 @model 지시문은 네임스페이스를 포함할 필요가 없습니다.

@namespace 지시문이 _ViewImports.cshtml에 포함되어 있으면 지정된 네임스페이스가 @namespace 지시문을 가져오는 페이지에서 생성된 네임스페이스에 대한 접두사를 제공합니다. 생성된 네임스페이스(접미사 부분)의 나머지 부분은 포함된 _ViewImports.cshtml 폴더와 페이지가 포함된 폴더 사이의 점으로 구분된 상대 경로입니다.

예를 들어 PageModel 클래스 Pages/Customers/Edit.cshtml.cs는 네임스페이스를 명시적으로 설정합니다.

namespace RazorPagesContacts.Pages
{
    public class EditModel : PageModel
    {
        private readonly AppDbContext _db;

        public EditModel(AppDbContext db)
        {
            _db = db;
        }

        // Code removed for brevity.

파일은 Pages/_ViewImports.cshtml 다음 네임스페이스를 설정합니다.

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Pages/Customers/Edit.cshtmlRazor Page에 대해 생성되는 네임스페이스는 PageModel 클래스와 동일합니다.

@namespace은 기존 Razor 보기에서도 작동합니다.

뷰 파일을 고려합니다.Pages/Customers/Create.cshtml

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Validation: customer name:</p>

<form method="post">
    <div asp-validation-summary="ModelOnly"></div>
    <span asp-validation-for="Customer!.Name"></span>
    Name:
    <input asp-for="Customer!.Name" />
    <input type="submit" />
</form>

<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>

업데이트 Pages/Customers/Create.cshtml 된 뷰 파일 및 _ViewImports.cshtml 이전 레이아웃 파일:

@page
@model CreateModel

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer!.Name" />
    <input type="submit" />
</form>

이전 코드 _ViewImports.cshtml 에서 네임스페이스와 태그 도우미를 가져왔습니다. 레이아웃 파일은 JavaScript 파일을 가져왔습니다.

Pages 시작 프로젝트에는 RazorPages/_ValidationScriptsPartial.cshtml클라이언트 쪽 유효성 검사를 연결하는 이 프로젝트가 포함되어 있습니다.

부분 뷰에 대한 자세한 내용은 ASP.NET Core의 부분 뷰를 참조하세요.

페이지에 대한 URL 생성

이전에 표시된 Create 페이지에서는 RedirectToPage를 사용합니다.

public class CreateModel : PageModel
{
    private readonly Data.CustomerDbContext _context;

    public CreateModel(Data.CustomerDbContext context)
    {
        _context = context;
    }

    public IActionResult OnGet()
    {
        return Page();
    }

    [BindProperty]
    public Customer? Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        if (Customer != null) _context.Customer.Add(Customer);
        await _context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

예제 앱은 다음과 같은 파일/폴더 구조를 갖고 있습니다.

  • Pages/

    • Index.cshtml

    • Privacy.cshtml

    • /Customers

      • Create.cshtml
      • Edit.cshtml
      • Index.cshtml

Pages/Customers/Create.cshtml 성공 후 페이지와 Pages/Customers/Edit.cshtml 페이지가 리디렉션됩니다Pages/Customers/Index.cshtml. 문자열 ./Index는 이전 페이지에 액세스하는 데 사용되는 상대 페이지 이름입니다. 페이지에 대한 URL을 Pages/Customers/Index.cshtml 생성하는 데 사용됩니다. 예시:

  • Url.Page("./Index", ...)
  • <a asp-page="./Index">Customers Index Page</a>
  • RedirectToPage("./Index")

절대 페이지 이름 /IndexPages/Index.cshtml 페이지에 대한 URL을 생성하는 데 사용됩니다. 예시:

  • Url.Page("/Index", ...)
  • <a asp-page="/Index">Home Index Page</a>
  • RedirectToPage("/Index")

페이지 이름은 루트 /Pages 폴더에서 선행 /를 포함한 페이지 경로입니다(예: /Index). 위의 URL 생성 예제는 URL 하드 코딩에 비해 향상된 옵션과 기능적 성능을 제공합니다. URL 생성에는 라우팅이 사용되며 경로가 대상 경로에서 정의되는 방식에 따라 매개 변수를 생성하고 인코딩할 수 있습니다.

페이지에 대한 URL 생성은 상대적 이름을 지원합니다. 다음 표는 Pages/Customers/Create.cshtml에서 다른 RedirectToPage 매개 변수를 사용할 때 어떤 인덱스 페이지가 선택되는지를 보여줍니다.

RedirectToPage(x) 페이지
RedirectToPage("/Index") Pages/Index
RedirectToPage("./Index") Pages/Customers/Index
RedirectToPage("../Index") Pages/Index
RedirectToPage("Index") Pages/Customers/Index

RedirectToPage("Index"), RedirectToPage("./Index")RedirectToPage("../Index")상대적 이름입니다. RedirectToPage의 매개 변수는 현재 페이지의 경로와 결합되어 대상 페이지의 이름을 컴퓨팅합니다.

상대적 이름 연결은 구조가 복잡한 사이트를 만들때 유용합니다. 상대적 이름을 사용하여 폴더의 페이지 간을 연결하는 경우:

  • 폴더의 이름을 바꾸면 상대적 링크는 중단되지 않습니다.
  • 링크는 폴더 이름을 포함하지 않으므로 손상되지 않습니다.

다른 영역에서 페이지로 리디렉션하려면 영역을 지정하세요.

RedirectToPage("/Index", new { area = "Services" });

자세한 내용은 ASP.NET Core의 영역ASP.NET Core RazorPages 경로 및 앱 규칙을 참조하세요.

ViewData 특성

ViewDataAttribute를 사용하여 데이터를 페이지에 전달할 수 있습니다. [ViewData] 특성을 가진 속성은 ViewDataDictionary에서 저장되고 로드된 값을 가집니다.

다음 예제에서 AboutModel[ViewData] 특성을 Title 속성에 적용합니다.

public class AboutModel : PageModel
{
    [ViewData]
    public string Title { get; } = "About";

    public void OnGet()
    {
    }
}

정보 페이지에서는 모델의 속성으로 Title 속성에 접근합니다.

<h1>@Model.Title</h1>

레이아웃에서 제목은 ViewData 사전에서 읽습니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <title>@ViewData["Title"] - WebApplication</title>
    ...

TempData

ASP.NET Core는 TempData를 노출합니다. 이 속성은 판독될 때까지 데이터를 저장합니다. KeepPeek 메서드를 사용하여 삭제 없이 데이터를 검사할 수 있습니다. TempData는 데이터가 둘 이상의 요청에 필요한 경우 리디렉션에 유용합니다.

다음 코드는 TempData를 사용하여 Message 값을 설정합니다.

public class CreateDotModel : PageModel
{
    private readonly AppDbContext _db;

    public CreateDotModel(AppDbContext db)
    {
        _db = db;
    }

    [TempData]
    public string Message { get; set; }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _db.Customers.Add(Customer);
        await _db.SaveChangesAsync();
        Message = $"Customer {Customer.Name} added";
        return RedirectToPage("./Index");
    }
}

파일의 Pages/Customers/Index.cshtml 다음 태그는 사용 TempData값을 Message 표시합니다.

<h3>Msg: @Model.Message</h3>

Pages/Customers/Index.cshtml.cs 페이지 모델은 속성에 [TempData] 특성을 Message 적용합니다.

[TempData]
public string Message { get; set; }

자세한 내용은 TempData를 참조하세요.

페이지당 여러 처리기

다음 페이지는 asp-page-handler 태그 도우미를 사용하여 두 처리기에 대한 태그를 생성합니다.

@page
@model CreateFATHModel

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div>Name: <input asp-for="Customer.Name" /></div>
        <!-- <snippet_Handlers> -->
        <input type="submit" asp-page-handler="JoinList" value="Join" />
        <input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
        <!-- </snippet_Handlers> -->
    </form>
</body>
</html>

이전 예제의 폼에는 두 개의 제출 단추가 있고, 각 단추는 FormActionTagHelper를 사용하여 다른 URL에 제출됩니다. asp-page-handler 특성은 asp-page와 함께 사용됩니다. asp-page-handler는 페이지에서 정의된 각 처리기 메서드에 제출되는 URL을 생성합니다. 이 예제의 경우 현재 페이지로 연결므로 asp-page는 지정되지 않았습니다.

페이지 모델:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;

namespace RazorPagesContacts.Pages.Customers
{
    public class CreateFATHModel : PageModel
    {
        private readonly AppDbContext _db;

        public CreateFATHModel(AppDbContext db)
        {
            _db = db;
        }

        [BindProperty]
        public Customer Customer { get; set; }

        public async Task<IActionResult> OnPostJoinListAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _db.Customers.Add(Customer);
            await _db.SaveChangesAsync();
            return RedirectToPage("/Index");
        }

        public async Task<IActionResult> OnPostJoinListUCAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }
            Customer.Name = Customer.Name?.ToUpperInvariant();
            return await OnPostJoinListAsync();
        }
    }
}

이 코드는 명명된 처리기 메서드를 사용합니다. 명명된 처리기 메서드는 On<HTTP Verb> 뒤와 Async(있는 경우) 앞에 이름의 텍스트를 결합해서 만들어집니다. 위의 예제에서 페이지 메서드는 OnPostJoinListAsync와 OnPostJoinListUCAsync입니다. OnPostAsync가 제거된 처리기 이름은 JoinListJoinListUC입니다.

<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />

이전 코드를 사용할 경우 OnPostJoinListAsync에 제출되는 URL 경로는 https://localhost:5001/Customers/CreateFATH?handler=JoinList입니다. OnPostJoinListUCAsync에 제출되는 URL 경로는 https://localhost:5001/Customers/CreateFATH?handler=JoinListUC입니다.

사용자 지정 경로

@page 지시문을 사용하여 다음을 수행합니다.

  • 페이지에 대한 사용자 지정 경로를 지정합니다. 예를 들어 @page "/Some/Other/Path"를 사용하여 About 페이지에 대한 경로를 /Some/Other/Path로 설정할 수 있습니다.
  • 페이지의 기본 경로에 세그먼트를 추가합니다. 예를 들어 @page "item"을 사용하여 페이지의 기본 경로에 "item" 세그먼트를 추가할 수 있습니다.
  • 페이지의 기본 경로에 매개 변수를 추가합니다. 예를 들어 @page "{id}"를 사용하여 ID 매개 변수 id를 페이지에 필수로 지정할 수 있습니다.

경로의 시작 부분에 물결표(~)로 지정된 루트 상대 경로가 지원됩니다. 예를 들어 @page "~/Some/Other/Path"@page "/Some/Other/Path"과 같습니다.

URL에서 쿼리 문자열 ?handler=JoinList를 사용하지 않으려면 경로를 변경하여 처리기 이름을 URL의 경로 부분에 넣습니다. @page 지시문 뒤에 큰따옴표로 묶은 경로 템플릿을 추가하여 경로를 사용자 지정할 수 있습니다.

@page "{handler?}"
@model CreateRouteModel

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div>Name: <input asp-for="Customer.Name" /></div>
        <input type="submit" asp-page-handler="JoinList" value="Join" />
        <input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
    </form>
</body>
</html>

이전 코드를 사용할 경우 OnPostJoinListAsync에 제출되는 URL 경로는 https://localhost:5001/Customers/CreateFATH/JoinList입니다. OnPostJoinListUCAsync에 제출되는 URL 경로는 https://localhost:5001/Customers/CreateFATH/JoinListUC입니다.

handler 뒤의 ?는 경로 매개 변수가 선택 사항임을 의미합니다.

JavaScript(JS) 파일의 배치

페이지 및 뷰에 대한 JavaScript(JS) 파일의 배치는 앱에서 스크립트를 구성하는 편리한 방법입니다.

다음 파일 이름 확장 규칙을 사용하여 JS 파일을 배치합니다.

  • Razor Pages 앱의 페이지와 MVC 앱의 보기: .cshtml.js. 예:
    • Pages/Index.cshtml에 있는 Razor Pages 앱의 Index 페이지에 대한 Pages/Index.cshtml.js.
    • Views/Home/Index.cshtml에 있는 MVC 앱의 Index 보기에 대한 Views/Home/Index.cshtml.js.

배치된 JS 파일은 프로젝트에서 파일 경로를 사용하여 공개적으로 주소를 지정합니다.

  • 앱에서 정렬된 스크립트 파일의 페이지 및 뷰:

    {PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js

    • {PATH} 자리 표시자는 페이지, 보기 또는 구성 요소의 경로입니다.
    • {PAGE, VIEW, OR COMPONENT} 자리 표시자는 페이지, 보기 또는 구성 요소입니다.
    • {EXTENSION} 자리 표시자는 페이지, 보기, 구성 요소, razor 또는 cshtml의 확장과 일치합니다.

    Razor Pages 예제:

    Index 페이지의 JS 파일은 Index 페이지(Pages/Index.cshtml) 옆에 있는 Pages 폴더(Pages/Index.cshtml.js)에 배치됩니다. Index 페이지에서 스크립트는 Pages 폴더의 경로에서 참조됩니다.

    @section Scripts {
      <script src="~/Pages/Index.cshtml.js"></script>
    }
    

    앱이 게시되면 프레임워크는 자동으로 스크립트를 웹 루트로 이동합니다. 앞선 예제에서 스크립트는 bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.cshtml.js로 이동합니다. 여기에서 {TARGET FRAMEWORK MONIKER} 자리 표시자는 TFM(대상 프레임워크 모니커)입니다. Index 페이지에서 스크립트의 상대 URL을 변경할 필요가 없습니다.

    앱이 게시되면 프레임워크는 자동으로 스크립트를 웹 루트로 이동합니다. 앞선 예제에서 스크립트는 bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Components\Pages\Index.razor.js로 이동합니다. 여기에서 {TARGET FRAMEWORK MONIKER} 자리 표시자는 TFM(대상 프레임워크 모니커)입니다. Index 구성 요소에서 스크립트의 상대 URL을 변경할 필요가 없습니다.

  • RCL(Razor 클래스 라이브러리)에서 제공하는 스크립트의 경우:

    _content/{PACKAGE ID}/{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js

    • {PACKAGE ID} 자리 표시자는 RCL의 패키지 식별자(또는 앱에서 참조하는 클래스 라이브러리의 라이브러리 이름)입니다.
    • {PATH} 자리 표시자는 페이지, 보기 또는 구성 요소의 경로입니다. Razor 구성 요소가 RCL의 루트에 있으면 경로 세그먼트가 포함되지 않습니다.
    • {PAGE, VIEW, OR COMPONENT} 자리 표시자는 페이지, 보기 또는 구성 요소입니다.
    • {EXTENSION} 자리 표시자는 페이지, 보기, 구성 요소, razor 또는 cshtml의 확장과 일치합니다.

고급 구성 및 설정

다음 섹션의 구성 및 설정은 대부분의 앱에서 필요하지 않습니다.

고급 옵션을 구성하려면 RazorPagesOptions를 구성하는 AddRazorPages 오버로드를 사용합니다.

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages(options =>
{
    options.RootDirectory = "/MyPages";
    options.Conventions.AuthorizeFolder("/MyPages/Admin");
});

builder.Services.AddDbContext<CustomerDbContext>(options =>
    options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

RazorPagesOptions를 사용하여 페이지의 루트 디렉터리를 설정하거나 페이지에 대한 애플리케이션 모델 규칙을 추가할 수 있습니다. 규칙에 대한 자세한 내용은 Razor Pages 권한 부여 규칙을 참조하세요.

뷰를 미리 컴파일하려면 Razor 뷰 컴파일을 참조하세요.

Razor Pages가 컨텐츠 루트에 있도록 지정

기본적으로 Razor Pages의 루트 경로는 /Pages 디렉터리입니다. WithRazorPagesAtContentRoot를 추가하여 Razor Pages가 앱의 콘텐츠 루트(ContentRootPath)가 되도록 지정합니다.

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages(options =>
{
    options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
  .WithRazorPagesAtContentRoot();

builder.Services.AddDbContext<CustomerDbContext>(options =>
    options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Razor Pages가 사용자 지정 루트 디렉터리에 있도록 지정

WithRazorPagesRoot를 추가하여 Razor Pages가 앱의 사용자 지정 루트 디렉터리에 있도록 지정합니다(상대 경로 제공).

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages(options =>
{
    options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
  .WithRazorPagesRoot("/path/to/razor/pages");

builder.Services.AddDbContext<CustomerDbContext>(options =>
    options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

추가 리소스

Razor Pages 프로젝트 만들기

Razor Pages 프로젝트를 만드는 방법에 대한 자세한 내용은 Razor Pages 시작을 참조하세요.

Razor Pages

RazorPages는 다음에서 사용하도록 설정됩니다.Startup.cs

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
        });
    }
}

기본적인 페이지를 살펴보겠습니다.

@page

<h1>Hello, world!</h1>
<h2>The time on the server is @DateTime.Now</h2>

앞의 코드는 컨트롤러 및 뷰가 포함된 ASP.NET Core 앱에서 사용되는 Razor 뷰 파일과 매우 유사합니다. 차이점은 @page 지시문입니다. @page는 파일을 MVC 액션으로 만드는데, 이 말은 컨트롤러를 거치지 않고 이 파일이 요청을 직접 처리한다는 뜻입니다. @page는 페이지의 첫 번째 Razor 지시문이어야 합니다. @page는 다른 Razor 구문의 동작에 영향을 줍니다. Razor Pages 파일 이름에는 .cshtml 접미사가 있습니다.

다음 두 파일은 PageModel 클래스를 사용하는 비슷한 페이지를 보여줍니다. 파일:Pages/Index2.cshtml

@page
@using RazorPagesIntro.Pages
@model Index2Model

<h2>Separate page model</h2>
<p>
    @Model.Message
</p>

Pages/Index2.cshtml.cs 페이지 모델:

using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;

namespace RazorPagesIntro.Pages
{
    public class Index2Model : PageModel
    {
        public string Message { get; private set; } = "PageModel in C#";

        public void OnGet()
        {
            Message += $" Server time is { DateTime.Now }";
        }
    }
}

규칙에 PageModel 따라 클래스 파일의 이름은 Razor 추가된 페이지 파일과 .cs 동일합니다. 예를 들어 위의 Razor Page는 Pages/Index2.cshtml입니다. 그리고 PageModel 클래스가 포함된 파일의 이름은 Pages/Index2.cshtml.cs입니다.

페이지에 대한 URL 경로 연결은 파일 시스템 상의 페이지 위치에 따라 결정됩니다. 다음 표는 Razor Page 경로 및 그와 일치하는 URL을 보여 줍니다.

파일 이름 및 경로 일치하는 URL
/Pages/Index.cshtml / 또는 /Index
/Pages/Contact.cshtml /Contact
/Pages/Store/Contact.cshtml /Store/Contact
/Pages/Store/Index.cshtml /Store 또는 /Store/Index

참고:

  • 런타임은 기본적으로 Pages 폴더에서 Razor Pages 파일을 검색합니다.
  • URL에 페이지가 지정되어 있지 않을 경우 Index가 기본 페이지입니다.

기본 양식 작성

Razor Pages는 앱을 만들 때 웹 브라우저에서 사용되는 일반적인 패턴을 손쉽게 구현할 수 있도록 설계되었습니다. 모델 바인딩, 태그 도우미 및 HTML 도우미는 모두 Razor Page 클래스에 정의된 속성을 통해서 ‘정확하게 작동’합니다. Contact 모델에 대한 기본적인 "연락처" 양식을 구현하는 페이지를 생각해보겠습니다.

이 문서의 예제에서 DbContextStartup.cs 파일에서 초기화됩니다.

메모리 내 데이터베이스에는 Microsoft.EntityFrameworkCore.InMemory NuGet 패키지가 필요합니다.

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<CustomerDbContext>(options =>
                      options.UseInMemoryDatabase("name"));
    services.AddRazorPages();
}

데이터 모델:

using System.ComponentModel.DataAnnotations;

namespace RazorPagesContacts.Models
{
    public class Customer
    {
        public int Id { get; set; }

        [Required, StringLength(10)]
        public string Name { get; set; }
    }
}

db 컨텍스트:

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Models;

namespace RazorPagesContacts.Data
{
    public class CustomerDbContext : DbContext
    {
        public CustomerDbContext(DbContextOptions options)
            : base(options)
        {
        }

        public DbSet<Customer> Customers { get; set; }
    }
}

Pages/Create.cshtml 뷰 파일:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>

Pages/Create.cshtml.cs 페이지 모델:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;
using RazorPagesContacts.Models;
using System.Threading.Tasks;

namespace RazorPagesContacts.Pages.Customers
{
    public class CreateModel : PageModel
    {
        private readonly CustomerDbContext _context;

        public CreateModel(CustomerDbContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            return Page();
        }

        [BindProperty]
        public Customer Customer { get; set; }

        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _context.Customers.Add(Customer);
            await _context.SaveChangesAsync();

            return RedirectToPage("./Index");
        }
    }
}

일반적으로 PageModel 클래스를 <PageName>Model이라고 하고 이 클래스는 페이지와 동일한 네임스페이스에 있습니다.

PageModel 클래스를 사용하면 페이지의 논리를 페이지의 표현으로부터 분리할 수 있습니다. 이 클래스는 페이지로 전송된 요청에 대한 페이지 처리기와 페이지를 렌더링하기 위해 사용되는 데이터를 정의합니다. 이러한 분리를 통해 다음을 수행할 수 있습니다.

페이지에는 POST 요청에서 실행되는 OnPostAsync처리기 메서드가 있습니다(사용자가 폼을 게시할 때). 모든 HTTP 동사에 대한 처리기 메서드를 추가할 수 있습니다. 가장 일반적인 처리기는 다음과 같습니다.

  • OnGet: 페이지에 필요한 상태를 초기화합니다. 위의 코드에서 OnGet 메서드는 CreateModel.cshtmlRazor Page를 표시합니다.
  • OnPost: 양식 제출을 처리합니다.

Async 명명 접미사는 선택 사항이지만, 규칙에 따라 비동기 함수에 자주 사용됩니다. 위의 코드는 Razor Pages의 일반적인 코드입니다.

컨트롤러 및 뷰를 사용하는 ASP.NET 앱에 대해 잘 알고 있는 경우:

  • 앞 예제의 OnPostAsync 코드는 일반적인 컨트롤러 코드와 비슷합니다.
  • 모델 바인딩, 유효성 검사 및 작업 결과와 같은 MVC의 기본적인 기능들이 대부분 컨트롤러 및 Razor Pages와 동일하게 작동합니다.

위의 OnPostAsync 메서드는 다음과 같습니다.

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Customers.Add(Customer);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

OnPostAsync의 기본 흐름:

유효성 검사 오류를 확인합니다.

  • 오류가 없는 경우 데이터를 저장하고 리디렉션합니다.
  • 오류가 있을 경우 유효성 검사 메시지가 포함된 페이지를 다시 표시합니다. 대부분의 경우 유효성 검사 오류는 클라이언트에서 검색되고 서버로 제출되지 않습니다.

Pages/Create.cshtml 뷰 파일:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>

Pages/Create.cshtml에서 렌더링된 HTML:

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input type="text" data-val="true"
           data-val-length="The field Name must be a string with a maximum length of 10."
           data-val-length-max="10" data-val-required="The Name field is required."
           id="Customer_Name" maxlength="10" name="Customer.Name" value="" />
    <input type="submit" />
    <input name="__RequestVerificationToken" type="hidden"
           value="<Antiforgery token here>" />
</form>

이전 코드에서 양식 게시:

  • 유효한 데이터 사용:

    • OnPostAsync 처리기 메서드는 RedirectToPage 도우미 메서드를 호출합니다. RedirectToPageRedirectToPageResult 인스턴스를 반환합니다. RedirectToPage:

      • 작업 결과입니다.
      • RedirectToAction 또는 RedirectToRoute(컨트롤러 및 뷰에서 사용됨)와 유사합니다.
      • 페이지에 맞게 사용자 지정됩니다. 위의 예제에서 이 메서드는 루트 인덱스 페이지(/Index)로 리디렉션합니다. RedirectToPage페이지에 대한 URL 생성 섹션에서 자세히 설명합니다.
  • 서버에 전달되는 유효성 검사 오류 포함:

    • OnPostAsync 처리기 메서드는 Page 도우미 메서드를 호출합니다. PagePageResult 인스턴스를 반환합니다. Page를 반환하는 것은 컨트롤의 액션에서 View를 반환하는 것과 비슷합니다. PageResult는 처리기 메서드의 기본 반환 형식입니다. void를 반환하는 처리기 메서드는 페이지를 렌더링합니다.
    • 앞의 예제에서 값이 없는 양식을 게시하면 false를 반환하는 ModelState.IsValid를 반환합니다. 이 샘플에서는 클라이언트에 유효성 검사 오류가 표시되지 않습니다. 유효성 검사 오류 처리는 이 문서의 뒷부분에 설명되어 있습니다.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }
    
        _context.Customers.Add(Customer);
        await _context.SaveChangesAsync();
    
        return RedirectToPage("./Index");
    }
    
  • 클라이언트 쪽 유효성 검사에서 검색된 유효성 검사 오류 포함:

    • 데이터가 서버에 게시되지 않습니다.
    • 클라이언트 쪽 유효성 검사는 이 문서의 뒷부분에 설명되어 있습니다.

Customer 속성은 [BindProperty] 특성을 이용하여 모델 바인딩에 옵트인(opt in)합니다.

public class CreateModel : PageModel
{
    private readonly CustomerDbContext _context;

    public CreateModel(CustomerDbContext context)
    {
        _context = context;
    }

    public IActionResult OnGet()
    {
        return Page();
    }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Customers.Add(Customer);
        await _context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

클라이언트에서 변경하면 안 되는 속성을 포함하는 모델에서는 [BindProperty]를 사용하지 않아야 합니다. 자세한 내용은 초과 게시를 참조하세요.

Razor Pages는 기본적으로 비 GET 동사에 대해서만 속성을 바인딩합니다. 속성에 바인딩하면 HTTP 데이터를 모델 형식으로 변환하는 코드를 작성할 필요가 없습니다. 바인딩은 동일한 속성을 사용하여 폼 필드(<input asp-for="Customer.Name">)를 렌더링하고 입력을 허용하는 방식으로 코드를 줄입니다.

Warning

보안상의 이유로 페이지 모델 속성에 GET 요청 데이터를 바인딩하기 위해 옵트인해야 합니다. 속성에 매핑하기 전에 사용자 입력을 확인합니다. 쿼리 문자열이나 경로 값을 사용하는 시나리오를 지정할 때 GET 바인딩을 옵트인하면 유용합니다.

GET 요청에 속성을 바인딩하려면 [BindProperty] 특성의 SupportsGet 속성을 true로 설정합니다.

[BindProperty(SupportsGet = true)]

자세한 내용은 ASP.NET Core Community 스탠드업을 참조하세요. 토론 가져오기(YouTube)에서 바인딩합니다.

Pages/Create.cshtml 보기 파일 검토:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>
  • 앞의 코드에서 입력 태그 도우미<input asp-for="Customer.Name" />은 HTML <input> 요소를 Customer.Name 모델 식에 바인딩합니다.
  • @addTagHelper는 태그 도우미를 사용할 수 있도록 설정합니다.

홈페이지

Index.cshtml은 홈페이지입니다.

@page
@model RazorPagesContacts.Pages.Customers.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>Contacts home page</h1>
<form method="post">
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            @foreach (var contact in Model.Customer)
            {
                <tr>
                    <td> @contact.Id  </td>
                    <td>@contact.Name</td>
                    <td>
                        <!-- <snippet_Edit> -->
                        <a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
                        <!-- </snippet_Edit> -->
                        <!-- <snippet_Delete> -->
                        <button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>
                        <!-- </snippet_Delete> -->
                    </td>
                </tr>
            }
        </tbody>
    </table>
    <a asp-page="Create">Create New</a>
</form>

연결된 PageModel클래스(Index.cshtml.cs):

public class IndexModel : PageModel
{
    private readonly CustomerDbContext _context;

    public IndexModel(CustomerDbContext context)
    {
        _context = context;
    }

    public IList<Customer> Customer { get; set; }

    public async Task OnGetAsync()
    {
        Customer = await _context.Customers.ToListAsync();
    }

    public async Task<IActionResult> OnPostDeleteAsync(int id)
    {
        var contact = await _context.Customers.FindAsync(id);

        if (contact != null)
        {
            _context.Customers.Remove(contact);
            await _context.SaveChangesAsync();
        }

        return RedirectToPage();
    }
}

파일에는 Index.cshtml 다음 태그가 포함됩니다.

<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |

<a /a>앵커 태그 도우미는 Edit 페이지에 대한 링크를 생성하기 위해 asp-route-{value} 특성을 사용합니다. 링크에는 연락처 ID와 함께 경로 데이터가 포함됩니다. 예: https://localhost:5001/Edit/1. 태그 도우미를 사용하면 Razor 파일에서 HTML 요소를 만들고 렌더링하는 데 서버 쪽 코드를 사용할 수 있습니다.

파일에는 Index.cshtml 각 고객 연락처에 대한 삭제 단추를 만드는 태그가 포함되어 있습니다.

<button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>

렌더링된 HTML:

<button type="submit" formaction="/Customers?id=1&amp;handler=delete">delete</button>

삭제 단추가 HTML로 렌더링되는 경우 해당 formaction에는 다음에 대한 매개 변수가 포함됩니다.

  • asp-route-id 특성으로 지정된 고객 연락처 ID.
  • asp-page-handler 특성으로 지정된 handler.

단추를 선택하면 양식의 POST 요청이 서버로 전송됩니다. 규약에 따라 처리기 메서드의 이름은 handler 매개 변수의 값을 기반으로 OnPost[handler]Async 체계에 의해 선택됩니다.

이번 예제에서는 handlerdelete이므로 POST 요청을 처리하기 위해 OnPostDeleteAsync 처리기 메서드가 사용됩니다. asp-page-handlerremove 같은 다른 값으로 설정되면 OnPostRemoveAsync라는 이름의 처리기 메서드가 선택됩니다.

public async Task<IActionResult> OnPostDeleteAsync(int id)
{
    var contact = await _context.Customers.FindAsync(id);

    if (contact != null)
    {
        _context.Customers.Remove(contact);
        await _context.SaveChangesAsync();
    }

    return RedirectToPage();
}

OnPostDeleteAsync 메서드는 다음 작업을 수행합니다.

  • 쿼리 문자열에서 id를 가져옵니다.
  • FindAsync를 사용하여 데이터베이스에서 고객 연락처를 쿼리합니다.
  • 고객 연락처가 발견되면 제거하고 데이터베이스를 업데이트합니다.
  • RedirectToPage를 호출하여 루트 인덱스 페이지(/Index)를 리디렉션합니다.

Edit.cshtml 파일

@page "{id:int}"
@model RazorPagesContacts.Pages.Customers.EditModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers


<h1>Edit Customer - @Model.Customer.Id</h1>
<form method="post">
    <div asp-validation-summary="All"></div>
    <input asp-for="Customer.Id" type="hidden" />
    <div>
        <label asp-for="Customer.Name"></label>
        <div>
            <input asp-for="Customer.Name" />
            <span asp-validation-for="Customer.Name"></span>
        </div>
    </div>

    <div>
        <button type="submit">Save</button>
    </div>
</form>

첫 번째 줄에는 @page "{id:int}" 지시문이 작성되어 있습니다. 라우팅 제약 조건인 "{id:int}"int 경로 데이터가 포함된 페이지에 대한 요청을 허용하도록 페이지에 지시합니다. 페이지에 대한 요청에 int로 변환될 수 있는 경로 데이터가 없으면 런타임은 HTTP 404(찾을 수 없음) 오류를 반환합니다. ID를 옵션으로 설정하려면 경로 제약 조건에 ?를 추가합니다.

@page "{id:int?}"

파일:Edit.cshtml.cs

public class EditModel : PageModel
{
    private readonly CustomerDbContext _context;

    public EditModel(CustomerDbContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnGetAsync(int id)
    {
        Customer = await _context.Customers.FindAsync(id);

        if (Customer == null)
        {
            return RedirectToPage("./Index");
        }

        return Page();
    }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Attach(Customer).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            throw new Exception($"Customer {Customer.Id} not found!");
        }

        return RedirectToPage("./Index");
    }

}

유효성 검사

유효성 검사 규칙:

  • 모델 클래스에서 선언적으로 지정됩니다.
  • 앱의 모든 위치에서 적용됩니다.

System.ComponentModel.DataAnnotations 네임스페이스는 클래스 또는 속성에 선언적으로 적용되는 유효성 검사 특성의 기본 제공 세트를 제공합니다. 또한 DataAnnotations는 서식 지정을 돕고 아무런 유효성 검사도 제공하지 않는 [DataType] 같은 서식 지정 특성도 포함하고 있습니다.

Customer 모델 고려:

using System.ComponentModel.DataAnnotations;

namespace RazorPagesContacts.Models
{
    public class Customer
    {
        public int Id { get; set; }

        [Required, StringLength(10)]
        public string Name { get; set; }
    }
}

다음 Create.cshtml 보기 파일 사용:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Validation: customer name:</p>

<form method="post">
    <div asp-validation-summary="ModelOnly"></div>
    <span asp-validation-for="Customer.Name"></span>
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>

<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>

앞의 코드가 하는 역할은 다음과 같습니다.

  • jQuery 및 jQuery 유효성 검사 스크립트를 포함합니다.

  • <div /><span />태그 도우미를 사용하여 다음을 사용하도록 설정합니다.

    • 클라이언트 쪽 유효성 검사.
    • 유효성 검사 오류 렌더링.
  • 다음과 같은 HTML을 생성합니다.

    <p>Enter a customer name:</p>
    
    <form method="post">
        Name:
        <input type="text" data-val="true"
               data-val-length="The field Name must be a string with a maximum length of 10."
               data-val-length-max="10" data-val-required="The Name field is required."
               id="Customer_Name" maxlength="10" name="Customer.Name" value="" />
        <input type="submit" />
        <input name="__RequestVerificationToken" type="hidden"
               value="<Antiforgery token here>" />
    </form>
    
    <script src="/lib/jquery/dist/jquery.js"></script>
    <script src="/lib/jquery-validation/dist/jquery.validate.js"></script>
    <script src="/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
    

이름 값 없이 만들기 양식을 게시하면 양식에 “이름 필드는 필수입니다.”라는 오류 메시지가 표시됩니다. 클라이언트에서 JavaScript를 사용하는 경우 서버에 게시하지 않고 브라우저에 오류를 표시합니다.

[StringLength(10)] 특성은 렌더링된 HTML에서 data-val-length-max="10"을 생성합니다. data-val-length-max는 브라우저에서 지정된 최대 길이를 초과하여 입력하지 못하도록 합니다. Fiddler와 같은 도구를 사용하여 게시물을 편집하고 재생하는 경우:

  • 이름이 10글자보다 긴 경우.
  • “필드 이름은 최대 길이가 10글자인 문자열이어야 합니다.”라는 오류 메시지가 반환됩니다.

다음 Movie 모델을 보세요.

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }

        [StringLength(60, MinimumLength = 3)]
        [Required]
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }

        [Range(1, 100)]
        [DataType(DataType.Currency)]
        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }

        [RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$")]
        [Required]
        [StringLength(30)]
        public string Genre { get; set; }

        [RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
        [StringLength(5)]
        [Required]
        public string Rating { get; set; }
    }
}

이 유효성 검사 특성은 적용되는 모델 속성에 시행하려는 동작을 지정합니다.

  • RequiredMinimumLength 특성은 속성에 값이 있어야 하지만 사용자가 이 유효성 검사를 만족하기 위해 공백을 입력하는 것을 막을 수 없다는 것을 나타냅니다.

  • RegularExpression 특성은 입력할 수 있는 문자를 제한하는 데 사용됩니다. 이전 코드에서 “Genre”는 다음 조건을 충족해야 합니다.

    • 문자만 사용해야 합니다.
    • 첫 번째 문자는 대문자여야 합니다 공백, 숫자 및 특수 문자는 허용되지 않습니다.
  • RegularExpression “Rating”은 다음 조건을 충족해야 합니다.

    • 첫 번째 문자는 대문자여야 합니다.
    • 이어지는 자리에서는 특수 문자와 숫자가 허용됩니다. “PG-13”은 등급에서는 유효하지만 “Genre”에서는 허용되지 않습니다.
  • Range 특성은 값을 지정된 범위 내로 제한합니다.

  • StringLength 특성은 문자열 속성의 최대 길이와, 필요에 따라 최소 길이를 설정합니다.

  • 값 형식(예: decimal, int, float, DateTime)은 기본적으로 필수적이며 [Required] 특성이 필요하지 않습니다.

Movie 모델에 대한 만들기 페이지에 잘못된 값이 포함된 오류가 표시됩니다.

Movie view form with multiple jQuery client-side validation errors

자세한 내용은 다음을 참조하세요.

OnGet 처리기 대체를 사용하여 HEAD 요청 처리

HEAD 요청을 사용하면 특정 리소스의 헤더를 검색할 수 있습니다. GET 요청과는 달리 HEAD 요청은 응답 본문을 반환하지 않습니다.

일반적으로 HEAD 요청에 대한 OnHead 처리기를 만들고 호출합니다.

public void OnHead()
{
    HttpContext.Response.Headers.Add("Head Test", "Handled by OnHead!");
}

정의된 OnHead 처리기가 없다면 Razor Pages가 OnGet 처리기 호출로 대체합니다.

XSRF/CSRF 및 Razor Pages

위조 방지 유효성 검사를 통해 Razor Pages를 보호합니다. FormTagHelper는 위조 방지 토큰을 HTML 양식 요소에 삽입합니다.

Razor Pages에서 레이아웃, 부분 뷰, 템플릿 및 태그 도우미 사용하기

Pages는 Razor 뷰 엔진의 모든 기능과 함께 작동합니다. 레이아웃, 부분, 템플릿, 태그 도우미 _ViewStart.cshtml_ViewImports.cshtml 기존 Razor 보기와 동일한 방식으로 작동합니다.

이 기능들 중 일부를 활용하여 페이지를 개선해 보겠습니다.

레이아웃 페이지를에 추가합니다Pages/Shared/_Layout.cshtml.

<!DOCTYPE html>
<html>
<head>
    <title>RP Sample</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
</head>
<body>
    <a asp-page="/Index">Home</a>
    <a asp-page="/Customers/Create">Create</a>
    <a asp-page="/Customers/Index">Customers</a> <br />

    @RenderBody()
    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</body>
</html>

레이아웃은 다음과 같은 역할을 수행합니다.

  • 각 페이지의 레이아웃을 제어합니다(페이지가 명시적으로 레이아웃을 사용하지 않는 경우 제외).
  • JavaScript 및 스타일시트 같은 HTML 구조를 가져옵니다.
  • Razor 페이지의 내용은 @RenderBody()가 호출되는 위치에서 렌더링됩니다.

자세한 내용은 레이아웃 페이지를 참조하세요.

Layout 속성은 다음에서 설정됩니다.Pages/_ViewStart.cshtml

@{
    Layout = "_Layout";
}

레이아웃은 Pages/Shared 폴더에 위치합니다. 페이지는 현재 페이지와 동일한 폴더에서부터 시작하여 계층적으로 다른 뷰(레이아웃, 템플릿, 부분 뷰)들을 검색합니다. Pages/Shared 폴더의 레이아웃은 Pages 폴더 하위의 모든 Razor 페이지에서 사용할 수 있습니다.

레이아웃 파일은 Pages/Shared 폴더에 위치해야 합니다.

레이아웃 파일은 Views/Shared 폴더에 두지 않는 것이 좋습니다. Views/Shared는 MVC 뷰 패턴입니다. Razor Pages는 경로 규칙이 아닌 폴더 계층 구조를 사용해야 합니다.

Razor Page의 뷰 검색에는 Pages 폴더가 포함됩니다. MVC 컨트롤러 및 기존 Razor 뷰에서 사용한 레이아웃, 템플릿 및 부분 뷰는 ‘정상적으로 작동’합니다.

Pages/_ViewImports.cshtml 파일을 추가합니다.

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@namespace는 잠시 뒤에 설명합니다. @addTagHelper 지시문은 기본 제공 태그 도우미Pages 폴더의 모든 페이지에 도입합니다.

페이지에서 설정된 @namespace 지시문:

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

<h2>Name space</h2>
<p>
    @Model.Message
</p>

@namespace 지시문은 페이지에 대한 네임스페이스를 설정합니다. 이렇게 하면 @model 지시문은 네임스페이스를 포함할 필요가 없습니다.

@namespace 지시문이 _ViewImports.cshtml에 포함되어 있으면 지정된 네임스페이스가 @namespace 지시문을 가져오는 페이지에서 생성된 네임스페이스에 대한 접두사를 제공합니다. 생성된 네임스페이스(접미사 부분)의 나머지 부분은 포함된 _ViewImports.cshtml 폴더와 페이지가 포함된 폴더 사이의 점으로 구분된 상대 경로입니다.

예를 들어 PageModel 클래스 Pages/Customers/Edit.cshtml.cs는 네임스페이스를 명시적으로 설정합니다.

namespace RazorPagesContacts.Pages
{
    public class EditModel : PageModel
    {
        private readonly AppDbContext _db;

        public EditModel(AppDbContext db)
        {
            _db = db;
        }

        // Code removed for brevity.

파일은 Pages/_ViewImports.cshtml 다음 네임스페이스를 설정합니다.

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Pages/Customers/Edit.cshtmlRazor Page에 대해 생성되는 네임스페이스는 PageModel 클래스와 동일합니다.

@namespace은 기존 Razor 보기에서도 작동합니다.

뷰 파일을 고려합니다.Pages/Create.cshtml

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Validation: customer name:</p>

<form method="post">
    <div asp-validation-summary="ModelOnly"></div>
    <span asp-validation-for="Customer.Name"></span>
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>

<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>

업데이트 Pages/Create.cshtml 된 뷰 파일 및 _ViewImports.cshtml 이전 레이아웃 파일:

@page
@model CreateModel

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>

이전 코드 _ViewImports.cshtml 에서 네임스페이스와 태그 도우미를 가져왔습니다. 레이아웃 파일은 JavaScript 파일을 가져왔습니다.

Pages 시작 프로젝트에는 RazorPages/_ValidationScriptsPartial.cshtml클라이언트 쪽 유효성 검사를 연결하는 이 프로젝트가 포함되어 있습니다.

부분 뷰에 대한 자세한 내용은 ASP.NET Core의 부분 뷰를 참조하세요.

페이지에 대한 URL 생성

이전에 표시된 Create 페이지에서는 RedirectToPage를 사용합니다.

public class CreateModel : PageModel
{
    private readonly CustomerDbContext _context;

    public CreateModel(CustomerDbContext context)
    {
        _context = context;
    }

    public IActionResult OnGet()
    {
        return Page();
    }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Customers.Add(Customer);
        await _context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

예제 앱은 다음과 같은 파일/폴더 구조를 갖고 있습니다.

  • Pages/

    • Index.cshtml

    • Privacy.cshtml

    • /Customers

      • Create.cshtml
      • Edit.cshtml
      • Index.cshtml

Pages/Customers/Create.cshtml 성공 후 페이지와 Pages/Customers/Edit.cshtml 페이지가 리디렉션됩니다Pages/Customers/Index.cshtml. 문자열 ./Index는 이전 페이지에 액세스하는 데 사용되는 상대 페이지 이름입니다. 페이지에 대한 URL을 Pages/Customers/Index.cshtml 생성하는 데 사용됩니다. 예시:

  • Url.Page("./Index", ...)
  • <a asp-page="./Index">Customers Index Page</a>
  • RedirectToPage("./Index")

절대 페이지 이름 /IndexPages/Index.cshtml 페이지에 대한 URL을 생성하는 데 사용됩니다. 예시:

  • Url.Page("/Index", ...)
  • <a asp-page="/Index">Home Index Page</a>
  • RedirectToPage("/Index")

페이지 이름은 루트 /Pages 폴더에서 선행 /를 포함한 페이지 경로입니다(예: /Index). 위의 URL 생성 예제는 URL 하드 코딩에 비해 향상된 옵션과 기능적 성능을 제공합니다. URL 생성에는 라우팅이 사용되며 경로가 대상 경로에서 정의되는 방식에 따라 매개 변수를 생성하고 인코딩할 수 있습니다.

페이지에 대한 URL 생성은 상대적 이름을 지원합니다. 다음 표는 Pages/Customers/Create.cshtml에서 다른 RedirectToPage 매개 변수를 사용할 때 어떤 인덱스 페이지가 선택되는지를 보여줍니다.

RedirectToPage(x) 페이지
RedirectToPage("/Index") Pages/Index
RedirectToPage("./Index") Pages/Customers/Index
RedirectToPage("../Index") Pages/Index
RedirectToPage("Index") Pages/Customers/Index

RedirectToPage("Index"), RedirectToPage("./Index")RedirectToPage("../Index")상대적 이름입니다. RedirectToPage의 매개 변수는 현재 페이지의 경로와 결합되어 대상 페이지의 이름을 컴퓨팅합니다.

상대적 이름 연결은 구조가 복잡한 사이트를 만들때 유용합니다. 상대적 이름을 사용하여 폴더의 페이지 간을 연결하는 경우:

  • 폴더의 이름을 바꾸면 상대적 링크는 중단되지 않습니다.
  • 링크는 폴더 이름을 포함하지 않으므로 손상되지 않습니다.

다른 영역에서 페이지로 리디렉션하려면 영역을 지정하세요.

RedirectToPage("/Index", new { area = "Services" });

자세한 내용은 ASP.NET Core의 영역ASP.NET Core RazorPages 경로 및 앱 규칙을 참조하세요.

ViewData 특성

ViewDataAttribute를 사용하여 데이터를 페이지에 전달할 수 있습니다. [ViewData] 특성을 가진 속성은 ViewDataDictionary에서 저장되고 로드된 값을 가집니다.

다음 예제에서 AboutModel[ViewData] 특성을 Title 속성에 적용합니다.

public class AboutModel : PageModel
{
    [ViewData]
    public string Title { get; } = "About";

    public void OnGet()
    {
    }
}

정보 페이지에서는 모델의 속성으로 Title 속성에 접근합니다.

<h1>@Model.Title</h1>

레이아웃에서 제목은 ViewData 사전에서 읽습니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <title>@ViewData["Title"] - WebApplication</title>
    ...

TempData

ASP.NET Core는 TempData를 노출합니다. 이 속성은 판독될 때까지 데이터를 저장합니다. KeepPeek 메서드를 사용하여 삭제 없이 데이터를 검사할 수 있습니다. TempData는 데이터가 둘 이상의 요청에 필요한 경우 리디렉션에 유용합니다.

다음 코드는 TempData를 사용하여 Message 값을 설정합니다.

public class CreateDotModel : PageModel
{
    private readonly AppDbContext _db;

    public CreateDotModel(AppDbContext db)
    {
        _db = db;
    }

    [TempData]
    public string Message { get; set; }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _db.Customers.Add(Customer);
        await _db.SaveChangesAsync();
        Message = $"Customer {Customer.Name} added";
        return RedirectToPage("./Index");
    }
}

파일의 Pages/Customers/Index.cshtml 다음 태그는 사용 TempData값을 Message 표시합니다.

<h3>Msg: @Model.Message</h3>

Pages/Customers/Index.cshtml.cs 페이지 모델은 속성에 [TempData] 특성을 Message 적용합니다.

[TempData]
public string Message { get; set; }

자세한 내용은 TempData를 참조하세요.

페이지당 여러 처리기

다음 페이지는 asp-page-handler 태그 도우미를 사용하여 두 처리기에 대한 태그를 생성합니다.

@page
@model CreateFATHModel

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div>Name: <input asp-for="Customer.Name" /></div>
        <!-- <snippet_Handlers> -->
        <input type="submit" asp-page-handler="JoinList" value="Join" />
        <input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
        <!-- </snippet_Handlers> -->
    </form>
</body>
</html>

이전 예제의 폼에는 두 개의 제출 단추가 있고, 각 단추는 FormActionTagHelper를 사용하여 다른 URL에 제출됩니다. asp-page-handler 특성은 asp-page와 함께 사용됩니다. asp-page-handler는 페이지에서 정의된 각 처리기 메서드에 제출되는 URL을 생성합니다. 이 예제의 경우 현재 페이지로 연결므로 asp-page는 지정되지 않았습니다.

페이지 모델:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;

namespace RazorPagesContacts.Pages.Customers
{
    public class CreateFATHModel : PageModel
    {
        private readonly AppDbContext _db;

        public CreateFATHModel(AppDbContext db)
        {
            _db = db;
        }

        [BindProperty]
        public Customer Customer { get; set; }

        public async Task<IActionResult> OnPostJoinListAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _db.Customers.Add(Customer);
            await _db.SaveChangesAsync();
            return RedirectToPage("/Index");
        }

        public async Task<IActionResult> OnPostJoinListUCAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }
            Customer.Name = Customer.Name?.ToUpperInvariant();
            return await OnPostJoinListAsync();
        }
    }
}

이 코드는 명명된 처리기 메서드를 사용합니다. 명명된 처리기 메서드는 On<HTTP Verb> 뒤와 Async(있는 경우) 앞에 이름의 텍스트를 결합해서 만들어집니다. 위의 예제에서 페이지 메서드는 OnPostJoinListAsync와 OnPostJoinListUCAsync입니다. OnPostAsync가 제거된 처리기 이름은 JoinListJoinListUC입니다.

<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />

이전 코드를 사용할 경우 OnPostJoinListAsync에 제출되는 URL 경로는 https://localhost:5001/Customers/CreateFATH?handler=JoinList입니다. OnPostJoinListUCAsync에 제출되는 URL 경로는 https://localhost:5001/Customers/CreateFATH?handler=JoinListUC입니다.

사용자 지정 경로

@page 지시문을 사용하여 다음을 수행합니다.

  • 페이지에 대한 사용자 지정 경로를 지정합니다. 예를 들어 @page "/Some/Other/Path"를 사용하여 About 페이지에 대한 경로를 /Some/Other/Path로 설정할 수 있습니다.
  • 페이지의 기본 경로에 세그먼트를 추가합니다. 예를 들어 @page "item"을 사용하여 페이지의 기본 경로에 "item" 세그먼트를 추가할 수 있습니다.
  • 페이지의 기본 경로에 매개 변수를 추가합니다. 예를 들어 @page "{id}"를 사용하여 ID 매개 변수 id를 페이지에 필수로 지정할 수 있습니다.

경로의 시작 부분에 물결표(~)로 지정된 루트 상대 경로가 지원됩니다. 예를 들어 @page "~/Some/Other/Path"@page "/Some/Other/Path"과 같습니다.

URL에서 쿼리 문자열 ?handler=JoinList를 사용하지 않으려면 경로를 변경하여 처리기 이름을 URL의 경로 부분에 넣습니다. @page 지시문 뒤에 큰따옴표로 묶은 경로 템플릿을 추가하여 경로를 사용자 지정할 수 있습니다.

@page "{handler?}"
@model CreateRouteModel

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div>Name: <input asp-for="Customer.Name" /></div>
        <input type="submit" asp-page-handler="JoinList" value="Join" />
        <input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
    </form>
</body>
</html>

이전 코드를 사용할 경우 OnPostJoinListAsync에 제출되는 URL 경로는 https://localhost:5001/Customers/CreateFATH/JoinList입니다. OnPostJoinListUCAsync에 제출되는 URL 경로는 https://localhost:5001/Customers/CreateFATH/JoinListUC입니다.

handler 뒤의 ?는 경로 매개 변수가 선택 사항임을 의미합니다.

고급 구성 및 설정

다음 섹션의 구성 및 설정은 대부분의 앱에서 필요하지 않습니다.

고급 옵션을 구성하려면 RazorPagesOptions를 구성하는 AddRazorPages 오버로드를 사용합니다.

public void ConfigureServices(IServiceCollection services)
{            
    services.AddRazorPages(options =>
    {
        options.RootDirectory = "/MyPages";
        options.Conventions.AuthorizeFolder("/MyPages/Admin");
    });
}

RazorPagesOptions를 사용하여 페이지의 루트 디렉터리를 설정하거나 페이지에 대한 애플리케이션 모델 규칙을 추가할 수 있습니다. 규칙에 대한 자세한 내용은 Razor Pages 권한 부여 규칙을 참조하세요.

뷰를 미리 컴파일하려면 Razor 뷰 컴파일을 참조하세요.

Razor Pages가 컨텐츠 루트에 있도록 지정

기본적으로 Razor Pages의 루트 경로는 /Pages 디렉터리입니다. WithRazorPagesAtContentRoot를 추가하여 Razor Pages가 앱의 콘텐츠 루트(ContentRootPath)가 되도록 지정합니다.

public void ConfigureServices(IServiceCollection services)
{            
    services.AddRazorPages(options =>
        {
            options.Conventions.AuthorizeFolder("/MyPages/Admin");
        })
        .WithRazorPagesAtContentRoot();
}

Razor Pages가 사용자 지정 루트 디렉터리에 있도록 지정

WithRazorPagesRoot를 추가하여 Razor Pages가 앱의 사용자 지정 루트 디렉터리에 있도록 지정합니다(상대 경로 제공).

public void ConfigureServices(IServiceCollection services)
{            
    services.AddRazorPages(options =>
        {
            options.Conventions.AuthorizeFolder("/MyPages/Admin");
        })
        .WithRazorPagesRoot("/path/to/razor/pages");
}

추가 리소스