Einführung in Razor Pages in ASP.NET Core

Von Rick Anderson und Ryan Nowak

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

@page
@using RazorPagesIntro.Pages
@model Index2Model

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

Das Seitenmodell Pages/Index2.cshtml.cs:

using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;

namespace RazorPagesIntro.Pages
{
    public class Index2Model : PageModel
    {
        public string Message { get; private set; } = "PageModel in C#";

        public void OnGet()
        {
            Message += $" Server time is { DateTime.Now }";
        }
    }
}

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 Razor 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 Umgebungsdatei 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 verfügt über eine OnPostAsync-Handlermethode, die bei 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 Razor-Seite CreateModel.cshtml 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 Umgebungsdatei 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>
                        <a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
                        <button type="submit" asp-page-handler="delete"
                                asp-route-id="@contact.Id">delete
                        </button>
                    </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:

<td>

Das Anchor-Taghilfsprogramm <a /a> verwendet das asp-route-{value}-Attribut, um einen Link zur Bearbeitungsseite 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:

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

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 sieht wie folgt aus:

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 />, um Folgendes zu ermöglichen:

    • 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 Namensfeld 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 Datei Pages/_ViewImports.cshtml 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 in Pages/Customers/Edit.cshtml.cs legt den Namespace z.B. 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 Razor Page Pages/Customers/Edit.cshtml ist identisch mit der PageModel-Klasse.

@namespace funktioniert 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 Verwenden von 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. Zum 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 Razor Pages: Routen- und App-Konventionen 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>
        <input type="submit" asp-page-handler="JoinList" value="Join" />
        <input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
    </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 OnPost JoinList Async und OnPost JoinListUC Async 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

Warnung

Wenn Sie Visual Studio 2017 verwenden, finden Sie unter dotnet/sdk issue #3124 Informationen zu .NET Core SDK-Versionen, die nicht mit Visual Studio verwendet werden können.

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 void ConfigureServices(IServiceCollection services)
    {
        // Includes support for Razor Pages and controllers.
        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseMvc();
    }
}

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.

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

@page
@using RazorPagesIntro.Pages
@model IndexModel2

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

Das Seitenmodell Pages/Index2.cshtml.cs:

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

namespace RazorPagesIntro.Pages
{
    public class IndexModel2 : 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 Razor 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.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesContacts.Data;

namespace RazorPagesContacts
{
    public class Startup
    {
        public IHostingEnvironment HostingEnvironment { get; }

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

        public void Configure(IApplicationBuilder app)
        {
            app.UseMvc();
        }
    }
}

Das Datenmodell:

using System.ComponentModel.DataAnnotations;

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

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

Der db-Kontext:

using Microsoft.EntityFrameworkCore;

namespace RazorPagesContacts.Data
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions options)
            : base(options)
        {
        }

        public DbSet<Customer> Customers { get; set; }
    }
}

Die Umgebungsdatei Pages/Create.cshtml:

@page
@model RazorPagesContacts.Pages.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<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" />
    </form>
</body>
</html>

Das Seitenmodell Pages/Create.cshtml.cs:

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

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

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

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

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

            _db.Customers.Add(Customer);
            await _db.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 verfügt über eine OnPostAsync-Handlermethode, die bei POST-Anforderungen ausgeführt wird (wenn ein Benutzer das Formular sendet). Sie können Handlermethoden für alle HTTP-Verben hinzufügen. Die am häufigsten verwendeten Handler sind:

  • OnGet, um den für eine Seite erforderlichen Status zu initialisieren. OnGet-Beispiel
  • 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, Validierungen und Aktionsergebnisse werden gemeinsam verwendet.

Die vorherige OnPostAsync-Methode:

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

    _db.Customers.Add(Customer);
    await _db.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. Die clientseitige Validierung ist identisch mit herkömmlichen ASP.NET Core MVC-Anwendungen, denn Validierungsfehler werden oftmals auf dem Client erkannt und nie an den Server übermittelt.

Wenn die Daten erfolgreich eingegeben wurden, ruft die OnPostAsync-Handlermethode die RedirectToPage-Hilfsmethode auf, um eine Instanz von RedirectToPageResult zurückzugeben. RedirectToPage ist ein neues Aktionsergebnis und ähnelt RedirectToAction oder RedirectToRoute, ist aber für Seiten angepasst. Im vorhergehenden Beispiel leitet es an die Stammindexseite (/Index) weiter. Informationen zu RedirectToPage finden Sie im Abschnitt URL-Generierung für Seiten.

Wenn das übermittelte Formular Validierungsfehler enthält (die an den Server übergeben wurden), ruft die OnPostAsync-Handlermethode 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.

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

public class CreateModel : PageModel
{
    private readonly AppDbContext _db;

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

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

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

        _db.Customers.Add(Customer);
        await _db.SaveChangesAsync();
        return RedirectToPage("/Index");
    }
}

Razor Pages binden Eigenschaften standardmäßig nur an Nicht-GET-Verben. Durch die Bindung an Eigenschaften können Sie den Umfang von Codes reduzieren, den Sie schreiben müssen. 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).

Die Startseite (Index.cshtml):

@page
@model RazorPagesContacts.Pages.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>Contacts</h1>
<form method="post">
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var contact in Model.Customers)
            {
                <tr>
                    <td>@contact.Id</td>
                    <td>@contact.Name</td>
                    <td>
                        <a asp-page="./Edit" asp-route-id="@contact.Id">edit</a>
                        <button type="submit" asp-page-handler="delete" 
                                asp-route-id="@contact.Id">delete</button>
                    </td>
                </tr>
            }
        </tbody>
    </table>

    <a asp-page="./Create">Create</a>
</form>

Die zugeordnete PageModel-Klasse (Index.cshtml.cs):

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;

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

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

        public IList<Customer> Customers { get; private set; }

        public async Task OnGetAsync()
        {
            Customers = await _db.Customers.AsNoTracking().ToListAsync();
        }

        public async Task<IActionResult> OnPostDeleteAsync(int id)
        {
            var contact = await _db.Customers.FindAsync(id);

            if (contact != null)
            {
                _db.Customers.Remove(contact);
                await _db.SaveChangesAsync();
            }

            return RedirectToPage();
        }
    }
}

Die Datei Index.cshtml enthält das folgende Markup, um einen Bearbeitungslink für jeden Kontakt zu erstellen:

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

Das Anchor-Taghilfsprogramm <a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> verwendet das asp-route-{value}-Attribut, um einen Link zur Bearbeitungsseite 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. Taghilfsprogramme werden durch @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers aktiviert.

Die Datei Pages/Edit.cshtml:

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

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

<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 Pages/Edit.cshtml.cs:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;

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

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

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

        public async Task<IActionResult> OnGetAsync(int id)
        {
            Customer = await _db.Customers.FindAsync(id);

            if (Customer == null)
            {
                return RedirectToPage("/Index");
            }

            return Page();
        }

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

            _db.Attach(Customer).State = EntityState.Modified;

            try
            {
                await _db.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                throw new Exception($"Customer {Customer.Id} not found!");
            }

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

Die Datei index.cshtml enthält auch Markup zum Erstellen der Schaltfläche „Löschen“ für jeden benutzerdefinierten Kontakt:

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

Wenn die „Löschen“-Schaltfläche in HTML gerendert wird, enthält ihr formaction-Element Parameter für Folgendes:

  • Die benutzerdefinierte Kontakt-ID, die vom asp-route-id-Attribut angegeben wird
  • Der handler, der vom asp-page-handler-Attribut angegeben wird

Hier sehen Sie ein Beispiel für eine gerenderte „Löschen“-Schaltfläche mit einer benutzerdefinierten Kontakt-ID von 1:

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

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. Im folgenden Code wird der OnPostDeleteAsync-Handler definiert:

public async Task<IActionResult> OnPostDeleteAsync(int id)
{
    var contact = await _db.Customers.FindAsync(id);

    if (contact != null)
    {
        _db.Customers.Remove(contact);
        await _db.SaveChangesAsync();
    }

    return RedirectToPage();
}

Die OnPostDeleteAsync-Methode:

  • Akzeptiert die id der Abfragezeichenfolge. Wenn die Anweisung der Seite Index.cshtml die Routingeinschränkung "{id:int?}" enthielte, würde id aus den Routendaten abgerufen werden. Die Routendaten für id werden beispielsweise wie folgt im URI angegeben: https://localhost:5001/Customers/2.
  • Fragt mit FindAsync die Datenbank nach dem Kundenkontakt ab.
  • Wenn der Kundenkontakt gefunden wird, wird er aus der Liste der Kundenkontakte entfernt. Die Datenbank wurde aktualisiert.
  • Ruft RedirectToPage auf, um die Stammindexseite (/Index) umzuleiten.

Markieren von Eigenschaften als „Required“ (Erforderlich)

Eigenschaften in einem PageModel können mit dem Required-Attribut markiert werden:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel.DataAnnotations;

namespace RazorPagesMovie.Pages.Movies
{
    public class CreateModel : PageModel
    {
        public IActionResult OnGet()
        {
            return Page();
        }

        [BindProperty]
        [Required(ErrorMessage = "Color is required")]
        public string Color { get; set; }

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

            // Process color.

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

Weitere Informationen finden Sie unter Modellvalidierung.

Verarbeiten von HEAD-Anforderungen mit einem OnGet-Handlerfallback

HEAD-Anforderungen ermöglichen Ihnen das Abrufen des Headers 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("HandledBy", "Handled by OnHead!");
}

In ASP.NET Core 2.1 oder höher greift Razor Pages auf den OnGet-Handler zurück, wenn kein OnHead-Handler definiert ist. Dieses Verhalten wird durch den Aufruf von SetCompatibilityVersion in Startup.ConfigureServices ermöglicht:

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

Die Standardvorlagen generieren den SetCompatibilityVersion-Aufruf in ASP.NET Core 2.1 und 2.2. Tatsächlich legt SetCompatibilityVersion die Razor Pages-Option AllowMappingHeadRequestsToGetHandler auf true fest.

Sie müssen nicht alle Verhalten in SetCompatibilityVersion aktivieren, sondern können sich nur bestimmte Verhalten aussuchen. Der folgende Code gibt an, dass HEAD-Anforderungen dem OnGet-Handler zugeordnet werden dürfen:

services.AddMvc()
    .AddRazorPagesOptions(options =>
    {
        options.AllowMappingHeadRequestsToGetHandler = true;
    });

XSRF/CSRF und Razor Pages

Sie müssen keinen Code für die Antifälschungsvalidierung schreiben. Die Generierung und Validierung von Antifälschungstoken ist automatisch in Razor Pages enthalten.

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, _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>Razor Pages Sample</title>      
</head>
<body>    
   <a asp-page="/Index">Home</a>
    @RenderBody()  
    <a asp-page="/Customers/Create">Create</a> <br />
</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.

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 Sie mit MVC-Controllern und herkömmlichen Razor-Ansichten verwenden, funktionieren einfach.

Fügen Sie eine Datei Pages/_ViewImports.cshtml 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.

Wenn die @namespace-Anweisung explizit auf eine Seite angewendet wird:

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

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

Die 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 in Pages/Customers/Edit.cshtml.cs legt den Namespace z.B. 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 Razor Page Pages/Customers/Edit.cshtml ist identisch mit der PageModel-Klasse.

@namespace funktioniert auch mit konventionellen Razor-Ansichten.

Die ursprüngliche Umgebungsdatei Pages/Create.cshtml:

@page
@model RazorPagesContacts.Pages.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<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" />
    </form>
</body>
</html>

Die aktualisierte Umgebungsdatei Pages/Create.cshtml:

@page
@model CreateModel

<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" />
    </form>
</body>
</html>

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

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

URL-Generierung für Seiten

Die zuvor gezeigte Create-Seite verwendet RedirectToPage:

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

    _db.Customers.Add(Customer);
    await _db.SaveChangesAsync();
    return RedirectToPage("/Index");
}

Die App hat die folgende Datei/Ordner-Struktur:

  • /Pages

    • Index.cshtml

    • /Customers

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

Die Seiten Pages/Customers/Create.cshtml und Pages/Customers/Edit.cshtml leiten bei Erfolg an Pages/Index.cshtml weiter. Die Zeichenfolge /Index ist Teil des URI, der auf die vorhergehende Seite zugreifen soll. Die Zeichenfolge /Index kann für das Generieren von URIs für die Seite Pages/Index.cshtml verwendet werden. Zum Beispiel:

  • Url.Page("/Index", ...)
  • <a asp-page="/Index">My 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 für verschiedene RedirectToPage-Parameter aus 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 Sie relative Namen verwenden, um Seiten in einem Ordner zu verknüpfen, können Sie diesen Ordner umbenennen. Alle Links funktionieren weiterhin, da sie nicht den Namen des Ordners 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.

Attribut „ViewData“

Daten können mit ViewDataAttribute an eine Seite übergeben werden. Die Werte der Eigenschaften auf Controllern oder Razor Pages-Modellen, die mit dem [ViewData]-Attribut versehen sind, werden in ViewDataDictionary gespeichert und daraus geladen.

Im folgenden Beispiel enthält das AboutModel eine Title-Eigenschaft, die mit [ViewData] markiert ist. Die Eigenschaft Title wird auf den Titel der Infoseite festgelegt:

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 die Eigenschaft TempData auf einem Controller 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 Weiterleitung, 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>
        <input type="submit" asp-page-handler="JoinList" value="Join" />
        <input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
    </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 OnPost JoinList Async und OnPost JoinListUC Async 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.

Konfiguration und Einstellungen

Um die erweiterten Optionen zu konfigurieren, verwenden Sie die Erweiterungsmethode AddRazorPagesOptions auf dem MVC-Generator:

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

Derzeit können Sie RazorPagesOptions verwenden, um das Stammverzeichnis für Seiten festzulegen oder Anwendungsmodellkonventionen für Seiten hinzuzufügen. Auf diese Weise wird in Zukunft eine höhere Erweiterbarkeit erreicht.

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

Laden Sie Beispielcode herunter, oder zeigen Sie ihn an.

Lesen Sie auch den Artikel Erste Schritte mit Razor Pages, der auf dieser Einführung aufbaut.

Festlegen des Inhaltsstammverzeichnisses für Razor Pages

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

services.AddMvc()
    .AddRazorPagesOptions(options =>
    {
        ...
    })
    .WithRazorPagesAtContentRoot();

Festlegen eines benutzerdefinierten Stammverzeichnisses für Razor Pages

Fügen Sie WithRazorPagesRoot zu AddMvc hinzu, um anzugeben, dass Ihre Razor Pages sich in einem benutzerdefinierten Stammverzeichnis in der App befinden (geben Sie einen relativen Pfad an):

services.AddMvc()
    .AddRazorPagesOptions(options =>
    {
        ...
    })
    .WithRazorPagesRoot("/path/to/razor/pages");

Zusätzliche Ressourcen