Einführung in Razor Pages in ASP.NET Core

Von Rick Anderson, Dave Brock und Kirk Larkin

Razor Pages kann im Vergleich zu Controllern und Ansichten das Programmieren seitenbasierter Anwendungen vereinfachen und die Produktivität erhöhen.

Ein Tutorial, in dem der Model-View-Controller-Ansatz verwendet wird, finden Sie unter Erste Schritte mit ASP.NET Core MVC und Visual Studio.

Dieses Dokument bietet eine Einführung in Razor Pages. Es handelt sich nicht um ein Schritt-für-Schritt-Tutorial. Wenn es Ihnen Probleme bereitet, die Ausführungen in einigen Abschnitten nachzuvollziehen, lesen Sie Erste Schritte mit Razor Pages in ASP.NET Core. Eine Übersicht über ASP.NET Core finden Sie unter Einführung in ASP.NET Core.

Voraussetzungen

Erstellen eines Razor Pages-Projekts

Ausführliche Informationen zum Erstellen eines Razor Pages-Projekts finden Sie unter Erste Schritte mit Razor Pages.

Razor Pages

Razor Pages ist in Program.cs aktiviert:

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

Für den Code oben gilt:

Sehen Sie sich diese einfache Seite an:

@page

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

Der vorherige Code ähnelt sehr einer Razor-Ansichtsdatei, die in einer ASP.NET Core-App mit Controllern und Ansichten verwendet wird. Der Unterschied besteht in der @page-Anweisung. @page macht die Datei zu einer MVC-Aktion, d. h. dass Anforderungen direkt ohne Durchlaufen eines Controllers verarbeitet werden. @page muss die erste Razor-Anweisung auf einer Seite sein. @page wirkt sich auf das Verhalten aller anderen Razor-Konstrukte aus. Razor Pages-Dateinamen haben das Suffix .cshtml.

Eine ähnliche Seite, die die PageModel-Klasse verwendet, wird in den folgenden zwei Dateien angezeigt. Die Datei Pages/Index2.cshtml enthält Folgendes:

@page
@using RazorPagesIntro.Pages
@model Index2Model

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

Das Pages/Index2.cshtml.cs Page-Modell:

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

Die PageModel-Klassendatei hat standardmäßig den gleichen Namen wie die Datei mit Razor Pages, nur dass außerdem .cs angefügt wird. Die vorherige Datei mit Razor Page lautet beispielsweise Pages/Index2.cshtml. Die Datei mit der PageModel-Klasse heißt Pages/Index2.cshtml.cs.

Die Zuordnungen von URL-Pfaden zu Seiten werden durch den Speicherort der Seite im Dateisystem bestimmt. Die folgende Tabelle zeigt einen Pfad zu Razor Pages und die entsprechende URL:

Dateiname und Pfad Entsprechende URL
/Pages/Index.cshtml / oder /Index
/Pages/Contact.cshtml /Contact
/Pages/Store/Contact.cshtml /Store/Contact
/Pages/Store/Index.cshtml /Store oder /Store/Index

Notizen:

  • Die Runtime sucht standardmäßig im Ordner Pages (Seiten) nach Dateien mit RRazor Pages.
  • Wenn eine Seite nicht in einer URL enthalten ist, ist Index die Standardseite.

Schreiben eines einfachen Formulars

Razor Pages ist darauf ausgelegt, allgemeine Muster, die mit Webbrowsern verwendet werden können, beim Erstellen einer App leichter implementieren zu können. Die Modellbindung, Taghilfsprogramme und alle HTML-Hilfsprogramme funktionieren mit den Eigenschaften, die in einer Razor Pages-Klasse definiert sind. Nehmen wir z.B. eine Seite, die ein allgemeines Kontaktformular für das Contact-Modell implementiert:

Für die Beispiele in diesem Dokument wird DbContext in der Datei Startup.cs initialisiert.

Für die In-Memory-Datenbank ist das NuGet-Paket Microsoft.EntityFrameworkCore.InMemory erforderlich.

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

Das Datenmodell:

using System.ComponentModel.DataAnnotations;

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

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

Der db-Kontext:

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

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

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

Die PageModel -Klasse heißt standardmäßig <PageName>Model und befindet sich im selben Namespace wie die Seite.

Mit der Klasse PageModel kann die Logik einer Seite von deren Darstellung getrennt werden. Sie definiert Seitenhandler für Anforderungen, die an die Seite geschickt wurden, und für zum Rendern der Seite verwendete Daten. Diese Trennung ermöglicht Folgendes:

Die Seite hat eine OnPostAsync-Handlermethode, die auf POST-Anforderungen ausgeführt wird (wenn ein Benutzer das Formular sendet). Für alle HTTP-Verben können Handlermethoden hinzugefügt werden. Die am häufigsten verwendeten Handler sind:

  • OnGet, um den für eine Seite erforderlichen Status zu initialisieren. Im vorangehenden Code wird die CreateModel.cshtmlRazor-Seite durch die OnGet-Methode dargestellt.
  • OnPost, um Formularübermittlungen zu behandeln

Das Namenssuffix Async ist optional. Es wird jedoch standardmäßig häufig für asynchrone Funktionen verwendet. Der vorhergehende Code ist typisch für Razor Pages.

Wenn Sie mit ASP.NET-Apps vertraut sind, die Controller und Ansichten verwenden, werden Ihnen folgende Fakten bekannt vorkommen:

  • Der OnPostAsync-Code im vorangehenden Beispiel ähnelt dem typischen Controllercode.
  • Die meisten primitiven MVC-Typen wie solche für Modellbindungen, Validierungen und Aktionsergebnisse werden in Controllern und Razor Pages auf dieselbe Weise eingesetzt.

Die vorherige OnPostAsync-Methode:

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

Der grundlegende Ablauf von OnPostAsync:

Prüfen auf Validierungsfehler

  • Wenn keine Fehler vorliegen, werden die Daten gespeichert und weitergeleitet.
  • Wenn es Fehler gibt, zeigen Sie die Seite erneut mit den Validierungsmeldungen an. denn Validierungsfehler werden oftmals auf dem Client erkannt und nie an den Server übermittelt.

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

Der von Pages/Customers/Create.cshtml gerenderte HTML-Code sieht wie folgt aus:

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

Im vorherigen Code gilt für die Formularübermittlung mittels POST Folgendes:

  • Bei gültigen Daten:

    • Die OnPostAsync-Handlermethode ruft die RedirectToPage-Hilfsmethode auf. RedirectToPage gibt eine Instanz von RedirectToPageResult zurück. RedirectToPage:

      • ist ein Aktionsergebnis.
      • ähnelt RedirectToAction oder RedirectToRoute (wird in Controllern und Ansichten verwendet).
      • ist an Seiten angepasst. Im vorhergehenden Beispiel leitet es an die Stammindexseite (/Index) weiter. Informationen zu RedirectToPage finden Sie im Abschnitt URL-Generierung für Seiten.
  • Bei Validierungsfehlern, die an den Server übermittelt werden:

    • Die OnPostAsync-Handlermethode ruft die Page-Hilfsmethode auf. Page gibt eine Instanz von PageResult zurück. Der Vorgang, bei dem Page zurückgegeben wird, ähnelt dem Vorgang, bei dem Aktionen im Controller View zurückgeben. PageResult ist der Standardrückgabetyp für eine Handlermethode. Eine Handlermethode, die void zurückgibt, rendert die Seite.
    • Wenn im vorangehenden Beispiel das Formular mithilfe von POST übermittelt und dabei kein Wert angegeben wird, gibt ModelState.IsValid „false“ zurück. In diesem Beispiel werden keine Validierungsfehler auf dem Client angezeigt. Die Verarbeitung von Validierungsfehlern wird weiter unten in diesem Artikel behandelt.
    [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");
    }
    
  • Bei Validierungsfehlern, die durch eine clientseitige Validierung erkannt werden:

    • Die Daten werden nicht per POST an den Server gesendet.
    • Die clientseitige Validierung wird weiter unten in diesem Artikel erläutert.

Die Eigenschaft Customer verwendet das [BindProperty]-Attribut, um die Modellbindung zu aktivieren:

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

[BindProperty] sollte nicht in Modellen mit Eigenschaften verwendet werden, die vom Client nicht geändert werden dürfen. Weitere Informationen finden Sie unter Overposting.

Razor Pages binden Eigenschaften standardmäßig nur an Nicht-GET-Verben. Durch die Bindung an Eigenschaften entfällt das Schreiben von Code, mit dem HTTP-Daten in den Modelltyp konvertiert werden. Die Bindung reduziert den Code mithilfe der gleichen Eigenschaft, um Formularfelder (<input asp-for="Customer.Name">) zu rendern und die Eingabe zu akzeptieren.

Warnung

Aus Sicherheitsgründen müssen Sie Daten von GET-Anforderungen in die Seitenmodelleigenschaften einbinden. Überprüfen Sie die Benutzereingaben, bevor Sie sie den Eigenschaften zuordnen. Die Verwendung der GET-Bindung ist von Vorteil, wenn Sie Szenarios behandeln, die von Abfragezeichenfolgen oder Routenwerten abhängig sind.

Legen Sie die SupportsGet-Eigenschaft des [BindProperty]-Attributs auf true fest, um eine Eigenschaft an GET-Anforderungen zu binden:

[BindProperty(SupportsGet = true)]

Weitere Informationen finden Sie unter ASP.NET Core Community Standup: Binden auf GET-Diskussion (YouTube).

Sehen Sie sich die Ansichtsdatei Pages/Customers/Create.cshtml an:

@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>
  • Im vorangehenden Code bindet das Eingabetag-Hilfsprogramm<input asp-for="Customer.Name" /> das HTML-Element <input> an den Modellausdruck Customer.Name.
  • @addTagHelper stellt Taghilfsprogramme zur Verfügung.

Die Homepage

Index.cshtml ist die Homepage:

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

Die zugeordnete PageModel-Klasse (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();
    }
}

Die Datei Index.cshtml enthält das folgende Markup:

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

Das <a /a>Anchor-Taghilfsprogramm verwendet das asp-route-{value}-Attribut, um einen Link zur Seite „Edit“ (Bearbeiten) zu generieren. Der Link enthält die Routendaten mit der Kontakt-ID. Beispielsweise https://localhost:5001/Edit/1. Taghilfsprogramme ermöglichen serverseitigem Code das Mitwirken am Erstellen und Rendern von HTML-Elementen in Razor-Dateien.

Die Datei Index.cshtml enthält das Markup zum Erstellen der Schaltfläche „delete“ (Löschen) für jeden Kundenkontakt:

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

Der gerenderte HTML-Code sieht wie folgt aus:

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

Wenn die „delete“-Schaltfläche in HTML gerendert wird, enthält das zugehörige formaction-Element Parameter für Folgendes:

  • die Kundenkontakt-ID, die durch das asp-route-id-Attribut angegeben wird
  • den handler, der durch das asp-page-handler-Attribut angegeben wird

Wenn die Schaltfläche ausgewählt wird, wird eine POST-Anforderung an den Server gesendet. Durch Konvention wird der Name der Handlermethode auf Grundlage des Werts des handler-Parameters gemäß dem Schema OnPost[handler]Async ausgewählt.

Da der handler in diesem Beispiel delete ist, wird die Handlermethode OnPostDeleteAsync verwendet, um die POST-Anforderung zu verarbeiten. Wenn asp-page-handler auf einen anderen Wert (z. B. remove) festgelegt wird, wird eine Handlermethode namens OnPostRemoveAsync ausgewählt.

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

Die OnPostDeleteAsync-Methode:

  • ruft die id der Abfragezeichenfolge ab.
  • Fragt mit FindAsync die Datenbank nach dem Kundenkontakt ab.
  • Wenn der Kundenkontakt gefunden wird, wird er entfernt, und die Datenbank wird aktualisiert.
  • Ruft RedirectToPage auf, um die Stammindexseite (/Index) umzuleiten.

Die Datei „Edit.cshtml“

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

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

<h1>Edit</h1>

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

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

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

Die erste Zeile enthält die @page "{id:int}"-Anweisung. Die Routingbeschränkung "{id:int}" weist die Seite an, die Anforderungen für die Seite zu akzeptieren, die int-Routingdaten enthalten. Wenn eine Anforderung an die Seite bestimmte Routingdaten nicht enthält, die in einen int konvertiert werden können, gibt die Runtime einen Fehler vom Typ „HTTP 404: Nicht gefunden“ zurück. Um die ID optional zu machen, fügen Sie ? an die Routeneinschränkung an:

@page "{id:int?}"

Die Datei Edit.cshtml.cs enthält Folgendes:

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

Validierung

Für Validierungsregeln gilt Folgendes:

  • Sie werden deklarativ in der Modellklasse angegeben.
  • Sie werden überall in der App erzwungen.

Der Namespace System.ComponentModel.DataAnnotations stellt eine Gruppe integrierter Validierungsattribute bereit, die deklarativ auf eine Klasse oder Eigenschaft angewendet werden. „DataAnnotations“ enthält auch Formatierungsattribute wie [DataType], die bei der Formatierung helfen und keinerlei Validierung bereitstellen.

Sehen Sie sich das Customer-Modell an:

using System.ComponentModel.DataAnnotations;

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

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

Für die folgende Ansichtsdatei Create.cshtml gilt Folgendes:

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

Der vorangehende Code:

  • umfasst jQuery-Skripts einschließlich solcher zur Validierung.

  • verwendet die Taghilfsprogramme<div /> und <span /> zur Aktivierung von Folgendem:

    • clientseitiger Validierung.
    • Rendering von Validierungsfehlern.
  • wird der folgende HTML-Code generiert:

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

Wenn Sie das Formular „Create“ (Erstellen) ohne einen Wert für den Namen mit POST übermitteln, wird die Fehlermeldung „The Name field is required“ (Für das Feld ‚Name‘ muss ein Wert angegeben werden) auf dem Formular angezeigt Wenn JavaScript auf dem Client aktiviert ist, zeigt der Browser den Fehler an, ohne dass Daten per POST an den Server gesendet werden.

Das [StringLength(10)]-Attribut generiert data-val-length-max="10" für den gerenderten HTML-Code. data-val-length-max verhindert, dass im Browser ein Wert eingegeben wird, der die angegebene Maximallänge überschreitet. Wenn ein Tool wie Fiddler zum Bearbeiten und erneuten Senden einer POST-Anforderung verwendet wird

  • und die Länge des Namens 10 Zeichen überschreitet,
  • geschieht Folgendes: Die Fehlermeldung „The field Name must be a string with a maximum length of 10“ (Das Namensfeld muss eine Zeichenfolge mit maximal 10 Zeichen enthalten) wird zurückgegeben.

Sehen Sie sich das folgende Movie-Modell an:

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

Die Validierungsattribute geben das Verhalten an, das für die Modelleigenschaften erzwungen werden soll:

  • Die Attribute Required und MinimumLength geben an, dass eine Eigenschaft einen Wert haben muss. Ein Benutzer kann allerdings ein Leerzeichen eingeben, um diese Anforderung zu erfüllen.

  • Das Attribut RegularExpression wird verwendet, um einzuschränken, welche Zeichen eingegeben werden dürfen. Für „Genre“ im Code oben gilt Folgendes:

    • Es dürfen nur Buchstaben enthalten sein.
    • Der erste Buchstabe muss ein Großbuchstabe sein. Leerzeichen, Zahlen und Sonderzeichen sind nicht zulässig.
  • Für RegularExpression-„Rating“ (Bewertung) gilt Folgendes:

    • Das erste Zeichen muss ein Großbuchstabe sein.
    • Sonderzeichen und Zahlen sind als darauffolgende Zeichen zulässig. „PG-13“ ist als Bewertung („Rating“) gültig, nicht jedoch als „Genre“.
  • Das Attribut Range schränkt einen Wert auf einen bestimmten Bereich ein.

  • Mit dem Attribut StringLength kann die maximale Länge einer Zeichenfolgeneigenschaft und optional die minimale Länge festlegt werden.

  • Werttypen (wie decimal, int, float, DateTime) sind grundsätzlich erforderlich und benötigen nicht das Attribut [Required].

Auf der Seite „Create“ (Erstellen) für das Movie-Modell werden ungültige Werte und die daraus resultierenden Fehler angezeigt:

Ansichtsformular „Movie“ mit mehreren clientseitigen jQuery-Validierungsfehlern

Weitere Informationen finden Sie unter:

CSS-Isolation

Isolieren Sie zur Reduzierung oder Vermeidung CSS-Formatvorlagen auf einzelne Seiten, Ansichten und Komponenten:

  • Abhängigkeiten von globalen Stilen, die eine Herausforderung darstellen können
  • Formatkonflikte in geschachtelten Inhalten

Um eine bereichsbezogene CSS-Datei für eine Seite oder Ansicht hinzuzufügen, platzieren Sie die CSS-Stile in der Begleitdatei .cshtml.css, die dem Namen der .cshtml-Datei entspricht. Im folgenden Beispiel liefert eine Index.cshtml.css-Datei CSS-Stile, die nur auf die Index.cshtml-Seite oder -Ansicht angewendet werden.

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

h1 {
    color: red;
}

Die CSS-Isolation erfolgt zum Zeitpunkt der Erstellung. Das Framework schreibt CSS-Selektoren so um, dass sie mit Markup übereinstimmen, das von den Seiten oder Ansichten der App gerendert wird. Die umgeschriebenen CSS-Stile werden gebündelt und als statisches Objekt ({APP ASSEMBLY}.styles.css) erzeugt. Der Platzhalter {APP ASSEMBLY} ist der Assemblyname des Projekts. Ein Link zu den gebündelten CSS-Stilen wird im Layout der App platziert.

Im Inhalt von <head> der Datei Pages/Shared/_Layout.cshtml (Razor Pages) oder Views/Shared/_Layout.cshtml (MVC) der App fügen Sie den Link zu den gebündelten CSS-Styles hinzu oder bestätigen ihn:

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

Im folgenden Beispiel lautet der Assemblyname der App WebApp:

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

Die in einer bereichsbezogenen CSS-Datei definierten Stile werden nur auf die gerenderte Ausgabe der entsprechenden Datei angewendet. Im vorherigen Beispiel stehen alle CSS-Deklarationen von h1, die an anderer Stelle in der App definiert sind, nicht in Konflikt mit dem Überschriftenstil von Index. CSS- und Vererbungsregeln bleiben für bereichsbezogene CSS-Dateien gültig. Beispielsweise überschreiben Stile, die direkt auf ein <h1>-Element in der Index.cshtml-Datei angewendet werden, die Stile der bereichsbezogenen CSS-Datei in Index.cshtml.css.

Hinweis

Um die Isolation des CSS-Stils beim Bündeln zu gewährleisten, wird der Import von CSS in Razor-Codeblöcken nicht unterstützt.

CSS-Isolation gilt nur für HTML-Elemente. CSS-Isolation wird für Taghilfsprogramme nicht unterstützt.

Innerhalb der gebündelten CSS-Datei ist jede Seite, Ansicht oder Razor-Komponente mit einem Bereichsbezeichner im Format b-{STRING} verknüpft, wobei der Platzhalter {STRING} eine zehnstellige Zeichenfolge ist, die vom Framework generiert wird. Das folgende Beispiel liefert den Stil für das vorherige <h1>-Element der Seite Index einer Razor Pages-App:

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

Auf der Seite Index, auf der der CSS-Stil aus der gebündelten Datei angewendet wird, wird der Bereichsbezeichner als HTML-Attribut angefügt:

<h1 b-3xxtam6d07>

Der Bezeichner ist für eine App eindeutig. Zum Zeitpunkt der Erstellung wird ein Projektbündel mit der Konvention {STATIC WEB ASSETS BASE PATH}/Project.lib.scp.css erstellt, wobei der Platzhalter {STATIC WEB ASSETS BASE PATH} der statische Basispfad für Webressourcen ist.

Wenn andere Projekte verwendet werden, z. B. NuGet-Pakete oder Razor-Klassenbibliotheken, gilt für die gebündelte Datei Folgendes:

  • Verweise auf Stile erfolgen mithilfe von CSS-Importen.
  • Sie wird nicht als statische Webressource der App veröffentlicht, die die Stile verwendet.

Unterstützung für CSS-Präprozessoren

CSS-Präprozessoren tragen zur Verbesserung der CSS-Entwicklung bei, indem sie Features wie Variablen, Schachtelung, Module, Mischung und Vererbung nutzen. CSS-Isolation unterstützt CSS-Präprozessoren wie Sass oder Less zwar nicht nativ, dennoch können CSS-Präprozessoren nahtlos integriert werden, sofern die Kompilierung des Präprozessors erfolgt, bevor das Framework die CSS-Selektoren während des Buildprozesses umschreibt. Konfigurieren Sie z. B. mithilfe von Visual Studio die vorhandene Präprozessorkompilierung als Aufgabe Vor Build im Aufgabenausführungs-Explorer von Visual Studio.

Viele NuGet-Pakete von Drittanbietern wie z. B. Delegate.SassBuilder, können die SASS-/SCSS-Dateien am Anfang des Buildprozesses – noch vor der CSS-Isolation – kompilieren, ohne dass eine Konfiguration erforderlich ist.

Konfiguration der CSS-Isolation

CSS-Isolation ermöglicht die Konfiguration einiger fortgeschrittener Szenarien, z. B. wenn Abhängigkeiten von vorhandenen Tools oder Workflows bestehen.

Anpassen des Formats von Bereichsbezeichnern

In diesem Abschnitt ist der Platzhalter {Pages|Views} entweder Pages für Razor Pages-Apps oder Views für MVC-Apps.

Standardmäßig verwenden Bereichsbezeichner das Format b-{STRING}, wobei der Platzhalter {STRING} eine vom Framework generierte Zeichenfolge mit zehn Zeichen ist. Um das Format der Bereichsbezeichner anzupassen, aktualisieren Sie die Projektdatei mit dem gewünschten Muster:

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

Im vorherigen Beispiel ändert der für Index.cshtml.css generierte CSS-Code seinen Bereichsbezeichner von b-{STRING} in custom-scope-identifier.

Verwenden Sie Bereichsbezeichner, um eine Vererbung mit bereichsbezogenen CSS-Dateien zu erzielen. Im folgenden Beispiel für eine Projektdatei enthält eine BaseView.cshtml.css-Datei allgemeine Stile für Ansichten. Eine DerivedView.cshtml.css-Datei erbt diese Stile.

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

Verwenden Sie den Platzhalteroperator (*), um Bereichsbezeichner für mehrere Dateien freizugeben:

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

Ändern des Basispfads für statische Webressourcen

Die bereichsbezogene CSS-Datei wird im Stammverzeichnis der App generiert. Verwenden Sie in der Projektdatei die StaticWebAssetBasePath-Eigenschaft, um den Standardpfad zu ändern. Im folgenden Beispiel werden die bereichsbezogene CSS-Datei und die restlichen Ressourcen der App unter dem Pfad _content gespeichert:

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

Deaktivieren der automatischen Bündelung

Wenn Sie das Verhalten des Frameworks beim Veröffentlichen und Laden bereichsbezogener Dateien zur Laufzeit ändern möchten, verwenden Sie die DisableScopedCssBundling-Eigenschaft. Die Verwendung dieser Eigenschaft bedeutet, dass die isolierten CSS-Dateien durch andere Tools oder Prozesse aus dem Verzeichnis obj übernommen und zur Laufzeit veröffentlicht und geladen werden:

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

Unterstützung für Razor-Klassenbibliothek (RCL)

Wenn eine Razor-Klassenbibliothek (RCL) isolierte Stile bereitstellt, zeigt das href-Attribut des <link>-Tags auf {STATIC WEB ASSET BASE PATH}/{PACKAGE ID}.bundle.scp.css, wobei die Platzhalter für Folgendes stehen:

  • {STATIC WEB ASSET BASE PATH}: Basispfad der statischen Webressource
  • {PACKAGE ID}: Der Paketbezeichner der Bibliothek. Der Paketbezeichner ist standardmäßig der Assemblyname des Projekts, wenn der Paketbezeichner nicht in der Projektdatei angegeben ist.

Im folgenden Beispiel:

  • Der Basispfad der statischen Webressource lautet _content/ClassLib.
  • Der Assemblyname der Klassenbibliothek lautet ClassLib.

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

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

Weitere Informationen zu RCLs finden Sie in den folgenden Artikeln:

Informationen zur CSS-Isolation in Blazor finden Sie unter CSS-Isolation in Blazor in ASP.NET Core.

Verarbeiten von HEAD-Anforderungen mit einem OnGet-Handlerfallback

HEAD-Anforderungen ermöglichen das Abrufen der Header für eine bestimmte Ressource. Im Gegensatz zu GET-Anforderungen geben HEAD-Anforderungen keinen Antworttext zurück.

Normalerweise wird ein OnHead-Handler erstellt und für HEAD-Anforderungen aufgerufen:

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

Razor Pages ruft den OnGet-Handler auf, wenn kein OnHead-Handler definiert ist.

XSRF/CSRF und Razor Pages

Razor Pages wird durch Validierungsmaßnahmen vor XSRF/CSRF-Angriffen geschützt. Das Formulartag-Hilfsprogramm injiziert Anti-XSRF/CSRF-Token in HTML-Formularelemente.

Verwenden von Layouts, Teilansichten, Vorlagen und Taghilfsprogrammen mit Razor Pages

Razor Pages beinhaltet alle Funktionen der Razor-Anzeige-Engine. Layouts, Teilansichten, Vorlagen, Taghilfsprogramme, _ViewStart.cshtml und _ViewImports.cshtml funktionieren auf die gleiche Weise wie für herkömmliche Razor-Ansichten.

Strukturieren Sie diese Seite mit einigen dieser praktischen Funktionen.

Fügen Sie der Pages/Shared/_Layout.cshtml eine Layoutseite hinzu:

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

Das Layout:

  • Steuert das Layout der einzelnen Seiten, es sei denn, das Layout wird für eine Seite deaktiviert.
  • Importiert HTML-Strukturen, z.B. JavaScript und Stylesheets.
  • Der Inhalt der Razor-Seite wird gerendert, wenn @RenderBody() aufgerufen wird.

Weitere Informationen finden Sie unter Layoutseite.

Die Eigenschaft Layout wird in Pages/_ViewStart.cshtml festgelegt:

@{
    Layout = "_Layout";
}

Das Layout befindet sich im Ordner Pages/Shared. Seiten suchen hierarchisch nach anderen Ansichten (Layouts, Vorlagen oder Teilansichten) und beginnen im gleichen Ordner wie die aktuelle Seite. Ein Layout im Ordner Pages/Shared kann von jeder Razor-Seite aus unter dem Ordner Pages verwendet werden.

Die Layoutdatei sollte im Ordner Pages/Shared gespeichert werden.

Wir empfehlen Ihnen, die Layoutdatei nicht im Ordner Views/Shared (Ansichten/Freigegeben) zu platzieren. Views/Shared ist ein MVC-Ansichtsmuster. Razor Pages basieren auf der Ordnerhierarchie, nicht auf Pfadkonventionen.

Die Ansichtensuche in einer Razor Page enthält den Ordner Pages. Die Layouts, Vorlagen und Teilansichten, die mit MVC-Controllern und herkömmlichen Razor-Ansichten verwendet werden, funktionieren problemlos.

Fügen Sie eine Pages/_ViewImports.cshtml-Datei hinzu:

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

@namespace wird weiter unten im Tutorial erläutert. Die @addTagHelper-Anweisung bringt die integrierten Taghilfsprogramme zu allen Seiten in der Ordner Pages.

Die @namespace-Anweisung wird wie folgt für eine Seite festgelegt:

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

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

Die @namespace-Anweisung legt den Namespace für die Seite fest. Die @model-Anweisung muss den Namespace nicht enthalten.

Wenn sich die @namespace-Anweisung in _ViewImports.cshtml befindet, stellt der angegebene Namespace das Präfix für den generierten Namespace auf der Seite bereit, die die @namespace-Anweisung importiert. Der Rest der generierten Namespaces (der Suffixteil) ist der durch Punkte getrennte relative Pfad zwischen dem Ordner mit _ViewImports.cshtml und dem Ordner, der die Seite enthält.

Die PageModel-Klasse Pages/Customers/Edit.cshtml.cs legt den Namespace explizit fest:

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

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

        // Code removed for brevity.

Die Datei Pages/_ViewImports.cshtml legt den folgenden Namespace fest:

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

Der generierte Namespace für die Pages/Customers/Edit.cshtmlRazor-Seite ist identisch mit der PageModel-Klasse.

@namespacefunktioniert auch mit konventionellen Razor-Ansichten.

Sehen Sie sich die Ansichtsdatei Pages/Customers/Create.cshtml an:

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

Die aktualisierte Ansichtsdatei Pages/Customers/Create.cshtml mit _ViewImports.cshtml und der vorherigen Layoutdatei sieht wie folgt aus:

@page
@model CreateModel

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

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

Im vorangehenden Code werden von _ViewImports.cshtml der Namespace und die Taghilfsprogramme importiert. Die JavaScript-Dateien werden von der Layoutdatei importiert.

Das Razor Pages-Startprojekt enthält die Seite Pages/_ValidationScriptsPartial.cshtml, die die clientseitige Validierung bindet.

Weitere Informationen zu Teilansichten finden Sie unter Teilansichten in ASP.NET Core.

URL-Generierung für Seiten

Die zuvor gezeigte Create-Seite verwendet 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");
    }
}

Die App hat die folgende Datei/Ordner-Struktur:

  • /Pages

    • Index.cshtml

    • Privacy.cshtml

    • /Customers

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

Die Seiten Pages/Customers/Create.cshtml und Pages/Customers/Edit.cshtml führen bei Erfolg eine Umleitung zu Pages/Customers/Index.cshtml durch. Die Zeichenfolge ./Index stellt einen relativen Seitennamen dar, der für den Zugriff auf die vorherige Seite verwendet wird. Sie wird für das Generieren von URIs für die Seite Pages/Customers/Index.cshtml verwendet. Beispiel:

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

Der absolute Seitenname /Index wird zum Generieren der URLs für die Seite Pages/Index.cshtml verwendet. Zum Beispiel:

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

Der Seitenname ist der Pfad zu der Seite vom Stammordner /Pages (einschließlich eines vorangestellten /, z.B. /Index). Die oben stehenden Beispiele für eine URL-Generierung bieten erweiterte Optionen und Funktionen, durch die Sie URLs nicht mehr hartcodieren müssen. Bei der URL-Generierung wird Routing verwendet. Außerdem können damit Parameter generiert und entsprechend der Definition der Route im Zielpfad codiert werden.

Die URL-Generierung für Seiten unterstützt relative Namen. In der folgenden Tabelle wird dargestellt, welche Indexseite durch verschiedene RedirectToPage-Parameter in Pages/Customers/Create.cshtml ausgewählt wird.

RedirectToPage(x) Seite
RedirectToPage("/Index") Pages/Index
RedirectToPage("./Index"); Pages/Customers/Index
RedirectToPage("../Index") Pages/Index
RedirectToPage("Index") Pages/Customers/Index

RedirectToPage("Index"), RedirectToPage("./Index") und RedirectToPage("../Index") sind relative Namen. Der RedirectToPage-Parameter wird mit dem Pfad der aktuellen Seite kombiniert, um den Namen der Zielseite zu berechnen.

Das Verknüpfen relativer Namen eignet sich beim Erstellen von Websites mit einer komplexen Struktur. Wenn durch relative Namen Seiten in einem Ordner verknüpft werden, hat das folgende Vorteile:

  • Relative Links funktionieren weiterhin, wenn ein Ordner umbenannt wird.
  • Links funktionieren weiterhin, da sie keinen Ordnernamen enthalten.

Um auf eine Seite in einem anderen Bereich umzuleiten, geben Sie den Bereich an:

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

Weitere Informationen finden Sie unter Bereiche in ASP.NET Core und Routen- und App-Konventionen für Razor Pages in ASP.NET Core.

Attribut „ViewData“

Daten können mit ViewDataAttribute an eine Seite übermittelt werden. Für Eigenschaften mit dem [ViewData]-Attribut werden die Werte in ViewDataDictionary gespeichert und daraus geladen.

Im folgenden Beispiel wendet AboutModel das [ViewData]-Attribut auf die Title-Eigenschaft an:

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

    public void OnGet()
    {
    }
}

Greifen Sie auf der Infoseite auf die Eigenschaft Title als Modelleigenschaft zu:

<h1>@Model.Title</h1>

Im Layout wird der Titel aus dem ViewData-Wörterbuch gelesen:

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

TempData

ASP.NET Core macht TempData verfügbar. Diese Eigenschaft speichert Daten, bis sie gelesen wurden. Die Methoden Keep und Peek können verwendet werden, um die Daten zu überprüfen, ohne sie zu löschen. TempData eignet sich für die Umleitung, wenn Daten für mehr als eine Anforderung benötigt werden.

Im folgenden Code wird der Wert von Message mit TempData festgelegt:

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

Das folgende Markup in der Datei Pages/Customers/Index.cshtml zeigt den Wert von Message mit TempData an.

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

Das Seitenmodell Pages/Customers/Index.cshtml.cs wendet das [TempData]-Attribut auf die Eigenschaft Message an.

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

Weitere Informationen finden Sie unter TempData.

Mehrere Handler pro Seite

Die folgende Seite generiert mit dem asp-page-handler-Taghilfsprogramm Markup für zwei 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>

Das Formular im vorherigen Beispiel hat zwei Sendeschaltflächen, und jede verwendet FormActionTagHelper, um an eine andere URL zu übermitteln. Das asp-page-handler-Attribut ist eine Ergänzung für asp-page. asp-page-handler generiert URLs, die als Übermittlungsziel jeweils die durch eine Seite festgelegte Handlermethode verwenden. asp-page wird nicht angegeben, weil das Beispiel mit der aktuellen Seite verknüpft.

Das Seitenmodell:

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

Der vorherige Code verwendet benannte Handlermethoden. Benannte Handlermethoden werden aus dem Text im Namen nach On<HTTP Verb> und vor Async (falls vorhanden) erstellt. Im vorherigen Beispiel sind OnPostJoinListAsync und OnPostJoinListUCAsync die Seitenmethoden. Wenn Sie OnPost und Async entfernen, lauten die Handlernamen JoinList und JoinListUC.

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

Mit dem vorherigen Code lautet der URL-Pfad, der an OnPostJoinListAsync übermittelt, https://localhost:5001/Customers/CreateFATH?handler=JoinList. Der URL-Pfad, der an OnPostJoinListUCAsync übermittelt, lautet https://localhost:5001/Customers/CreateFATH?handler=JoinListUC.

Benutzerdefinierte Routen

Verwenden Sie die @page-Anweisung für Folgendes:

  • Das Angeben einer benutzerdefinierten Route zu einer Seite. Die Route zur Seite „Info“ kann mit @page "/Some/Other/Path" beispielsweise auf /Some/Other/Path festgelegt werden.
  • Das Anfügen von Segmenten an die Standardroute einer Seite. Mit @page "item" kann beispielsweise ein item-Segment an die Standardroute der Seite angefügt werden.
  • Das Anfügen von Parametern an die Standardroute einer Seite. Mit @page "{id}" kann beispielsweise ein ID-Parameter (id) für eine Seite angefordert werden.

Es wird ein relativer Pfad zum Stamm unterstützt, der durch eine Tilde (~) festgelegt wird. @page "~/Some/Other/Path" entspricht beispielsweise @page "/Some/Other/Path".

Wenn Sie nicht möchten, dass die Abfragezeichenfolge ?handler=JoinList in der URL enthalten ist, ändern Sie die Route so, dass der Handlername im Pfadteil der URL eingefügt wird. Sie können die Route anpassen, indem Sie nach der @page-Anweisung eine Routenvorlage in doppelten Anführungszeichen hinzufügen.

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

Mit dem vorherigen Code lautet der URL-Pfad, der an OnPostJoinListAsync übermittelt, https://localhost:5001/Customers/CreateFATH/JoinList. Der URL-Pfad, der an OnPostJoinListUCAsync übermittelt, lautet https://localhost:5001/Customers/CreateFATH/JoinListUC.

Das ? nach handler bedeutet, dass der Routenparameter optional ist.

Erweiterte Konfigurationen und Einstellungen

Die Konfigurationen und Einstellungen in den folgenden Abschnitten sind für die meisten Apps nicht erforderlich.

Verwenden Sie die Überladung AddRazorPages, die RazorPagesOptions konfiguriert, um die erweiterten Optionen zu konfigurieren:

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

Verwenden Sie RazorPagesOptions, um das Stammverzeichnis für Seiten festzulegen oder Anwendungsmodellkonventionen für Seiten hinzuzufügen. Weitere Informationen zu Konventionen finden Sie unter Razor Pages-Autorisierungskonventionen.

Informationen zum Vorkompilieren von Ansichten finden Sie unter Razor-Ansichtenkompilierung.

Festlegen des Inhaltsstammverzeichnisses für Razor Pages

Standardmäßig lautet das Stammverzeichnis für Razor Pages /Pages. Fügen Sie WithRazorPagesAtContentRoot hinzu, um anzugeben, dass sich Ihre Razor-Seiten im Inhaltsstammverzeichnis (ContentRootPath) der App befinden:

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

Festlegen eines benutzerdefinierten Stammverzeichnisses für Razor Pages

Fügen Sie WithRazorPagesRoot hinzu, um anzugeben, dass sich Ihre Razor-Seiten in einem benutzerdefinierten Stammverzeichnis der App befinden. Geben Sie dabei einen relativen Pfad an:

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

Zusätzliche Ressourcen

Erstellen eines Razor Pages-Projekts

Ausführliche Informationen zum Erstellen eines Razor Pages-Projekts finden Sie unter Erste Schritte mit Razor Pages.

Razor Pages

Razor Pages ist in Startup.cs aktiviert:

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

Sehen Sie sich diese einfache Seite an:

@page

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

Der vorherige Code ähnelt sehr einer Razor-Ansichtsdatei, die in einer ASP.NET Core-App mit Controllern und Ansichten verwendet wird. Der Unterschied besteht in der @page-Anweisung. @page macht die Datei zu einer MVC-Aktion, d.h. dass Anfragen direkt ohne einen Controller verarbeitet werden. @page muss die erste Razor-Anweisung auf einer Seite sein. @page wirkt sich auf das Verhalten aller anderen Razor-Konstrukte aus. Razor Pages-Dateinamen haben das Suffix .cshtml.

Eine ähnliche Seite, die die PageModel-Klasse verwendet, wird in den folgenden zwei Dateien angezeigt. Die Datei Pages/Index2.cshtml enthält Folgendes:

@page
@using RazorPagesIntro.Pages
@model Index2Model

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

Das Pages/Index2.cshtml.cs Page-Modell:

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

Die PageModel-Klassendatei hat standardmäßig den gleichen Namen wie die Datei mit Razor Pages, nur dass außerdem .cs angefügt wird. Die vorherige Datei mit Razor Page lautet beispielsweise Pages/Index2.cshtml. Die Datei mit der PageModel-Klasse heißt Pages/Index2.cshtml.cs.

Die Zuordnungen von URL-Pfaden zu Seiten werden durch den Speicherort der Seite im Dateisystem bestimmt. Die folgende Tabelle zeigt einen Pfad zu Razor Pages und die entsprechende URL:

Dateiname und Pfad Entsprechende URL
/Pages/Index.cshtml / oder /Index
/Pages/Contact.cshtml /Contact
/Pages/Store/Contact.cshtml /Store/Contact
/Pages/Store/Index.cshtml /Store oder /Store/Index

Notizen:

  • Die Runtime sucht standardmäßig im Ordner Pages (Seiten) nach Dateien mit RRazor Pages.
  • Wenn eine Seite nicht in einer URL enthalten ist, ist Index die Standardseite.

Schreiben eines einfachen Formulars

Razor Pages ist darauf ausgelegt, allgemeine Muster, die mit Webbrowsern verwendet werden können, beim Erstellen einer App leichter implementieren zu können. Die Modellbindung, Taghilfsprogramme und alle HTML-Hilfsprogramme funktionieren nur mit den Eigenschaften, die in einer Klasse der Razor Pages definiert wurden. Nehmen wir z.B. eine Seite, die ein allgemeines Kontaktformular für das Contact-Modell implementiert:

Für die Beispiele in diesem Dokument wird DbContext in der Datei Startup.cs initialisiert.

Für die In-Memory-Datenbank ist das NuGet-Paket Microsoft.EntityFrameworkCore.InMemory erforderlich.

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

Das Datenmodell:

using System.ComponentModel.DataAnnotations;

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

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

Der db-Kontext:

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

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

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

Die PageModel -Klasse heißt standardmäßig <PageName>Model und befindet sich im selben Namespace wie die Seite.

Mit der Klasse PageModel kann die Logik einer Seite von deren Darstellung getrennt werden. Sie definiert Seitenhandler für Anforderungen, die an die Seite geschickt wurden, und für zum Rendern der Seite verwendete Daten. Diese Trennung ermöglicht Folgendes:

Die Seite hat eine OnPostAsync-Handlermethode, die auf POST-Anforderungen ausgeführt wird (wenn ein Benutzer das Formular sendet). Für alle HTTP-Verben können Handlermethoden hinzugefügt werden. Die am häufigsten verwendeten Handler sind:

  • OnGet, um den für eine Seite erforderlichen Status zu initialisieren. Im vorangehenden Code wird die CreateModel.cshtmlRazor-Seite durch die OnGet-Methode dargestellt.
  • OnPost, um Formularübermittlungen zu behandeln

Das Namenssuffix Async ist optional. Es wird jedoch standardmäßig häufig für asynchrone Funktionen verwendet. Der vorhergehende Code ist typisch für Razor Pages.

Wenn Sie mit ASP.NET-Apps vertraut sind, die Controller und Ansichten verwenden, werden Ihnen folgende Fakten bekannt vorkommen:

  • Der OnPostAsync-Code im vorangehenden Beispiel ähnelt dem typischen Controllercode.
  • Die meisten primitiven MVC-Typen wie solche für Modellbindungen, Validierungen und Aktionsergebnisse werden in Controllern und Razor Pages auf dieselbe Weise eingesetzt.

Die vorherige OnPostAsync-Methode:

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

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

    return RedirectToPage("./Index");
}

Der grundlegende Ablauf von OnPostAsync:

Prüfen auf Validierungsfehler

  • Wenn keine Fehler vorliegen, werden die Daten gespeichert und weitergeleitet.
  • Wenn es Fehler gibt, zeigen Sie die Seite erneut mit den Validierungsmeldungen an. denn Validierungsfehler werden oftmals auf dem Client erkannt und nie an den Server übermittelt.

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

Der von Pages/Create.cshtml gerenderte HTML-Code sieht wie folgt aus:

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

Im vorherigen Code gilt für die Formularübermittlung mittels POST Folgendes:

  • Bei gültigen Daten:

    • Die OnPostAsync-Handlermethode ruft die RedirectToPage-Hilfsmethode auf. RedirectToPage gibt eine Instanz von RedirectToPageResult zurück. RedirectToPage:

      • ist ein Aktionsergebnis.
      • ähnelt RedirectToAction oder RedirectToRoute (wird in Controllern und Ansichten verwendet).
      • ist an Seiten angepasst. Im vorhergehenden Beispiel leitet es an die Stammindexseite (/Index) weiter. Informationen zu RedirectToPage finden Sie im Abschnitt URL-Generierung für Seiten.
  • Bei Validierungsfehlern, die an den Server übermittelt werden:

    • Die OnPostAsync-Handlermethode ruft die Page-Hilfsmethode auf. Page gibt eine Instanz von PageResult zurück. Der Vorgang, bei dem Page zurückgegeben wird, ähnelt dem Vorgang, bei dem Aktionen im Controller View zurückgeben. PageResult ist der Standardrückgabetyp für eine Handlermethode. Eine Handlermethode, die void zurückgibt, rendert die Seite.
    • Wenn im vorangehenden Beispiel das Formular mithilfe von POST übermittelt und dabei kein Wert angegeben wird, gibt ModelState.IsValid „false“ zurück. In diesem Beispiel werden keine Validierungsfehler auf dem Client angezeigt. Die Verarbeitung von Validierungsfehlern wird weiter unten in diesem Artikel behandelt.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }
    
        _context.Customers.Add(Customer);
        await _context.SaveChangesAsync();
    
        return RedirectToPage("./Index");
    }
    
  • Bei Validierungsfehlern, die durch eine clientseitige Validierung erkannt werden:

    • Die Daten werden nicht per POST an den Server gesendet.
    • Die clientseitige Validierung wird weiter unten in diesem Artikel erläutert.

Die Eigenschaft Customer verwendet das [BindProperty]-Attribut, um die Modellbindung zu aktivieren:

public class CreateModel : PageModel
{
    private readonly CustomerDbContext _context;

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

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

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

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

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

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

[BindProperty] sollte nicht in Modellen mit Eigenschaften verwendet werden, die vom Client nicht geändert werden dürfen. Weitere Informationen finden Sie unter Overposting.

Razor Pages binden Eigenschaften standardmäßig nur an Nicht-GET-Verben. Durch die Bindung an Eigenschaften entfällt das Schreiben von Code, mit dem HTTP-Daten in den Modelltyp konvertiert werden. Die Bindung reduziert den Code mithilfe der gleichen Eigenschaft, um Formularfelder (<input asp-for="Customer.Name">) zu rendern und die Eingabe zu akzeptieren.

Warnung

Aus Sicherheitsgründen müssen Sie Daten von GET-Anforderungen in die Seitenmodelleigenschaften einbinden. Überprüfen Sie die Benutzereingaben, bevor Sie sie den Eigenschaften zuordnen. Die Verwendung der GET-Bindung ist von Vorteil, wenn Sie Szenarios behandeln, die von Abfragezeichenfolgen oder Routenwerten abhängig sind.

Legen Sie die SupportsGet-Eigenschaft des [BindProperty]-Attributs auf true fest, um eine Eigenschaft an GET-Anforderungen zu binden:

[BindProperty(SupportsGet = true)]

Weitere Informationen finden Sie unter ASP.NET Core Community Standup: Binden auf GET-Diskussion (YouTube).

Sehen Sie sich die Ansichtsdatei Pages/Create.cshtml an:

@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>
  • Im vorangehenden Code bindet das Eingabetag-Hilfsprogramm<input asp-for="Customer.Name" /> das HTML-Element <input> an den Modellausdruck Customer.Name.
  • @addTagHelper stellt Taghilfsprogramme zur Verfügung.

Die Homepage

Index.cshtml ist die Homepage:

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

Die zugeordnete PageModel-Klasse (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();
    }
}

Die Datei Index.cshtml enthält das folgende Markup:

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

Das <a /a>Anchor-Taghilfsprogramm verwendet das asp-route-{value}-Attribut, um einen Link zur Seite „Edit“ (Bearbeiten) zu generieren. Der Link enthält die Routendaten mit der Kontakt-ID. Beispielsweise https://localhost:5001/Edit/1. Taghilfsprogramme ermöglichen serverseitigem Code das Mitwirken am Erstellen und Rendern von HTML-Elementen in Razor-Dateien.

Die Datei Index.cshtml enthält das Markup zum Erstellen der Schaltfläche „delete“ (Löschen) für jeden Kundenkontakt:

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

Der gerenderte HTML-Code sieht wie folgt aus:

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

Wenn die „delete“-Schaltfläche in HTML gerendert wird, enthält das zugehörige formaction-Element Parameter für Folgendes:

  • die Kundenkontakt-ID, die durch das asp-route-id-Attribut angegeben wird
  • den handler, der durch das asp-page-handler-Attribut angegeben wird

Wenn die Schaltfläche ausgewählt wird, wird eine POST-Anforderung an den Server gesendet. Durch Konvention wird der Name der Handlermethode auf Grundlage des Werts des handler-Parameters gemäß dem Schema OnPost[handler]Async ausgewählt.

Da der handler in diesem Beispiel delete ist, wird die Handlermethode OnPostDeleteAsync verwendet, um die POST-Anforderung zu verarbeiten. Wenn asp-page-handler auf einen anderen Wert (z. B. remove) festgelegt wird, wird eine Handlermethode namens OnPostRemoveAsync ausgewählt.

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

Die OnPostDeleteAsync-Methode:

  • ruft die id der Abfragezeichenfolge ab.
  • Fragt mit FindAsync die Datenbank nach dem Kundenkontakt ab.
  • Wenn der Kundenkontakt gefunden wird, wird er entfernt, und die Datenbank wird aktualisiert.
  • Ruft RedirectToPage auf, um die Stammindexseite (/Index) umzuleiten.

Die Datei „Edit.cshtml“

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


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

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

Die erste Zeile enthält die @page "{id:int}"-Anweisung. Die Routingbeschränkung "{id:int}" weist die Seite an, die Anforderungen für die Seite zu akzeptieren, die int-Routingdaten enthalten. Wenn eine Anforderung an die Seite bestimmte Routingdaten nicht enthält, die in einen int konvertiert werden können, gibt die Runtime einen Fehler vom Typ „HTTP 404: Nicht gefunden“ zurück. Um die ID optional zu machen, fügen Sie ? an die Routeneinschränkung an:

@page "{id:int?}"

Die Datei Edit.cshtml.cs enthält Folgendes:

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

}

Validierung

Für Validierungsregeln gilt Folgendes:

  • Sie werden deklarativ in der Modellklasse angegeben.
  • Sie werden überall in der App erzwungen.

Der Namespace System.ComponentModel.DataAnnotations stellt eine Gruppe integrierter Validierungsattribute bereit, die deklarativ auf eine Klasse oder Eigenschaft angewendet werden. „DataAnnotations“ enthält auch Formatierungsattribute wie [DataType], die bei der Formatierung helfen und keinerlei Validierung bereitstellen.

Sehen Sie sich das Customer-Modell an:

using System.ComponentModel.DataAnnotations;

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

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

Für die folgende Ansichtsdatei Create.cshtml gilt Folgendes:

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

Der vorangehende Code:

  • umfasst jQuery-Skripts einschließlich solcher zur Validierung.

  • verwendet die Taghilfsprogramme<div /> und <span /> zur Aktivierung von Folgendem:

    • clientseitiger Validierung.
    • Rendering von Validierungsfehlern.
  • wird der folgende HTML-Code generiert:

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

Wenn Sie das Formular „Create“ (Erstellen) ohne einen Wert für den Namen mit POST übermitteln, wird die Fehlermeldung „The Name field is required“ (Für das Feld ‚Name‘ muss ein Wert angegeben werden) auf dem Formular angezeigt Wenn JavaScript auf dem Client aktiviert ist, zeigt der Browser den Fehler an, ohne dass Daten per POST an den Server gesendet werden.

Das [StringLength(10)]-Attribut generiert data-val-length-max="10" für den gerenderten HTML-Code. data-val-length-max verhindert, dass im Browser ein Wert eingegeben wird, der die angegebene Maximallänge überschreitet. Wenn ein Tool wie Fiddler zum Bearbeiten und erneuten Senden einer POST-Anforderung verwendet wird

  • und die Länge des Namens 10 Zeichen überschreitet,
  • geschieht Folgendes: Die Fehlermeldung „The field Name must be a string with a maximum length of 10“ (Das Namensfeld muss eine Zeichenfolge mit maximal 10 Zeichen enthalten) wird zurückgegeben.

Sehen Sie sich das folgende Movie-Modell an:

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

Die Validierungsattribute geben das Verhalten an, das für die Modelleigenschaften erzwungen werden soll:

  • Die Attribute Required und MinimumLength geben an, dass eine Eigenschaft einen Wert haben muss. Ein Benutzer kann allerdings ein Leerzeichen eingeben, um diese Anforderung zu erfüllen.

  • Das Attribut RegularExpression wird verwendet, um einzuschränken, welche Zeichen eingegeben werden dürfen. Für „Genre“ im Code oben gilt Folgendes:

    • Es dürfen nur Buchstaben enthalten sein.
    • Der erste Buchstabe muss ein Großbuchstabe sein. Leerzeichen, Zahlen und Sonderzeichen sind nicht zulässig.
  • Für RegularExpression-„Rating“ (Bewertung) gilt Folgendes:

    • Das erste Zeichen muss ein Großbuchstabe sein.
    • Sonderzeichen und Zahlen sind als darauffolgende Zeichen zulässig. „PG-13“ ist als Bewertung („Rating“) gültig, nicht jedoch als „Genre“.
  • Das Attribut Range schränkt einen Wert auf einen bestimmten Bereich ein.

  • Mit dem Attribut StringLength kann die maximale Länge einer Zeichenfolgeneigenschaft und optional die minimale Länge festlegt werden.

  • Werttypen (wie decimal, int, float, DateTime) sind grundsätzlich erforderlich und benötigen nicht das Attribut [Required].

Auf der Seite „Create“ (Erstellen) für das Movie-Modell werden ungültige Werte und die daraus resultierenden Fehler angezeigt:

Ansichtsformular „Movie“ mit mehreren clientseitigen jQuery-Validierungsfehlern

Weitere Informationen finden Sie unter:

Verarbeiten von HEAD-Anforderungen mit einem OnGet-Handlerfallback

HEAD-Anforderungen ermöglichen das Abrufen der Header für eine bestimmte Ressource. Im Gegensatz zu GET-Anforderungen geben HEAD-Anforderungen keinen Antworttext zurück.

Normalerweise wird ein OnHead-Handler erstellt und für HEAD-Anforderungen aufgerufen:

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

Razor Pages ruft den OnGet-Handler auf, wenn kein OnHead-Handler definiert ist.

XSRF/CSRF und Razor Pages

Razor Pages wird durch Validierungsmaßnahmen vor XSRF/CSRF-Angriffen geschützt. Das Formulartag-Hilfsprogramm injiziert Anti-XSRF/CSRF-Token in HTML-Formularelemente.

Verwenden von Layouts, Teilansichten, Vorlagen und Taghilfsprogrammen mit Razor Pages

Razor Pages beinhaltet alle Funktionen der Razor-Anzeige-Engine. Layouts, Teilansichten, Vorlagen, Taghilfsprogramme, _ViewStart.cshtml und _ViewImports.cshtml funktionieren auf die gleiche Weise wie für herkömmliche Razor-Ansichten.

Strukturieren Sie diese Seite mit einigen dieser praktischen Funktionen.

Fügen Sie der Pages/Shared/_Layout.cshtml eine Layoutseite hinzu:

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

Das Layout:

  • Steuert das Layout der einzelnen Seiten, es sei denn, das Layout wird für eine Seite deaktiviert.
  • Importiert HTML-Strukturen, z.B. JavaScript und Stylesheets.
  • Der Inhalt der Razor-Seite wird gerendert, wenn @RenderBody() aufgerufen wird.

Weitere Informationen finden Sie unter Layoutseite.

Die Eigenschaft Layout wird in Pages/_ViewStart.cshtml festgelegt:

@{
    Layout = "_Layout";
}

Das Layout befindet sich im Ordner Pages/Shared. Seiten suchen hierarchisch nach anderen Ansichten (Layouts, Vorlagen oder Teilansichten) und beginnen im gleichen Ordner wie die aktuelle Seite. Ein Layout im Ordner Pages/Shared kann von jeder Razor-Seite aus unter dem Ordner Pages verwendet werden.

Die Layoutdatei sollte im Ordner Pages/Shared gespeichert werden.

Wir empfehlen Ihnen, die Layoutdatei nicht im Ordner Views/Shared (Ansichten/Freigegeben) zu platzieren. Views/Shared ist ein MVC-Ansichtsmuster. Razor Pages basieren auf der Ordnerhierarchie, nicht auf Pfadkonventionen.

Die Ansichtensuche in einer Razor Page enthält den Ordner Pages. Die Layouts, Vorlagen und Teilansichten, die mit MVC-Controllern und herkömmlichen Razor-Ansichten verwendet werden, funktionieren problemlos.

Fügen Sie eine Pages/_ViewImports.cshtml-Datei hinzu:

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

@namespace wird weiter unten im Tutorial erläutert. Die @addTagHelper-Anweisung bringt die integrierten Taghilfsprogramme zu allen Seiten in der Ordner Pages.

Die @namespace-Anweisung wird wie folgt für eine Seite festgelegt:

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

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

Die @namespace-Anweisung legt den Namespace für die Seite fest. Die @model-Anweisung muss den Namespace nicht enthalten.

Wenn sich die @namespace-Anweisung in _ViewImports.cshtml befindet, stellt der angegebene Namespace das Präfix für den generierten Namespace auf der Seite bereit, die die @namespace-Anweisung importiert. Der Rest der generierten Namespaces (der Suffixteil) ist der durch Punkte getrennte relative Pfad zwischen dem Ordner mit _ViewImports.cshtml und dem Ordner, der die Seite enthält.

Die PageModel-Klasse Pages/Customers/Edit.cshtml.cs legt den Namespace explizit fest:

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

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

        // Code removed for brevity.

Die Datei Pages/_ViewImports.cshtml legt den folgenden Namespace fest:

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

Der generierte Namespace für die Pages/Customers/Edit.cshtmlRazor-Seite ist identisch mit der PageModel-Klasse.

@namespacefunktioniert auch mit konventionellen Razor-Ansichten.

Sehen Sie sich die Ansichtsdatei Pages/Create.cshtml an:

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

Die aktualisierte Ansichtsdatei Pages/Create.cshtml mit _ViewImports.cshtml und der vorherigen Layoutdatei sieht wie folgt aus:

@page
@model CreateModel

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

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

Im vorangehenden Code werden von _ViewImports.cshtml der Namespace und die Taghilfsprogramme importiert. Die JavaScript-Dateien werden von der Layoutdatei importiert.

Das Razor Pages-Startprojekt enthält die Seite Pages/_ValidationScriptsPartial.cshtml, die die clientseitige Validierung bindet.

Weitere Informationen zu Teilansichten finden Sie unter Teilansichten in ASP.NET Core.

URL-Generierung für Seiten

Die zuvor gezeigte Create-Seite verwendet 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");
    }
}

Die App hat die folgende Datei/Ordner-Struktur:

  • /Pages

    • Index.cshtml

    • Privacy.cshtml

    • /Customers

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

Die Seiten Pages/Customers/Create.cshtml und Pages/Customers/Edit.cshtml führen bei Erfolg eine Umleitung zu Pages/Customers/Index.cshtml durch. Die Zeichenfolge ./Index stellt einen relativen Seitennamen dar, der für den Zugriff auf die vorherige Seite verwendet wird. Sie wird für das Generieren von URIs für die Seite Pages/Customers/Index.cshtml verwendet. Beispiel:

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

Der absolute Seitenname /Index wird zum Generieren der URLs für die Seite Pages/Index.cshtml verwendet. Zum Beispiel:

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

Der Seitenname ist der Pfad zu der Seite vom Stammordner /Pages (einschließlich eines vorangestellten /, z.B. /Index). Die oben stehenden Beispiele für eine URL-Generierung bieten erweiterte Optionen und Funktionen, durch die Sie URLs nicht mehr hartcodieren müssen. Bei der URL-Generierung wird Routing verwendet. Außerdem können damit Parameter generiert und entsprechend der Definition der Route im Zielpfad codiert werden.

Die URL-Generierung für Seiten unterstützt relative Namen. In der folgenden Tabelle wird dargestellt, welche Indexseite durch verschiedene RedirectToPage-Parameter in Pages/Customers/Create.cshtml ausgewählt wird.

RedirectToPage(x) Seite
RedirectToPage("/Index") Pages/Index
RedirectToPage("./Index"); Pages/Customers/Index
RedirectToPage("../Index") Pages/Index
RedirectToPage("Index") Pages/Customers/Index

RedirectToPage("Index"), RedirectToPage("./Index") und RedirectToPage("../Index") sind relative Namen. Der RedirectToPage-Parameter wird mit dem Pfad der aktuellen Seite kombiniert, um den Namen der Zielseite zu berechnen.

Das Verknüpfen relativer Namen eignet sich beim Erstellen von Websites mit einer komplexen Struktur. Wenn durch relative Namen Seiten in einem Ordner verknüpft werden, hat das folgende Vorteile:

  • Relative Links funktionieren weiterhin, wenn ein Ordner umbenannt wird.
  • Links funktionieren weiterhin, da sie keinen Ordnernamen enthalten.

Um auf eine Seite in einem anderen Bereich umzuleiten, geben Sie den Bereich an:

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

Weitere Informationen finden Sie unter Bereiche in ASP.NET Core und Routen- und App-Konventionen für Razor Pages in ASP.NET Core.

Attribut „ViewData“

Daten können mit ViewDataAttribute an eine Seite übermittelt werden. Für Eigenschaften mit dem [ViewData]-Attribut werden die Werte in ViewDataDictionary gespeichert und daraus geladen.

Im folgenden Beispiel wendet AboutModel das [ViewData]-Attribut auf die Title-Eigenschaft an:

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

    public void OnGet()
    {
    }
}

Greifen Sie auf der Infoseite auf die Eigenschaft Title als Modelleigenschaft zu:

<h1>@Model.Title</h1>

Im Layout wird der Titel aus dem ViewData-Wörterbuch gelesen:

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

TempData

ASP.NET Core macht TempData verfügbar. Diese Eigenschaft speichert Daten, bis sie gelesen wurden. Die Methoden Keep und Peek können verwendet werden, um die Daten zu überprüfen, ohne sie zu löschen. TempData eignet sich für die Umleitung, wenn Daten für mehr als eine Anforderung benötigt werden.

Im folgenden Code wird der Wert von Message mit TempData festgelegt:

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

Das folgende Markup in der Datei Pages/Customers/Index.cshtml zeigt den Wert von Message mit TempData an.

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

Das Seitenmodell Pages/Customers/Index.cshtml.cs wendet das [TempData]-Attribut auf die Eigenschaft Message an.

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

Weitere Informationen finden Sie unter TempData.

Mehrere Handler pro Seite

Die folgende Seite generiert mit dem asp-page-handler-Taghilfsprogramm Markup für zwei 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>

Das Formular im vorherigen Beispiel hat zwei Sendeschaltflächen, und jede verwendet FormActionTagHelper, um an eine andere URL zu übermitteln. Das asp-page-handler-Attribut ist eine Ergänzung für asp-page. asp-page-handler generiert URLs, die als Übermittlungsziel jeweils die durch eine Seite festgelegte Handlermethode verwenden. asp-page wird nicht angegeben, weil das Beispiel mit der aktuellen Seite verknüpft.

Das Seitenmodell:

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

Der vorherige Code verwendet benannte Handlermethoden. Benannte Handlermethoden werden aus dem Text im Namen nach On<HTTP Verb> und vor Async (falls vorhanden) erstellt. Im vorherigen Beispiel sind OnPostJoinListAsync und OnPostJoinListUCAsync die Seitenmethoden. Wenn Sie OnPost und Async entfernen, lauten die Handlernamen JoinList und JoinListUC.

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

Mit dem vorherigen Code lautet der URL-Pfad, der an OnPostJoinListAsync übermittelt, https://localhost:5001/Customers/CreateFATH?handler=JoinList. Der URL-Pfad, der an OnPostJoinListUCAsync übermittelt, lautet https://localhost:5001/Customers/CreateFATH?handler=JoinListUC.

Benutzerdefinierte Routen

Verwenden Sie die @page-Anweisung für Folgendes:

  • Das Angeben einer benutzerdefinierten Route zu einer Seite. Die Route zur Seite „Info“ kann mit @page "/Some/Other/Path" beispielsweise auf /Some/Other/Path festgelegt werden.
  • Das Anfügen von Segmenten an die Standardroute einer Seite. Mit @page "item" kann beispielsweise ein item-Segment an die Standardroute der Seite angefügt werden.
  • Das Anfügen von Parametern an die Standardroute einer Seite. Mit @page "{id}" kann beispielsweise ein ID-Parameter (id) für eine Seite angefordert werden.

Es wird ein relativer Pfad zum Stamm unterstützt, der durch eine Tilde (~) festgelegt wird. @page "~/Some/Other/Path" entspricht beispielsweise @page "/Some/Other/Path".

Wenn Sie nicht möchten, dass die Abfragezeichenfolge ?handler=JoinList in der URL enthalten ist, ändern Sie die Route so, dass der Handlername im Pfadteil der URL eingefügt wird. Sie können die Route anpassen, indem Sie nach der @page-Anweisung eine Routenvorlage in doppelten Anführungszeichen hinzufügen.

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

Mit dem vorherigen Code lautet der URL-Pfad, der an OnPostJoinListAsync übermittelt, https://localhost:5001/Customers/CreateFATH/JoinList. Der URL-Pfad, der an OnPostJoinListUCAsync übermittelt, lautet https://localhost:5001/Customers/CreateFATH/JoinListUC.

Das ? nach handler bedeutet, dass der Routenparameter optional ist.

Erweiterte Konfigurationen und Einstellungen

Die Konfigurationen und Einstellungen in den folgenden Abschnitten sind für die meisten Apps nicht erforderlich.

Verwenden Sie die Überladung AddRazorPages, die RazorPagesOptions konfiguriert, um die erweiterten Optionen zu konfigurieren:

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

Verwenden Sie RazorPagesOptions, um das Stammverzeichnis für Seiten festzulegen oder Anwendungsmodellkonventionen für Seiten hinzuzufügen. Weitere Informationen zu Konventionen finden Sie unter Razor Pages-Autorisierungskonventionen.

Informationen zum Vorkompilieren von Ansichten finden Sie unter Razor-Ansichtenkompilierung.

Festlegen des Inhaltsstammverzeichnisses für Razor Pages

Standardmäßig lautet das Stammverzeichnis für Razor Pages /Pages. Fügen Sie WithRazorPagesAtContentRoot hinzu, um anzugeben, dass sich Ihre Razor-Seiten im Inhaltsstammverzeichnis (ContentRootPath) der App befinden:

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

Festlegen eines benutzerdefinierten Stammverzeichnisses für Razor Pages

Fügen Sie WithRazorPagesRoot hinzu, um anzugeben, dass sich Ihre Razor-Seiten in einem benutzerdefinierten Stammverzeichnis der App befinden. Geben Sie dabei einen relativen Pfad an:

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

Zusätzliche Ressourcen