Wprowadzenie do rozwiązania Razor Pages na platformie ASP.NET Core

Autorzy: Rick Anderson, Dave Brock i Kirk Larkin

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

Ważne

Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.

Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

W scenariuszach opartych na stronach rozwiązanie Razor Pages może zwiększyć wydajność i łatwość programowania w porównaniu z używaniem kontrolerów i widoków.

Jeśli szukasz samouczka, w którym wykorzystano podejście Model-View-Controller, zobacz Wprowadzenie do podejścia MVC na platformie ASP.NET Core.

Ten dokument zawiera wprowadzenie do rozwiązania Razor Pages. Nie jest to samouczek krok po kroku. Jeśli okaże się, że niektóre sekcje są zbyt zaawansowane, zobacz Wprowadzenie do rozwiązaniaRazor Pages. Aby zapoznać się z omówieniem platformy ASP.NET Core, zobacz Wprowadzenie do platformy ASP.NET Core.

Wymagania wstępne

Tworzenie projektu rozwiązania Razor Pages

Zobacz temat Wprowadzenie do rozwiązania Razor Pages, aby uzyskać szczegółowe instrukcje dotyczące tworzenia projektu za pomocą rozwiązania Razor Pages.

Razor Pages

Rozwiązanie Razor Pages można włączyć w pliku 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();

Powyższy kod:

Rozważ stronę podstawową:

@page

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

Powyższy kod w dużym stopniu przypomina plik widoku Razor używany w aplikacji platformy ASP.NET Core z kontrolerami i widokami. Różni się dyrektywą @page. Dyrektywa @page zamienia plik w działanie MVC, co oznacza, że obsługuje żądania bezpośrednio, bez udziału kontrolera. Dyrektywa @page musi być pierwszą dyrektywą Razor na stronie. Dyrektywa @page wpływa na działanie innych konstrukcji Razor. Nazwy plików rozwiązania Razor Pages mają sufiks .cshtml.

Podobna strona, korzystająca z klasy PageModel, jest pokazana w następujących dwóch plikach. Plik Pages/Index2.cshtml:

@page
@using RazorPagesIntro.Pages
@model Index2Model

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

Model strony 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 }";
        }
    }
}

Zgodnie z konwencją plik klasy PageModel ma taką samą nazwę jak plik strony Razor z dodanym rozszerzeniem .cs. Na przykład poprzednia strona Razor to Pages/Index2.cshtml. Plik zawierający klasę PageModel nosi nazwę Pages/Index2.cshtml.cs.

Skojarzenia ścieżek URL ze stronami są określane przez lokalizację strony w systemie plików. W poniższej tabeli przedstawiono ścieżkę strony Razor i odpowiadający jej adres URL:

Nazwa i ścieżka pliku Odpowiedni adres URL
/Pages/Index.cshtml / lub /Index
/Pages/Contact.cshtml /Contact
/Pages/Store/Contact.cshtml /Store/Contact
/Pages/Store/Index.cshtml /Store lub /Store/Index

Uwagi:

  • Środowisko uruchomieniowe domyślnie wyszukuje pliki rozwiązania Razor Pages w folderze Pages.
  • Index to strona domyślna, jeśli adres URL nie zawiera strony.

Pisanie podstawowego formularza

Rozwiązanie Razor Pages zostało stworzone, aby ułatwić wdrożenie typowych wzorców używanych w przeglądarkach internetowych podczas tworzenia aplikacji. Powiązanie modelu, pomocnicy tagów i pomocnicy HTML działają przy użyciu właściwości zdefiniowanych w klasie strony Razor. Rozważ stronę, w której wdrożony będzie podstawowy formularz kontaktowy w przypadku modelu Contact:

W przypadku przykładów w tym dokumencie DbContext plik jest inicjowany w pliku Program.cs .

Baza danych w pamięci wymaga pakietu NuGet Microsoft.EntityFrameworkCore.InMemory.

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();

Model danych:

using System.ComponentModel.DataAnnotations;

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

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

Kontekst bazy danych:

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>();
    }
}

Plik widoku 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>

Model strony 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");
    }
}

Zgodnie z konwencją klasa PageModel nazywa się <PageName>Model i znajduje się w tej samej przestrzeni nazw co strona.

Klasa PageModel umożliwia oddzielenie logiki strony od jej prezentacji. Definiuje procedury obsługi stron dla żądań wysyłanych do strony oraz dla danych używanych do renderowania strony. To rozdzielenie umożliwia:

Strona posiada metodę procedury obsługiOnPostAsync, która jest uruchamiana w przypadku żądań POST (gdy użytkownik opublikuje formularz). Można dodawać metody procedury obsługi dla dowolnego czasownika HTTP. Najczęstsze procedury obsługi to:

  • OnGet — na potrzeby inicjowania stanu wymaganego dla strony. W poprzednim kodzie metoda OnGet spowodowała wyświetlenie strony CreateModel.cshtmlRazor.
  • OnPost — na potrzeby obsługi przesłanych formularzy.

Sufiks nazewnictwa Async jest opcjonalny, ale jest często używany zgodnie z konwencją na potrzeby funkcji asynchronicznych. Powyższy kod jest typowy dla rozwiązania Razor Pages.

Jeśli znasz aplikacje ASP.NET korzystające z kontrolerów i widoków:

  • Kod OnPostAsync w poprzednim przykładzie wygląda podobnie do typowego kodu kontrolera.
  • Większość typów pierwotnych MVC, takich jak powiązanie modelu, walidacja i wyniki akcji działają tak samo w przypadku kontrolerów i rozwiązania Razor Pages.

Poprzednia metoda 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");
}

Podstawowy przepływ metody OnPostAsync:

Sprawdź, czy wystąpiły błędy walidacji.

  • Jeśli nie ma żadnych błędów, zapisz dane i przeprowadź przekierowanie.
  • Jeśli wystąpią jakieś błędy, pokaż stronę ponownie z komunikatami walidacji. W wielu przypadkach błędy weryfikacji zostaną wykryte w kliencie i nigdy nie zostaną przesłane do serwera.

Plik widoku 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>

Renderowany kod HTML z pliku Pages/Customers/Create.cshtml:

<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>

W poprzednim kodzie w przypadku opublikowania formularza:

  • Z prawidłowymi danymi:

    • Metoda procedury obsługi OnPostAsync wywołuje metodę pomocnika RedirectToPage. Metoda RedirectToPage zwraca wystąpienie klasy RedirectToPageResult. RedirectToPage:

      • Jest wynikiem akcji.
      • Przypomina metodę RedirectToAction lub RedirectToRoute (używaną w kontrolerach i widokach).
      • Jest dostosowana pod kątem stron. W poprzednim przykładzie nastąpiło przekierowanie do głównej strony indeksu (/Index). Metoda RedirectToPage jest opisana szczegółowo w sekcji dotyczącej generowania adresu URL w rozwiązaniu Pages.
  • Z błędami walidacji, które są przekazywane do serwera:

    • Metoda procedury obsługi OnPostAsync wywołuje metodę pomocnika Page. Metoda Page zwraca wystąpienie klasy PageResult. Zwrócenie elementu Page wygląda podobnie jak zwrócenie elementu View przez akcje w kontrolerach. Element PageResult jest domyślnym zwracanym typem w przypadku metody procedury obsługi. Metoda procedury obsługi, która zwraca wartość void, renderuje stronę.
    • W poprzednim przykładzie opublikowanie formularza bez żadnych wartości spowodowało, że właściwość ModelState.IsValid zwróciła wartość false. W tym przykładzie po stronie klienta nie są wyświetlane żadne błędy walidacji. Obsługa błędów walidacji jest omówiona w dalszej części tego dokumentu.
    [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");
    }
    
  • W przypadku wykrycia błędów walidacji po stronie klienta:

    • Dane nie są publikowane na serwerze.
    • Walidacja po stronie klienta zostanie wyjaśniona w dalszej części tego dokumentu.

Do wyrażenia zgody na powiązanie modelu właściwość Customer wykorzystuje atrybut [BindProperty]:

[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");
}

Właściwość [BindProperty]nie powinna być używana w modelach zawierających właściwości, które nie powinny być zmieniane przez klienta. Aby uzyskać więcej informacji, zobacz Przesyłanie dodatkowych danych.

Rozwiązanie Razor Pages domyślnie wiąże właściwości wyłącznie z czasownikami innymi niż GET. Powiązanie z właściwościami eliminuje konieczność pisania kodu w celu przekonwertowania danych HTTP na typ modelu. Powiązanie redukuje kod poprzez użycie tej samej właściwości do renderowania pól formularza (<input asp-for="Customer.Name">) i akceptowania danych wejściowych.

Ostrzeżenie

Ze względów bezpieczeństwa należy wyrazić zgodę na powiązanie danych żądania GET z właściwościami modelu strony. Przed mapowaniem danych wejściowych użytkownika na właściwości zweryfikuj je. Wyrażenie zgody na powiązanie danych GET jest przydatne w scenariuszach, które wykorzystują ciągi zapytania lub wartości trasy.

Aby powiązać właściwość w przypadku żądań GET, ustaw właściwość SupportsGet atrybutu [BindProperty] na true:

[BindProperty(SupportsGet = true)]

Aby uzyskać więcej informacji, zobacz Podsumowanie ASP.NET Core Community Standup: dyskusja na temat powiązania z użyciem metody GET (YouTube).

Przegląd pliku widoku 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>
  • W poprzednim kodzie pomocnik tagu wejściowego<input asp-for="Customer.Name" /> wiąże element kodu HTML <input> z wyrażeniem modelu Customer.Name.
  • Dyrektywa @addTagHelper udostępnia pomocników tagów.

Strona główna

Index.cshtml to strona główna:

@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>

Powiązana klasa 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();
    }
}

Plik Index.cshtml zawiera następujące znaczniki:

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

Pomocnik tagu kotwicy <a /a> użył atrybutu asp-route-{value} do wygenerowania linku do strony edytowania. Link zawiera dane trasy z identyfikatorem kontaktu. Na przykład https://localhost:5001/Edit/1. Pomocnicy tagów umożliwiają kodowi po stronie serwera uczestniczenie w tworzeniu i renderowaniu elementów HTML w plikach Razor.

Plik Index.cshtml zawiera znaczniki umożliwiające tworzenie przycisku usuwania dla każdego kontaktu klienta:

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

Renderowany kod HTML:

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

Gdy przycisk usuwania jest renderowany w formacie HTML, jego akcja formularza zawiera następujące parametry:

  • Identyfikator kontaktu klienta określony przez atrybut asp-route-id.
  • Element handler określony przez atrybut asp-page-handler.

Po wybraniu przycisku żądanie formularza POST jest wysyłane do serwera. Zgodnie z konwencją nazwa metody procedury obsługi jest wybierana na podstawie wartości parametru handler zgodnie ze schematem OnPost[handler]Async.

Ponieważ procedura obsługi handler w tym przykładzie to delete, metoda procedury obsługi OnPostDeleteAsync jest używana do przetworzenia żądania POST. Jeśli procedura obsługi asp-page-handler zostanie ustawiona na inną wartość, na przykład remove, zostanie wybrana metoda procedury obsługi o nazwie 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();
}

Metoda OnPostDeleteAsync:

  • Pobiera identyfikator id z ciągu zapytania.
  • Tworzy zapytanie w bazie danych dotyczące kontaktu klienta przy użyciu metody FindAsync.
  • Jeśli kontakt klienta zostanie odnaleziony, zostaje usunięty, a baza danych zostaje zaktualizowana.
  • Tworzy wywołanie RedirectToPage w celu przekierowania do głównej strony indeksu (/Index).

Plik Edit.cshtml file

@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");}
}

Pierwszy wiersz zawiera dyrektywę @page "{id:int}". Ograniczenie routingu "{id:int}" instruuje stronę, aby akceptowała żądania do strony zawierające dane trasy int. Jeśli żądanie do strony nie zawiera danych trasy, które można przekonwertować na element int, środowisko uruchomieniowe zwraca kod błędu HTTP 404 (nie znaleziono). Aby określić identyfikator jako opcjonalny, dołącz kod ? do ograniczenia trasy:

@page "{id:int?}"

Plik 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);
    }
}

Walidacja

Reguły walidacji:

  • Są deklaratywnie określone w klasie modelu.
  • Są wymuszane w każdym miejscu w aplikacji.

Przestrzeń nazw System.ComponentModel.DataAnnotations zapewnia zestaw wbudowanych atrybutów walidacji, które są stosowane deklaratywnie wobec klasy lub właściwości. Przestrzeń nazw DataAnnotations zawiera również atrybuty formatowania, takie jak [DataType], które pomagają w formatowaniu i nie zapewniają żadnej walidacji.

Rozważmy model Customer:

using System.ComponentModel.DataAnnotations;

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

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

W przypadku użycia następującego pliku widoku 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>

Powyższy kod ma następujące działanie:

  • Obejmuje skrypty walidacji jQuery i jQuery.

  • Korzysta z pomocników tagów<div /> i <span /> w celu umożliwienia:

    • Walidacji po stronie klienta.
    • Renderowania błędów walidacji.
  • Generuje następujący kod 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>
    

Opublikowanie formularza tworzenia bez wartości nazwy powoduje wyświetlenie komunikatu o błędzie „Pole nazwy jest wymagane” w formularzu. Jeśli po stronie klienta włączono język JavaScript, błąd zostanie wyświetlony w przeglądarce bez publikowania na serwerze.

Atrybut [StringLength(10)] generuje element data-val-length-max="10" w renderowanym kodzie HTML. Element data-val-length-max uniemożliwia przeglądarkom wprowadzanie większej liczby znaków niż określono. Jeśli do edytowania i powtarzania publikowania używane jest narzędzie takie jak Fiddler:

  • W przypadku nazwy dłuższej niż 10 znaków.
  • Zwracany jest komunikat o błędzie „Pole Nazwa musi być ciągiem o maksymalnej długości 10 znaków”.

Rozważ następujący model 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; }
    }
}

Atrybuty walidacji określają działanie wymuszane wobec właściwości modelu, do których są stosowane:

  • Atrybuty Required i MinimumLength wskazują, że właściwość musi mieć wartość, ale nic nie uniemożliwia użytkownikowi wprowadzania białych znaków w celu spełnienia zasad tej walidacji.

  • Atrybut RegularExpression służy do ograniczania liczby znaków, które mogą być wprowadzane jako dane wejściowe. W poprzednim kodzie w polu „Genre” (Gatunek):

    • Muszą być używane tylko litery.
    • Pierwsza litera musi być wielką literą. Białe znaki, liczby i znaki specjalne nie są dozwolone.
  • W atrybucie RegularExpression „Rating” (Klasyfikacja):

    • Pierwszy znak musi być wielką literą.
    • Kolejne znaki mogą być znakami specjalnymi i cyframi. W atrybucie Rating wartość „PG-13” jest prawidłowa, ale w przypadku atrybutu Genre — nie.
  • Atrybut Range ogranicza wartość do określonego zakresu.

  • Atrybut StringLength wyznacza maksymalną długość właściwości ciągu oraz opcjonalnie jego minimalną długość.

  • Typy wartości (takie jak decimal, int, float, DateTime) są z natury wymagane i nie wymagają atrybutu [Required].

Na stronie tworzenia w przypadku modelu Movie wyświetlane są błędy z nieprawidłowymi wartościami:

Formularz widoku Movie (Film) z wieloma błędami walidacji jQuery po stronie klienta

Aby uzyskać więcej informacji, zobacz:

Izolacja CSS

Izolowanie stylu CSS do pojedynczych stron, widoków i składników umożliwia zmniejszenie lub uniknięcie:

  • Zależności stylów globalnych, które mogą być trudne do utrzymania.
  • Konfliktów stylów w zawartości zagnieżdżonej.

Aby dodać plik CSS o określonym zakresie dla strony lub widoku, umieść style CSS w pliku towarzyszącym .cshtml.css pasującym do nazwy pliku .cshtml. W poniższym przykładzie plik Index.cshtml.css dostarcza style CSS, które są stosowane tylko w przypadku strony lub widoku Index.cshtml.

Pages/Index.cshtml.css (Razor Pages) lub Views/Index.cshtml.css (MVC):

h1 {
    color: red;
}

Izolacja stylów CSS ma miejsce w czasie kompilacji. Struktura ponownie zapisuje selektory CSS, tak aby były zgodne ze znacznikami renderowanymi przez strony lub widoki aplikacji. Ponownie napisane style CSS są umieszczane w pakiecie i przedstawiane jako zasób statyczny {APP ASSEMBLY}.styles.css. Symbol zastępczy {APP ASSEMBLY} to nazwa zestawu projektu. Link do stylów CSS w pakiecie jest umieszczany w układzie aplikacji.

W zawartości tagu <head> aplikacji Pages/Shared/_Layout.cshtml (Razor Pages) lub Views/Shared/_Layout.cshtml (MVC) dodaj lub potwierdź obecność linku do stylów CSS w pakiecie:

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

W poniższym przykładzie nazwa zestawu aplikacji to WebApp:

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

Style zdefiniowane w pliku CSS o określonym zakresie są stosowane tylko w przypadku renderowanych danych wyjściowych pasującego pliku. W poprzednim przykładzie wszystkie deklaracje CSS h1 zdefiniowane w innym miejscu w aplikacji nie powodują konfliktu ze stylem nagłówka strony Index. Reguły dziedziczenia i kaskadowania stylu CSS pozostają aktualne w przypadku plików CSS o określonym zakresie. Na przykład style zastosowane bezpośrednio do elementu <h1> w pliku Index.cshtml zastępują style pliku CSS o określonym zakresie w pliku Index.cshtml.css.

Uwaga

Aby zagwarantować izolację stylu CSS podczas tworzenia pakietu, nie jest obsługiwane importowanie pliku CSS w blokach kodu Razor.

Izolacja CSS ma zastosowanie tylko w przypadku elementów kodu HTML. Izolacja CSS nie jest obsługiwana w przypadku pomocników tagów.

W pakiecie pliku CSS każda strona, widok lub składnik Razor są skojarzone z identyfikatorem zakresu w formacie b-{STRING}, gdzie symbol zastępczy {STRING} to ciąg dziesięciu znaków generowany przez strukturę. W poniższym przykładzie przedstawiono styl dla poprzedniego elementu <h1> na stronie Index aplikacji Razor Pages:

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

Na stronie Index, na której jest stosowany styl CSS z pliku w pakiecie, identyfikator zakresu jest dołączany jako atrybut kodu HTML:

<h1 b-3xxtam6d07>

Identyfikator jest unikatowy dla aplikacji. W czasie kompilacji pakiet projektu jest tworzony przy użyciu konwencji {STATIC WEB ASSETS BASE PATH}/Project.lib.scp.css, gdzie symbol zastępczy {STATIC WEB ASSETS BASE PATH} to statyczna podstawowa ścieżka zasobów internetowych.

Jeśli są używane inne projekty, takie jak pakiety NuGet lub biblioteki klas Razor, plik w pakiecie:

  • Odwołuje się do stylów przy użyciu zaimportowanych plików CSS.
  • Nie jest publikowany jako statyczny zasób internetowy aplikacji, która korzysta ze stylów.

Obsługa preprocesora CSS

Preprocesory CSS poprawiają tworzenie plików CSS poprzez wykorzystanie funkcji takich jak zmienne, zagnieżdżanie, moduły, domieszki i dziedziczenie. Mimo że izolacja CSS nie obsługuje natywnie preprocesorów CSS, takich jak Sass lub Less, integracja preprocesorów CSS jest bezproblemowa, o ile kompilacja preprocesora nastąpi, zanim struktura ponownie napisze selektory CSS podczas procesu kompilacji. Na przykład przy użyciu programu Visual Studio można skonfigurować istniejącą kompilację preprocesora jako zadanie Przed kompilowaniem w eksploratorze modułu uruchamiającego zadanie programu Visual Studio.

Wiele pakietów NuGet innych firm, takich jak AspNetCore.SassCompiler, umożliwia kompilowanie plików SASS/SCSS na początku procesu kompilowania przed wystąpieniem izolacji CSS i nie jest wymagana żadna dodatkowa konfiguracja.

Konfiguracja izolacji CSS

Izolacja CSS umożliwia konfigurację w przypadku niektórych bardziej zaawansowanych scenariuszy, na przykład wtedy, gdy w istniejących narzędziach lub przepływach pracy występują zależności.

Dostosowywanie formatu identyfikatora zakresu

W tej sekcji symbol zastępczy {Pages|Views} to albo Pages w przypadku aplikacji Razor Pages, albo Views w przypadku aplikacji MVC.

Domyślnie identyfikatory zakresu korzystają z formatu b-{STRING}, w którym symbol zastępczy {STRING} to ciąg zawierający dziesięć znaków wygenerowany przez strukturę. Aby dostosować format identyfikatora zakresu, zaktualizuj plik projektu do odpowiedniego wzorca:

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

W poprzednim przykładzie plik CSS wygenerowany dla pliku Index.cshtml.css zmienia identyfikator zakresu z b-{STRING} na custom-scope-identifier.

Użyj identyfikatorów zakresu, aby umożliwić dziedziczenie w przypadku plików CSS z określonym zakresem. W poniższym przykładzie pliku projektu plik BaseView.cshtml.css zawiera popularne style w widokach. Plik DerivedView.cshtml.css dziedziczy te style.

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

Użyj operatora symbolu wieloznacznego (*), aby udostępnić identyfikatory zakresu w wielu plikach:

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

Zmienianie podstawowej ścieżki w przypadku statycznych zasobów internetowych

Plik CSS o określonym zakresie jest generowany na poziomie głównym aplikacji. W pliku projektu użyj właściwości StaticWebAssetBasePath, aby zmienić domyślną ścieżkę. W poniższym przykładzie umieszczono plik CSS o określonym zakresie oraz resztę zasobów aplikacji w ścieżce _content:

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

Wyłączanie automatycznego tworzenia pakietów

Aby zrezygnować ze sposobu, w jaki struktura publikuje i ładuje pliki o określonym zakresie w środowisku uruchomieniowym, użyj właściwości DisableScopedCssBundling. W przypadku użycia tej właściwości inne narzędzia lub procesy są odpowiedzialne za pobieranie wyizolowanych plików CSS z katalogu obj, a następnie publikowanie i ładowanie ich w środowisku uruchomieniowym:

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

Obsługa biblioteki klas Razor (RCL)

Gdy biblioteka klas Razor (RCL) zapewnia wyizolowane style, atrybut href tagu <link> wskazuje lokalizację {STATIC WEB ASSET BASE PATH}/{PACKAGE ID}.bundle.scp.css, gdzie symbole zastępcze mają następujące znaczenie:

  • {STATIC WEB ASSET BASE PATH}: podstawowa ścieżka statycznego zasobu internetowego.
  • {PACKAGE ID}: identyfikator pakietu biblioteki. Identyfikator pakietu domyślnie odpowiada nazwie zestawu projektu, jeśli identyfikator pakietu nie jest określony w pliku projektu.

W poniższym przykładzie:

  • Podstawowa ścieżka statycznego zasobu internetowego to _content/ClassLib.
  • Nazwa pakietu biblioteki klas to ClassLib.

Pages/Shared/_Layout.cshtml (Razor Pages) lub Views/Shared/_Layout.cshtml (MVC):

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

Aby uzyskać więcej informacji na temat bibliotek klas Razor, zobacz następujące artykuły:

Aby uzyskać więcej informacji na temat izolacji CSS w przypadku rozwiązania Blazor, zobacz Izolacja CSS w rozwiązaniu Blazor na platformie ASP.NET Core.

Obsługa żądań HEAD za pomocą elementu fallback procedury obsługi OnGet

Żądania HEAD umożliwiają pobieranie nagłówków dla określonego zasobu. W przeciwieństwie do żądań GET żądania HEAD nie zwracają treści odpowiedzi.

Zwykle procedura obsługi OnHead jest tworzona i wywoływana pod kątem żądań HEAD:

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

Rozwiązanie Razor Pages korzysta z wywołania procedury obsługi OnGet rezerwowo, jeśli procedura obsługi OnHead została zdefiniowana.

XSRF/CSRF i rozwiązanie Razor Pages

RozwiązanieRazor Pages jest chronione przez walidację chroniącą przed fałszerstwem. Element FormTagHelper wprowadza tokeny chroniące przed fałszerstwem do elementów formularza HTML.

Korzystanie z układów, stron częściowych, szablonów i pomocników tagu w rozwiązaniu Razor Pages

Strony działają ze wszystkimi funkcjami aparatu widoku Razor. Układy, strony częściowe, szablony, pomocnicy tagów oraz pliki _ViewStart.cshtml i _ViewImports.cshtml działają w taki sam sposób, jak w przypadku konwencjonalnych widoków rozwiązania Razor.

Uporządkujmy tę stronę, wykorzystując niektóre z tych funkcji.

Dodaj stronę układu do pliku 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>

Układ:

  • Steruje układem każdej strony (chyba, że strona jest wyłączona z układu).
  • Importuje struktury HTML, takie jak JavaScript i arkusze stylów.
  • Zawartość strony Razor jest renderowana, tam gdzie jest wywoływana dyrektywa @RenderBody().

Aby uzyskać więcej informacji, zobacz Strona układu.

Właściwość Layout jest ustawiona w pliku Pages/_ViewStart.cshtml:

@{
    Layout = "_Layout";
}

Układ znajduje się w folderze Pages/Shared. Strony szukają innych widoków (układów, szablonów, stron częściowych) hierarchicznie, zaczynając od tego samego folderu, w którym znajduje się bieżąca strona. Układ w folderze Pages/Shared może być używany z poziomu dowolnej strony Razor w folderze Pages.

Plik układu powinien znajdować się w folderze Pages/Shared.

Nie zalecamy umieszczania pliku układu w folderze Views/Shared. Folder Views/Shared jest zgodny ze wzorcem widoków MVC. Rozwiązanie Razor Pages ma wykorzystywać hierarchię folderów, a nie konwencje ścieżek.

Wyszukiwanie widoków z poziomu rozwiązania Razor Pages obejmuje folder Pages. Układy, szablony i strony częściowe używane w przypadku kontrolerów MVC i konwencjonalnych widoków Razorpo prostu działają.

Dodaj plik Pages/_ViewImports.cshtml:

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

Dyrektywa @namespace jest omówiona w dalszej części samouczka. Dyrektywa @addTagHelper wprowadza wbudowanych pomocników tagów na wszystkich stronach w folderze Pages.

Dyrektywa @namespace ustawiona na stronie:

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

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

Dyrektywa @namespace ustawia przestrzeń nazw dla strony. Dyrektywa @model nie musi zawierać przestrzeni nazw.

Gdy dyrektywa @namespace jest zawarta w pliku _ViewImports.cshtml, określona przestrzeń nazw dostarcza prefiks dla wygenerowanej przestrzeni nazw na stronie, która importuje dyrektywę @namespace. Pozostała część wygenerowanej przestrzeni nazw (czyli sufiks) to rozdzielona kropkami ścieżka względna pomiędzy folderem zawierającym plik _ViewImports.cshtml a folderem zawierającym stronę.

Na przykład klasa PageModel w pliku Pages/Customers/Edit.cshtml.cs jawnie konfiguruje przestrzeń nazw:

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

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

        // Code removed for brevity.

Plik Pages/_ViewImports.cshtml konfiguruje następującą przestrzeń nazw:

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

Wygenerowana przestrzeń nazw dla strony RazorPages/Customers/Edit.cshtml jest taka sama jak klasa PageModel.

Dyrektywa @namespacedziała również z konwencjonalnymi widokami Razor.

Rozważ plik widoku 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>

Zaktualizowany plik widoku Pages/Customers/Create.cshtml z plikiem _ViewImports.cshtml i poprzednim plikiem układu:

@page
@model CreateModel

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

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

W poprzednim kodzie plik _ViewImports.cshtml służył do zaimportowania przestrzeni nazw i pomocników tagów. Plik układu zaimportował pliki JavaScript.

Projekt początkowy rozwiązania Razor Pages zawiera plik Pages/_ValidationScriptsPartial.cshtml, który podłącza walidację po stronie klienta.

Aby uzyskać więcej informacji na temat widoków częściowych, zobacz Widoki częściowe na platformie ASP.NET Core.

Generowanie adresów URL dla stron

Na wcześniej wyświetlonej stronie Create użyto metody 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");
    }
}

Aplikacja ma następującą strukturę plików/folderów:

  • /Pages

    • Index.cshtml

    • Privacy.cshtml

    • /Customers

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

Jeśli proces przebiega pomyślnie, strony Pages/Customers/Create.cshtml i Pages/Customers/Edit.cshtml przekierowują do pliku Pages/Customers/Index.cshtml. Ciąg ./Index to względna nazwa strony używana do uzyskiwania dostępu do poprzedniej strony. Służy do generowania adresów URL strony Pages/Customers/Index.cshtml. Na przykład:

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

Bezwzględna nazwa strony /Index jest używana do generowania adresów URL strony Pages/Index.cshtml. Na przykład:

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

Nazwa strony to ścieżka prowadząca do tej strony z folderu głównego /Pages, zawierająca element rozpoczynający / (na przykład /Index). Poprzednie przykłady generowania adresów URL oferują rozszerzone opcje i funkcje w porównaniu z trwałym kodowaniem adresu URL. W trakcie generowania adresu URL wykorzystywany jest routing, co umożliwia generowanie i kodowanie parametrów zgodnie z tym, jak zdefiniowana jest trasa w ścieżce docelowej.

Generowanie adresów URL dla stron obsługuje nazwy względne. W poniższej tabeli pokazano, która strona indeksu jest wybierana w przypadku użycia różnych parametrów RedirectToPage w ścieżce Pages/Customers/Create.cshtml.

RedirectToPage(x) Strona
RedirectToPage(„/Index”) Pages/Index
RedirectToPage(„./Index”); Pages/Customers/Index
RedirectToPage(„../Index”) Pages/Index
RedirectToPage(„Index”) Pages/Customers/Index

RedirectToPage("Index"), RedirectToPage("./Index") oraz RedirectToPage("../Index") to nazwy względne. Parametr RedirectToPage jest łączony ze ścieżką bieżącej strony w celu obliczenia nazwy strony docelowej.

Łączenie nazw względnych jest przydatne podczas tworzenia stron o złożonej strukturze. Gdy nazwy względne są używane do łączenia stron w folderze:

  • Zmiana nazwy folderu nie powoduje przerwania linków względnych.
  • Linki nie zostają uszkodzone, ponieważ nie zawierają nazwy folderu.

Aby przekierować do strony w innym obszarze, określ obszar:

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

Aby uzyskać więcej informacji, zobacz Obszary na platformie ASP.NET Core i Konwencje tras i aplikacji rozwiązania Razor Pages na platformie ASP.NET Core.

Atrybut ViewData

Dane można przekazać do strony za pomocą polecenia ViewDataAttribute. Wartości właściwości z atrybutem [ViewData] są przechowywane i ładowane z elementu ViewDataDictionary.

W poniższym przykładzie atrybut AboutModel stosuje wartość [ViewData] do właściwości Title:

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

    public void OnGet()
    {
    }
}

Na stronie Informacje można uzyskać dostęp do właściwości Title jako właściwości modelu:

<h1>@Model.Title</h1>

W układzie tytuł jest odczytywany ze słownika ViewData:

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

TempData

Platforma ASP.NET Core uwidocznia właściwość TempData. Ta właściwość przechowuje dane do momentu ich odczytania. Metody Keep i Peek mogą służyć do badania danych bez ich usuwania. Właściwość TempData przydaje się do przekierowywania, gdy dane są potrzebne w przypadku więcej niż jednego żądania.

Poniższy kod ustawia wartość Message przy użyciu właściwości TempData:

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");
    }
}

Poniższe znaczniki w pliku Pages/Customers/Index.cshtml wyświetlają wartość Message przy użyciu właściwości TempData.

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

Model strony Pages/Customers/Index.cshtml.cs stosuje atrybut [TempData] do właściwości Message.

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

Aby uzyskać więcej informacji, zobacz TempData.

Wiele procedur obsługi na stronę

Na poniższej stronie wygenerowano znaczniki dla dwóch procedur obsługi przy użyciu pomocnika tagów 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>

Formularz w poprzednim przykładzie zawiera dwa przyciski przesyłania, z których każdy używa elementu FormActionTagHelper do przesyłania do innego adresu URL. Atrybut asp-page-handler jest atrybutem towarzyszącym elementu asp-page. Atrybut asp-page-handler generuje adresy URL przesyłane do poszczególnych metod procedur obsługi zdefiniowanych przez stronę. Element asp-page nie jest określony, ponieważ przykład jest połączony z bieżącą stroną.

Model strony:

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();
        }
    }
}

W powyższym kodzie użyto nazwanych metod procedury obsługi. Nazwane metody procedury obsługi są tworzone przy użyciu tekstu w nazwie po elemencie On<HTTP Verb>, a przed elementem Async (jeśli występuje). W poprzednim przykładzie metody stron to OnPostJoinListAsync i OnPostJoinListUCAsync. Po usunięciu metod OnPost i Async nazwy procedury obsługi to JoinList i JoinListUC.

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

Korzystając z poprzedniego kodu, ścieżka adresu URL przesyłana do metody OnPostJoinListAsync to https://localhost:5001/Customers/CreateFATH?handler=JoinList. Ścieżka adresu URL przesyłana do metody OnPostJoinListUCAsync to https://localhost:5001/Customers/CreateFATH?handler=JoinListUC.

Trasy niestandardowe

Użyj dyrektywy @page, aby:

  • Określić trasę niestandardową do strony. Na przykład trasę do strony Informacje można ustawić na wartość /Some/Other/Path za pomocą kodu @page "/Some/Other/Path".
  • Dołączyć segmenty do trasy domyślnej strony. Na przykład segment „Produkt" można dodać do domyślnej trasy strony przy użyciu polecenia @page "item".
  • Dołączyć parametry do trasy domyślnej strony. Na przykład parametr identyfikatora id może być wymagany w przypadku strony z elementem @page "{id}".

Obsługiwana jest ścieżka względna katalogu głównego oznaczona tyldą (~) na początku ścieżki. Na przykład kod @page "~/Some/Other/Path" jest taki sam jak kod @page "/Some/Other/Path".

Jeśli ciąg zapytania ?handler=JoinList w adresie URL nie jest dla Ciebie odpowiedni, zmień trasę, umieszczając nazwę procedury obsługi w części ścieżki adresu URL. Trasę można dostosowywać poprzez dodanie szablonu trasy w podwójnym cudzysłowie po dyrektywie @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>

Korzystając z poprzedniego kodu, ścieżka adresu URL przesyłana do metody OnPostJoinListAsync to https://localhost:5001/Customers/CreateFATH/JoinList. Ścieżka adresu URL przesyłana do metody OnPostJoinListUCAsync to https://localhost:5001/Customers/CreateFATH/JoinListUC.

Znak ? następujący po elemencie handler oznacza, że parametr ścieżki jest opcjonalny.

Kolokacja plików JavaScript (JS)

Kolokacja plików JavaScript (JS) dla stron i widoków jest wygodnym sposobem organizowania skryptów w aplikacji.

Umieść w tej samej lokalizacji pliki JS przy użyciu następujących konwencji rozszerzenia nazwy pliku:

  • Strony aplikacji Razor Pages i widoki aplikacji MVC: .cshtml.js. Przykłady:
    • Skrypt Pages/Index.cshtml.js dla strony Index aplikacji Razor Pages w lokalizacji Pages/Index.cshtml.
    • Skrypt Views/Home/Index.cshtml.js dla widoku Index aplikacji MVC w lokalizacji Views/Home/Index.cshtml.

Do umieszczonych w tej samej lokalizacji plików JS można odwoływać się przez adres publiczny przy użyciu ścieżki do pliku w projekcie:

  • Strony i widoki ze stronicowanego pliku skryptów w aplikacji:

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

    • Symbol zastępczy {PATH} to ścieżka do strony, widoku lub składnika.
    • Symbol zastępczy {PAGE, VIEW, OR COMPONENT} to strona, widok lub składnik.
    • Symbol zastępczy {EXTENSION} jest zgodny z rozszerzeniem strony, widoku lub składnika razor albo cshtml.

    Przykład produktu Razor Pages:

    Plik JS dla strony Index jest umieszczany w folderze Pages (Pages/Index.cshtml.js) obok strony Index (Pages/Index.cshtml). Na stronie Index odwołanie do skryptu występuje w ścieżce w folderze Pages:

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

    Po opublikowaniu aplikacji struktura automatycznie przenosi skrypt do katalogu głównego sieci Web. W poprzednim przykładzie skrypt jest przenoszony do lokalizacji bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.cshtml.js, gdzie symbol zastępczy {TARGET FRAMEWORK MONIKER} to moniker platformy docelowej. Na stronie Index nie jest wymagana żadna zmiana względnego adresu URL skryptu.

    Po opublikowaniu aplikacji struktura automatycznie przenosi skrypt do katalogu głównego sieci Web. W poprzednim przykładzie skrypt jest przenoszony do lokalizacji bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Components\Pages\Index.razor.js, gdzie symbol zastępczy {TARGET FRAMEWORK MONIKER} to moniker platformy docelowej. W składniku Index nie jest wymagana żadna zmiana względnego adresu URL skryptu.

  • W przypadku skryptów dostarczanych przez bibliotekę klas Razor (RCL):

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

    • Symbol zastępczy {PACKAGE ID} to identyfikator pakietu biblioteki RCL (lub nazwa biblioteki dla biblioteki klas, do której odwołuje się aplikacja).
    • Symbol zastępczy {PATH} to ścieżka do strony, widoku lub składnika. Jeśli składnik Razor znajduje się w katalogu głównym biblioteki RCL, segment ścieżki nie jest uwzględniany.
    • Symbol zastępczy {PAGE, VIEW, OR COMPONENT} to strona, widok lub składnik.
    • Symbol zastępczy {EXTENSION} jest zgodny z rozszerzeniem strony, widoku lub składnika razor albo cshtml.

Zaawansowane ustawienia i konfiguracja

Konfiguracja i ustawienia w poniższych sekcjach nie są wymagane przez większość aplikacji.

Aby skonfigurować opcje zaawansowane, użyj przeciążenia AddRazorPages, które konfiguruje klasę RazorPagesOptions:

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();

Użyj klasy RazorPagesOptions, aby ustawić katalog główny dla stron lub dodać konwencje modeli aplikacji dla stron. Aby uzyskać więcej informacji na temat konwencji, zobacz Konwencje autoryzacji rozwiązania Razor Pages.

Aby wstępnie skompilować widoki, zobacz Kompilowanie widoków Razor.

Określanie, że strony Razor znajdują się w katalogu głównym zawartości

Domyślnie strony Razor znajdują się w katalogu /Pages. Dodaj metodę WithRazorPagesAtContentRoot, aby określić, że strony Razor znajdują się w katalogu głównym zawartości (ContentRootPath) aplikacji:

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();

Określanie, że strony Razor znajdują się w niestandardowym katalogu głównym

Dodaj metodę WithRazorPagesRoot, aby określić, że strony Razor znajdują się w niestandardowym katalogu głównym w aplikacji (podaj ścieżkę względną):

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();

Dodatkowe zasoby

Tworzenie projektu rozwiązania Razor Pages

Zobacz temat Wprowadzenie do rozwiązania Razor Pages, aby uzyskać szczegółowe instrukcje dotyczące tworzenia projektu za pomocą rozwiązania Razor Pages.

Razor Pages

Rozwiązanie Razor Pages można włączyć w pliku 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();
        });
    }
}

Rozważ stronę podstawową:

@page

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

Powyższy kod w dużym stopniu przypomina plik widoku Razor używany w aplikacji platformy ASP.NET Core z kontrolerami i widokami. Różni się dyrektywą @page. Dyrektywa @page zamienia plik w działanie MVC, co oznacza, że obsługuje żądania bezpośrednio, bez udziału kontrolera. Dyrektywa @page musi być pierwszą dyrektywą Razor na stronie. Dyrektywa @page wpływa na działanie innych konstrukcji Razor. Nazwy plików rozwiązania Razor Pages mają sufiks .cshtml.

Podobna strona, korzystająca z klasy PageModel, jest pokazana w następujących dwóch plikach. Plik Pages/Index2.cshtml:

@page
@using RazorPagesIntro.Pages
@model Index2Model

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

Model strony 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 }";
        }
    }
}

Zgodnie z konwencją plik klasy PageModel ma taką samą nazwę jak plik strony Razor z dodanym rozszerzeniem .cs. Na przykład poprzednia strona Razor to Pages/Index2.cshtml. Plik zawierający klasę PageModel nosi nazwę Pages/Index2.cshtml.cs.

Skojarzenia ścieżek URL ze stronami są określane przez lokalizację strony w systemie plików. W poniższej tabeli przedstawiono ścieżkę strony Razor i odpowiadający jej adres URL:

Nazwa i ścieżka pliku Odpowiedni adres URL
/Pages/Index.cshtml / lub /Index
/Pages/Contact.cshtml /Contact
/Pages/Store/Contact.cshtml /Store/Contact
/Pages/Store/Index.cshtml /Store lub /Store/Index

Uwagi:

  • Środowisko uruchomieniowe domyślnie wyszukuje pliki rozwiązania Razor Pages w folderze Pages.
  • Index to strona domyślna, jeśli adres URL nie zawiera strony.

Pisanie podstawowego formularza

Rozwiązanie Razor Pages zostało stworzone, aby ułatwić wdrożenie typowych wzorców używanych w przeglądarkach internetowych podczas tworzenia aplikacji. Powiązanie modelu, pomocnicy tagów i pomocnicy HTML działają przy użyciu właściwości zdefiniowanych w klasie strony Razor. Rozważ stronę, w której wdrożony będzie podstawowy formularz kontaktowy w przypadku modelu Contact:

W przykładach kodu w tym dokumencie element DbContext jest inicjowany w pliku Startup.cs..

Baza danych w pamięci wymaga pakietu NuGet Microsoft.EntityFrameworkCore.InMemory.

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

Model danych:

using System.ComponentModel.DataAnnotations;

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

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

Kontekst bazy danych:

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; }
    }
}

Plik widoku 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>

Model strony 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");
        }
    }
}

Zgodnie z konwencją klasa PageModel nazywa się <PageName>Model i znajduje się w tej samej przestrzeni nazw co strona.

Klasa PageModel umożliwia oddzielenie logiki strony od jej prezentacji. Definiuje procedury obsługi stron dla żądań wysyłanych do strony oraz dla danych używanych do renderowania strony. To rozdzielenie umożliwia:

Strona posiada metodę procedury obsługiOnPostAsync, która jest uruchamiana w przypadku żądań POST (gdy użytkownik opublikuje formularz). Można dodawać metody procedury obsługi dla dowolnego czasownika HTTP. Najczęstsze procedury obsługi to:

  • OnGet — na potrzeby inicjowania stanu wymaganego dla strony. W poprzednim kodzie metoda OnGet spowodowała wyświetlenie strony CreateModel.cshtmlRazor.
  • OnPost — na potrzeby obsługi przesłanych formularzy.

Sufiks nazewnictwa Async jest opcjonalny, ale jest często używany zgodnie z konwencją na potrzeby funkcji asynchronicznych. Powyższy kod jest typowy dla rozwiązania Razor Pages.

Jeśli znasz aplikacje ASP.NET korzystające z kontrolerów i widoków:

  • Kod OnPostAsync w poprzednim przykładzie wygląda podobnie do typowego kodu kontrolera.
  • Większość typów pierwotnych MVC, takich jak powiązanie modelu, walidacja i wyniki akcji działają tak samo w przypadku kontrolerów i rozwiązania Razor Pages.

Poprzednia metoda OnPostAsync:

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

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

    return RedirectToPage("./Index");
}

Podstawowy przepływ metody OnPostAsync:

Sprawdź, czy wystąpiły błędy walidacji.

  • Jeśli nie ma żadnych błędów, zapisz dane i przeprowadź przekierowanie.
  • Jeśli wystąpią jakieś błędy, pokaż stronę ponownie z komunikatami walidacji. W wielu przypadkach błędy weryfikacji zostaną wykryte w kliencie i nigdy nie zostaną przesłane do serwera.

Plik widoku 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>

Renderowany kod HTML z pliku Pages/Create.cshtml:

<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>

W poprzednim kodzie w przypadku opublikowania formularza:

  • Z prawidłowymi danymi:

    • Metoda procedury obsługi OnPostAsync wywołuje metodę pomocnika RedirectToPage. Metoda RedirectToPage zwraca wystąpienie klasy RedirectToPageResult. RedirectToPage:

      • Jest wynikiem akcji.
      • Przypomina metodę RedirectToAction lub RedirectToRoute (używaną w kontrolerach i widokach).
      • Jest dostosowana pod kątem stron. W poprzednim przykładzie nastąpiło przekierowanie do głównej strony indeksu (/Index). Metoda RedirectToPage jest opisana szczegółowo w sekcji dotyczącej generowania adresu URL w rozwiązaniu Pages.
  • Z błędami walidacji, które są przekazywane do serwera:

    • Metoda procedury obsługi OnPostAsync wywołuje metodę pomocnika Page. Metoda Page zwraca wystąpienie klasy PageResult. Zwrócenie elementu Page wygląda podobnie jak zwrócenie elementu View przez akcje w kontrolerach. Element PageResult jest domyślnym zwracanym typem w przypadku metody procedury obsługi. Metoda procedury obsługi, która zwraca wartość void, renderuje stronę.
    • W poprzednim przykładzie opublikowanie formularza bez żadnych wartości spowodowało, że właściwość ModelState.IsValid zwróciła wartość false. W tym przykładzie po stronie klienta nie są wyświetlane żadne błędy walidacji. Obsługa błędów walidacji jest omówiona w dalszej części tego dokumentu.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }
    
        _context.Customers.Add(Customer);
        await _context.SaveChangesAsync();
    
        return RedirectToPage("./Index");
    }
    
  • W przypadku wykrycia błędów walidacji po stronie klienta:

    • Dane nie są publikowane na serwerze.
    • Walidacja po stronie klienta zostanie wyjaśniona w dalszej części tego dokumentu.

Do wyrażenia zgody na powiązanie modelu właściwość Customer wykorzystuje atrybut [BindProperty]:

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");
    }
}

Właściwość [BindProperty]nie powinna być używana w modelach zawierających właściwości, które nie powinny być zmieniane przez klienta. Aby uzyskać więcej informacji, zobacz Przesyłanie dodatkowych danych.

Rozwiązanie Razor Pages domyślnie wiąże właściwości wyłącznie z czasownikami innymi niż GET. Powiązanie z właściwościami eliminuje konieczność pisania kodu w celu przekonwertowania danych HTTP na typ modelu. Powiązanie redukuje kod poprzez użycie tej samej właściwości do renderowania pól formularza (<input asp-for="Customer.Name">) i akceptowania danych wejściowych.

Ostrzeżenie

Ze względów bezpieczeństwa należy wyrazić zgodę na powiązanie danych żądania GET z właściwościami modelu strony. Przed mapowaniem danych wejściowych użytkownika na właściwości zweryfikuj je. Wyrażenie zgody na powiązanie danych GET jest przydatne w scenariuszach, które wykorzystują ciągi zapytania lub wartości trasy.

Aby powiązać właściwość w przypadku żądań GET, ustaw właściwość SupportsGet atrybutu [BindProperty] na true:

[BindProperty(SupportsGet = true)]

Aby uzyskać więcej informacji, zobacz Podsumowanie ASP.NET Core Community Standup: dyskusja na temat powiązania z użyciem metody GET (YouTube).

Przegląd pliku widoku 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>
  • W poprzednim kodzie pomocnik tagu wejściowego<input asp-for="Customer.Name" /> wiąże element kodu HTML <input> z wyrażeniem modelu Customer.Name.
  • Dyrektywa @addTagHelper udostępnia pomocników tagów.

Strona główna

Index.cshtml to strona główna:

@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>

Powiązana klasa 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();
    }
}

Plik Index.cshtml zawiera następujące znaczniki:

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

Pomocnik tagu kotwicy <a /a> użył atrybutu asp-route-{value} do wygenerowania linku do strony edytowania. Link zawiera dane trasy z identyfikatorem kontaktu. Na przykład https://localhost:5001/Edit/1. Pomocnicy tagów umożliwiają kodowi po stronie serwera uczestniczenie w tworzeniu i renderowaniu elementów HTML w plikach Razor.

Plik Index.cshtml zawiera znaczniki umożliwiające tworzenie przycisku usuwania dla każdego kontaktu klienta:

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

Renderowany kod HTML:

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

Gdy przycisk usuwania jest renderowany w formacie HTML, jego akcja formularza zawiera następujące parametry:

  • Identyfikator kontaktu klienta określony przez atrybut asp-route-id.
  • Element handler określony przez atrybut asp-page-handler.

Po wybraniu przycisku żądanie formularza POST jest wysyłane do serwera. Zgodnie z konwencją nazwa metody procedury obsługi jest wybierana na podstawie wartości parametru handler zgodnie ze schematem OnPost[handler]Async.

Ponieważ procedura obsługi handler w tym przykładzie to delete, metoda procedury obsługi OnPostDeleteAsync jest używana do przetworzenia żądania POST. Jeśli procedura obsługi asp-page-handler zostanie ustawiona na inną wartość, na przykład remove, zostanie wybrana metoda procedury obsługi o nazwie 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();
}

Metoda OnPostDeleteAsync:

  • Pobiera identyfikator id z ciągu zapytania.
  • Tworzy zapytanie w bazie danych dotyczące kontaktu klienta przy użyciu metody FindAsync.
  • Jeśli kontakt klienta zostanie odnaleziony, zostaje usunięty, a baza danych zostaje zaktualizowana.
  • Tworzy wywołanie RedirectToPage w celu przekierowania do głównej strony indeksu (/Index).

Plik Edit.cshtml file

@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>

Pierwszy wiersz zawiera dyrektywę @page "{id:int}". Ograniczenie routingu "{id:int}" instruuje stronę, aby akceptowała żądania do strony zawierające dane trasy int. Jeśli żądanie do strony nie zawiera danych trasy, które można przekonwertować na element int, środowisko uruchomieniowe zwraca kod błędu HTTP 404 (nie znaleziono). Aby określić identyfikator jako opcjonalny, dołącz kod ? do ograniczenia trasy:

@page "{id:int?}"

Plik 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");
    }

}

Walidacja

Reguły walidacji:

  • Są deklaratywnie określone w klasie modelu.
  • Są wymuszane w każdym miejscu w aplikacji.

Przestrzeń nazw System.ComponentModel.DataAnnotations zapewnia zestaw wbudowanych atrybutów walidacji, które są stosowane deklaratywnie wobec klasy lub właściwości. Przestrzeń nazw DataAnnotations zawiera również atrybuty formatowania, takie jak [DataType], które pomagają w formatowaniu i nie zapewniają żadnej walidacji.

Rozważmy model Customer:

using System.ComponentModel.DataAnnotations;

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

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

W przypadku użycia następującego pliku widoku 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>

Powyższy kod ma następujące działanie:

  • Obejmuje skrypty walidacji jQuery i jQuery.

  • Korzysta z pomocników tagów<div /> i <span /> w celu umożliwienia:

    • Walidacji po stronie klienta.
    • Renderowania błędów walidacji.
  • Generuje następujący kod 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>
    

Opublikowanie formularza tworzenia bez wartości nazwy powoduje wyświetlenie komunikatu o błędzie „Pole nazwy jest wymagane” w formularzu. Jeśli po stronie klienta włączono język JavaScript, błąd zostanie wyświetlony w przeglądarce bez publikowania na serwerze.

Atrybut [StringLength(10)] generuje element data-val-length-max="10" w renderowanym kodzie HTML. Element data-val-length-max uniemożliwia przeglądarkom wprowadzanie większej liczby znaków niż określono. Jeśli do edytowania i powtarzania publikowania używane jest narzędzie takie jak Fiddler:

  • W przypadku nazwy dłuższej niż 10 znaków.
  • Zwracany jest komunikat o błędzie „Pole Nazwa musi być ciągiem o maksymalnej długości 10 znaków”.

Rozważ następujący model 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; }
    }
}

Atrybuty walidacji określają działanie wymuszane wobec właściwości modelu, do których są stosowane:

  • Atrybuty Required i MinimumLength wskazują, że właściwość musi mieć wartość, ale nic nie uniemożliwia użytkownikowi wprowadzania białych znaków w celu spełnienia zasad tej walidacji.

  • Atrybut RegularExpression służy do ograniczania liczby znaków, które mogą być wprowadzane jako dane wejściowe. W poprzednim kodzie w polu „Genre” (Gatunek):

    • Muszą być używane tylko litery.
    • Pierwsza litera musi być wielką literą. Białe znaki, liczby i znaki specjalne nie są dozwolone.
  • W atrybucie RegularExpression „Rating” (Klasyfikacja):

    • Pierwszy znak musi być wielką literą.
    • Kolejne znaki mogą być znakami specjalnymi i cyframi. W atrybucie Rating wartość „PG-13” jest prawidłowa, ale w przypadku atrybutu Genre — nie.
  • Atrybut Range ogranicza wartość do określonego zakresu.

  • Atrybut StringLength wyznacza maksymalną długość właściwości ciągu oraz opcjonalnie jego minimalną długość.

  • Typy wartości (takie jak decimal, int, float, DateTime) są z natury wymagane i nie wymagają atrybutu [Required].

Na stronie tworzenia w przypadku modelu Movie wyświetlane są błędy z nieprawidłowymi wartościami:

Formularz widoku Movie (Film) z wieloma błędami walidacji jQuery po stronie klienta

Aby uzyskać więcej informacji, zobacz:

Obsługa żądań HEAD za pomocą elementu fallback procedury obsługi OnGet

Żądania HEAD umożliwiają pobieranie nagłówków dla określonego zasobu. W przeciwieństwie do żądań GET żądania HEAD nie zwracają treści odpowiedzi.

Zwykle procedura obsługi OnHead jest tworzona i wywoływana pod kątem żądań HEAD:

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

Rozwiązanie Razor Pages korzysta z wywołania procedury obsługi OnGet rezerwowo, jeśli procedura obsługi OnHead została zdefiniowana.

XSRF/CSRF i rozwiązanie Razor Pages

RozwiązanieRazor Pages jest chronione przez walidację chroniącą przed fałszerstwem. Element FormTagHelper wprowadza tokeny chroniące przed fałszerstwem do elementów formularza HTML.

Korzystanie z układów, stron częściowych, szablonów i pomocników tagu w rozwiązaniu Razor Pages

Strony działają ze wszystkimi funkcjami aparatu widoku Razor. Układy, strony częściowe, szablony, pomocnicy tagów oraz pliki _ViewStart.cshtml i _ViewImports.cshtml działają w taki sam sposób, jak w przypadku konwencjonalnych widoków rozwiązania Razor.

Uporządkujmy tę stronę, wykorzystując niektóre z tych funkcji.

Dodaj stronę układu do pliku 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>

Układ:

  • Steruje układem każdej strony (chyba, że strona jest wyłączona z układu).
  • Importuje struktury HTML, takie jak JavaScript i arkusze stylów.
  • Zawartość strony Razor jest renderowana, tam gdzie jest wywoływana dyrektywa @RenderBody().

Aby uzyskać więcej informacji, zobacz Strona układu.

Właściwość Layout jest ustawiona w pliku Pages/_ViewStart.cshtml:

@{
    Layout = "_Layout";
}

Układ znajduje się w folderze Pages/Shared. Strony szukają innych widoków (układów, szablonów, stron częściowych) hierarchicznie, zaczynając od tego samego folderu, w którym znajduje się bieżąca strona. Układ w folderze Pages/Shared może być używany z poziomu dowolnej strony Razor w folderze Pages.

Plik układu powinien znajdować się w folderze Pages/Shared.

Nie zalecamy umieszczania pliku układu w folderze Views/Shared. Folder Views/Shared jest zgodny ze wzorcem widoków MVC. Rozwiązanie Razor Pages ma wykorzystywać hierarchię folderów, a nie konwencje ścieżek.

Wyszukiwanie widoków z poziomu rozwiązania Razor Pages obejmuje folder Pages. Układy, szablony i strony częściowe używane w przypadku kontrolerów MVC i konwencjonalnych widoków Razorpo prostu działają.

Dodaj plik Pages/_ViewImports.cshtml:

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

Dyrektywa @namespace jest omówiona w dalszej części samouczka. Dyrektywa @addTagHelper wprowadza wbudowanych pomocników tagów na wszystkich stronach w folderze Pages.

Dyrektywa @namespace ustawiona na stronie:

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

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

Dyrektywa @namespace ustawia przestrzeń nazw dla strony. Dyrektywa @model nie musi zawierać przestrzeni nazw.

Gdy dyrektywa @namespace jest zawarta w pliku _ViewImports.cshtml, określona przestrzeń nazw dostarcza prefiks dla wygenerowanej przestrzeni nazw na stronie, która importuje dyrektywę @namespace. Pozostała część wygenerowanej przestrzeni nazw (czyli sufiks) to rozdzielona kropkami ścieżka względna pomiędzy folderem zawierającym plik _ViewImports.cshtml a folderem zawierającym stronę.

Na przykład klasa PageModel w pliku Pages/Customers/Edit.cshtml.cs jawnie konfiguruje przestrzeń nazw:

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

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

        // Code removed for brevity.

Plik Pages/_ViewImports.cshtml konfiguruje następującą przestrzeń nazw:

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

Wygenerowana przestrzeń nazw dla strony RazorPages/Customers/Edit.cshtml jest taka sama jak klasa PageModel.

Dyrektywa @namespacedziała również z konwencjonalnymi widokami Razor.

Rozważ plik widoku 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>

Zaktualizowany plik widoku Pages/Create.cshtml z plikiem _ViewImports.cshtml i poprzednim plikiem układu:

@page
@model CreateModel

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

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

W poprzednim kodzie plik _ViewImports.cshtml służył do zaimportowania przestrzeni nazw i pomocników tagów. Plik układu zaimportował pliki JavaScript.

Projekt początkowy rozwiązania Razor Pages zawiera plik Pages/_ValidationScriptsPartial.cshtml, który podłącza walidację po stronie klienta.

Aby uzyskać więcej informacji na temat widoków częściowych, zobacz Widoki częściowe na platformie ASP.NET Core.

Generowanie adresów URL dla stron

Na wcześniej wyświetlonej stronie Create użyto metody 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");
    }
}

Aplikacja ma następującą strukturę plików/folderów:

  • /Pages

    • Index.cshtml

    • Privacy.cshtml

    • /Customers

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

Jeśli proces przebiega pomyślnie, strony Pages/Customers/Create.cshtml i Pages/Customers/Edit.cshtml przekierowują do pliku Pages/Customers/Index.cshtml. Ciąg ./Index to względna nazwa strony używana do uzyskiwania dostępu do poprzedniej strony. Służy do generowania adresów URL strony Pages/Customers/Index.cshtml. Na przykład:

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

Bezwzględna nazwa strony /Index jest używana do generowania adresów URL strony Pages/Index.cshtml. Na przykład:

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

Nazwa strony to ścieżka prowadząca do tej strony z folderu głównego /Pages, zawierająca element rozpoczynający / (na przykład /Index). Poprzednie przykłady generowania adresów URL oferują rozszerzone opcje i funkcje w porównaniu z trwałym kodowaniem adresu URL. W trakcie generowania adresu URL wykorzystywany jest routing, co umożliwia generowanie i kodowanie parametrów zgodnie z tym, jak zdefiniowana jest trasa w ścieżce docelowej.

Generowanie adresów URL dla stron obsługuje nazwy względne. W poniższej tabeli pokazano, która strona indeksu jest wybierana w przypadku użycia różnych parametrów RedirectToPage w ścieżce Pages/Customers/Create.cshtml.

RedirectToPage(x) Strona
RedirectToPage(„/Index”) Pages/Index
RedirectToPage(„./Index”); Pages/Customers/Index
RedirectToPage(„../Index”) Pages/Index
RedirectToPage(„Index”) Pages/Customers/Index

RedirectToPage("Index"), RedirectToPage("./Index") oraz RedirectToPage("../Index") to nazwy względne. Parametr RedirectToPage jest łączony ze ścieżką bieżącej strony w celu obliczenia nazwy strony docelowej.

Łączenie nazw względnych jest przydatne podczas tworzenia stron o złożonej strukturze. Gdy nazwy względne są używane do łączenia stron w folderze:

  • Zmiana nazwy folderu nie powoduje przerwania linków względnych.
  • Linki nie zostają uszkodzone, ponieważ nie zawierają nazwy folderu.

Aby przekierować do strony w innym obszarze, określ obszar:

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

Aby uzyskać więcej informacji, zobacz Obszary na platformie ASP.NET Core i Konwencje tras i aplikacji rozwiązania Razor Pages na platformie ASP.NET Core.

Atrybut ViewData

Dane można przekazać do strony za pomocą polecenia ViewDataAttribute. Wartości właściwości z atrybutem [ViewData] są przechowywane i ładowane z elementu ViewDataDictionary.

W poniższym przykładzie atrybut AboutModel stosuje wartość [ViewData] do właściwości Title:

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

    public void OnGet()
    {
    }
}

Na stronie Informacje można uzyskać dostęp do właściwości Title jako właściwości modelu:

<h1>@Model.Title</h1>

W układzie tytuł jest odczytywany ze słownika ViewData:

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

TempData

Platforma ASP.NET Core uwidocznia właściwość TempData. Ta właściwość przechowuje dane do momentu ich odczytania. Metody Keep i Peek mogą służyć do badania danych bez ich usuwania. Właściwość TempData przydaje się do przekierowywania, gdy dane są potrzebne w przypadku więcej niż jednego żądania.

Poniższy kod ustawia wartość Message przy użyciu właściwości TempData:

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");
    }
}

Poniższe znaczniki w pliku Pages/Customers/Index.cshtml wyświetlają wartość Message przy użyciu właściwości TempData.

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

Model strony Pages/Customers/Index.cshtml.cs stosuje atrybut [TempData] do właściwości Message.

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

Aby uzyskać więcej informacji, zobacz TempData.

Wiele procedur obsługi na stronę

Na poniższej stronie wygenerowano znaczniki dla dwóch procedur obsługi przy użyciu pomocnika tagów 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>

Formularz w poprzednim przykładzie zawiera dwa przyciski przesyłania, z których każdy używa elementu FormActionTagHelper do przesyłania do innego adresu URL. Atrybut asp-page-handler jest atrybutem towarzyszącym elementu asp-page. Atrybut asp-page-handler generuje adresy URL przesyłane do poszczególnych metod procedur obsługi zdefiniowanych przez stronę. Element asp-page nie jest określony, ponieważ przykład jest połączony z bieżącą stroną.

Model strony:

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();
        }
    }
}

W powyższym kodzie użyto nazwanych metod procedury obsługi. Nazwane metody procedury obsługi są tworzone przy użyciu tekstu w nazwie po elemencie On<HTTP Verb>, a przed elementem Async (jeśli występuje). W poprzednim przykładzie metody stron to OnPostJoinListAsync i OnPostJoinListUCAsync. Po usunięciu metod OnPost i Async nazwy procedury obsługi to JoinList i JoinListUC.

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

Korzystając z poprzedniego kodu, ścieżka adresu URL przesyłana do metody OnPostJoinListAsync to https://localhost:5001/Customers/CreateFATH?handler=JoinList. Ścieżka adresu URL przesyłana do metody OnPostJoinListUCAsync to https://localhost:5001/Customers/CreateFATH?handler=JoinListUC.

Trasy niestandardowe

Użyj dyrektywy @page, aby:

  • Określić trasę niestandardową do strony. Na przykład trasę do strony Informacje można ustawić na wartość /Some/Other/Path za pomocą kodu @page "/Some/Other/Path".
  • Dołączyć segmenty do trasy domyślnej strony. Na przykład segment „Produkt" można dodać do domyślnej trasy strony przy użyciu polecenia @page "item".
  • Dołączyć parametry do trasy domyślnej strony. Na przykład parametr identyfikatora id może być wymagany w przypadku strony z elementem @page "{id}".

Obsługiwana jest ścieżka względna katalogu głównego oznaczona tyldą (~) na początku ścieżki. Na przykład kod @page "~/Some/Other/Path" jest taki sam jak kod @page "/Some/Other/Path".

Jeśli ciąg zapytania ?handler=JoinList w adresie URL nie jest dla Ciebie odpowiedni, zmień trasę, umieszczając nazwę procedury obsługi w części ścieżki adresu URL. Trasę można dostosowywać poprzez dodanie szablonu trasy w podwójnym cudzysłowie po dyrektywie @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>

Korzystając z poprzedniego kodu, ścieżka adresu URL przesyłana do metody OnPostJoinListAsync to https://localhost:5001/Customers/CreateFATH/JoinList. Ścieżka adresu URL przesyłana do metody OnPostJoinListUCAsync to https://localhost:5001/Customers/CreateFATH/JoinListUC.

Znak ? następujący po elemencie handler oznacza, że parametr ścieżki jest opcjonalny.

Zaawansowane ustawienia i konfiguracja

Konfiguracja i ustawienia w poniższych sekcjach nie są wymagane przez większość aplikacji.

Aby skonfigurować opcje zaawansowane, użyj przeciążenia AddRazorPages, które konfiguruje klasę RazorPagesOptions:

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

Użyj klasy RazorPagesOptions, aby ustawić katalog główny dla stron lub dodać konwencje modeli aplikacji dla stron. Aby uzyskać więcej informacji na temat konwencji, zobacz Konwencje autoryzacji rozwiązania Razor Pages.

Aby wstępnie skompilować widoki, zobacz Kompilowanie widoków Razor.

Określanie, że strony Razor znajdują się w katalogu głównym zawartości

Domyślnie strony Razor znajdują się w katalogu /Pages. Dodaj metodę WithRazorPagesAtContentRoot, aby określić, że strony Razor znajdują się w katalogu głównym zawartości (ContentRootPath) aplikacji:

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

Określanie, że strony Razor znajdują się w niestandardowym katalogu głównym

Dodaj metodę WithRazorPagesRoot, aby określić, że strony Razor znajdują się w niestandardowym katalogu głównym w aplikacji (podaj ścieżkę względną):

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

Dodatkowe zasoby