Razor Pages – podstawy

Świeże spojrzenie na stare koncepcje

Piotr Wandycz
Narzędzia

“Uproszczona wersja MVC” – tak w trzech słowach opisalibyśmy istotę Razor Pages. Jednak jest to definicja zbyt ogólna, oraz bardzo krzywdząca. Chociaż w słowniku znaleźć możemy i gorsze epitety. Bawiąc się w małe szkalowanko – posłużylibyśmy się nazwą “Współczesne Web Formsy”. Razor Pages nie jest ani jednym, ani drugim. To po prostu kolejna ewolucja, zamiast rewolucji. Każdy, kto napisał cokolwiek w MVC lub Web Formsach, powinien czuć się swobodnie, pracując z wykorzystaniem Razor Pages. A prawdopodobnie najszybciej odnajdą się tu osoby mające styczność z WPF / Windows Phone.

Minimum teorii

Dzieje się tak z prostego powodu – całość przypomina bardziej MVVM, niż MVC. Jeśli słowo XAML jest Ci znajome to dobrze trafiłeś/-aś. Operujemy tu na stronach (Page), a na każdą z nich składają sie dwa pliki:

  • widok – standardowy plik Razora z rozszerzeniem .cshtml, np. Index.cshtml
  • code-behind – plik C#, który powinien nazywać się tak jak widok + .cs, np. Index.cshtml.cs

Dzięki takiej konwencji nazewnictwa Visual Studio pozwala na ładne zwijanie i całość wygląda jak jeden plik. Konfiguracja od .Net Core 3.1 w najprostszej wersji jest bardzo krótka. Przykładowy plik Startup.cs wygląda następująco:

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

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
    }
    app.UseStaticFiles();
    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Wcześniejsze wersje frameworka jawnie wskazywały na wykorzystywanie MVC. W najnowszej zostało to ukryte i zamiast pisać AddMvc() posługujemy się metodą AddRazorPages().

Widok (View)

Poniżej zamieściłem dwa kawałki kodu. Oba z prostego CRUDa, a dokładniej ze strony odpowiedzialnej za aktualizacje jednej tabelki w bazie danych. Zacznijmy od tego, co widzi użytkownik. Od aplikacji MVC różni go chyba jedynie pierwsza linia kodu:

@page "{Id:int}"
@model Interview.Question.Update.IndexViewModel
@{
    ViewData["Title"] = "Edytuj pytanie";
}

<h4>@ViewData["Title"]</h4>

<div class="row d-flex justify-content-center">
    <div class="col-md-4">
        <section>
            <form method="post">
                <div asp-validation-summary="All" class="text-danger"></div>
                <label asp-for="Data.Question.CategoryName"></label>
                <select name="Data.Question.CategoryId" asp-items="Model.Data.Categories"></select>
                <input asp-for="Data.Question.Id" />
                <div class="form-group">
                    <label asp-for="Data.Question.Content"></label>
                    <input asp-for="Data.Question.Content" class="form-control" />
                    <span asp-validation-for="Data.Question.Content" class="text-danger"></span>
                </div>
                <div class="form-group">
                    <label asp-for="Data.Question.Answer"></label>
                    <input asp-for="Data.Question.Answer" class="form-control" />
                </div>
                <div class="center">
                    <button type="submit" name="edit" class="btn btn-primary">Zmień</button>
                </div>
            </form>
        </section>
    </div>
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

Patrząc od góry:

  • 1 – schemat routingu, spodziewamy się parametru typu int o nazwie Id
  • 2 – wskazujemy na ViewModel, aby z jego polami zrobić Model Binding
  • 14-16 to przykładowe użycie pól z Modelu. To w tych zmiennych otrzymamy wartości wpisane przez użytkownika.

Code-behind (ViewModel)

Tutaj sprawy mają się zdecydowanie ciekawiej. Aby zaprzęgnąć technologię do pracy, musimy podziedziczyć po PageModel (linijki 3 i 7). Do tego, by otrzymać wpisane przez użytkownika informacje, wykorzystujemy Model Binding zachodzący w liniach: 2, 16-17.

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

namespace Teacher.Website.Feature.Interview.Question.Update
{
    public class IndexViewModel : PageModel
    {
        private readonly IPageFacade _facade;

        public IndexViewModel(IPageFacade facade)
        {
            _facade = facade;
        }

        [BindProperty]
        public Model Data { get; set; }

        public async Task OnGetAsync(Query query)
        {
            Data = await _facade.OnGetAsync(query);
        }

        public async Task<IActionResult> OnPostAsync()
        {
            return await _facade.OnPostAsync(new Command { Question = Data.Question });
        }
    }
}

Najlepsze zostawiłem na sam koniec. Linie 19 i 24 – metoda wywoływania przy wejściu do strony, oraz przy wysyłaniu formularza. Opiera się to na konwencji On + czasownik (HTTP verb). Async w nazwie jest opcjonalne, pomaga jedynie utrzymać lepszą czytelność. Gdyby zaszła potrzeba wyświetlenia dodatkowego formularza, to możemy mu dopisać w HTML właściwość z jego nazwą asp-page-handler=”Dodatkowy”, a następnie dodać metodę w code-behind OnPostDodatkowyAsync(). Jednak osobiście wolę odsyłać do innej strony, zamiast obsługiwać dwa formularze na tej samej.

Zagrożenia

Razor Pages nie jest panaceum na wszystkie problemy programowania aplikacji internetowych w .NET. Przede wszystkim musisz pilnować kodu ściśle związanego z tym, co widzi użytkownik lub z obsługą żądań HTTP, a faktycznym kodem logiki aplikacji. Puchnące kontrolery z MVC mogą przenieść się na puchnące strony Razora. Ja do podziału obowiązków wprowadziłem dodatkową warstwę – widoczną na kawałkach kodu powyżej IPageFacade. Kod odpowiedzialny za webówkę (np. Claimsy) znajduje się w code-behind strony, a realna logika siedzi trochę głębiej – w implementacji interfejsu fasady strony. Dodatkową zaletą tego rozwiązania jest ułatwienie sobie testowania, ale o tym opowiem innym razem.

Prawdopodobnie w przyszłości będę opisywał trochę więcej aspektów tej technologii. Nawet kiedy planujesz pozostać przy MVC, to jeśli chcesz dalej korzystać z Microsoft Identity do zarządzania użytkownikami – spotkasz się z Razor Pages. Mi całość świetnie skomponowała mi się z Vertical Slice Architecture i feature folderami. A gdybyś chciał/-a już dziś zacząć z Razor Pages, to zachęcam Cię do odwiedzenia dobrej dokumentacji pod adresem https://www.learnrazorpages.com/

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *