Teil 5: Aktualisieren der generierten Seiten in einer ASP.NET Core-App

Hinweis

Dies ist nicht die neueste Version dieses Artikels. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Wichtig

Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.

Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Für den Anfang ist die mit einem Gerüst erstellte Movie-App schon recht ansprechend, doch es gibt Raum für Verbesserungen. ReleaseDate sollte Release Date lauten (zwei Wörter).

In Chrome geöffnete Movie-Anwendung

Aktualisieren des Modells

Aktualisieren Sie Models/Movie.cs mit dem folgenden hervorgehobenen Code:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;

    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string Genre { get; set; } = string.Empty;

    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

Im vorherigen Code:

  • Mit der Datenanmerkung [Column(TypeName = "decimal(18, 2)")] kann Entity Framework Core Price ordnungsgemäß einer Währung in der Datenbank zuordnen. Weitere Informationen finden Sie unter Datentypen.
  • Das Attribut [Display] gibt den Anzeigenamen eines Felds an. Im Code oben ist dies Release Date anstelle von ReleaseDate.
  • Das Attribut [DataType] gibt den Typ der Daten (Date) an. Die im Feld gespeicherten Zeitinformationen werden nicht angezeigt.

Datenanmerkungen werden im nächsten Tutorial behandelt.

Navigieren Sie zu Pages/Movies, und bewegen Sie den Mauszeiger über dem Link Bearbeiten, um die Ziel-URL anzuzeigen.

Browserfenster mit Maus über dem Link „Bearbeiten“ und der angezeigten Link-URL von https://localhost:1234/Movies/Edit/5

Die Links Bearbeiten, Details und Löschen werden mithilfe des Hilfsprogramms für Ankertags in der Datei Pages/Movies/Index.cshtml generiert.

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

Taghilfsprogramme ermöglichen serverseitigem Code das Mitwirken am Erstellen und Rendern von HTML-Elementen in Razor-Dateien.

Im vorherige Code generiert das Hilfsprogramm für Ankertags dynamisch den Wert des HTML-Attributs href auf der Razor Page (die Route ist relativ), das Element asp-page und die Routen-ID (asp-route-id). Weitere Informationen finden Sie unter URL-Generierung für Seiten.

Rufen Sie in einem Browser Quelltext anzeigen auf, um das generierte Markup zu untersuchen. Ein Teil des generierten HTML-Codes wird unten gezeigt:

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

Die dynamisch generierten Links übergeben die Film-ID mit einer Abfragezeichenfolge. Beispiel: ?id=1 in https://localhost:5001/Movies/Details?id=1.

Hinzufügen der Routenvorlage

Aktualisieren Sie die Razor Pages „Edit“ (Bearbeiten), „Details“ und „Delete“ (Löschen) so, dass die Routenvorlage {id:int} verwendet wird. Ändern Sie die „page“-Direktive für jede dieser Seiten aus @page in @page "{id:int}". Führen Sie die App aus, und zeigen Sie dann den Quelltext an.

Der generierte HTML-Code fügt die ID dem Pfadteil der URL hinzu:

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

Eine Anforderung an die Seite mit der Routenvorlage {id:int}, die nicht den Integer enthält, gibt den HTTP-Fehler 404 (Nicht gefunden) zurück. https://localhost:5001/Movies/Details gibt beispielsweise den Fehler 404 zurück. Um die ID optional zu machen, fügen Sie ? an die Routeneinschränkung an:

@page "{id:int?}"

Testen Sie das Verhalten von @page "{id:int?}":

  1. Legen Sie die Seitenanweisung in Pages/Movies/Details.cshtml auf @page "{id:int?}" fest.
  2. Legen Sie einen Breakpoint in public async Task<IActionResult> OnGetAsync(int? id) (in Pages/Movies/Details.cshtml.cs) fest.
  3. Navigieren Sie zu https://localhost:5001/Movies/Details/.

Mit der @page "{id:int}"-Direktive wird der Haltepunkt nie erreicht. Die Routing-Engine gibt den HTTP-Fehler 404 zurück. Bei Verwendung von @page "{id:int?}" gibt die OnGetAsync-Methode NotFound zurück (HTTP 404):

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

Überprüfen der Behandlung von Ausnahmen bei Parallelität

Überprüfen Sie die OnPostAsync-Methode in der Datei Pages/Movies/Edit.cshtml.cs:

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

    _context.Attach(Movie).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.Id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
  return _context.Movie.Any(e => e.Id == id);
}

Der obige Code erkennt Parallelitätsausnahmen, wenn ein Client den Film löscht und der andere Client Änderungen am Film übermittelt.

So testen Sie den Block catch

  1. Legen Sie einen Haltepunkt bei catch (DbUpdateConcurrencyException) fest.
  2. Wählen Sie Edit für einen Film aus, nehmen Sie Änderungen vor, aber geben Sie nicht Save ein.
  3. Klicken Sie in einem anderen Browserfenster auf den Link Delete für denselben Film, und löschen Sie dann den Film.
  4. Übermitteln Sie im vorherigen Browserfenster Änderungen am Film.

Der Produktionscode sollte Nebenläufigkeitskonflikte erkennen. Weitere Informationen finden Sie unter Verarbeiten von Nebenläufigkeitskonflikten.

Überprüfen der Bereitstellung und Bindung

Untersuchen Sie die Datei Pages/Movies/Edit.cshtml.cs:

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; } = default!;

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null || _context.Movie == null)
        {
            return NotFound();
        }

        var movie =  await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
        if (movie == null)
        {
            return NotFound();
        }
        Movie = movie;
        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();
        }

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
      return _context.Movie.Any(e => e.Id == id);
    }

Wenn eine HTTP GET-Anforderung an die Seite „Movies/Edit“ gerichtet wird (z. B. https://localhost:5001/Movies/Edit/3):

  • Die OnGetAsync-Methode ruft den Film aus der Datenbank ab und gibt die Page-Methode zurück.
  • Die Page-Methode rendert die Razor-Seite Pages/Movies/Edit.cshtml. Die Datei Pages/Movies/Edit.cshtml enthält die Modellanweisung @model RazorPagesMovie.Pages.Movies.EditModel, die das Filmmodell auf der Seite verfügbar macht.
  • Das Bearbeitungsformular wird mit den Werten aus dem Film angezeigt.

Wenn die Seite „Filme/Bearbeiten“ bereitgestellt wird:

  • Die Formularwerte auf der Seite sind an die Movie-Eigenschaft gebunden. Das [BindProperty]-Attribut ermöglicht die Modellbindung.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • Bei Fehlern beim Modellstatus (Beispiel: ReleaseDate kann nicht in ein Datum konvertiert werden) wird das Formular mit den übermittelten Werten erneut angezeigt.

  • Wenn keine Modellfehler vorhanden sind, wird der Film gespeichert.

Die HTTP GET-Methoden auf den Razor Pages „Index“, „Create“ und „Delete“ folgen einem ähnlichen Muster. Die HTTP-POST-Methode OnPostAsync auf der Razor Page „Create“ folgt einem ähnlichen Muster wie die OnPostAsync-Methode auf der Razor Page „Edit“.

Nächste Schritte

Für den Anfang ist die mit einem Gerüst erstellte Movie-App schon recht ansprechend, doch es gibt Raum für Verbesserungen. ReleaseDate sollte Release Date lauten (zwei Wörter).

In Chrome geöffnete Movie-Anwendung

Aktualisieren des Modells

Aktualisieren Sie Models/Movie.cs mit dem folgenden hervorgehobenen Code:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;

    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string Genre { get; set; } = string.Empty;

    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

Im vorherigen Code:

  • Mit der Datenanmerkung [Column(TypeName = "decimal(18, 2)")] kann Entity Framework Core Price ordnungsgemäß einer Währung in der Datenbank zuordnen. Weitere Informationen finden Sie unter Datentypen.
  • Das Attribut [Display] gibt den Anzeigenamen eines Felds an. Im Code oben ist dies Release Date anstelle von ReleaseDate.
  • Das Attribut [DataType] gibt den Typ der Daten (Date) an. Die im Feld gespeicherten Zeitinformationen werden nicht angezeigt.

Datenanmerkungen werden im nächsten Tutorial behandelt.

Navigieren Sie zu Pages/Movies, und bewegen Sie den Mauszeiger über dem Link Bearbeiten, um die Ziel-URL anzuzeigen.

Browserfenster mit Maus über dem Link „Bearbeiten“ und der angezeigten Link-URL von https://localhost:1234/Movies/Edit/5

Die Links Bearbeiten, Details und Löschen werden mithilfe des Hilfsprogramms für Ankertags in der Datei Pages/Movies/Index.cshtml generiert.

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

Taghilfsprogramme ermöglichen serverseitigem Code das Mitwirken am Erstellen und Rendern von HTML-Elementen in Razor-Dateien.

Im vorherige Code generiert das Hilfsprogramm für Ankertags dynamisch den Wert des HTML-Attributs href auf der Razor Page (die Route ist relativ), das Element asp-page und die Routen-ID (asp-route-id). Weitere Informationen finden Sie unter URL-Generierung für Seiten.

Rufen Sie in einem Browser Quelltext anzeigen auf, um das generierte Markup zu untersuchen. Ein Teil des generierten HTML-Codes wird unten gezeigt:

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

Die dynamisch generierten Links übergeben die Film-ID mit einer Abfragezeichenfolge. Beispiel: ?id=1 in https://localhost:5001/Movies/Details?id=1.

Hinzufügen der Routenvorlage

Aktualisieren Sie die Razor Pages „Edit“ (Bearbeiten), „Details“ und „Delete“ (Löschen) so, dass die Routenvorlage {id:int} verwendet wird. Ändern Sie die „page“-Direktive für jede dieser Seiten aus @page in @page "{id:int}". Führen Sie die App aus, und zeigen Sie dann den Quelltext an.

Der generierte HTML-Code fügt die ID dem Pfadteil der URL hinzu:

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

Eine Anforderung an die Seite mit der Routenvorlage {id:int}, die nicht den Integer enthält, gibt den HTTP-Fehler 404 (Nicht gefunden) zurück. https://localhost:5001/Movies/Details gibt beispielsweise den Fehler 404 zurück. Um die ID optional zu machen, fügen Sie ? an die Routeneinschränkung an:

@page "{id:int?}"

Testen Sie das Verhalten von @page "{id:int?}":

  1. Legen Sie die Seitenanweisung in Pages/Movies/Details.cshtml auf @page "{id:int?}" fest.
  2. Legen Sie einen Breakpoint in public async Task<IActionResult> OnGetAsync(int? id) (in Pages/Movies/Details.cshtml.cs) fest.
  3. Navigieren Sie zu https://localhost:5001/Movies/Details/.

Mit der @page "{id:int}"-Direktive wird der Haltepunkt nie erreicht. Die Routing-Engine gibt den HTTP-Fehler 404 zurück. Bei Verwendung von @page "{id:int?}" gibt die OnGetAsync-Methode NotFound zurück (HTTP 404):

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

Überprüfen der Behandlung von Ausnahmen bei Parallelität

Überprüfen Sie die OnPostAsync-Methode in der Datei Pages/Movies/Edit.cshtml.cs:

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

    _context.Attach(Movie).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.Id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
  return _context.Movie.Any(e => e.Id == id);
}

Der obige Code erkennt Parallelitätsausnahmen, wenn ein Client den Film löscht und der andere Client Änderungen am Film übermittelt.

So testen Sie den Block catch

  1. Legen Sie einen Haltepunkt bei catch (DbUpdateConcurrencyException) fest.
  2. Wählen Sie Edit für einen Film aus, nehmen Sie Änderungen vor, aber geben Sie nicht Save ein.
  3. Klicken Sie in einem anderen Browserfenster auf den Link Delete für denselben Film, und löschen Sie dann den Film.
  4. Übermitteln Sie im vorherigen Browserfenster Änderungen am Film.

Der Produktionscode sollte Nebenläufigkeitskonflikte erkennen. Weitere Informationen finden Sie unter Verarbeiten von Nebenläufigkeitskonflikten.

Überprüfen der Bereitstellung und Bindung

Untersuchen Sie die Datei Pages/Movies/Edit.cshtml.cs:

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; } = default!;

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null || _context.Movie == null)
        {
            return NotFound();
        }

        var movie =  await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
        if (movie == null)
        {
            return NotFound();
        }
        Movie = movie;
        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();
        }

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
      return _context.Movie.Any(e => e.Id == id);
    }

Wenn eine HTTP GET-Anforderung an die Seite „Movies/Edit“ gerichtet wird (z. B. https://localhost:5001/Movies/Edit/3):

  • Die OnGetAsync-Methode ruft den Film aus der Datenbank ab und gibt die Page-Methode zurück.
  • Die Page-Methode rendert die Razor-Seite Pages/Movies/Edit.cshtml. Die Datei Pages/Movies/Edit.cshtml enthält die Modellanweisung @model RazorPagesMovie.Pages.Movies.EditModel, die das Filmmodell auf der Seite verfügbar macht.
  • Das Bearbeitungsformular wird mit den Werten aus dem Film angezeigt.

Wenn die Seite „Filme/Bearbeiten“ bereitgestellt wird:

  • Die Formularwerte auf der Seite sind an die Movie-Eigenschaft gebunden. Das [BindProperty]-Attribut ermöglicht die Modellbindung.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • Bei Fehlern beim Modellstatus (Beispiel: ReleaseDate kann nicht in ein Datum konvertiert werden) wird das Formular mit den übermittelten Werten erneut angezeigt.

  • Wenn keine Modellfehler vorhanden sind, wird der Film gespeichert.

Die HTTP GET-Methoden auf den Razor Pages „Index“, „Create“ und „Delete“ folgen einem ähnlichen Muster. Die HTTP-POST-Methode OnPostAsync auf der Razor Page „Create“ folgt einem ähnlichen Muster wie die OnPostAsync-Methode auf der Razor Page „Edit“.

Nächste Schritte

Für den Anfang ist die mit einem Gerüst erstellte Movie-App schon recht ansprechend, doch es gibt Raum für Verbesserungen. ReleaseDate sollte Release Date lauten (zwei Wörter).

In Chrome geöffnete Movie-Anwendung

Aktualisieren des generierten Codes

Aktualisieren Sie Models/Movie.cs mit dem folgenden hervorgehobenen Code:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; } = string.Empty;

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; } = string.Empty;

        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }
    }
}

Im vorherigen Code:

  • Mit der Datenanmerkung [Column(TypeName = "decimal(18, 2)")] kann Entity Framework Core Price ordnungsgemäß einer Währung in der Datenbank zuordnen. Weitere Informationen finden Sie unter Datentypen.
  • Das Attribut [Display] gibt den Anzeigenamen eines Felds an. Im vorherigen Code „Release Date“ anstelle von ReleaseDate.
  • Das Attribut [DataType] gibt den Typ der Daten (Date) an. Die im Feld gespeicherten Zeitinformationen werden nicht angezeigt.

Datenanmerkungen werden im nächsten Tutorial behandelt.

Navigieren Sie zu Pages/Movies, und bewegen Sie den Mauszeiger über dem Link Bearbeiten, um die Ziel-URL anzuzeigen.

Browserfenster mit Maus über dem Link „Bearbeiten“ und der angezeigten Link-URL von https://localhost:1234/Movies/Edit/5

Die Links Bearbeiten, Details und Löschen werden mithilfe des Hilfsprogramms für Ankertags in der Datei Pages/Movies/Index.cshtml generiert.

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

Taghilfsprogramme ermöglichen serverseitigem Code das Mitwirken am Erstellen und Rendern von HTML-Elementen in Razor-Dateien.

Im vorherige Code generiert das Hilfsprogramm für Ankertags dynamisch den Wert des HTML-Attributs href auf der Razor Page (die Route ist relativ), das Element asp-page und die Routen-ID (asp-route-id). Weitere Informationen finden Sie unter URL-Generierung für Seiten.

Rufen Sie in einem Browser Quelltext anzeigen auf, um das generierte Markup zu untersuchen. Ein Teil des generierten HTML-Codes wird unten gezeigt:

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

Die dynamisch generierten Links übergeben die Film-ID mit einer Abfragezeichenfolge. Beispiel: ?id=1 in https://localhost:5001/Movies/Details?id=1.

Hinzufügen der Routenvorlage

Aktualisieren Sie die Razor Pages „Edit“ (Bearbeiten), „Details“ und „Delete“ (Löschen) so, dass die Routenvorlage {id:int} verwendet wird. Ändern Sie die „page“-Direktive für jede dieser Seiten aus @page in @page "{id:int}". Führen Sie die App aus, und zeigen Sie dann den Quelltext an.

Der generierte HTML-Code fügt die ID dem Pfadteil der URL hinzu:

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

Eine Anforderung an die Seite mit der Routenvorlage {id:int}, die nicht die ganze Zahl enthält, gibt den HTTP-Fehler 404 (Nicht gefunden) zurück. https://localhost:5001/Movies/Details gibt beispielsweise den Fehler 404 zurück. Um die ID optional zu machen, fügen Sie ? an die Routeneinschränkung an:

@page "{id:int?}"

Testen Sie das Verhalten von @page "{id:int?}":

  1. Legen Sie die Seitenanweisung in Pages/Movies/Details.cshtml auf @page "{id:int?}" fest.
  2. Legen Sie einen Breakpoint in public async Task<IActionResult> OnGetAsync(int? id) (in Pages/Movies/Details.cshtml.cs) fest.
  3. Navigieren Sie zu https://localhost:5001/Movies/Details/.

Mit der @page "{id:int}"-Direktive wird der Haltepunkt nie erreicht. Die Routing-Engine gibt den HTTP-Fehler 404 zurück. Bei Verwendung von @page "{id:int?}" gibt die OnGetAsync-Methode NotFound zurück (HTTP 404):

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

Überprüfen der Behandlung von Ausnahmen bei Parallelität

Überprüfen Sie die OnPostAsync-Methode in der Datei Pages/Movies/Edit.cshtml.cs:

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

    _context.Attach(Movie).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.ID))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
  return (_context.Movie?.Any(e => e.ID == id)).GetValueOrDefault();
}

Der obige Code erkennt Parallelitätsausnahmen, wenn ein Client den Film löscht und der andere Client Änderungen am Film übermittelt. Der vorherige Code erkennt keine Konflikte, die auftreten, wenn zwei oder mehr Clients denselben Film gleichzeitig bearbeiten. In diesem Fall werden Bearbeitungen von mehreren Clients in der Reihenfolge angewandt, in der SaveChanges aufgerufen wird. Dadurch können später angewandte Bearbeitungen frühere mit veralteten Werten überschreiben.

So testen Sie den Block catch

  1. Legen Sie einen Haltepunkt bei catch (DbUpdateConcurrencyException) fest.
  2. Wählen Sie Edit für einen Film aus, nehmen Sie Änderungen vor, aber geben Sie nicht Save ein.
  3. Klicken Sie in einem anderen Browserfenster auf den Link Delete für denselben Film, und löschen Sie dann den Film.
  4. Übermitteln Sie im vorherigen Browserfenster Änderungen am Film.

Mit Produktionscode möchten Sie möglicherweise zusätzliche Nebenläufigkeitskonflikte erkennen, z. B. wenn mehrere Clients eine Entität gleichzeitig bearbeiten. Weitere Informationen finden Sie unter Verarbeiten von Nebenläufigkeitskonflikten.

Überprüfen der Bereitstellung und Bindung

Untersuchen Sie die Datei Pages/Movies/Edit.cshtml.cs:

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; } = default!;

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null || _context.Movie == null)
        {
            return NotFound();
        }

        var movie =  await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);
        if (movie == null)
        {
            return NotFound();
        }
        Movie = movie;
        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();
        }

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
      return (_context.Movie?.Any(e => e.ID == id)).GetValueOrDefault();
    }

Wenn eine HTTP GET-Anforderung an die Seite „Movies/Edit“ gerichtet wird (z. B. https://localhost:5001/Movies/Edit/3):

  • Die OnGetAsync-Methode ruft den Film aus der Datenbank ab und gibt die Page-Methode zurück.
  • Die Page-Methode rendert die Razor-Seite Pages/Movies/Edit.cshtml. Die Datei Pages/Movies/Edit.cshtml enthält die Modellanweisung @model RazorPagesMovie.Pages.Movies.EditModel, die das Filmmodell auf der Seite verfügbar macht.
  • Das Bearbeitungsformular wird mit den Werten aus dem Film angezeigt.

Wenn die Seite „Filme/Bearbeiten“ bereitgestellt wird:

  • Die Formularwerte auf der Seite sind an die Movie-Eigenschaft gebunden. Das [BindProperty]-Attribut ermöglicht die Modellbindung.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • Bei Fehlern beim Modellstatus (Beispiel: ReleaseDate kann nicht in ein Datum konvertiert werden) wird das Formular mit den übermittelten Werten erneut angezeigt.

  • Wenn keine Modellfehler vorhanden sind, wird der Film gespeichert.

Die HTTP GET-Methoden auf den Razor Pages „Index“, „Create“ und „Delete“ folgen einem ähnlichen Muster. Die HTTP-POST-Methode OnPostAsync auf der Razor Page „Create“ folgt einem ähnlichen Muster wie die OnPostAsync-Methode auf der Razor Page „Edit“.

Nächste Schritte

Für den Anfang ist die mit einem Gerüst erstellte Movie-App schon recht ansprechend, doch es gibt Raum für Verbesserungen. ReleaseDate sollte Release Date lauten (zwei Wörter).

In Chrome geöffnete Movie-Anwendung

Aktualisieren des generierten Codes

Öffnen Sie die Datei Models/Movie.cs, und fügen Sie die im folgenden Code gezeigten markierten Zeilen hinzu:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }

        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }
    }
}

Im vorherigen Code:

  • Mit der Datenanmerkung [Column(TypeName = "decimal(18, 2)")] kann Entity Framework Core Price ordnungsgemäß einer Währung in der Datenbank zuordnen. Weitere Informationen finden Sie unter Datentypen.
  • Das Attribut [Display] gibt den Anzeigenamen eines Felds an. Im vorherigen Code „Release Date“ anstelle von ReleaseDate.
  • Das Attribut [DataType] gibt den Typ der Daten (Date) an. Die im Feld gespeicherten Zeitinformationen werden nicht angezeigt.

Datenanmerkungen werden im nächsten Tutorial behandelt.

Navigieren Sie zu Pages/Movies, und bewegen Sie den Mauszeiger über dem Link Bearbeiten, um die Ziel-URL anzuzeigen.

Browserfenster mit Maus über dem Link „Bearbeiten“ und der angezeigten Link-URL von https://localhost:1234/Movies/Edit/5

Die Links Bearbeiten, Details und Löschen werden mithilfe des Hilfsprogramms für Ankertags in der Datei Pages/Movies/Index.cshtml generiert.

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

Taghilfsprogramme ermöglichen serverseitigem Code das Mitwirken am Erstellen und Rendern von HTML-Elementen in Razor-Dateien.

Im vorherige Code generiert das Hilfsprogramm für Ankertags dynamisch den Wert des HTML-Attributs href auf der Razor Page (die Route ist relativ), das Element asp-page und die Routen-ID (asp-route-id). Weitere Informationen finden Sie unter URL-Generierung für Seiten.

Rufen Sie in einem Browser Quelltext anzeigen auf, um das generierte Markup zu untersuchen. Ein Teil des generierten HTML-Codes wird unten gezeigt:

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

Die dynamisch generierten Links übergeben die Film-ID mit einer Abfragezeichenfolge. Beispiel: ?id=1 in https://localhost:5001/Movies/Details?id=1.

Hinzufügen der Routenvorlage

Aktualisieren Sie die Razor Pages „Edit“ (Bearbeiten), „Details“ und „Delete“ (Löschen) so, dass die Routenvorlage {id:int} verwendet wird. Ändern Sie die „page“-Direktive für jede dieser Seiten aus @page in @page "{id:int}". Führen Sie die App aus, und zeigen Sie dann den Quelltext an.

Der generierte HTML-Code fügt die ID dem Pfadteil der URL hinzu:

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

Eine Anforderung an die Seite mit der Routenvorlage {id:int}, die nicht die ganze Zahl enthält, gibt den HTTP-Fehler 404 (Nicht gefunden) zurück. https://localhost:5001/Movies/Details gibt beispielsweise den Fehler 404 zurück. Um die ID optional zu machen, fügen Sie ? an die Routeneinschränkung an:

@page "{id:int?}"

Testen Sie das Verhalten von @page "{id:int?}":

  1. Legen Sie die Seitenanweisung in Pages/Movies/Details.cshtml auf @page "{id:int?}" fest.
  2. Legen Sie einen Breakpoint in public async Task<IActionResult> OnGetAsync(int? id) (in Pages/Movies/Details.cshtml.cs) fest.
  3. Navigieren Sie zu https://localhost:5001/Movies/Details/.

Mit der @page "{id:int}"-Direktive wird der Haltepunkt nie erreicht. Die Routing-Engine gibt den HTTP-Fehler 404 zurück. Bei Verwendung von @page "{id:int?}" gibt die OnGetAsync-Methode NotFound zurück (HTTP 404):

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

Überprüfen der Behandlung von Ausnahmen bei Parallelität

Überprüfen Sie die OnPostAsync-Methode in der Datei Pages/Movies/Edit.cshtml.cs:

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

    _context.Attach(Movie).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.ID))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
    return _context.Movie.Any(e => e.ID == id);
}

Der obige Code erkennt Parallelitätsausnahmen, wenn ein Client den Film löscht und der andere Client Änderungen am Film übermittelt.

So testen Sie den Block catch

  1. Legen Sie einen Haltepunkt bei catch (DbUpdateConcurrencyException) fest.
  2. Wählen Sie Edit für einen Film aus, nehmen Sie Änderungen vor, aber geben Sie nicht Save ein.
  3. Klicken Sie in einem anderen Browserfenster auf den Link Delete für denselben Film, und löschen Sie dann den Film.
  4. Übermitteln Sie im vorherigen Browserfenster Änderungen am Film.

Der Produktionscode sollte Nebenläufigkeitskonflikte erkennen. Weitere Informationen finden Sie unter Verarbeiten von Nebenläufigkeitskonflikten.

Überprüfen der Bereitstellung und Bindung

Untersuchen Sie die Datei Pages/Movies/Edit.cshtml.cs:

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

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

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

        if (Movie == null)
        {
            return NotFound();
        }
        return Page();
    }

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

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
        return _context.Movie.Any(e => e.ID == id);
    }

Wenn eine HTTP GET-Anforderung an die Seite „Movies/Edit“ gerichtet wird (z. B. https://localhost:5001/Movies/Edit/3):

  • Die OnGetAsync-Methode ruft den Film aus der Datenbank ab und gibt die Page-Methode zurück.
  • Die Page-Methode rendert die Razor-Seite Pages/Movies/Edit.cshtml. Die Datei Pages/Movies/Edit.cshtml enthält die Modellanweisung @model RazorPagesMovie.Pages.Movies.EditModel, die das Filmmodell auf der Seite verfügbar macht.
  • Das Bearbeitungsformular wird mit den Werten aus dem Film angezeigt.

Wenn die Seite „Filme/Bearbeiten“ bereitgestellt wird:

  • Die Formularwerte auf der Seite sind an die Movie-Eigenschaft gebunden. Das [BindProperty]-Attribut ermöglicht die Modellbindung.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • Bei Fehlern beim Modellstatus (Beispiel: ReleaseDate kann nicht in ein Datum konvertiert werden) wird das Formular mit den übermittelten Werten erneut angezeigt.

  • Wenn keine Modellfehler vorhanden sind, wird der Film gespeichert.

Die HTTP GET-Methoden auf den Razor Pages „Index“, „Create“ und „Delete“ folgen einem ähnlichen Muster. Die HTTP-POST-Methode OnPostAsync auf der Razor Page „Create“ folgt einem ähnlichen Muster wie die OnPostAsync-Methode auf der Razor Page „Edit“.

Nächste Schritte