본문 바로가기
ASP.NET Core

ASP.NET Core Razor Pages] 2. Page Models, Routing, Filters

by Fastlane 2023. 2. 27.
728x90
반응형

출처 : ASP.NET Core - Simpler ASP.NET MVC Apps with Razor Pages | Microsoft Learn

A Sample Project

여러 종류의 entities를 관리하고 조회하는 sample 앱을 만들어보자. 전형적인 MVC 구조적 접근을 사용한다면, 다른 종류의 folder와 각각 controllers, views, viewmodels 등이 필요할 것이다. Razor Pages를 사용하면, URL 구조와 매핑되는 폴더 hierarchy만 만들면 된다. 

 

이 경우에, 앱은 Pages 폴더 하위에 각 entities를 위한 subfolder를 갖는다. 폴더 구조는 매우 단순하다. homepage(Index.cshtml)와 Pages folder의 root에서 몇가지 지원파일과 각 entities를 위한 subfolder이다. 

 

단순한 pages는 종종 각각 나뉜 page models이 필요하지 않다. 예를들어 ninja swords 리스트에서는 단순히 inline 변수를 사용한다. 

@page
@{ 
  var swords = new List<string>()
  {
    "Katana",
    "Ninjago"
  };
}
<h2>Ninja Swords</h2>
<ul>
  @foreach (var item in swords)
  {
    <li>@item</li>
  }
</ul>
<a asp-page="/Ninjas/Index">Ninja List</a>

Page Models

Razor Pages는 Strongly typed page models을 지원한다. MVC View와 동일하게 @model 지시어 사용한다. 

@page
@using WithRazorPages.Core.Interfaces;
@using WithRazorPages.Core.Model;
@model IndexModel
@functions
{
  public class IndexModel : PageModel
  {
    private readonly IRepository<Zombie> _zombieRepository;
       
    public IndexModel(IRepository<Zombie> zombieRepository)
    {
      _zombieRepository = zombieRepository;
    }
    // additional code omitted
  }
}

별도의 codebehind file인 Pagename.cshtml.cs파일에 page model을 정의할 수 있다. @function block안의 코드가 별도의 cs파일에 위치할 수 있다. 

 

page-model logic이 Razor Page에 있으면 파일 수가 줄고 runtime compilation의 유연성을 허용한다. 따라서 page의 로직을 app 배포없이 수정할 수 있다. 반면, Razor Pages에서 정의된 page models의 error는 runtime전까지 발견하지 어렵다. Visual Studio는 open Razor files의 error를 보여주지만, Running dotnet build command는 Razor Pages를 compile하지 않고 잠재 error에 대한 어떤 정보도 제공하지 않는다. 

 

Separate page-model class는 역할분리의 관점에서는 낫다. 또한 compile-time error checking과 unit test가 용이하다. 결과적으로 개발자가 no model, inline model, separate page models 중에서 선택하면 된다. 

 

Routing, Model Binding and Handlers

MVC Controller class의 2가지 특징은 routing과 model binding이다. 대부분의 ASP.NET Core MVC 앱은 routes, HTTP verbs, route parameters를 정의하기 위해 attributes를 사용한다. 

syntax는 다음과 같다. 

[HttpGet("{id}")]
public Task<IActionResult> GetById(int id)

Razor Pages의 route path는 /Pages 폴더 내부의 page 위치에 매칭된다. @page 지시어를 사용해서 route parameters를 지원할 수 있다. HTTP verb 설정을 위해서는 handlers를 사용한다. 

 

아래 코드에서 route paremeters, dependency injection, handler의 사용을 보여준다. 

@page "{id:int}"
@using WithRazorPages.Core.Interfaces;
@using WithRazorPages.Core.Model;
@inject IRepository<Ninja> _repository

@functions {
  public Ninja Ninja { get; set; }

  public IActionResult OnGet(int id)
  {
    Ninja = _repository.GetById(id);

    // A void handler (no return) is equivalent to return Page()
    return Page();
  }
}
<h2>Ninja: @Ninja.Name</h2>
<div>
    Id: @Ninja.Id
</div>
<div>
    <a asp-page="..">Ninja List</a>
</div>

한 페이지에서 multiple handlers 지원한다. OnGet, OnPost 등등 설정할 수 있다. Razor Page framework 관점에서 OnGet, OnGetAsync는 동일한 handler이다. 따라서 한 페이지에 OnGet, OnGetAsync 를 모두 가질 순 없다. OnGet에 parameter가 있고, OnGetAsync에 Parameter가 없더라도 말이다.

 

Razor Pages는 또한 새로운 model-binding attribute인 [BindProperty]를 소개한다. [BindProperty]는 특히, forms에서 유용하다. 그리고 asp-for, asp-validation-for와 같은 tag helpers를 사용할 수 있도록 한다. 

@page
@using WithRazorPages.Core.Interfaces;
@using WithRazorPages.Core.Model;
@inject IRepository<Plant> _repository

@functions {
  [BindProperty]
  public Plant Plant { get; set; }

  public IActionResult OnPost()
  {
    if(!ModelState.IsValid) return Page();

    _repository.Add(Plant);

    return RedirectToPage("./Index");
  }
}
<h1>New Plant</h1>
<form method="post" class="form-horizontal">
  <div asp-validation-summary="All" class="text-danger"></div>
  <div class="form-group">
    <label asp-for="Plant.Name" class="col-md-2 control-label"></label>
    <div class="col-md-10">
      <input asp-for="Plant.Name" class="form-control" />
      <span asp-validation-for="Plant.Name" class="text-danger"></span>
    </div>
  </div>
  <div class="form-group">
    <div class="col-md-offset-2 col-md-10">
      <button type="submit" class="btn btn-primary">Save</button>
    </div>
  </div>
</form>
<div>
  <a asp-page="./Index">Plant List</a>
</div>

하나의 verb를 사용하는 하나 이상의 함수를 정의할 수 있다.  method의 name은 OnPost, OnGet 뒤에 붙는다. 

@page   
@functions{
    public async Task OnGetAsync()
    {
      Ninjas = _ninjaRepository.List()
        .Select(n => new NinjaViewModel { Id = n.Id, Name = n.Name }).ToList();
    }

    public async Task<IActionResult> OnPostAddAsync()
    {
      var entity = new Ninja()
      {
        Name = "Random Ninja"
      };
    _  ninjaRepository.Add(entity);

      //RedirectToPage()함수를 사용하여 현재 Razor Page의 GET handler로 redirect할 수 있다. 
      return RedirectToPage();
    }

    public async Task<IActionResult> OnPostDeleteAsync(int id)
    {
      var entityToDelete = _ninjaRepository.GetById(id);
    _ ninjaRepository.Delete(entityToDelete);

      return RedirectToPage();
    }
}

<a asp-page-handler="Handler">Link Text</a>
<button type="submit" asp-page-handler="delete" asp-route-id="@id">Delete</button>

Filters

ASP.NET Core MVC에서 Filters는 또 다른 강력한 특징이다. 별도의 Page Model 파일을 사용한다면 attribute-based filter를 Razor Pages와 함께 사용할 수 있고, MVC 설정을 할때 global filters를 설정할 수 있다. 

 

이런 filters는 Action filters를 제외하고  Razor Pages에도 사용할 수 있다. Razor Pages는 새로운 Page filter(IPageFilter, IAsyncPageFilter)를 소개한다. 

Razor Pages에는 CSRF or XSRF 에 대한 보안처리가 되어 있어서 ValidationAntiforgeryToken filter는 필요하지 않다. 

Architectural Pattern

구조적으로 Razor Pages는 Controllers가 없기 때문에, Model-View-Controller (MVC) pattern을 따르지 않는다. Razor Pages는 오히려 Model-View-ViewModel (MVVM) pattern을 따른다. ASP.NET Web Forms과 달리 Razor Pages는 ASP.NET Core기반이고 loose coupling, separation of concerns, SOLIC principles을 지원한다. Razor Pages는 ASP.NET Core application을 폴더와 파일 수를 줄임으로 디자인 측면에서 개선할 수 있다. 

 

Migrating

Razor Pages가 MVC pattern을 따르지 않지만, 기존의 ASP.NET Core MVC 앱과 비슷하기 때문에, 쉽게 서로의 구조로 변경할 수 있다. 기존의 Controller/View-based page를 Razor Pages로 migration하기 위해서 아래의 순서를 따르면 된다. 

  1. Copy the Razor View file to the appropriate location in the /Pages folder.
  2. Add the @page directive to the View. If this was a GET-only View, you’re done.
  3. Add a PageModel file named viewname.cshtml.cs and place it in the folder with the Razor Page.
  4. If the View had a ViewModel, copy it to a PageModel file.
  5. Copy any actions associated with the view from its Controller to the PageModel class.
  6. Rename the actions to use the Razor Pages handler syntax (for example, “OnGet”).
  7. Replace references to View helper methods with Page methods.
  8. Copy any constructor dependency injection code from the Controller to the PageModel.
  9. Replace code-passing model to views with a [BindProperty] property on the PageModel.
  10. Replace action method parameters accepting view model objects with a [BindProperty] property, as well.
728x90
반응형

댓글