다음을 통해 공유


5부. ASP.NET Core 앱에서 생성된 페이지 업데이트

참고 항목

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

Warning

이 버전의 ASP.NET Core는 더 이상 지원되지 않습니다. 자세한 내용은 .NET 및 .NET Core 지원 정책을 참조 하세요. 현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.

Important

이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.

현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.

스캐폴드된 동영상 앱을 사용하는 것이 좋지만 프레젠테이션은 이상적이지 않습니다. ReleaseDate는 두 단어, Release Date여야 합니다.

크롬에서 열린 동영상 애플리케이션

모델 업데이트

다음 강조 표시된 코드로 업데이트 Models/Movie.cs 합니다.

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

namespace RazorPagesMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;

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

    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

위의 코드에서:

  • [Column(TypeName = "decimal(18, 2)")] 데이터 주석을 사용하면 Entity Framework Core에서 Price를 데이터베이스의 통화에 올바르게 매핑할 수 있습니다. 자세한 내용은 데이터 형식을 참조하세요.
  • Display 특성은 필드의 표시 이름을 지정합니다. 앞의 코드에서 ReleaseDate 대신 Release Date입니다.
  • DataType 특성은 데이터 형식(Date)을 지정합니다. 필드에 저장된 시간 정보는 표시되지 않습니다.

다음 자습서에서 DataAnnotations를 다룹니다.

Pages/Movies로 이동하고 편집 링크로 마우스를 가져가 대상 URL을 확인합니다.

브라우저 창에서 편집 링크에 마우스를 가져가면 https://localhost:1234/Movies/Edit/5의 링크 Url이 표시됩니다.

편집, 세부 정보삭제 링크는 Pages/Movies/Index.cshtml 파일에서 앵커 태그 도우미에 의해 생성됩니다.

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

태그 도우미를 사용하면 Razor 파일에서 HTML 요소를 만들고 렌더링하는 데 서버 쪽 코드를 사용할 수 있습니다.

위의 코드에서 앵커 태그 도우미는 Razor 페이지(경로는 상대적), asp-page, 경로 ID(asp-route-id)에서 HTML href 속성 값을 동적으로 생성합니다. 자세한 내용은 페이지 URL 생성을 참조하세요.

브라우저에서 소스 보기를 사용하여 생성된 태그를 검사합니다. 생성된 HTML의 일부는 다음과 같습니다.

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

동적으로 생성된 링크는 쿼리 문자열이 포함된 동영상 ID를 전달합니다. https://localhost:5001/Movies/Details?id=1?id=1이 그 예입니다.

경로 템플릿 추가

{id:int} 경로 템플릿을 사용하도록 편집, 세부 정보 및 삭제 Razor Pages를 업데이트합니다. 이러한 각 페이지에 대한 page 지시문을 @page에서 @page "{id:int}"로 변경합니다. 앱을 실행한 다음 소스를 봅니다.

생성된 HTML에서 URL의 경로 부분에 ID를 추가합니다.

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

정수를 포함하지 않는 {id:int} 경로 템플릿이 있는 페이지에 대한 요청은 HTTP 404(찾을 수 없음) 오류를 반환합니다. 예를 들어 https://localhost:5001/Movies/Details는 404 오류를 반환합니다. ID를 옵션으로 설정하려면 경로 제약 조건에 ?를 추가합니다.

@page "{id:int?}"

@page "{id:int?}"의 동작을 테스트합니다.

  1. Pages/Movies/Details.cshtml에서 page 지시문을 @page "{id:int?}"로 설정합니다.
  2. public async Task<IActionResult> OnGetAsync(int? id)에서, Pages/Movies/Details.cshtml.cs에서 중단점을 설정합니다.
  3. https://localhost:5001/Movies/Details/으로 이동합니다.

@page "{id:int}" 지시문을 사용하면 중단점에 도달하지 않습니다. 라우팅 엔진은 HTTP 404를 반환합니다. OnGetAsync 메서드는 @page "{id:int?}"를 사용하여 NotFound(HTTP 404)를 반환합니다.

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

    var movie = await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
    if (movie == null)
    {
        return NotFound();
    }
    else
    {
        Movie = movie;
    }
    return Page();
}

동시성 예외 처리 검토

파일의 OnPostAsync 메서드를 검토합니다.Pages/Movies/Edit.cshtml.cs

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

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

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

    return RedirectToPage("./Index");
}

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

한 클라이언트가 영화를 삭제하고 다른 클라이언트가 영화 변경 내용을 게시하는 경우 위의 코드는 동시성 예외를 검색합니다.

catch 블록을 테스트하려면:

  1. catch (DbUpdateConcurrencyException)에서 중단점을 설정합니다.
  2. 동영상에 대한 편집을 선택하고, 변경하지만 저장을 입력하지 않습니다.
  3. 다른 브라우저 창에서 동일한 동영상에 대한 삭제 링크를 선택한 다음 동영상을 삭제합니다.
  4. 이전 브라우저 창에서 동영상에 변경 내용을 게시합니다.

프로덕션 코드는 동시성 충돌을 검색할 수 있습니다. 자세한 내용은 동시성 충돌 처리를 참조하세요.

검토 게시 및 바인딩

Pages/Movies/Edit.cshtml.cs 파일을 검사합니다.

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; } = default!;

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

        var movie =  await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
        if (movie == null)
        {
            return NotFound();
        }
        Movie = movie;
        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();
        }

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

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

        return RedirectToPage("./Index");
    }

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

Movies/Edit 페이지(예: https://localhost:5001/Movies/Edit/3)에 HTTP GET 요청을 하는 경우:

  • OnGetAsync 메서드는 데이터베이스에서 동영상을 가져오고 Page 메서드를 반환합니다.
  • Page 메서드는 Pages/Movies/Edit.cshtmlRazor 페이지를 렌더링합니다. 파일에는 Pages/Movies/Edit.cshtml 페이지에서 영화 모델을 사용할 수 있도록 하는 모델 지시문 @model RazorPagesMovie.Pages.Movies.EditModel이 포함되어 있습니다.
  • 편집 양식은 동영상에서 값으로 표시됩니다.

동영상/편집 페이지가 게시될 때:

  • 페이지에서 양식 값은 Movie 속성으로 바인딩됩니다. [BindProperty] 특성은 모델 바인딩을 활성화합니다.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • 모델 상태에 오류가 있는 경우(예: ReleaseDate를 날짜로 변환할 수 없는 경우) 양식은 제출된 값으로 다시 표시됩니다.

  • 모델 오류가 없는 경우 동영상이 저장됩니다.

인덱스, 만들기 및 삭제 Razor Pages의 HTTP GET 메서드는 유사한 패턴을 따릅니다. 만들기 Razor 페이지에서 HTTP POST OnPostAsync 메서드는 편집 Razor 페이지의 OnPostAsync 메서드와 유사한 패턴을 따릅니다.

다음 단계

스캐폴드된 동영상 앱을 사용하는 것이 좋지만 프레젠테이션은 이상적이지 않습니다. ReleaseDate는 두 단어, Release Date여야 합니다.

크롬에서 열린 동영상 애플리케이션

모델 업데이트

다음 강조 표시된 코드로 업데이트 Models/Movie.cs 합니다.

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

namespace RazorPagesMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;

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

    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

위의 코드에서:

  • [Column(TypeName = "decimal(18, 2)")] 데이터 주석을 사용하면 Entity Framework Core에서 Price를 데이터베이스의 통화에 올바르게 매핑할 수 있습니다. 자세한 내용은 데이터 형식을 참조하세요.
  • Display 특성은 필드의 표시 이름을 지정합니다. 앞의 코드에서 ReleaseDate 대신 Release Date입니다.
  • DataType 특성은 데이터 형식(Date)을 지정합니다. 필드에 저장된 시간 정보는 표시되지 않습니다.

다음 자습서에서 DataAnnotations를 다룹니다.

Pages/Movies로 이동하고 편집 링크로 마우스를 가져가 대상 URL을 확인합니다.

브라우저 창에서 편집 링크에 마우스를 가져가면 https://localhost:1234/Movies/Edit/5의 링크 Url이 표시됩니다.

편집, 세부 정보삭제 링크는 Pages/Movies/Index.cshtml 파일에서 앵커 태그 도우미에 의해 생성됩니다.

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

태그 도우미를 사용하면 Razor 파일에서 HTML 요소를 만들고 렌더링하는 데 서버 쪽 코드를 사용할 수 있습니다.

위의 코드에서 앵커 태그 도우미는 Razor 페이지(경로는 상대적), asp-page, 경로 ID(asp-route-id)에서 HTML href 속성 값을 동적으로 생성합니다. 자세한 내용은 페이지 URL 생성을 참조하세요.

브라우저에서 소스 보기를 사용하여 생성된 태그를 검사합니다. 생성된 HTML의 일부는 다음과 같습니다.

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

동적으로 생성된 링크는 쿼리 문자열이 포함된 동영상 ID를 전달합니다. https://localhost:5001/Movies/Details?id=1?id=1이 그 예입니다.

경로 템플릿 추가

{id:int} 경로 템플릿을 사용하도록 편집, 세부 정보 및 삭제 Razor Pages를 업데이트합니다. 이러한 각 페이지에 대한 page 지시문을 @page에서 @page "{id:int}"로 변경합니다. 앱을 실행한 다음 소스를 봅니다.

생성된 HTML에서 URL의 경로 부분에 ID를 추가합니다.

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

정수를 포함하지 않는 {id:int} 경로 템플릿이 있는 페이지에 대한 요청은 HTTP 404(찾을 수 없음) 오류를 반환합니다. 예를 들어 https://localhost:5001/Movies/Details는 404 오류를 반환합니다. ID를 옵션으로 설정하려면 경로 제약 조건에 ?를 추가합니다.

@page "{id:int?}"

@page "{id:int?}"의 동작을 테스트합니다.

  1. Pages/Movies/Details.cshtml에서 page 지시문을 @page "{id:int?}"로 설정합니다.
  2. public async Task<IActionResult> OnGetAsync(int? id)에서, Pages/Movies/Details.cshtml.cs에서 중단점을 설정합니다.
  3. https://localhost:5001/Movies/Details/으로 이동합니다.

@page "{id:int}" 지시문을 사용하면 중단점에 도달하지 않습니다. 라우팅 엔진은 HTTP 404를 반환합니다. OnGetAsync 메서드는 @page "{id:int?}"를 사용하여 NotFound(HTTP 404)를 반환합니다.

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

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

동시성 예외 처리 검토

파일의 OnPostAsync 메서드를 검토합니다.Pages/Movies/Edit.cshtml.cs

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

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

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

    return RedirectToPage("./Index");
}

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

한 클라이언트가 영화를 삭제하고 다른 클라이언트가 영화 변경 내용을 게시하는 경우 위의 코드는 동시성 예외를 검색합니다.

catch 블록을 테스트하려면:

  1. catch (DbUpdateConcurrencyException)에서 중단점을 설정합니다.
  2. 동영상에 대한 편집을 선택하고, 변경하지만 저장을 입력하지 않습니다.
  3. 다른 브라우저 창에서 동일한 동영상에 대한 삭제 링크를 선택한 다음 동영상을 삭제합니다.
  4. 이전 브라우저 창에서 동영상에 변경 내용을 게시합니다.

프로덕션 코드는 동시성 충돌을 검색할 수 있습니다. 자세한 내용은 동시성 충돌 처리를 참조하세요.

검토 게시 및 바인딩

Pages/Movies/Edit.cshtml.cs 파일을 검사합니다.

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; } = default!;

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

        var movie =  await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
        if (movie == null)
        {
            return NotFound();
        }
        Movie = movie;
        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();
        }

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

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

        return RedirectToPage("./Index");
    }

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

Movies/Edit 페이지(예: https://localhost:5001/Movies/Edit/3)에 HTTP GET 요청을 하는 경우:

  • OnGetAsync 메서드는 데이터베이스에서 동영상을 가져오고 Page 메서드를 반환합니다.
  • Page 메서드는 Pages/Movies/Edit.cshtmlRazor 페이지를 렌더링합니다. 파일에는 Pages/Movies/Edit.cshtml 페이지에서 영화 모델을 사용할 수 있도록 하는 모델 지시문 @model RazorPagesMovie.Pages.Movies.EditModel이 포함되어 있습니다.
  • 편집 양식은 동영상에서 값으로 표시됩니다.

동영상/편집 페이지가 게시될 때:

  • 페이지에서 양식 값은 Movie 속성으로 바인딩됩니다. [BindProperty] 특성은 모델 바인딩을 활성화합니다.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • 모델 상태에 오류가 있는 경우(예: ReleaseDate를 날짜로 변환할 수 없는 경우) 양식은 제출된 값으로 다시 표시됩니다.

  • 모델 오류가 없는 경우 동영상이 저장됩니다.

인덱스, 만들기 및 삭제 Razor Pages의 HTTP GET 메서드는 유사한 패턴을 따릅니다. 만들기 Razor 페이지에서 HTTP POST OnPostAsync 메서드는 편집 Razor 페이지의 OnPostAsync 메서드와 유사한 패턴을 따릅니다.

다음 단계

스캐폴드된 동영상 앱을 사용하는 것이 좋지만 프레젠테이션은 이상적이지 않습니다. ReleaseDate는 두 단어, Release Date여야 합니다.

크롬에서 열린 동영상 애플리케이션

모델 업데이트

다음 강조 표시된 코드로 업데이트 Models/Movie.cs 합니다.

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

namespace RazorPagesMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;

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

    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

위의 코드에서:

  • [Column(TypeName = "decimal(18, 2)")] 데이터 주석을 사용하면 Entity Framework Core에서 Price를 데이터베이스의 통화에 올바르게 매핑할 수 있습니다. 자세한 내용은 데이터 형식을 참조하세요.
  • Display 특성은 필드의 표시 이름을 지정합니다. 앞의 코드에서 ReleaseDate 대신 Release Date입니다.
  • DataType 특성은 데이터 형식(Date)을 지정합니다. 필드에 저장된 시간 정보는 표시되지 않습니다.

다음 자습서에서 DataAnnotations를 다룹니다.

Pages/Movies로 이동하고 편집 링크로 마우스를 가져가 대상 URL을 확인합니다.

브라우저 창에서 편집 링크에 마우스를 가져가면 https://localhost:1234/Movies/Edit/5의 링크 Url이 표시됩니다.

편집, 세부 정보삭제 링크는 Pages/Movies/Index.cshtml 파일에서 앵커 태그 도우미에 의해 생성됩니다.

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

태그 도우미를 사용하면 Razor 파일에서 HTML 요소를 만들고 렌더링하는 데 서버 쪽 코드를 사용할 수 있습니다.

위의 코드에서 앵커 태그 도우미는 Razor 페이지(경로는 상대적), asp-page, 경로 ID(asp-route-id)에서 HTML href 속성 값을 동적으로 생성합니다. 자세한 내용은 페이지 URL 생성을 참조하세요.

브라우저에서 소스 보기를 사용하여 생성된 태그를 검사합니다. 생성된 HTML의 일부는 다음과 같습니다.

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

동적으로 생성된 링크는 쿼리 문자열이 포함된 동영상 ID를 전달합니다. https://localhost:5001/Movies/Details?id=1?id=1이 그 예입니다.

경로 템플릿 추가

{id:int} 경로 템플릿을 사용하도록 편집, 세부 정보 및 삭제 Razor Pages를 업데이트합니다. 이러한 각 페이지에 대한 page 지시문을 @page에서 @page "{id:int}"로 변경합니다. 앱을 실행한 다음 소스를 봅니다.

생성된 HTML에서 URL의 경로 부분에 ID를 추가합니다.

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

정수를 포함하지 않는 {id:int} 경로 템플릿이 있는 페이지에 대한 요청은 HTTP 404(찾을 수 없음) 오류를 반환합니다. 예를 들어 https://localhost:5001/Movies/Details는 404 오류를 반환합니다. ID를 옵션으로 설정하려면 경로 제약 조건에 ?를 추가합니다.

@page "{id:int?}"

@page "{id:int?}"의 동작을 테스트합니다.

  1. Pages/Movies/Details.cshtml에서 page 지시문을 @page "{id:int?}"로 설정합니다.
  2. public async Task<IActionResult> OnGetAsync(int? id)에서, Pages/Movies/Details.cshtml.cs에서 중단점을 설정합니다.
  3. https://localhost:5001/Movies/Details/으로 이동합니다.

@page "{id:int}" 지시문을 사용하면 중단점에 도달하지 않습니다. 라우팅 엔진은 HTTP 404를 반환합니다. OnGetAsync 메서드는 @page "{id:int?}"를 사용하여 NotFound(HTTP 404)를 반환합니다.

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

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

동시성 예외 처리 검토

파일의 OnPostAsync 메서드를 검토합니다.Pages/Movies/Edit.cshtml.cs

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

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

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

    return RedirectToPage("./Index");
}

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

한 클라이언트가 영화를 삭제하고 다른 클라이언트가 영화 변경 내용을 게시하는 경우 위의 코드는 동시성 예외를 검색합니다.

catch 블록을 테스트하려면:

  1. catch (DbUpdateConcurrencyException)에서 중단점을 설정합니다.
  2. 동영상에 대한 편집을 선택하고, 변경하지만 저장을 입력하지 않습니다.
  3. 다른 브라우저 창에서 동일한 동영상에 대한 삭제 링크를 선택한 다음 동영상을 삭제합니다.
  4. 이전 브라우저 창에서 동영상에 변경 내용을 게시합니다.

프로덕션 코드는 동시성 충돌을 검색할 수 있습니다. 자세한 내용은 동시성 충돌 처리를 참조하세요.

검토 게시 및 바인딩

Pages/Movies/Edit.cshtml.cs 파일을 검사합니다.

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; } = default!;

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

        var movie =  await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
        if (movie == null)
        {
            return NotFound();
        }
        Movie = movie;
        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();
        }

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

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

        return RedirectToPage("./Index");
    }

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

Movies/Edit 페이지(예: https://localhost:5001/Movies/Edit/3)에 HTTP GET 요청을 하는 경우:

  • OnGetAsync 메서드는 데이터베이스에서 동영상을 가져오고 Page 메서드를 반환합니다.
  • Page 메서드는 Pages/Movies/Edit.cshtmlRazor 페이지를 렌더링합니다. 파일에는 Pages/Movies/Edit.cshtml 페이지에서 영화 모델을 사용할 수 있도록 하는 모델 지시문 @model RazorPagesMovie.Pages.Movies.EditModel이 포함되어 있습니다.
  • 편집 양식은 동영상에서 값으로 표시됩니다.

동영상/편집 페이지가 게시될 때:

  • 페이지에서 양식 값은 Movie 속성으로 바인딩됩니다. [BindProperty] 특성은 모델 바인딩을 활성화합니다.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • 모델 상태에 오류가 있는 경우(예: ReleaseDate를 날짜로 변환할 수 없는 경우) 양식은 제출된 값으로 다시 표시됩니다.

  • 모델 오류가 없는 경우 동영상이 저장됩니다.

인덱스, 만들기 및 삭제 Razor Pages의 HTTP GET 메서드는 유사한 패턴을 따릅니다. 만들기 Razor 페이지에서 HTTP POST OnPostAsync 메서드는 편집 Razor 페이지의 OnPostAsync 메서드와 유사한 패턴을 따릅니다.

다음 단계

스캐폴드된 동영상 앱을 사용하는 것이 좋지만 프레젠테이션은 이상적이지 않습니다. ReleaseDate는 두 단어, Release Date여야 합니다.

크롬에서 열린 동영상 애플리케이션

생성된 코드 업데이트

다음 강조 표시된 코드로 업데이트 Models/Movie.cs 합니다.

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

namespace RazorPagesMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; } = string.Empty;

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

        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }
    }
}

위의 코드에서:

  • [Column(TypeName = "decimal(18, 2)")] 데이터 주석을 사용하면 Entity Framework Core에서 Price를 데이터베이스의 통화에 올바르게 매핑할 수 있습니다. 자세한 내용은 데이터 형식을 참조하세요.
  • Display 특성은 필드의 표시 이름을 지정합니다. 위의 코드에서는 "ReleaseDate" 대신 "Release Date"입니다.
  • DataType 특성은 데이터 형식(Date)을 지정합니다. 필드에 저장된 시간 정보는 표시되지 않습니다.

다음 자습서에서 DataAnnotations를 다룹니다.

Pages/Movies로 이동하고 편집 링크로 마우스를 가져가 대상 URL을 확인합니다.

브라우저 창에서 편집 링크에 마우스를 가져가면 https://localhost:1234/Movies/Edit/5의 링크 Url이 표시됩니다.

편집, 세부 정보삭제 링크는 Pages/Movies/Index.cshtml 파일에서 앵커 태그 도우미에 의해 생성됩니다.

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

태그 도우미를 사용하면 Razor 파일에서 HTML 요소를 만들고 렌더링하는 데 서버 쪽 코드를 사용할 수 있습니다.

위의 코드에서 앵커 태그 도우미는 Razor 페이지(경로는 상대적), asp-page, 경로 ID(asp-route-id)에서 HTML href 속성 값을 동적으로 생성합니다. 자세한 내용은 페이지 URL 생성을 참조하세요.

브라우저에서 소스 보기를 사용하여 생성된 태그를 검사합니다. 생성된 HTML의 일부는 다음과 같습니다.

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

동적으로 생성된 링크는 쿼리 문자열이 포함된 동영상 ID를 전달합니다. https://localhost:5001/Movies/Details?id=1?id=1이 그 예입니다.

경로 템플릿 추가

{id:int} 경로 템플릿을 사용하도록 편집, 세부 정보 및 삭제 Razor Pages를 업데이트합니다. 이러한 각 페이지에 대한 page 지시문을 @page에서 @page "{id:int}"로 변경합니다. 앱을 실행한 다음 소스를 봅니다.

생성된 HTML에서 URL의 경로 부분에 ID를 추가합니다.

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

정수를 포함하지 않는 {id:int} 경로 템플릿이 있는 페이지에 대한 요청은 HTTP 404(찾을 수 없음) 오류를 반환합니다. 예를 들어 https://localhost:5001/Movies/Details는 404 오류를 반환합니다. ID를 옵션으로 설정하려면 경로 제약 조건에 ?를 추가합니다.

@page "{id:int?}"

@page "{id:int?}"의 동작을 테스트합니다.

  1. Pages/Movies/Details.cshtml에서 page 지시문을 @page "{id:int?}"로 설정합니다.
  2. public async Task<IActionResult> OnGetAsync(int? id)에서, Pages/Movies/Details.cshtml.cs에서 중단점을 설정합니다.
  3. https://localhost:5001/Movies/Details/으로 이동합니다.

@page "{id:int}" 지시문을 사용하면 중단점에 도달하지 않습니다. 라우팅 엔진은 HTTP 404를 반환합니다. OnGetAsync 메서드는 @page "{id:int?}"를 사용하여 NotFound(HTTP 404)를 반환합니다.

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

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

동시성 예외 처리 검토

파일의 OnPostAsync 메서드를 검토합니다.Pages/Movies/Edit.cshtml.cs

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

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

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.ID))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
  return (_context.Movie?.Any(e => e.ID == id)).GetValueOrDefault();
}

한 클라이언트가 영화를 삭제하고 다른 클라이언트가 영화 변경 내용을 게시하는 경우 위의 코드는 동시성 예외를 검색합니다. 이전 코드는 동일한 동영상을 동시에 편집하는 두 개 이상의 클라이언로 인해 발생하는 충돌을 감지하지 않습니다. 이 경우 여러 클라이언트의 편집은 SaveChanges가 호출되는 순서대로 적용되며 나중에 적용되는 편집은 이전 편집을 부실 값으로 덮어쓸 수 있습니다.

catch 블록을 테스트하려면:

  1. catch (DbUpdateConcurrencyException)에서 중단점을 설정합니다.
  2. 동영상에 대한 편집을 선택하고, 변경하지만 저장을 입력하지 않습니다.
  3. 다른 브라우저 창에서 동일한 동영상에 대한 삭제 링크를 선택한 다음 동영상을 삭제합니다.
  4. 이전 브라우저 창에서 동영상에 변경 내용을 게시합니다.

프로덕션 코드는 동시에 엔터티를 편집하는 여러 클라이언트와 같은 추가 동시성 충돌을 검색할 수 있습니다. 자세한 내용은 동시성 충돌 처리를 참조하세요.

검토 게시 및 바인딩

Pages/Movies/Edit.cshtml.cs 파일을 검사합니다.

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; } = default!;

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

        var movie =  await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);
        if (movie == null)
        {
            return NotFound();
        }
        Movie = movie;
        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();
        }

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

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
      return (_context.Movie?.Any(e => e.ID == id)).GetValueOrDefault();
    }

Movies/Edit 페이지(예: https://localhost:5001/Movies/Edit/3)에 HTTP GET 요청을 하는 경우:

  • OnGetAsync 메서드는 데이터베이스에서 동영상을 가져오고 Page 메서드를 반환합니다.
  • Page 메서드는 Pages/Movies/Edit.cshtmlRazor 페이지를 렌더링합니다. 파일에는 Pages/Movies/Edit.cshtml 페이지에서 영화 모델을 사용할 수 있도록 하는 모델 지시문 @model RazorPagesMovie.Pages.Movies.EditModel이 포함되어 있습니다.
  • 편집 양식은 동영상에서 값으로 표시됩니다.

동영상/편집 페이지가 게시될 때:

  • 페이지에서 양식 값은 Movie 속성으로 바인딩됩니다. [BindProperty] 특성은 모델 바인딩을 활성화합니다.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • 모델 상태에 오류가 있는 경우(예: ReleaseDate를 날짜로 변환할 수 없는 경우) 양식은 제출된 값으로 다시 표시됩니다.

  • 모델 오류가 없는 경우 동영상이 저장됩니다.

인덱스, 만들기 및 삭제 Razor Pages의 HTTP GET 메서드는 유사한 패턴을 따릅니다. 만들기 Razor 페이지에서 HTTP POST OnPostAsync 메서드는 편집 Razor 페이지의 OnPostAsync 메서드와 유사한 패턴을 따릅니다.

다음 단계

스캐폴드된 동영상 앱을 사용하는 것이 좋지만 프레젠테이션은 이상적이지 않습니다. ReleaseDate는 두 단어, Release Date여야 합니다.

크롬에서 열린 동영상 애플리케이션

생성된 코드 업데이트

Models/Movie.cs 파일을 열고 다음 코드에 표시된 강조 표시된 줄을 추가합니다.

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

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

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

        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }
    }
}

위의 코드에서:

  • [Column(TypeName = "decimal(18, 2)")] 데이터 주석을 사용하면 Entity Framework Core에서 Price를 데이터베이스의 통화에 올바르게 매핑할 수 있습니다. 자세한 내용은 데이터 형식을 참조하세요.
  • Display 특성은 필드의 표시 이름을 지정합니다. 위의 코드에서는 "ReleaseDate" 대신 "Release Date"입니다.
  • DataType 특성은 데이터 형식(Date)을 지정합니다. 필드에 저장된 시간 정보는 표시되지 않습니다.

다음 자습서에서 DataAnnotations를 다룹니다.

Pages/Movies로 이동하고 편집 링크로 마우스를 가져가 대상 URL을 확인합니다.

브라우저 창에서 편집 링크에 마우스를 가져가면 https://localhost:1234/Movies/Edit/5의 링크 Url이 표시됩니다.

편집, 세부 정보삭제 링크는 Pages/Movies/Index.cshtml 파일에서 앵커 태그 도우미에 의해 생성됩니다.

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

태그 도우미를 사용하면 Razor 파일에서 HTML 요소를 만들고 렌더링하는 데 서버 쪽 코드를 사용할 수 있습니다.

위의 코드에서 앵커 태그 도우미는 Razor 페이지(경로는 상대적), asp-page, 경로 ID(asp-route-id)에서 HTML href 속성 값을 동적으로 생성합니다. 자세한 내용은 페이지 URL 생성을 참조하세요.

브라우저에서 소스 보기를 사용하여 생성된 태그를 검사합니다. 생성된 HTML의 일부는 다음과 같습니다.

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

동적으로 생성된 링크는 쿼리 문자열이 포함된 동영상 ID를 전달합니다. https://localhost:5001/Movies/Details?id=1?id=1이 그 예입니다.

경로 템플릿 추가

{id:int} 경로 템플릿을 사용하도록 편집, 세부 정보 및 삭제 Razor Pages를 업데이트합니다. 이러한 각 페이지에 대한 page 지시문을 @page에서 @page "{id:int}"로 변경합니다. 앱을 실행한 다음 소스를 봅니다.

생성된 HTML에서 URL의 경로 부분에 ID를 추가합니다.

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

정수를 포함하지 않는 {id:int} 경로 템플릿이 있는 페이지에 대한 요청은 HTTP 404(찾을 수 없음) 오류를 반환합니다. 예를 들어 https://localhost:5001/Movies/Details는 404 오류를 반환합니다. ID를 옵션으로 설정하려면 경로 제약 조건에 ?를 추가합니다.

@page "{id:int?}"

@page "{id:int?}"의 동작을 테스트합니다.

  1. Pages/Movies/Details.cshtml에서 page 지시문을 @page "{id:int?}"로 설정합니다.
  2. public async Task<IActionResult> OnGetAsync(int? id)에서, Pages/Movies/Details.cshtml.cs에서 중단점을 설정합니다.
  3. https://localhost:5001/Movies/Details/으로 이동합니다.

@page "{id:int}" 지시문을 사용하면 중단점에 도달하지 않습니다. 라우팅 엔진은 HTTP 404를 반환합니다. OnGetAsync 메서드는 @page "{id:int?}"를 사용하여 NotFound(HTTP 404)를 반환합니다.

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

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

동시성 예외 처리 검토

파일의 OnPostAsync 메서드를 검토합니다.Pages/Movies/Edit.cshtml.cs

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

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

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.ID))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
    return _context.Movie.Any(e => e.ID == id);
}

한 클라이언트가 영화를 삭제하고 다른 클라이언트가 영화 변경 내용을 게시하는 경우 위의 코드는 동시성 예외를 검색합니다.

catch 블록을 테스트하려면:

  1. catch (DbUpdateConcurrencyException)에서 중단점을 설정합니다.
  2. 동영상에 대한 편집을 선택하고, 변경하지만 저장을 입력하지 않습니다.
  3. 다른 브라우저 창에서 동일한 동영상에 대한 삭제 링크를 선택한 다음 동영상을 삭제합니다.
  4. 이전 브라우저 창에서 동영상에 변경 내용을 게시합니다.

프로덕션 코드는 동시성 충돌을 검색할 수 있습니다. 자세한 내용은 동시성 충돌 처리를 참조하세요.

검토 게시 및 바인딩

Pages/Movies/Edit.cshtml.cs 파일을 검사합니다.

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

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

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

        Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

        if (Movie == null)
        {
            return NotFound();
        }
        return Page();
    }

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

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

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
        return _context.Movie.Any(e => e.ID == id);
    }

Movies/Edit 페이지(예: https://localhost:5001/Movies/Edit/3)에 HTTP GET 요청을 하는 경우:

  • OnGetAsync 메서드는 데이터베이스에서 동영상을 가져오고 Page 메서드를 반환합니다.
  • Page 메서드는 Pages/Movies/Edit.cshtmlRazor 페이지를 렌더링합니다. 파일에는 Pages/Movies/Edit.cshtml 페이지에서 영화 모델을 사용할 수 있도록 하는 모델 지시문 @model RazorPagesMovie.Pages.Movies.EditModel이 포함되어 있습니다.
  • 편집 양식은 동영상에서 값으로 표시됩니다.

동영상/편집 페이지가 게시될 때:

  • 페이지에서 양식 값은 Movie 속성으로 바인딩됩니다. [BindProperty] 특성은 모델 바인딩을 활성화합니다.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • 모델 상태에 오류가 있는 경우(예: ReleaseDate를 날짜로 변환할 수 없는 경우) 양식은 제출된 값으로 다시 표시됩니다.

  • 모델 오류가 없는 경우 동영상이 저장됩니다.

인덱스, 만들기 및 삭제 Razor Pages의 HTTP GET 메서드는 유사한 패턴을 따릅니다. 만들기 Razor 페이지에서 HTTP POST OnPostAsync 메서드는 편집 Razor 페이지의 OnPostAsync 메서드와 유사한 패턴을 따릅니다.

다음 단계