Część 7. Dodawanie wyszukiwania do aplikacji MVC platformy ASP.NET Core

Uwaga

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

Ważne

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

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

Autor: Rick Anderson

W tej sekcji dodasz możliwość wyszukiwania do Index metody akcji, która umożliwia wyszukiwanie filmów według gatunku lub nazwy.

Zaktualizuj metodę znajdującą Index się wewnątrz Controllers/MoviesController.cs za pomocą następującego kodu:

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

Poniższy wiersz w metodzie Index akcji tworzy zapytanie LINQ w celu wybrania filmów:

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

Zapytanie jest zdefiniowane tylko w tym momencie, nie zostało ono uruchomione względem bazy danych.

searchString Jeśli parametr zawiera ciąg, zapytanie filmów zostanie zmodyfikowane w celu filtrowania wartości ciągu wyszukiwania:

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

Powyższy s => s.Title!.Contains(searchString) kod jest wyrażeniem lambda. Lambdas są używane w zapytaniach LINQ opartych na metodzie jako argumentach do standardowych metod operatorów zapytań, takich jak Where metoda lub Contains (używane w powyższym kodzie). Zapytania LINQ nie są wykonywane podczas ich definiowania lub modyfikacji przez wywołanie metody, takiej jak Where, Containslub OrderBy. Zamiast tego wykonywanie zapytań jest odroczone. Oznacza to, że obliczanie wyrażenia jest opóźnione, dopóki nie zostanie rzeczywiście iterowana wartość lub ToListAsync metoda jest wywoływana. Aby uzyskać więcej informacji na temat odroczonego wykonywania zapytań, zobacz Wykonywanie zapytań.

Uwaga: Contains metoda jest uruchamiana w bazie danych, a nie w kodzie c# pokazanym powyżej. Ważność wielkości liter w zapytaniu zależy od bazy danych i sortowania. W programie SQL Server Contains mapuje na sql LIKE, co jest bez uwzględniania wielkości liter. W programie SQLite z domyślnym sortowaniem jest uwzględniana wielkość liter.

Przejdź do /Movies/Index. Dołącz ciąg zapytania, taki jak ?searchString=Ghost do adresu URL. Zostaną wyświetlone przefiltrowane filmy.

Widok indeksu

Jeśli zmienisz sygnaturę Index metody tak, aby parametr miał nazwę id, id parametr będzie zgodny z opcjonalnym {id} symbolem zastępczym dla tras domyślnych ustawionych w pliku Program.cs.

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

Zmień parametr na id i zmień wszystkie wystąpienia elementu searchString na id.

Poprzednia metoda Index:

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

Zaktualizowana Index metoda z parametrem id :

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

Teraz możesz przekazać tytuł wyszukiwania jako dane trasy (segment adresu URL) zamiast jako wartość ciągu zapytania.

Widok indeksu ze słowem ghost dodanym do adresu URL i zwróconą listą filmów: Ghostbusters i Ghostbusters 2

Nie można jednak oczekiwać, że użytkownicy będą modyfikować adres URL za każdym razem, gdy chcą wyszukać film. Teraz dodasz elementy interfejsu użytkownika, aby ułatwić filtrowanie filmów. Jeśli zmieniono sygnaturę metody w celu przetestowania sposobu przekazywania parametru Index powiązanego ID trasą, zmień go z powrotem, aby pobierał parametr o nazwie searchString:

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

Views/Movies/Index.cshtml Otwórz plik i dodaj <form> znacznik wyróżniony poniżej:

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

Tag HTML <form> używa pomocnika tagów formularza, więc po przesłaniu formularza ciąg filtru jest publikowany do Index akcji kontrolera filmów. Zapisz zmiany, a następnie przetestuj filtr.

Widok indeksu ze słowem ghost wpisanym w polu tekstowym Filtr tytułu

Nie ma [HttpPost] przeciążenia Index metody, jak można się spodziewać. Nie potrzebujesz go, ponieważ metoda nie zmienia stanu aplikacji, po prostu filtrując dane.

Możesz dodać następującą [HttpPost] Index metodę.

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

Parametr notUsed służy do tworzenia przeciążenia dla Index metody . Omówimy to w dalszej części tego samouczka.

Jeśli dodasz tę metodę, wywołanie akcji będzie zgodne z [HttpPost] Index metodą, a [HttpPost] Index metoda zostanie uruchomiona, jak pokazano na poniższej ilustracji.

Okno przeglądarki z odpowiedzią aplikacji z indeksu HttpPost: filtr na duchu

Jednak nawet jeśli dodasz tę [HttpPost] wersję Index metody, istnieje ograniczenie dotyczące sposobu implementacji wszystkich tych metod. Załóżmy, że chcesz dodać zakładkę do określonego wyszukiwania lub chcesz wysłać link do znajomych, którzy mogą kliknąć, aby wyświetlić tę samą przefiltrowaną listę filmów. Zwróć uwagę, że adres URL żądania HTTP POST jest taki sam jak adres URL żądania GET (localhost:{PORT}/Movies/Index) — w adresie URL nie ma żadnych informacji dotyczących wyszukiwania. Informacje o ciągu wyszukiwania są wysyłane do serwera jako wartość pola formularza. Możesz sprawdzić, czy za pomocą przeglądarki Narzędzia programistyczne lub doskonałego narzędzia Fiddler. Na poniższej ilustracji przedstawiono Narzędzia programistyczne przeglądarki Chrome:

Karta Sieć narzędzi deweloperskich przeglądarki Microsoft Edge z treścią żądania z wartością searchString ghost

Parametr wyszukiwania i token XSRF można zobaczyć w treści żądania. Uwaga, jak wspomniano w poprzednim samouczku, pomocnik tagów formularzy generuje token antyugeryjny XSRF . Nie modyfikujemy danych, więc nie musimy weryfikować tokenu w metodzie kontrolera.

Ponieważ parametr wyszukiwania znajduje się w treści żądania, a nie w adresie URL, nie można przechwycić tych informacji wyszukiwania, aby dodać zakładkę lub udostępnić innym osobom. Rozwiąż ten problem, określając żądanie, HTTP GET które należy znaleźć w Views/Movies/Index.cshtml pliku.

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

Teraz po przesłaniu wyszukiwania adres URL zawiera ciąg zapytania wyszukiwania. Wyszukiwanie spowoduje również przejście do HttpGet Index metody akcji, nawet jeśli masz metodę HttpPost Index .

Okno przeglądarki przedstawiające element searchString=ghost w adresie URL i zwrócone filmy, Ghostbusters i Ghostbusters 2, zawierają słowo ghost

Poniższy znacznik pokazuje zmianę tagu form :

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

Dodawanie wyszukiwania według gatunku

Dodaj następującą MovieGenreViewModel klasę do folderu Models :

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

Model widoku gatunku filmowego będzie zawierać:

  • Lista filmów.
  • Element SelectList zawierający listę gatunków. Dzięki temu użytkownik może wybrać gatunek z listy.
  • MovieGenre, który zawiera wybrany gatunek.
  • SearchString, który zawiera tekst wprowadzany przez użytkowników w polu tekstowym wyszukiwania.

Zastąp metodę Index w MoviesController.cs pliku następującym kodem:

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

Poniższy kod to LINQ zapytanie, które pobiera wszystkie gatunki z bazy danych.

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

Gatunek SelectList jest tworzony przez projekcję odrębnych gatunków (nie chcemy, aby nasza lista wyboru miała zduplikowane gatunki).

Gdy użytkownik wyszukuje element, wartość wyszukiwania jest zachowywana w polu wyszukiwania.

Dodawanie wyszukiwania według gatunku do widoku indeksu

Aktualizacja Index.cshtml znaleziona w widokach/filmach/ w następujący sposób:

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

Sprawdź wyrażenie lambda używane w następującym pomocniku HTML:

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

W poprzednim kodzie DisplayNameFor Pomocnik HTML sprawdza Title właściwość, do których odwołuje się wyrażenie lambda, aby określić nazwę wyświetlaną. Ponieważ wyrażenie lambda jest sprawdzane, a nie oceniane, nie otrzymujesz naruszenia dostępu, gdy model, lub model.Moviesmodel.Movies[0] są lub pustenull. Gdy wyrażenie lambda jest obliczane (na przykład @Html.DisplayFor(modelItem => item.Title)), wartości właściwości modelu są obliczane. model.Movies Po ! jest operatorem forgiving o wartości null, który służy do deklarowania, że Movies nie ma wartości null.

Przetestuj aplikację, wyszukując według gatunku, według tytułu filmu i przez oba:

Okno przeglądarki przedstawiające wyniki polecenia https://localhost:5001/Movies?MovieGenre=Comedy& SearchString=2

W tej sekcji dodasz możliwość wyszukiwania do Index metody akcji, która umożliwia wyszukiwanie filmów według gatunku lub nazwy.

Zaktualizuj metodę znajdującą Index się wewnątrz Controllers/MoviesController.cs za pomocą następującego kodu:

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

Poniższy wiersz w metodzie Index akcji tworzy zapytanie LINQ w celu wybrania filmów:

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

Zapytanie jest zdefiniowane tylko w tym momencie, nie zostało ono uruchomione względem bazy danych.

searchString Jeśli parametr zawiera ciąg, zapytanie filmów zostanie zmodyfikowane w celu filtrowania wartości ciągu wyszukiwania:

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

Powyższy s => s.Title!.Contains(searchString) kod jest wyrażeniem lambda. Lambdas są używane w zapytaniach LINQ opartych na metodzie jako argumentach do standardowych metod operatorów zapytań, takich jak Where metoda lub Contains (używane w powyższym kodzie). Zapytania LINQ nie są wykonywane podczas ich definiowania lub modyfikacji przez wywołanie metody, takiej jak Where, Containslub OrderBy. Zamiast tego wykonywanie zapytań jest odroczone. Oznacza to, że obliczanie wyrażenia jest opóźnione, dopóki nie zostanie rzeczywiście iterowana wartość lub ToListAsync metoda jest wywoływana. Aby uzyskać więcej informacji na temat odroczonego wykonywania zapytań, zobacz Wykonywanie zapytań.

Uwaga: Contains metoda jest uruchamiana w bazie danych, a nie w kodzie c# pokazanym powyżej. Ważność wielkości liter w zapytaniu zależy od bazy danych i sortowania. W programie SQL Server Contains mapuje na sql LIKE, co jest bez uwzględniania wielkości liter. W programie SQLite z domyślnym sortowaniem jest uwzględniana wielkość liter.

Przejdź do /Movies/Index. Dołącz ciąg zapytania, taki jak ?searchString=Ghost do adresu URL. Zostaną wyświetlone przefiltrowane filmy.

Widok indeksu

Jeśli zmienisz sygnaturę Index metody tak, aby parametr miał nazwę id, id parametr będzie zgodny z opcjonalnym {id} symbolem zastępczym dla tras domyślnych ustawionych w pliku Program.cs.

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

Zmień parametr na id i zmień wszystkie wystąpienia elementu searchString na id.

Poprzednia metoda Index:

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

Zaktualizowana Index metoda z parametrem id :

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

Teraz możesz przekazać tytuł wyszukiwania jako dane trasy (segment adresu URL) zamiast jako wartość ciągu zapytania.

Widok indeksu ze słowem ghost dodanym do adresu URL i zwróconą listą filmów: Ghostbusters i Ghostbusters 2

Nie można jednak oczekiwać, że użytkownicy będą modyfikować adres URL za każdym razem, gdy chcą wyszukać film. Teraz dodasz elementy interfejsu użytkownika, aby ułatwić filtrowanie filmów. Jeśli zmieniono sygnaturę metody w celu przetestowania sposobu przekazywania parametru Index powiązanego ID trasą, zmień go z powrotem, aby pobierał parametr o nazwie searchString:

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

Views/Movies/Index.cshtml Otwórz plik i dodaj <form> znacznik wyróżniony poniżej:

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

Tag HTML <form> używa pomocnika tagów formularza, więc po przesłaniu formularza ciąg filtru jest publikowany do Index akcji kontrolera filmów. Zapisz zmiany, a następnie przetestuj filtr.

Widok indeksu ze słowem ghost wpisanym w polu tekstowym Filtr tytułu

Nie ma [HttpPost] przeciążenia Index metody, jak można się spodziewać. Nie potrzebujesz go, ponieważ metoda nie zmienia stanu aplikacji, po prostu filtrując dane.

Możesz dodać następującą [HttpPost] Index metodę.

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

Parametr notUsed służy do tworzenia przeciążenia dla Index metody . Omówimy to w dalszej części tego samouczka.

Jeśli dodasz tę metodę, wywołanie akcji będzie zgodne z [HttpPost] Index metodą, a [HttpPost] Index metoda zostanie uruchomiona, jak pokazano na poniższej ilustracji.

Okno przeglądarki z odpowiedzią aplikacji z indeksu HttpPost: filtr na duchu

Jednak nawet jeśli dodasz tę [HttpPost] wersję Index metody, istnieje ograniczenie dotyczące sposobu implementacji wszystkich tych metod. Załóżmy, że chcesz dodać zakładkę do określonego wyszukiwania lub chcesz wysłać link do znajomych, którzy mogą kliknąć, aby wyświetlić tę samą przefiltrowaną listę filmów. Zwróć uwagę, że adres URL żądania HTTP POST jest taki sam jak adres URL żądania GET (localhost:{PORT}/Movies/Index) — w adresie URL nie ma żadnych informacji dotyczących wyszukiwania. Informacje o ciągu wyszukiwania są wysyłane do serwera jako wartość pola formularza. Możesz sprawdzić, czy za pomocą przeglądarki Narzędzia programistyczne lub doskonałego narzędzia Fiddler. Na poniższej ilustracji przedstawiono Narzędzia programistyczne przeglądarki Chrome:

Karta Sieć narzędzi deweloperskich przeglądarki Microsoft Edge z treścią żądania z wartością searchString ghost

Parametr wyszukiwania i token XSRF można zobaczyć w treści żądania. Uwaga, jak wspomniano w poprzednim samouczku, pomocnik tagów formularzy generuje token antyugeryjny XSRF . Nie modyfikujemy danych, więc nie musimy weryfikować tokenu w metodzie kontrolera.

Ponieważ parametr wyszukiwania znajduje się w treści żądania, a nie w adresie URL, nie można przechwycić tych informacji wyszukiwania, aby dodać zakładkę lub udostępnić innym osobom. Rozwiąż ten problem, określając żądanie, HTTP GET które należy znaleźć w Views/Movies/Index.cshtml pliku.

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

Teraz po przesłaniu wyszukiwania adres URL zawiera ciąg zapytania wyszukiwania. Wyszukiwanie spowoduje również przejście do HttpGet Index metody akcji, nawet jeśli masz metodę HttpPost Index .

Okno przeglądarki przedstawiające element searchString=ghost w adresie URL i zwrócone filmy, Ghostbusters i Ghostbusters 2, zawierają słowo ghost

Poniższy znacznik pokazuje zmianę tagu form :

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

Dodawanie wyszukiwania według gatunku

Dodaj następującą MovieGenreViewModel klasę do folderu Models :

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

Model widoku gatunku filmowego będzie zawierać:

  • Lista filmów.
  • Element SelectList zawierający listę gatunków. Dzięki temu użytkownik może wybrać gatunek z listy.
  • MovieGenre, który zawiera wybrany gatunek.
  • SearchString, który zawiera tekst wprowadzany przez użytkowników w polu tekstowym wyszukiwania.

Zastąp metodę Index w MoviesController.cs pliku następującym kodem:

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

Poniższy kod to LINQ zapytanie, które pobiera wszystkie gatunki z bazy danych.

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

Gatunek SelectList jest tworzony przez projekcję odrębnych gatunków (nie chcemy, aby nasza lista wyboru miała zduplikowane gatunki).

Gdy użytkownik wyszukuje element, wartość wyszukiwania jest zachowywana w polu wyszukiwania.

Dodawanie wyszukiwania według gatunku do widoku indeksu

Aktualizacja Index.cshtml znaleziona w widokach/filmach/ w następujący sposób:

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

Sprawdź wyrażenie lambda używane w następującym pomocniku HTML:

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

W poprzednim kodzie DisplayNameFor Pomocnik HTML sprawdza Title właściwość, do których odwołuje się wyrażenie lambda, aby określić nazwę wyświetlaną. Ponieważ wyrażenie lambda jest sprawdzane, a nie oceniane, nie otrzymujesz naruszenia dostępu, gdy model, lub model.Moviesmodel.Movies[0] są lub pustenull. Gdy wyrażenie lambda jest obliczane (na przykład @Html.DisplayFor(modelItem => item.Title)), wartości właściwości modelu są obliczane. model.Movies Po ! jest operatorem forgiving o wartości null, który służy do deklarowania, że Movies nie ma wartości null.

Przetestuj aplikację, wyszukując według gatunku, według tytułu filmu i przez oba:

Okno przeglądarki przedstawiające wyniki polecenia https://localhost:5001/Movies?MovieGenre=Comedy& SearchString=2

W tej sekcji dodasz możliwość wyszukiwania do Index metody akcji, która umożliwia wyszukiwanie filmów według gatunku lub nazwy.

Zaktualizuj metodę znajdującą Index się wewnątrz Controllers/MoviesController.cs za pomocą następującego kodu:

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

Pierwszy wiersz Index metody akcji tworzy zapytanie LINQ w celu wybrania filmów:

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

Zapytanie jest zdefiniowane tylko w tym momencie, nie zostało ono uruchomione względem bazy danych.

searchString Jeśli parametr zawiera ciąg, zapytanie filmów zostanie zmodyfikowane w celu filtrowania wartości ciągu wyszukiwania:

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

Powyższy s => s.Title!.Contains(searchString) kod jest wyrażeniem lambda. Lambdas są używane w zapytaniach LINQ opartych na metodzie jako argumentach do standardowych metod operatorów zapytań, takich jak Where metoda lub Contains (używane w powyższym kodzie). Zapytania LINQ nie są wykonywane podczas ich definiowania lub modyfikacji przez wywołanie metody, takiej jak Where, Containslub OrderBy. Zamiast tego wykonywanie zapytań jest odroczone. Oznacza to, że obliczanie wyrażenia jest opóźnione, dopóki nie zostanie rzeczywiście iterowana wartość lub ToListAsync metoda jest wywoływana. Aby uzyskać więcej informacji na temat odroczonego wykonywania zapytań, zobacz Wykonywanie zapytań.

Uwaga: Contains metoda jest uruchamiana w bazie danych, a nie w kodzie c# pokazanym powyżej. Ważność wielkości liter w zapytaniu zależy od bazy danych i sortowania. W programie SQL Server Contains mapuje na sql LIKE, co jest bez uwzględniania wielkości liter. W programie SQLite z domyślnym sortowaniem jest uwzględniana wielkość liter.

Przejdź do /Movies/Index. Dołącz ciąg zapytania, taki jak ?searchString=Ghost do adresu URL. Zostaną wyświetlone przefiltrowane filmy.

Widok indeksu

Jeśli zmienisz sygnaturę Index metody tak, aby parametr miał nazwę id, id parametr będzie zgodny z opcjonalnym {id} symbolem zastępczym dla tras domyślnych ustawionych w pliku Program.cs.

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

Zmień parametr na id i zmień wszystkie wystąpienia elementu searchString na id.

Poprzednia metoda Index:

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

Zaktualizowana Index metoda z parametrem id :

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

Teraz możesz przekazać tytuł wyszukiwania jako dane trasy (segment adresu URL) zamiast jako wartość ciągu zapytania.

Widok indeksu ze słowem ghost dodanym do adresu URL i zwróconą listą filmów: Ghostbusters i Ghostbusters 2

Nie można jednak oczekiwać, że użytkownicy będą modyfikować adres URL za każdym razem, gdy chcą wyszukać film. Teraz dodasz elementy interfejsu użytkownika, aby ułatwić filtrowanie filmów. Jeśli zmieniono sygnaturę metody w celu przetestowania sposobu przekazywania parametru Index powiązanego ID trasą, zmień go z powrotem, aby pobierał parametr o nazwie searchString:

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

Views/Movies/Index.cshtml Otwórz plik i dodaj <form> znacznik wyróżniony poniżej:

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

Tag HTML <form> używa pomocnika tagów formularza, więc po przesłaniu formularza ciąg filtru jest publikowany do Index akcji kontrolera filmów. Zapisz zmiany, a następnie przetestuj filtr.

Widok indeksu ze słowem ghost wpisanym w polu tekstowym Filtr tytułu

Nie ma [HttpPost] przeciążenia Index metody, jak można się spodziewać. Nie potrzebujesz go, ponieważ metoda nie zmienia stanu aplikacji, po prostu filtrując dane.

Możesz dodać następującą [HttpPost] Index metodę.

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

Parametr notUsed służy do tworzenia przeciążenia dla Index metody . Omówimy to w dalszej części tego samouczka.

Jeśli dodasz tę metodę, wywołanie akcji będzie zgodne z [HttpPost] Index metodą, a [HttpPost] Index metoda zostanie uruchomiona, jak pokazano na poniższej ilustracji.

Okno przeglądarki z odpowiedzią aplikacji z indeksu HttpPost: filtr na duchu

Jednak nawet jeśli dodasz tę [HttpPost] wersję Index metody, istnieje ograniczenie dotyczące sposobu implementacji wszystkich tych metod. Załóżmy, że chcesz dodać zakładkę do określonego wyszukiwania lub chcesz wysłać link do znajomych, którzy mogą kliknąć, aby wyświetlić tę samą przefiltrowaną listę filmów. Zwróć uwagę, że adres URL żądania HTTP POST jest taki sam jak adres URL żądania GET (localhost:{PORT}/Movies/Index) — w adresie URL nie ma żadnych informacji dotyczących wyszukiwania. Informacje o ciągu wyszukiwania są wysyłane do serwera jako wartość pola formularza. Możesz sprawdzić, czy za pomocą przeglądarki Narzędzia programistyczne lub doskonałego narzędzia Fiddler. Na poniższej ilustracji przedstawiono Narzędzia programistyczne przeglądarki Chrome:

Karta Sieć narzędzi deweloperskich przeglądarki Microsoft Edge z treścią żądania z wartością searchString ghost

Parametr wyszukiwania i token XSRF można zobaczyć w treści żądania. Uwaga, jak wspomniano w poprzednim samouczku, pomocnik tagów formularzy generuje token antyugeryjny XSRF . Nie modyfikujemy danych, więc nie musimy weryfikować tokenu w metodzie kontrolera.

Ponieważ parametr wyszukiwania znajduje się w treści żądania, a nie w adresie URL, nie można przechwycić tych informacji wyszukiwania, aby dodać zakładkę lub udostępnić innym osobom. Rozwiąż ten problem, określając żądanie, HTTP GET które należy znaleźć w Views/Movies/Index.cshtml pliku.

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

Teraz po przesłaniu wyszukiwania adres URL zawiera ciąg zapytania wyszukiwania. Wyszukiwanie spowoduje również przejście do HttpGet Index metody akcji, nawet jeśli masz metodę HttpPost Index .

Okno przeglądarki przedstawiające element searchString=ghost w adresie URL i zwrócone filmy, Ghostbusters i Ghostbusters 2, zawierają słowo ghost

Poniższy znacznik pokazuje zmianę tagu form :

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

Dodawanie wyszukiwania według gatunku

Dodaj następującą MovieGenreViewModel klasę do folderu Models :

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

Model widoku gatunku filmowego będzie zawierać:

  • Lista filmów.
  • Element SelectList zawierający listę gatunków. Dzięki temu użytkownik może wybrać gatunek z listy.
  • MovieGenre, który zawiera wybrany gatunek.
  • SearchString, który zawiera tekst wprowadzany przez użytkowników w polu tekstowym wyszukiwania.

Zastąp metodę Index w MoviesController.cs pliku następującym kodem:

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

Poniższy kod to LINQ zapytanie, które pobiera wszystkie gatunki z bazy danych.

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

Gatunek SelectList jest tworzony przez projekcję odrębnych gatunków (nie chcemy, aby nasza lista wyboru miała zduplikowane gatunki).

Gdy użytkownik wyszukuje element, wartość wyszukiwania jest zachowywana w polu wyszukiwania.

Dodawanie wyszukiwania według gatunku do widoku indeksu

Aktualizacja Index.cshtml znaleziona w widokach/filmach/ w następujący sposób:

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

Sprawdź wyrażenie lambda używane w następującym pomocniku HTML:

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

W poprzednim kodzie DisplayNameFor Pomocnik HTML sprawdza Title właściwość, do których odwołuje się wyrażenie lambda, aby określić nazwę wyświetlaną. Ponieważ wyrażenie lambda jest sprawdzane, a nie oceniane, nie otrzymujesz naruszenia dostępu, gdy model, lub model.Moviesmodel.Movies[0] są lub pustenull. Gdy wyrażenie lambda jest obliczane (na przykład @Html.DisplayFor(modelItem => item.Title)), wartości właściwości modelu są obliczane.

Przetestuj aplikację, wyszukując według gatunku, według tytułu filmu i przez oba:

Okno przeglądarki przedstawiające wyniki polecenia https://localhost:5001/Movies?MovieGenre=Comedy& SearchString=2

W tej sekcji dodasz możliwość wyszukiwania do Index metody akcji, która umożliwia wyszukiwanie filmów według gatunku lub nazwy.

Zaktualizuj metodę znajdującą Index się wewnątrz Controllers/MoviesController.cs za pomocą następującego kodu:

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

Pierwszy wiersz Index metody akcji tworzy zapytanie LINQ w celu wybrania filmów:

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

Zapytanie jest zdefiniowane tylko w tym momencie, nie zostało ono uruchomione względem bazy danych.

searchString Jeśli parametr zawiera ciąg, zapytanie filmów zostanie zmodyfikowane w celu filtrowania wartości ciągu wyszukiwania:

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

Powyższy s => s.Title.Contains() kod jest wyrażeniem lambda. Lambdas są używane w zapytaniach LINQ opartych na metodzie jako argumentach do standardowych metod operatorów zapytań, takich jak Where metoda lub Contains (używane w powyższym kodzie). Zapytania LINQ nie są wykonywane podczas ich definiowania lub modyfikacji przez wywołanie metody, takiej jak Where, Containslub OrderBy. Zamiast tego wykonywanie zapytań jest odroczone. Oznacza to, że obliczanie wyrażenia jest opóźnione, dopóki nie zostanie rzeczywiście iterowana wartość lub ToListAsync metoda jest wywoływana. Aby uzyskać więcej informacji na temat odroczonego wykonywania zapytań, zobacz Wykonywanie zapytań.

Uwaga: Contains metoda jest uruchamiana w bazie danych, a nie w kodzie c# pokazanym powyżej. Ważność wielkości liter w zapytaniu zależy od bazy danych i sortowania. W programie SQL Server Contains mapuje na sql LIKE, co jest bez uwzględniania wielkości liter. W programie SQLite z domyślnym sortowaniem jest uwzględniana wielkość liter.

Przejdź do /Movies/Index. Dołącz ciąg zapytania, taki jak ?searchString=Ghost do adresu URL. Zostaną wyświetlone przefiltrowane filmy.

Widok indeksu

Jeśli zmienisz sygnaturę Index metody tak, aby parametr miał nazwę id, id parametr będzie zgodny z opcjonalnym {id} symbolem zastępczym dla tras domyślnych ustawionych w pliku Startup.cs.

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

Zmień parametr na id i wszystkie wystąpienia searchString zmiany na id.

Poprzednia metoda Index:

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

Zaktualizowana Index metoda z parametrem id :

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

Teraz możesz przekazać tytuł wyszukiwania jako dane trasy (segment adresu URL) zamiast jako wartość ciągu zapytania.

Widok indeksu ze słowem ghost dodanym do adresu URL i zwróconą listą filmów: Ghostbusters i Ghostbusters 2

Nie można jednak oczekiwać, że użytkownicy będą modyfikować adres URL za każdym razem, gdy chcą wyszukać film. Teraz dodasz elementy interfejsu użytkownika, aby ułatwić filtrowanie filmów. Jeśli zmieniono sygnaturę metody w celu przetestowania sposobu przekazywania parametru Index powiązanego ID trasą, zmień go z powrotem, aby pobierał parametr o nazwie searchString:

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

Views/Movies/Index.cshtml Otwórz plik i dodaj <form> znacznik wyróżniony poniżej:

    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>

Tag HTML <form> używa pomocnika tagów formularza, więc po przesłaniu formularza ciąg filtru jest publikowany do Index akcji kontrolera filmów. Zapisz zmiany, a następnie przetestuj filtr.

Widok indeksu ze słowem ghost wpisanym w polu tekstowym Filtr tytułu

Nie ma [HttpPost] przeciążenia Index metody, jak można się spodziewać. Nie potrzebujesz go, ponieważ metoda nie zmienia stanu aplikacji, po prostu filtrując dane.

Możesz dodać następującą [HttpPost] Index metodę.

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

Parametr notUsed służy do tworzenia przeciążenia dla Index metody . Omówimy to w dalszej części tego samouczka.

Jeśli dodasz tę metodę, wywołanie akcji będzie zgodne z [HttpPost] Index metodą, a [HttpPost] Index metoda zostanie uruchomiona, jak pokazano na poniższej ilustracji.

Okno przeglądarki z odpowiedzią aplikacji z indeksu HttpPost: filtr na duchu

Jednak nawet jeśli dodasz tę [HttpPost] wersję Index metody, istnieje ograniczenie dotyczące sposobu implementacji wszystkich tych metod. Załóżmy, że chcesz dodać zakładkę do określonego wyszukiwania lub chcesz wysłać link do znajomych, którzy mogą kliknąć, aby wyświetlić tę samą przefiltrowaną listę filmów. Zwróć uwagę, że adres URL żądania HTTP POST jest taki sam jak adres URL żądania GET (localhost:{PORT}/Movies/Index) — w adresie URL nie ma żadnych informacji dotyczących wyszukiwania. Informacje o ciągu wyszukiwania są wysyłane do serwera jako wartość pola formularza. Możesz sprawdzić, czy za pomocą przeglądarki Narzędzia programistyczne lub doskonałego narzędzia Fiddler. Na poniższej ilustracji przedstawiono Narzędzia programistyczne przeglądarki Chrome:

Karta Sieć narzędzi deweloperskich przeglądarki Microsoft Edge z treścią żądania z wartością searchString ghost

Parametr wyszukiwania i token XSRF można zobaczyć w treści żądania. Uwaga, jak wspomniano w poprzednim samouczku, pomocnik tagów formularzy generuje token antyugeryjny XSRF . Nie modyfikujemy danych, więc nie musimy weryfikować tokenu w metodzie kontrolera.

Ponieważ parametr wyszukiwania znajduje się w treści żądania, a nie w adresie URL, nie można przechwycić tych informacji wyszukiwania, aby dodać zakładkę lub udostępnić innym osobom. Rozwiąż ten problem, określając żądanie, HTTP GET które należy znaleźć w Views/Movies/Index.cshtml pliku.

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

Teraz po przesłaniu wyszukiwania adres URL zawiera ciąg zapytania wyszukiwania. Wyszukiwanie spowoduje również przejście do HttpGet Index metody akcji, nawet jeśli masz metodę HttpPost Index .

Okno przeglądarki przedstawiające element searchString=ghost w adresie URL i zwrócone filmy, Ghostbusters i Ghostbusters 2, zawierają słowo ghost

Poniższy znacznik pokazuje zmianę tagu form :

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

Dodawanie wyszukiwania według gatunku

Dodaj następującą MovieGenreViewModel klasę do folderu Models :

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

Model widoku gatunku filmowego będzie zawierać:

  • Lista filmów.
  • Element SelectList zawierający listę gatunków. Dzięki temu użytkownik może wybrać gatunek z listy.
  • MovieGenre, który zawiera wybrany gatunek.
  • SearchString, który zawiera tekst wprowadzany przez użytkowników w polu tekstowym wyszukiwania.

Zastąp metodę Index w MoviesController.cs pliku następującym kodem:

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

Poniższy kod to LINQ zapytanie, które pobiera wszystkie gatunki z bazy danych.

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

Gatunek SelectList jest tworzony przez projekcję odrębnych gatunków (nie chcemy, aby nasza lista wyboru miała zduplikowane gatunki).

Gdy użytkownik wyszukuje element, wartość wyszukiwania jest zachowywana w polu wyszukiwania.

Dodawanie wyszukiwania według gatunku do widoku indeksu

Aktualizacja Index.cshtml znaleziona w widokach/filmach/ w następujący sposób:

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

Sprawdź wyrażenie lambda używane w następującym pomocniku HTML:

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

W poprzednim kodzie DisplayNameFor Pomocnik HTML sprawdza Title właściwość, do których odwołuje się wyrażenie lambda, aby określić nazwę wyświetlaną. Ponieważ wyrażenie lambda jest sprawdzane, a nie oceniane, nie otrzymujesz naruszenia dostępu, gdy model, lub model.Moviesmodel.Movies[0] są lub pustenull. Gdy wyrażenie lambda jest obliczane (na przykład @Html.DisplayFor(modelItem => item.Title)), wartości właściwości modelu są obliczane.

Przetestuj aplikację, wyszukując według gatunku, według tytułu filmu i przez oba:

Okno przeglądarki przedstawiające wyniki polecenia https://localhost:5001/Movies?MovieGenre=Comedy& SearchString=2