Freigeben über


Teil 7: Hinzufügen der Suche zu einer ASP.NET Core MVC-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.

Von Rick Anderson

In diesem Abschnitt fügen Sie die Suchfunktion zur Aktionsmethode Index hinzu, mit der Sie Filme nach Genre oder Name suchen können.

Aktualisieren Sie die Index-Methode in Controllers/MoviesController.cs mit folgendem Code:

public async Task<IActionResult> Index(string searchString)
{
    if (_context.Movie == null)
    {
        return Problem("Entity set 'MvcMovieContext.Movie'  is null.");
    }

    var movies = from m in _context.Movie
                select m;

    if (!String.IsNullOrEmpty(searchString))
    {
        movies = movies.Where(s => s.Title!.Contains(searchString));
    }

    return View(await movies.ToListAsync());
}

Die erste Zeile der Aktionsmethode Index erstellt eine LINQ-Abfrage zum Auswählen der Filme:

var movies = from m in _context.Movie
             select m;

Die Abfrage wird an diesem Punkt nur definiert. Sie wurde noch nicht für die Datenbank ausgeführt.

Wenn der searchString-Parameter eine Zeichenfolge enthält, wird die Filmabfrage so geändert, dass nach dem Wert der Suchzeichenfolge gefiltert wird:

if (!String.IsNullOrEmpty(searchString))
{
    movies = movies.Where(s => s.Title!.Contains(searchString));
}

Der Code s => s.Title!.Contains(searchString) oben ist ein Lambdaausdruck. Lambdaausdrücke werden in methodenbasierten LINQ-Abfragen als Argumente für standardmäßige Abfrageoperatormethoden wie die Where-Methode oder Contains verwendet (siehe den vorangehenden Code). LINQ-Abfragen werden nicht ausgeführt, wenn sie definiert oder durch Aufrufen einer Methode geändert werden (z. B. Where, Contains oder OrderBy). Stattdessen wird die Ausführung der Abfrage verzögert. Dies bedeutet, dass die Auswertung eines Ausdrucks so lange hinausgezögert wird, bis dessen realisierter Wert tatsächlich durchlaufen oder die ToListAsync-Methode aufgerufen wird. Weitere Informationen zur verzögerten Abfrageausführung finden Sie unter Abfrageausführung.

Hinweis: Die Contains-Methode wird in der Datenbank und nicht im oben gezeigten C#-Code ausgeführt. Die Groß-/Kleinschreibung in der Abfrage hängt von der Datenbank und Sortierung ab. In SQL Server wird Contains zu SQL LIKE zugeordnet, das Groß-/Kleinschreibung nicht beachtet. In SQLite wird bei der Standardsortierung Groß-/Kleinschreibung beachtet.

Navigieren Sie zu /Movies/Index. Fügen Sie eine Abfragezeichenfolge wie ?searchString=Ghost an die URL an. Die gefilterten Filme werden angezeigt.

Indexansicht

Wenn Sie die Signatur der Index-Methode so ändern, dass sie einen Parameter mit dem Namen id hat, entspricht der Parameter id dem optionalen Platzhalter {id} für die Standardrouten, die in Program.cs festgelegt sind.

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

Ändern Sie den Parameter in id und alle Vorkommen von searchString in id.

Die vorherige Index-Methode:

public async Task<IActionResult> Index(string searchString)
{
    if (_context.Movie == null)
    {
        return Problem("Entity set 'MvcMovieContext.Movie'  is null.");
    }

    var movies = from m in _context.Movie
                select m;

    if (!String.IsNullOrEmpty(searchString))
    {
        movies = movies.Where(s => s.Title!.Contains(searchString));
    }

    return View(await movies.ToListAsync());
}

Die aktualisierte Index-Methode mit id-Parameter:

public async Task<IActionResult> Index(string id)
{
    if (_context.Movie == null)
    {
        return Problem("Entity set 'MvcMovieContext.Movie'  is null.");
    }

    var movies = from m in _context.Movie
                 select m;

    if (!String.IsNullOrEmpty(id))
    {
        movies = movies.Where(s => s.Title!.Contains(id));
    }

    return View(await movies.ToListAsync());
}

Sie können nun den Suchtitel als Routendaten (ein URL-Segment) anstatt als Wert einer Abfragezeichenfolge übergeben.

Indexansicht mit dem der URL hinzugefügten Wort „ghost“ und einer zurückgegebenen Filmliste mit zwei Filmen: Ghostbusters und Ghostbusters 2

Sie können jedoch von den Benutzern nicht erwarten, dass sie jedes Mal die URL ändern, wenn sie nach einem Film suchen möchten. Deshalb fügen Sie nun Benutzeroberflächenelemente zum besseren Filtern von Filmen hinzu. Wenn Sie die Signatur der Index-Methode geändert haben, um das Übergeben des routengebundenen Parameters ID zu testen, ändern Sie sie erneut so, dass sie einen Parameter namens searchString verwendet:

public async Task<IActionResult> Index(string searchString)
{
    if (_context.Movie == null)
    {
        return Problem("Entity set 'MvcMovieContext.Movie'  is null.");
    }

    var movies = from m in _context.Movie
                select m;

    if (!String.IsNullOrEmpty(searchString))
    {
        movies = movies.Where(s => s.Title!.Contains(searchString));
    }

    return View(await movies.ToListAsync());
}

Öffnen Sie die Datei Views/Movies/Index.cshtml, und fügen Sie das hervorgehobene <form>-Markup hinzu:

@model IEnumerable<MvcMovie.Models.Movie>

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

<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>

<form asp-controller="Movies" asp-action="Index">
    <p>
        Title: <input type="text" name="SearchString" />
        <input type="submit" value="Filter" />
    </p>
</form>
<table class="table">

Das HTML-Tag <form> nutzt das Hilfsprogramm für Formulartags. Wenn Sie nun das Formular senden, wird die Filterzeichenfolge an die Aktion Index des „movies“-Controllers übermittelt. Speichern Sie Ihre Änderungen, und testen Sie dann den Filter.

Indexansicht mit dem in das Filtertextfeld eingegebenen Wort „ghost“

Entgegen Ihrer Erwartung gibt es keine [HttpPost]-Überladung der Index-Methode. Diese ist nicht erforderlich, da die Methode nicht den Status der App ändert, sondern bloß Daten filtert.

Sie können die folgende [HttpPost] Index-Methode hinzufügen.

[HttpPost]
public string Index(string searchString, bool notUsed)
{
    return "From [HttpPost]Index: filter on " + searchString;
}

Der Parameter notUsed dient zum Erstellen einer Überladung für die Index-Methode. Damit beschäftigen wir uns später im Tutorial.

Wenn Sie diese Methode hinzufügen, entspricht die aufrufende Aktionsinstanz der [HttpPost] Index-Methode, und die [HttpPost] Index-Methode wird wie in der folgenden Abbildung gezeigt ausgeführt.

Browserfenster mit der Antwort der Anwendung von „Von HttpPost-Index“: Filter für „ghost“

Doch selbst wenn Sie diese [HttpPost]-Version der Index-Methode hinzufügen, gibt es eine Einschränkung für die gesamte Implementierung. Stellen Sie sich vor, Sie möchten eine bestimmte Suche als Favorit speichern oder einen Link an Freunde senden, auf den diese klicken können, um dieselbe gefilterte Liste von Filmen anzuzeigen. Beachten Sie, dass die URL der HTTP POST-Anforderung identisch mit der URL der GET-Anforderung (localhost:{PORT}/Movies/Index) ist. Es sind keine Suchinformationen in der URL vorhanden. Die Informationen in der Suchzeichenfolge werden an den Server als Formularfeldwert gesendet. Sie können dies mit den Entwicklertools für den Browser oder dem exzellenten Tool Fiddler überprüfen. Die folgende Abbildung zeigt die Entwicklertools für den Browser Chrome:

Registerkarte „Netzwerk“ der Entwicklertools in Microsoft Edge mit einem Anforderungstext mit dem „searchString“-Wert „ghost“

Sie können den Suchparameter und das XSRF-Token im Anforderungstext erkennen. Wie im vorherigen Tutorial erwähnt, generiert das Hilfsprogramm für Formulartags ein XSRF-Fälschungssicherheitstoken. Da wir keine Daten ändern, müssen wir nicht das Token in der Controllermethode validieren.

Da sich der Suchparameter im Anforderungstext und nicht in der URL befindet, können Sie diese Suchinformationen nicht als Favorit speichern oder mit anderen teilen. Beheben Sie dieses Problem, indem Sie angeben, dass eine HTTP GET-Anforderung verwendet werden soll, die sich in der Datei Views/Movies/Index.cshtml befindet.

@model IEnumerable<MvcMovie.Models.Movie>

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

<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>

<form asp-controller="Movies" asp-action="Index" method="get">
    <p>
        Title: <input type="text" name="SearchString" />
        <input type="submit" value="Filter" />
    </p>
</form>
<table class="table">

Wenn Sie nun eine Suche übermitteln, enthält die URL die Suchabfragezeichenfolge. Das Suchen wird auch an Aktionsmethode HttpGet Index übertragen, auch wenn Sie eine HttpPost Index-Methode haben.

Browserfenster mit „searchString=ghost“ in der URL und den zurückgegebenen Filmen Ghostbusters und Ghostbusters 2, die das Wort „Ghost“ enthalten

Das folgende Markup zeigt die Änderung am Tag form:

<form asp-controller="Movies" asp-action="Index" method="get">

Hinzufügen der Suche nach Genre

Fügen Sie dem Ordner Models die folgende MovieGenreViewModel-Klasse hinzu:

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace MvcMovie.Models;

public class MovieGenreViewModel
{
    public List<Movie>? Movies { get; set; }
    public SelectList? Genres { get; set; }
    public string? MovieGenre { get; set; }
    public string? SearchString { get; set; }
}

Das Ansichtsmodell „movie-genre“ enthält Folgendes:

  • Eine Liste von Filmen.
  • Ein SelectList-Element mit der Liste der Genres. Dies ermöglicht dem Benutzer, ein Genre in der Liste auszuwählen.
  • Ein MovieGenre-Element, das das ausgewählte Genre enthält.
  • SearchString, die den Text enthält, den Benutzer in das Suchtextfeld eingeben.

Ersetzen Sie die Index-Methode in MoviesController.cs durch folgenden Code:

// GET: Movies
public async Task<IActionResult> Index(string movieGenre, string searchString)
{
    if (_context.Movie == null)
    {
        return Problem("Entity set 'MvcMovieContext.Movie'  is null.");
    }

    // Use LINQ to get list of genres.
    IQueryable<string> genreQuery = from m in _context.Movie
                                    orderby m.Genre
                                    select m.Genre;
    var movies = from m in _context.Movie
                 select m;

    if (!string.IsNullOrEmpty(searchString))
    {
        movies = movies.Where(s => s.Title!.Contains(searchString));
    }

    if (!string.IsNullOrEmpty(movieGenre))
    {
        movies = movies.Where(x => x.Genre == movieGenre);
    }

    var movieGenreVM = new MovieGenreViewModel
    {
        Genres = new SelectList(await genreQuery.Distinct().ToListAsync()),
        Movies = await movies.ToListAsync()
    };

    return View(movieGenreVM);
}

Der folgende Code ist eine LINQ-Abfrage, die alle Genres aus der Datenbank abruft.

// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
                                orderby m.Genre
                                select m.Genre;

Das SelectList-Element von Genres wird durch Projizieren der unterschiedlichen Genres erstellt (wir möchten nicht, dass unsere Auswahlliste doppelte Genres enthält).

Wenn der Benutzer nach dem Element sucht, wird der Wert für die Suche im Suchfeld beibehalten.

Hinzufügen der Suche nach Genre zur Indexansicht

Aktualisieren Sie Index.cshtml in Views/Movies/ folgendermaßen:

@model MvcMovie.Models.MovieGenreViewModel

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

<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
    <p>

        <select asp-for="MovieGenre" asp-items="Model.Genres">
            <option value="">All</option>
        </select>

        Title: <input type="text" asp-for="SearchString" />
        <input type="submit" value="Filter" />
    </p>
</form>

<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Movies![0].Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movies![0].ReleaseDate)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movies![0].Genre)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movies![0].Price)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Movies!)
        {
            <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-action="Edit" asp-route-id="@item.Id">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

Überprüfen Sie den Lambdaausdruck, der im folgenden HTML-Hilfsprogramm verwendet wird:

@Html.DisplayNameFor(model => model.Movies![0].Title)

Das HTML-Hilfsprogramm DisplayNameFor im vorangehenden Code überprüft die Eigenschaft Title, auf die im Lambdaausdruck verwiesen wird, um den Anzeigenamen zu bestimmen. Da der Lambda-Ausdruck überprüft statt ausgewertet wird, erhalten Sie keine Zugriffsverletzung, wenn model, model.Movies, model.Movies[0] oder null leer sind. Wenn der Lambdaausdruck ausgewertet wird, (z.B. mit @Html.DisplayFor(modelItem => item.Title)), werden die Eigenschaftswerte ausgewertet. Der ! nach model.Movies ist der NULL-tolerante Operator, mit dem deklariert wird, das Movies nicht NULL ist.

Testen Sie die App mit einer Suche nach Genre, Filmtitel und beidem:

Browserfenster mit den Ergebnissen von https://localhost:5001/Movies?MovieGenre=Comedy& SearchString=2

In diesem Abschnitt fügen Sie die Suchfunktion zur Aktionsmethode Index hinzu, mit der Sie Filme nach Genre oder Name suchen können.

Aktualisieren Sie die Index-Methode in Controllers/MoviesController.cs mit folgendem Code:

public async Task<IActionResult> Index(string searchString)
{
    if (_context.Movie == null)
    {
        return Problem("Entity set 'MvcMovieContext.Movie'  is null.");
    }

    var movies = from m in _context.Movie
                select m;

    if (!String.IsNullOrEmpty(searchString))
    {
        movies = movies.Where(s => s.Title!.Contains(searchString));
    }

    return View(await movies.ToListAsync());
}

Die erste Zeile der Aktionsmethode Index erstellt eine LINQ-Abfrage zum Auswählen der Filme:

var movies = from m in _context.Movie
             select m;

Die Abfrage wird an diesem Punkt nur definiert. Sie wurde noch nicht für die Datenbank ausgeführt.

Wenn der searchString-Parameter eine Zeichenfolge enthält, wird die Filmabfrage so geändert, dass nach dem Wert der Suchzeichenfolge gefiltert wird:

if (!String.IsNullOrEmpty(searchString))
{
    movies = movies.Where(s => s.Title!.Contains(searchString));
}

Der Code s => s.Title!.Contains(searchString) oben ist ein Lambdaausdruck. Lambdaausdrücke werden in methodenbasierten LINQ-Abfragen als Argumente für standardmäßige Abfrageoperatormethoden wie die Where-Methode oder Contains verwendet (siehe den vorangehenden Code). LINQ-Abfragen werden nicht ausgeführt, wenn sie definiert oder durch Aufrufen einer Methode geändert werden (z. B. Where, Contains oder OrderBy). Stattdessen wird die Ausführung der Abfrage verzögert. Dies bedeutet, dass die Auswertung eines Ausdrucks so lange hinausgezögert wird, bis dessen realisierter Wert tatsächlich durchlaufen oder die ToListAsync-Methode aufgerufen wird. Weitere Informationen zur verzögerten Abfrageausführung finden Sie unter Abfrageausführung.

Hinweis: Die Contains-Methode wird in der Datenbank und nicht im oben gezeigten C#-Code ausgeführt. Die Groß-/Kleinschreibung in der Abfrage hängt von der Datenbank und Sortierung ab. In SQL Server wird Contains zu SQL LIKE zugeordnet, das Groß-/Kleinschreibung nicht beachtet. In SQLite wird bei der Standardsortierung Groß-/Kleinschreibung beachtet.

Navigieren Sie zu /Movies/Index. Fügen Sie eine Abfragezeichenfolge wie ?searchString=Ghost an die URL an. Die gefilterten Filme werden angezeigt.

Indexansicht

Wenn Sie die Signatur der Index-Methode so ändern, dass sie einen Parameter mit dem Namen id hat, entspricht der Parameter id dem optionalen Platzhalter {id} für die Standardrouten, die in Program.cs festgelegt sind.

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

Ändern Sie den Parameter in id und alle Vorkommen von searchString in id.

Die vorherige Index-Methode:

public async Task<IActionResult> Index(string searchString)
{
    if (_context.Movie == null)
    {
        return Problem("Entity set 'MvcMovieContext.Movie'  is null.");
    }

    var movies = from m in _context.Movie
                select m;

    if (!String.IsNullOrEmpty(searchString))
    {
        movies = movies.Where(s => s.Title!.Contains(searchString));
    }

    return View(await movies.ToListAsync());
}

Die aktualisierte Index-Methode mit id-Parameter:

public async Task<IActionResult> Index(string id)
{
    if (_context.Movie == null)
    {
        return Problem("Entity set 'MvcMovieContext.Movie'  is null.");
    }

    var movies = from m in _context.Movie
                 select m;

    if (!String.IsNullOrEmpty(id))
    {
        movies = movies.Where(s => s.Title!.Contains(id));
    }

    return View(await movies.ToListAsync());
}

Sie können nun den Suchtitel als Routendaten (ein URL-Segment) anstatt als Wert einer Abfragezeichenfolge übergeben.

Indexansicht mit dem der URL hinzugefügten Wort „ghost“ und einer zurückgegebenen Filmliste mit zwei Filmen: Ghostbusters und Ghostbusters 2

Sie können jedoch von den Benutzern nicht erwarten, dass sie jedes Mal die URL ändern, wenn sie nach einem Film suchen möchten. Deshalb fügen Sie nun Benutzeroberflächenelemente zum besseren Filtern von Filmen hinzu. Wenn Sie die Signatur der Index-Methode geändert haben, um das Übergeben des routengebundenen Parameters ID zu testen, ändern Sie sie erneut so, dass sie einen Parameter namens searchString verwendet:

public async Task<IActionResult> Index(string searchString)
{
    if (_context.Movie == null)
    {
        return Problem("Entity set 'MvcMovieContext.Movie'  is null.");
    }

    var movies = from m in _context.Movie
                select m;

    if (!String.IsNullOrEmpty(searchString))
    {
        movies = movies.Where(s => s.Title!.Contains(searchString));
    }

    return View(await movies.ToListAsync());
}

Öffnen Sie die Datei Views/Movies/Index.cshtml, und fügen Sie das hervorgehobene <form>-Markup hinzu:

@model IEnumerable<MvcMovie.Models.Movie>

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

<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>

<form asp-controller="Movies" asp-action="Index">
    <p>
        Title: <input type="text" name="SearchString" />
        <input type="submit" value="Filter" />
    </p>
</form>
<table class="table">

Das HTML-Tag <form> nutzt das Hilfsprogramm für Formulartags. Wenn Sie nun das Formular senden, wird die Filterzeichenfolge an die Aktion Index des „movies“-Controllers übermittelt. Speichern Sie Ihre Änderungen, und testen Sie dann den Filter.

Indexansicht mit dem in das Filtertextfeld eingegebenen Wort „ghost“

Entgegen Ihrer Erwartung gibt es keine [HttpPost]-Überladung der Index-Methode. Diese ist nicht erforderlich, da die Methode nicht den Status der App ändert, sondern bloß Daten filtert.

Sie können die folgende [HttpPost] Index-Methode hinzufügen.

[HttpPost]
public string Index(string searchString, bool notUsed)
{
    return "From [HttpPost]Index: filter on " + searchString;
}

Der Parameter notUsed dient zum Erstellen einer Überladung für die Index-Methode. Damit beschäftigen wir uns später im Tutorial.

Wenn Sie diese Methode hinzufügen, entspricht die aufrufende Aktionsinstanz der [HttpPost] Index-Methode, und die [HttpPost] Index-Methode wird wie in der folgenden Abbildung gezeigt ausgeführt.

Browserfenster mit der Antwort der Anwendung von „Von HttpPost-Index“: Filter für „ghost“

Doch selbst wenn Sie diese [HttpPost]-Version der Index-Methode hinzufügen, gibt es eine Einschränkung für die gesamte Implementierung. Stellen Sie sich vor, Sie möchten eine bestimmte Suche als Favorit speichern oder einen Link an Freunde senden, auf den diese klicken können, um dieselbe gefilterte Liste von Filmen anzuzeigen. Beachten Sie, dass die URL der HTTP POST-Anforderung identisch mit der URL der GET-Anforderung (localhost:{PORT}/Movies/Index) ist. Es sind keine Suchinformationen in der URL vorhanden. Die Informationen in der Suchzeichenfolge werden an den Server als Formularfeldwert gesendet. Sie können dies mit den Entwicklertools für den Browser oder dem exzellenten Tool Fiddler überprüfen. Die folgende Abbildung zeigt die Entwicklertools für den Browser Chrome:

Registerkarte „Netzwerk“ der Entwicklertools in Microsoft Edge mit einem Anforderungstext mit dem „searchString“-Wert „ghost“

Sie können den Suchparameter und das XSRF-Token im Anforderungstext erkennen. Wie im vorherigen Tutorial erwähnt, generiert das Hilfsprogramm für Formulartags ein XSRF-Fälschungssicherheitstoken. Da wir keine Daten ändern, müssen wir nicht das Token in der Controllermethode validieren.

Da sich der Suchparameter im Anforderungstext und nicht in der URL befindet, können Sie diese Suchinformationen nicht als Favorit speichern oder mit anderen teilen. Beheben Sie dieses Problem, indem Sie angeben, dass eine HTTP GET-Anforderung verwendet werden soll, die sich in der Datei Views/Movies/Index.cshtml befindet.

@model IEnumerable<MvcMovie.Models.Movie>

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

<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>

<form asp-controller="Movies" asp-action="Index" method="get">
    <p>
        Title: <input type="text" name="SearchString" />
        <input type="submit" value="Filter" />
    </p>
</form>
<table class="table">

Wenn Sie nun eine Suche übermitteln, enthält die URL die Suchabfragezeichenfolge. Das Suchen wird auch an Aktionsmethode HttpGet Index übertragen, auch wenn Sie eine HttpPost Index-Methode haben.

Browserfenster mit „searchString=ghost“ in der URL und den zurückgegebenen Filmen Ghostbusters und Ghostbusters 2, die das Wort „Ghost“ enthalten

Das folgende Markup zeigt die Änderung am Tag form:

<form asp-controller="Movies" asp-action="Index" method="get">

Hinzufügen der Suche nach Genre

Fügen Sie dem Ordner Models die folgende MovieGenreViewModel-Klasse hinzu:

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace MvcMovie.Models;

public class MovieGenreViewModel
{
    public List<Movie>? Movies { get; set; }
    public SelectList? Genres { get; set; }
    public string? MovieGenre { get; set; }
    public string? SearchString { get; set; }
}

Das Ansichtsmodell „movie-genre“ enthält Folgendes:

  • Eine Liste von Filmen.
  • Ein SelectList-Element mit der Liste der Genres. Dies ermöglicht dem Benutzer, ein Genre in der Liste auszuwählen.
  • Ein MovieGenre-Element, das das ausgewählte Genre enthält.
  • SearchString, die den Text enthält, den Benutzer in das Suchtextfeld eingeben.

Ersetzen Sie die Index-Methode in MoviesController.cs durch folgenden Code:

// GET: Movies
public async Task<IActionResult> Index(string movieGenre, string searchString)
{
    if (_context.Movie == null)
    {
        return Problem("Entity set 'MvcMovieContext.Movie'  is null.");
    }

    // Use LINQ to get list of genres.
    IQueryable<string> genreQuery = from m in _context.Movie
                                    orderby m.Genre
                                    select m.Genre;
    var movies = from m in _context.Movie
                 select m;

    if (!string.IsNullOrEmpty(searchString))
    {
        movies = movies.Where(s => s.Title!.Contains(searchString));
    }

    if (!string.IsNullOrEmpty(movieGenre))
    {
        movies = movies.Where(x => x.Genre == movieGenre);
    }

    var movieGenreVM = new MovieGenreViewModel
    {
        Genres = new SelectList(await genreQuery.Distinct().ToListAsync()),
        Movies = await movies.ToListAsync()
    };

    return View(movieGenreVM);
}

Der folgende Code ist eine LINQ-Abfrage, die alle Genres aus der Datenbank abruft.

// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
                                orderby m.Genre
                                select m.Genre;

Das SelectList-Element von Genres wird durch Projizieren der unterschiedlichen Genres erstellt (wir möchten nicht, dass unsere Auswahlliste doppelte Genres enthält).

Wenn der Benutzer nach dem Element sucht, wird der Wert für die Suche im Suchfeld beibehalten.

Hinzufügen der Suche nach Genre zur Indexansicht

Aktualisieren Sie Index.cshtml in Views/Movies/ folgendermaßen:

@model MvcMovie.Models.MovieGenreViewModel

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

<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
    <p>

        <select asp-for="MovieGenre" asp-items="Model.Genres">
            <option value="">All</option>
        </select>

        Title: <input type="text" asp-for="SearchString" />
        <input type="submit" value="Filter" />
    </p>
</form>

<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Movies![0].Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movies![0].ReleaseDate)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movies![0].Genre)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movies![0].Price)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Movies!)
        {
            <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-action="Edit" asp-route-id="@item.Id">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

Überprüfen Sie den Lambdaausdruck, der im folgenden HTML-Hilfsprogramm verwendet wird:

@Html.DisplayNameFor(model => model.Movies![0].Title)

Das HTML-Hilfsprogramm DisplayNameFor im vorangehenden Code überprüft die Eigenschaft Title, auf die im Lambdaausdruck verwiesen wird, um den Anzeigenamen zu bestimmen. Da der Lambda-Ausdruck überprüft statt ausgewertet wird, erhalten Sie keine Zugriffsverletzung, wenn model, model.Movies, model.Movies[0] oder null leer sind. Wenn der Lambdaausdruck ausgewertet wird, (z.B. mit @Html.DisplayFor(modelItem => item.Title)), werden die Eigenschaftswerte ausgewertet. Der ! nach model.Movies ist der NULL-tolerante Operator, mit dem deklariert wird, das Movies nicht NULL ist.

Testen Sie die App mit einer Suche nach Genre, Filmtitel und beidem:

Browserfenster mit den Ergebnissen von https://localhost:5001/Movies?MovieGenre=Comedy& SearchString=2

In diesem Abschnitt fügen Sie die Suchfunktion zur Aktionsmethode Index hinzu, mit der Sie Filme nach Genre oder Name suchen können.

Aktualisieren Sie die Index-Methode in Controllers/MoviesController.cs mit folgendem Code:

public async Task<IActionResult> Index(string searchString)
{
    var movies = from m in _context.Movie
                 select m;

    if (!String.IsNullOrEmpty(searchString))
    {
        movies = movies.Where(s => s.Title!.Contains(searchString));
    }

    return View(await movies.ToListAsync());
}

Die erste Zeile der Aktionsmethode Index erstellt eine LINQ-Abfrage zum Auswählen der Filme:

var movies = from m in _context.Movie
             select m;

Die Abfrage wird an dieser Stelle nur definiert und nicht auf die Datenbank angewendet.

Wenn der searchString-Parameter eine Zeichenfolge enthält, wird die Filmabfrage so geändert, dass nach dem Wert der Suchzeichenfolge gefiltert wird:

if (!String.IsNullOrEmpty(searchString))
{
    movies = movies.Where(s => s.Title!.Contains(searchString));
}

Der Code s => s.Title!.Contains(searchString) oben ist ein Lambdaausdruck. Lambdaausdrücke werden in methodenbasierten LINQ-Abfragen als Argumente für standardmäßige Abfrageoperatormethoden wie die Where-Methode oder Contains verwendet (siehe den vorangehenden Code). LINQ-Abfragen werden nicht ausgeführt, wenn sie definiert oder durch Aufrufen einer Methode geändert werden (z. B. Where, Contains oder OrderBy). Stattdessen wird die Ausführung der Abfrage verzögert. Dies bedeutet, dass die Auswertung eines Ausdrucks so lange hinausgezögert wird, bis dessen realisierter Wert tatsächlich durchlaufen oder die ToListAsync-Methode aufgerufen wird. Weitere Informationen zur verzögerten Abfrageausführung finden Sie unter Abfrageausführung.

Hinweis: Die Contains-Methode wird in der Datenbank und nicht im oben gezeigten C#-Code ausgeführt. Die Groß-/Kleinschreibung in der Abfrage hängt von der Datenbank und Sortierung ab. In SQL Server wird Contains zu SQL LIKE zugeordnet, das Groß-/Kleinschreibung nicht beachtet. In SQLite wird bei der Standardsortierung Groß-/Kleinschreibung beachtet.

Navigieren Sie zu /Movies/Index. Fügen Sie eine Abfragezeichenfolge wie ?searchString=Ghost an die URL an. Die gefilterten Filme werden angezeigt.

Indexansicht

Wenn Sie die Signatur der Index-Methode so ändern, dass sie einen Parameter mit dem Namen id hat, entspricht der Parameter id dem optionalen Platzhalter {id} für die Standardrouten, die in Program.cs festgelegt sind.

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

Ändern Sie den Parameter in id und alle Vorkommen von searchString in id.

Die vorherige Index-Methode:

public async Task<IActionResult> Index(string searchString)
{
    var movies = from m in _context.Movie
                 select m;

    if (!String.IsNullOrEmpty(searchString))
    {
        movies = movies.Where(s => s.Title!.Contains(searchString));
    }

    return View(await movies.ToListAsync());
}

Die aktualisierte Index-Methode mit id-Parameter:

public async Task<IActionResult> Index(string id)
{
    var movies = from m in _context.Movie
                 select m;

    if (!String.IsNullOrEmpty(id))
    {
        movies = movies.Where(s => s.Title!.Contains(id));
    }

    return View(await movies.ToListAsync());
}

Sie können nun den Suchtitel als Routendaten (ein URL-Segment) anstatt als Wert einer Abfragezeichenfolge übergeben.

Indexansicht mit dem der URL hinzugefügten Wort „ghost“ und einer zurückgegebenen Filmliste mit zwei Filmen: Ghostbusters und Ghostbusters 2

Sie können jedoch von den Benutzern nicht erwarten, dass sie jedes Mal die URL ändern, wenn sie nach einem Film suchen möchten. Deshalb fügen Sie nun Benutzeroberflächenelemente zum besseren Filtern von Filmen hinzu. Wenn Sie die Signatur der Index-Methode geändert haben, um das Übergeben des routengebundenen Parameters ID zu testen, ändern Sie sie erneut so, dass sie einen Parameter namens searchString verwendet:

public async Task<IActionResult> Index(string searchString)
{
    var movies = from m in _context.Movie
                 select m;

    if (!String.IsNullOrEmpty(searchString))
    {
        movies = movies.Where(s => s.Title!.Contains(searchString));
    }

    return View(await movies.ToListAsync());
}

Öffnen Sie die Datei Views/Movies/Index.cshtml, und fügen Sie das hervorgehobene <form>-Markup hinzu:

@model IEnumerable<MvcMovie.Models.Movie>

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

<h2>Index</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>

<form asp-controller="Movies" asp-action="Index">
    <p>
        Title: <input type="text" name="SearchString" />
        <input type="submit" value="Filter" />
    </p>
</form>

Das HTML-Tag <form> nutzt das Hilfsprogramm für Formulartags. Wenn Sie nun das Formular senden, wird die Filterzeichenfolge an die Aktion Index des „movies“-Controllers übermittelt. Speichern Sie Ihre Änderungen, und testen Sie dann den Filter.

Indexansicht mit dem in das Filtertextfeld eingegebenen Wort „ghost“

Entgegen Ihrer Erwartung gibt es keine [HttpPost]-Überladung der Index-Methode. Diese ist nicht erforderlich, da die Methode nicht den Status der App ändert, sondern bloß Daten filtert.

Sie können die folgende [HttpPost] Index-Methode hinzufügen.

[HttpPost]
public string Index(string searchString, bool notUsed)
{
    return "From [HttpPost]Index: filter on " + searchString;
}

Der Parameter notUsed dient zum Erstellen einer Überladung für die Index-Methode. Damit beschäftigen wir uns später im Tutorial.

Wenn Sie diese Methode hinzufügen, entspricht die aufrufende Aktionsinstanz der [HttpPost] Index-Methode, und die [HttpPost] Index-Methode wird wie in der folgenden Abbildung gezeigt ausgeführt.

Browserfenster mit der Antwort der Anwendung von „Von HttpPost-Index“: Filter für „ghost“

Doch selbst wenn Sie diese [HttpPost]-Version der Index-Methode hinzufügen, gibt es eine Einschränkung für die gesamte Implementierung. Stellen Sie sich vor, Sie möchten eine bestimmte Suche als Favorit speichern oder einen Link an Freunde senden, auf den diese klicken können, um dieselbe gefilterte Liste von Filmen anzuzeigen. Beachten Sie, dass die URL der HTTP POST-Anforderung identisch mit der URL der GET-Anforderung (localhost:{PORT}/Movies/Index) ist. Es sind keine Suchinformationen in der URL vorhanden. Die Informationen in der Suchzeichenfolge werden an den Server als Formularfeldwert gesendet. Sie können dies mit den Entwicklertools für den Browser oder dem exzellenten Tool Fiddler überprüfen. Die folgende Abbildung zeigt die Entwicklertools für den Browser Chrome:

Registerkarte „Netzwerk“ der Entwicklertools in Microsoft Edge mit einem Anforderungstext mit dem „searchString“-Wert „ghost“

Sie können den Suchparameter und das XSRF-Token im Anforderungstext erkennen. Wie im vorherigen Tutorial erwähnt, generiert das Hilfsprogramm für Formulartags ein XSRF-Fälschungssicherheitstoken. Da wir keine Daten ändern, müssen wir nicht das Token in der Controllermethode validieren.

Da sich der Suchparameter im Anforderungstext und nicht in der URL befindet, können Sie diese Suchinformationen nicht als Favorit speichern oder mit anderen teilen. Beheben Sie dieses Problem, indem Sie angeben, dass eine HTTP GET-Anforderung verwendet werden soll, die sich in der Datei Views/Movies/Index.cshtml befindet.

@model IEnumerable<MvcMovie.Models.Movie>

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

<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>

<form asp-controller="Movies" asp-action="Index" method="get">
    <p>
        Title: <input type="text" name="SearchString" />
        <input type="submit" value="Filter" />
    </p>
</form>
<table class="table">

Wenn Sie nun eine Suche übermitteln, enthält die URL die Suchabfragezeichenfolge. Das Suchen wird auch an Aktionsmethode HttpGet Index übertragen, auch wenn Sie eine HttpPost Index-Methode haben.

Browserfenster mit „searchString=ghost“ in der URL und den zurückgegebenen Filmen Ghostbusters und Ghostbusters 2, die das Wort „Ghost“ enthalten

Das folgende Markup zeigt die Änderung am Tag form:

<form asp-controller="Movies" asp-action="Index" method="get">

Hinzufügen der Suche nach Genre

Fügen Sie dem Ordner Models die folgende MovieGenreViewModel-Klasse hinzu:

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace MvcMovie.Models
{
    public class MovieGenreViewModel
    {
        public List<Movie>? Movies { get; set; }
        public SelectList? Genres { get; set; }
        public string? MovieGenre { get; set; }
        public string? SearchString { get; set; }
    }
}

Das Ansichtsmodell „movie-genre“ enthält Folgendes:

  • Eine Liste von Filmen.
  • Ein SelectList-Element mit der Liste der Genres. Dies ermöglicht dem Benutzer, ein Genre in der Liste auszuwählen.
  • Ein MovieGenre-Element, das das ausgewählte Genre enthält.
  • SearchString, die den Text enthält, den Benutzer in das Suchtextfeld eingeben.

Ersetzen Sie die Index-Methode in MoviesController.cs durch folgenden Code:

// GET: Movies
public async Task<IActionResult> Index(string movieGenre, string searchString)
{
    // Use LINQ to get list of genres.
    IQueryable<string> genreQuery = from m in _context.Movie
                                    orderby m.Genre
                                    select m.Genre;
    var movies = from m in _context.Movie
                 select m;

    if (!string.IsNullOrEmpty(searchString))
    {
        movies = movies.Where(s => s.Title!.Contains(searchString));
    }

    if (!string.IsNullOrEmpty(movieGenre))
    {
        movies = movies.Where(x => x.Genre == movieGenre);
    }

    var movieGenreVM = new MovieGenreViewModel
    {
        Genres = new SelectList(await genreQuery.Distinct().ToListAsync()),
        Movies = await movies.ToListAsync()
    };

    return View(movieGenreVM);
}

Der folgende Code ist eine LINQ-Abfrage, die alle Genres aus der Datenbank abruft.

// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
                                orderby m.Genre
                                select m.Genre;

Das SelectList-Element von Genres wird durch Projizieren der unterschiedlichen Genres erstellt (wir möchten nicht, dass unsere Auswahlliste doppelte Genres enthält).

Wenn der Benutzer nach dem Element sucht, wird der Wert für die Suche im Suchfeld beibehalten.

Hinzufügen der Suche nach Genre zur Indexansicht

Aktualisieren Sie Index.cshtml in Views/Movies/ folgendermaßen:

@model MvcMovie.Models.MovieGenreViewModel

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

<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
    <p>

        <select asp-for="MovieGenre" asp-items="Model.Genres">
            <option value="">All</option>
        </select>

        Title: <input type="text" asp-for="SearchString" />
        <input type="submit" value="Filter" />
    </p>
</form>

<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Movies[0].Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movies[0].ReleaseDate)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movies[0].Genre)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movies[0].Price)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Movies)
        {
            <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-action="Edit" asp-route-id="@item.Id">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

Überprüfen Sie den Lambdaausdruck, der im folgenden HTML-Hilfsprogramm verwendet wird:

@Html.DisplayNameFor(model => model.Movies[0].Title)

Das HTML-Hilfsprogramm DisplayNameFor im vorangehenden Code überprüft die Eigenschaft Title, auf die im Lambdaausdruck verwiesen wird, um den Anzeigenamen zu bestimmen. Da der Lambda-Ausdruck überprüft statt ausgewertet wird, erhalten Sie keine Zugriffsverletzung, wenn model, model.Movies, model.Movies[0] oder null leer sind. Wenn der Lambdaausdruck ausgewertet wird, (z.B. mit @Html.DisplayFor(modelItem => item.Title)), werden die Eigenschaftswerte ausgewertet.

Testen Sie die App mit einer Suche nach Genre, Filmtitel und beidem:

Browserfenster mit den Ergebnissen von https://localhost:5001/Movies?MovieGenre=Comedy& SearchString=2

In diesem Abschnitt fügen Sie die Suchfunktion zur Aktionsmethode Index hinzu, mit der Sie Filme nach Genre oder Name suchen können.

Aktualisieren Sie die Index-Methode in Controllers/MoviesController.cs mit folgendem Code:

public async Task<IActionResult> Index(string searchString)
{
    var movies = from m in _context.Movie
                 select m;

    if (!String.IsNullOrEmpty(searchString))
    {
        movies = movies.Where(s => s.Title.Contains(searchString));
    }

    return View(await movies.ToListAsync());
}

Die erste Zeile der Aktionsmethode Index erstellt eine LINQ-Abfrage zum Auswählen der Filme:

var movies = from m in _context.Movie
             select m;

Die Abfrage wird an dieser Stelle nur definiert und nicht auf die Datenbank angewendet.

Wenn der searchString-Parameter eine Zeichenfolge enthält, wird die Filmabfrage so geändert, dass nach dem Wert der Suchzeichenfolge gefiltert wird:

if (!String.IsNullOrEmpty(searchString))
{
    movies = movies.Where(s => s.Title.Contains(searchString));
}

Der Code s => s.Title.Contains() oben ist ein Lambdaausdruck. Lambdaausdrücke werden in methodenbasierten LINQ-Abfragen als Argumente für standardmäßige Abfrageoperatormethoden wie die Where-Methode oder Contains verwendet (siehe den vorangehenden Code). LINQ-Abfragen werden nicht ausgeführt, wenn sie definiert oder durch Aufrufen einer Methode geändert werden (z. B. Where, Contains oder OrderBy). Stattdessen wird die Ausführung der Abfrage verzögert. Dies bedeutet, dass die Auswertung eines Ausdrucks so lange hinausgezögert wird, bis dessen realisierter Wert tatsächlich durchlaufen oder die ToListAsync-Methode aufgerufen wird. Weitere Informationen zur verzögerten Abfrageausführung finden Sie unter Abfrageausführung.

Hinweis: Die Contains-Methode wird in der Datenbank und nicht im oben gezeigten C#-Code ausgeführt. Die Groß-/Kleinschreibung in der Abfrage hängt von der Datenbank und Sortierung ab. In SQL Server wird Contains zu SQL LIKE zugeordnet, das Groß-/Kleinschreibung nicht beachtet. In SQLite wird bei der Standardsortierung Groß-/Kleinschreibung beachtet.

Navigieren Sie zu /Movies/Index. Fügen Sie eine Abfragezeichenfolge wie ?searchString=Ghost an die URL an. Die gefilterten Filme werden angezeigt.

Indexansicht

Wenn Sie die Signatur der Index-Methode so ändern, dass sie einen Parameter mit dem Namen id hat, entspricht der Parameter id dem optionalen Platzhalter {id} für die Standardrouten, die in Startup.cs festgelegt sind.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

Ändern Sie den Parameter in id und alle Vorkommen von searchString in id.

Die vorherige Index-Methode:

public async Task<IActionResult> Index(string searchString)
{
    var movies = from m in _context.Movie
                 select m;

    if (!String.IsNullOrEmpty(searchString))
    {
        movies = movies.Where(s => s.Title.Contains(searchString));
    }

    return View(await movies.ToListAsync());
}

Die aktualisierte Index-Methode mit id-Parameter:

public async Task<IActionResult> Index(string id)
{
    var movies = from m in _context.Movie
                 select m;

    if (!String.IsNullOrEmpty(id))
    {
        movies = movies.Where(s => s.Title.Contains(id));
    }

    return View(await movies.ToListAsync());
}

Sie können nun den Suchtitel als Routendaten (ein URL-Segment) anstatt als Wert einer Abfragezeichenfolge übergeben.

Indexansicht mit dem der URL hinzugefügten Wort „ghost“ und einer zurückgegebenen Filmliste mit zwei Filmen: Ghostbusters und Ghostbusters 2

Sie können jedoch von den Benutzern nicht erwarten, dass sie jedes Mal die URL ändern, wenn sie nach einem Film suchen möchten. Deshalb fügen Sie nun Benutzeroberflächenelemente zum besseren Filtern von Filmen hinzu. Wenn Sie die Signatur der Index-Methode geändert haben, um das Übergeben des routengebundenen Parameters ID zu testen, ändern Sie sie erneut so, dass sie einen Parameter namens searchString verwendet:

public async Task<IActionResult> Index(string searchString)
{
    var movies = from m in _context.Movie
                 select m;

    if (!String.IsNullOrEmpty(searchString))
    {
        movies = movies.Where(s => s.Title.Contains(searchString));
    }

    return View(await movies.ToListAsync());
}

Öffnen Sie die Datei Views/Movies/Index.cshtml, und fügen Sie das hervorgehobene <form>-Markup hinzu:

    ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>

<form asp-controller="Movies" asp-action="Index">
    <p>
        Title: <input type="text" name="SearchString" />
        <input type="submit" value="Filter" />
    </p>
</form>

<table class="table">
    <thead>

Das HTML-Tag <form> nutzt das Hilfsprogramm für Formulartags. Wenn Sie nun das Formular senden, wird die Filterzeichenfolge an die Aktion Index des „movies“-Controllers übermittelt. Speichern Sie Ihre Änderungen, und testen Sie dann den Filter.

Indexansicht mit dem in das Filtertextfeld eingegebenen Wort „ghost“

Entgegen Ihrer Erwartung gibt es keine [HttpPost]-Überladung der Index-Methode. Diese ist nicht erforderlich, da die Methode nicht den Status der App ändert, sondern bloß Daten filtert.

Sie können die folgende [HttpPost] Index-Methode hinzufügen.

[HttpPost]
public string Index(string searchString, bool notUsed)
{
    return "From [HttpPost]Index: filter on " + searchString;
}

Der Parameter notUsed dient zum Erstellen einer Überladung für die Index-Methode. Damit beschäftigen wir uns später im Tutorial.

Wenn Sie diese Methode hinzufügen, entspricht die aufrufende Aktionsinstanz der [HttpPost] Index-Methode, und die [HttpPost] Index-Methode wird wie in der folgenden Abbildung gezeigt ausgeführt.

Browserfenster mit der Antwort der Anwendung von „Von HttpPost-Index“: Filter für „ghost“

Doch selbst wenn Sie diese [HttpPost]-Version der Index-Methode hinzufügen, gibt es eine Einschränkung für die gesamte Implementierung. Stellen Sie sich vor, Sie möchten eine bestimmte Suche als Favorit speichern oder einen Link an Freunde senden, auf den diese klicken können, um dieselbe gefilterte Liste von Filmen anzuzeigen. Beachten Sie, dass die URL der HTTP POST-Anforderung identisch mit der URL der GET-Anforderung (localhost:{PORT}/Movies/Index) ist. Es sind keine Suchinformationen in der URL vorhanden. Die Informationen in der Suchzeichenfolge werden an den Server als Formularfeldwert gesendet. Sie können dies mit den Entwicklertools für den Browser oder dem exzellenten Tool Fiddler überprüfen. Die folgende Abbildung zeigt die Entwicklertools für den Browser Chrome:

Registerkarte „Netzwerk“ der Entwicklertools in Microsoft Edge mit einem Anforderungstext mit dem „searchString“-Wert „ghost“

Sie können den Suchparameter und das XSRF-Token im Anforderungstext erkennen. Wie im vorherigen Tutorial erwähnt, generiert das Hilfsprogramm für Formulartags ein XSRF-Fälschungssicherheitstoken. Da wir keine Daten ändern, müssen wir nicht das Token in der Controllermethode validieren.

Da sich der Suchparameter im Anforderungstext und nicht in der URL befindet, können Sie diese Suchinformationen nicht als Favorit speichern oder mit anderen teilen. Beheben Sie dieses Problem, indem Sie angeben, dass eine HTTP GET-Anforderung verwendet werden soll, die sich in der Datei Views/Movies/Index.cshtml befindet.

@model IEnumerable<MvcMovie.Models.Movie>

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

<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
    <p>
        Title: <input type="text" name="SearchString" />
        <input type="submit" value="Filter" />
    </p>
</form>

<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Title)

Wenn Sie nun eine Suche übermitteln, enthält die URL die Suchabfragezeichenfolge. Das Suchen wird auch an Aktionsmethode HttpGet Index übertragen, auch wenn Sie eine HttpPost Index-Methode haben.

Browserfenster mit „searchString=ghost“ in der URL und den zurückgegebenen Filmen Ghostbusters und Ghostbusters 2, die das Wort „Ghost“ enthalten

Das folgende Markup zeigt die Änderung am Tag form:

<form asp-controller="Movies" asp-action="Index" method="get">

Hinzufügen der Suche nach Genre

Fügen Sie dem Ordner Models die folgende MovieGenreViewModel-Klasse hinzu:

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace MvcMovie.Models
{
    public class MovieGenreViewModel
    {
        public List<Movie> Movies { get; set; }
        public SelectList Genres { get; set; }
        public string MovieGenre { get; set; }
        public string SearchString { get; set; }
    }
}

Das Ansichtsmodell „movie-genre“ enthält Folgendes:

  • Eine Liste von Filmen.
  • Ein SelectList-Element mit der Liste der Genres. Dies ermöglicht dem Benutzer, ein Genre in der Liste auszuwählen.
  • Ein MovieGenre-Element, das das ausgewählte Genre enthält.
  • SearchString, die den Text enthält, den Benutzer in das Suchtextfeld eingeben.

Ersetzen Sie die Index-Methode in MoviesController.cs durch folgenden Code:

// GET: Movies
public async Task<IActionResult> Index(string movieGenre, string searchString)
{
    // Use LINQ to get list of genres.
    IQueryable<string> genreQuery = from m in _context.Movie
                                    orderby m.Genre
                                    select m.Genre;

    var movies = from m in _context.Movie
                 select m;

    if (!string.IsNullOrEmpty(searchString))
    {
        movies = movies.Where(s => s.Title.Contains(searchString));
    }

    if (!string.IsNullOrEmpty(movieGenre))
    {
        movies = movies.Where(x => x.Genre == movieGenre);
    }

    var movieGenreVM = new MovieGenreViewModel
    {
        Genres = new SelectList(await genreQuery.Distinct().ToListAsync()),
        Movies = await movies.ToListAsync()
    };

    return View(movieGenreVM);
}

Der folgende Code ist eine LINQ-Abfrage, die alle Genres aus der Datenbank abruft.

// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
                                orderby m.Genre
                                select m.Genre;

Das SelectList-Element von Genres wird durch Projizieren der unterschiedlichen Genres erstellt (wir möchten nicht, dass unsere Auswahlliste doppelte Genres enthält).

Wenn der Benutzer nach dem Element sucht, wird der Wert für die Suche im Suchfeld beibehalten.

Hinzufügen der Suche nach Genre zur Indexansicht

Aktualisieren Sie Index.cshtml in Views/Movies/ folgendermaßen:

@model MvcMovie.Models.MovieGenreViewModel

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

<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
    <p>

        <select asp-for="MovieGenre" asp-items="Model.Genres">
            <option value="">All</option>
        </select>

        Title: <input type="text" asp-for="SearchString" />
        <input type="submit" value="Filter" />
    </p>
</form>

<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Movies[0].Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movies[0].ReleaseDate)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movies[0].Genre)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movies[0].Price)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Movies)
        {
            <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-action="Edit" asp-route-id="@item.Id">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

Überprüfen Sie den Lambdaausdruck, der im folgenden HTML-Hilfsprogramm verwendet wird:

@Html.DisplayNameFor(model => model.Movies[0].Title)

Das HTML-Hilfsprogramm DisplayNameFor im vorangehenden Code überprüft die Eigenschaft Title, auf die im Lambdaausdruck verwiesen wird, um den Anzeigenamen zu bestimmen. Da der Lambda-Ausdruck überprüft statt ausgewertet wird, erhalten Sie keine Zugriffsverletzung, wenn model, model.Movies, model.Movies[0] oder null leer sind. Wenn der Lambdaausdruck ausgewertet wird, (z.B. mit @Html.DisplayFor(modelItem => item.Title)), werden die Eigenschaftswerte ausgewertet.

Testen Sie die App mit einer Suche nach Genre, Filmtitel und beidem:

Browserfenster mit den Ergebnissen von https://localhost:5001/Movies?MovieGenre=Comedy& SearchString=2