Dodawanie wyszukiwania do aplikacji ASP.NET Core MVCAdd search to an ASP.NET Core MVC app

Przez Rick AndersonBy 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.In this section, you add search capability to the Index action method that lets you search movies by genre or name.

Zaktualizuj metodę Index znajdującą się wewnątrz kontrolerów/MoviesController. cs przy użyciu następującego kodu:Update the Index method found inside Controllers/MoviesController.cs with the following 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());
}

Pierwszy wiersz Index metody akcji tworzy zapytanie LINQ do wybierania filmów:The first line of the Index action method creates a LINQ query to select the movies:

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

Zapytanie jest zdefiniowane tylko w tym momencie, nie zostało uruchomione względem bazy danych.The query is only defined at this point, it has not been run against the database.

searchString Jeśli parametr zawiera ciąg, zapytanie o filmy jest modyfikowane w celu filtrowania wartości ciągu wyszukiwania:If the searchString parameter contains a string, the movies query is modified to filter on the value of the search string:

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

Powyższy kod jest wyrażeniem lambda. s => s.Title.Contains()The s => s.Title.Contains() code above is a Lambda Expression. Wyrażenia lambda są używane w kwerendach LINQ opartych na metodach jako argumenty dla standardowych metod operatora zapytań, takich jak Contains Metoda WHERE lub (używana w kodzie powyżej).Lambdas are used in method-based LINQ queries as arguments to standard query operator methods such as the Where method or Contains (used in the code above). Zapytania LINQ nie są wykonywane, gdy są zdefiniowane lub są modyfikowane przez wywołanie metody takiej jak Where, Containslub OrderBy.LINQ queries are not executed when they're defined or when they're modified by calling a method such as Where, Contains, or OrderBy. Zamiast tego wykonywanie zapytania jest odroczone.Rather, query execution is deferred. Oznacza to, że Obliczanie wyrażenia jest opóźnione do momentu rzeczywistego przełączenia jego wartości rzeczywistej lub ToListAsync wywołania metody.That means that the evaluation of an expression is delayed until its realized value is actually iterated over or the ToListAsync method is called. Aby uzyskać więcej informacji o odroczonym wykonywaniu zapytań, zobacz wykonywanie zapytań.For more information about deferred query execution, see Query Execution.

Uwaga: Metoda Contains jest uruchamiana w bazie danych, a nie w kodzie c# pokazanym powyżej.Note: The Contains method is run on the database, not in the c# code shown above. Uwzględnianie wielkości liter w zapytaniu zależy od bazy danych i sortowania.The case sensitivity on the query depends on the database and the collation. Na SQL Server zawiera mapy do programu SQL, takie jak, w przypadku których wielkość liter nie jest uwzględniana.On SQL Server, Contains maps to SQL LIKE, which is case insensitive. W ramach programu SQLite domyślne sortowanie uwzględnia wielkość liter.In SQLite, with the default collation, it's case sensitive.

Przejdź do adresu /Movies/Index.Navigate to /Movies/Index. Dołącz ciąg zapytania, taki jak ?searchString=Ghost do adresu URL.Append a query string such as ?searchString=Ghost to the URL. Wyświetlane są filtrowane filmy.The filtered movies are displayed.

Widok indeksu

Index Jeśli zmienisz podpis metody w taki sposób, aby miał parametr o idnazwie, id parametr będzie zgodny z opcjonalnym {id} symbolem zastępczym dla tras domyślnych ustawionych w Startup.cs.If you change the signature of the Index method to have a parameter named id, the id parameter will match the optional {id} placeholder for the default routes set in Startup.cs.

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});

Zmień parametr na id i wszystkie wystąpienia searchString zmiany na id.Change the parameter to id and all occurrences of searchString change to id.

Poprzednia Index Metoda:The previous Index method:

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 id parametrem:The updated Index method with 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());
}

Teraz możesz przekazać tytuł wyszukiwania jako dane trasy (segment adresu URL), a nie jako wartość ciągu zapytania.You can now pass the search title as route data (a URL segment) instead of as a query string value.

Widok indeksu z wyrazem Ghost dodany do adresu URL i zwrotną listą filmów dwóch filmów, Ghostbusters i Ghostbusters 2

Nie można jednak oczekiwać, aby użytkownicy modyfikują adres URL za każdym razem, gdy chcą wyszukać film.However, you can't expect users to modify the URL every time they want to search for a movie. Teraz dodasz elementy interfejsu użytkownika, aby ułatwić im filtrowanie filmów.So now you'll add UI elements to help them filter movies. Jeśli zmieniono sygnaturę Index metody w celu przetestowania, jak przekazać parametr powiązany ID z trasą, należy zmienić go z powrotem tak, aby pobierał searchStringparametr o nazwie:If you changed the signature of the Index method to test how to pass the route-bound ID parameter, change it back so that it takes a parameter named 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());
}

Otwórz plik views/filmy/index. cshtml i Dodaj <form> wyróżniony poniżej znacznik:Open the Views/Movies/Index.cshtml file, and add the <form> markup highlighted below:

    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, dlatego podczas przesyłania formularza ciąg filtru Index jest ogłaszany w akcji kontrolera filmów.The HTML <form> tag uses the Form Tag Helper, so when you submit the form, the filter string is posted to the Index action of the movies controller. Zapisz zmiany, a następnie przetestuj filtr.Save your changes and then test the filter.

Widok indeksu z słowem Ghost wpisanych do pola tekstowego filtru tytułu

Nie [HttpPost] istnieje Przeciążenie Index metody w oczekiwany sposób.There's no [HttpPost] overload of the Index method as you might expect. Nie jest to potrzebne, ponieważ metoda nie zmienia stanu aplikacji, tylko filtrowanie danych.You don't need it, because the method isn't changing the state of the app, just filtering data.

Można dodać następującą [HttpPost] Index metodę.You could add the following [HttpPost] Index method.

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

Parametr służy do tworzenia przeciążenia Index dla metody. notUsedThe notUsed parameter is used to create an overload for the Index method. Będziemy mówić o tym w dalszej części tego samouczka.We'll talk about that later in the tutorial.

W przypadku dodania tej metody akcja Źródło będzie zgodna z [HttpPost] Index metodą, [HttpPost] Index a metoda zostanie uruchomiona, jak pokazano na poniższej ilustracji.If you add this method, the action invoker would match the [HttpPost] Index method, and the [HttpPost] Index method would run as shown in the image below.

Okno przeglądarki z odpowiedzią aplikacji z indeksu HttpPost: filtr dla elementu Ghost

Jednak nawet w przypadku dodania tej [HttpPost] wersji Index metody istnieje ograniczenie, w jaki sposób to wszystko zostało zaimplementowane.However, even if you add this [HttpPost] version of the Index method, there's a limitation in how this has all been implemented. Załóżmy, że chcesz utworzyć zakładką określonego wyszukiwania, lub chcesz wysłać link do znajomych, które mogą kliknąć, aby zobaczyć tę samą filtrowaną listę filmów.Imagine that you want to bookmark a particular search or you want to send a link to friends that they can click in order to see the same filtered list of movies. Zwróć uwagę, że adres URL żądania HTTP POST jest taki sam jak adres URL żądania GET (localhost: {PORT}/filmy/indeks) — w adresie URL nie ma informacji o wyszukiwaniu.Notice that the URL for the HTTP POST request is the same as the URL for the GET request (localhost:{PORT}/Movies/Index) -- there's no search information in the URL. Informacje o ciągu wyszukiwania są wysyłane do serwera jako wartość pola formularza.The search string information is sent to the server as a form field value. Możesz sprawdzić, czy za pomocą narzędzi deweloperskich przeglądarki lub doskonałego Narzędzia programu Fiddler.You can verify that with the browser Developer tools or the excellent Fiddler tool. Na poniższej ilustracji przedstawiono narzędzia deweloperskie przeglądarki Chrome:The image below shows the Chrome browser Developer tools:

Karta sieciowa Narzędzia deweloperskie w przeglądarce Microsoft Edge pokazująca treść żądania z Ciągwyszukiwania wartością Ghost

W treści żądania można zobaczyć parametr Search i token XSRF .You can see the search parameter and XSRF token in the request body. Uwaga, jak wspomniano w poprzednim samouczku, pomocnik tagów formularza generuje token XSRF chroniący przed fałszerstwem.Note, as mentioned in the previous tutorial, the Form Tag Helper generates an XSRF anti-forgery token. Dane nie są modyfikowane, więc nie trzeba sprawdzać tokenu w metodzie kontrolera.We're not modifying data, so we don't need to validate the token in the controller method.

Ponieważ parametr wyszukiwania znajduje się w treści żądania, a nie w adresie URL, nie można przechwytywać tych informacji wyszukiwania do zakładek lub udostępniania innym osobom.Because the search parameter is in the request body and not the URL, you can't capture that search information to bookmark or share with others. Aby rozwiązać ten problem, należy określić żądanie HTTP GET należy znaleźć w pliku viewss/filmów/index. cshtml .Fix this by specifying the request should be HTTP GET found in the Views/Movies/Index.cshtml file.

@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, gdy wyślesz wyszukiwanie, adres URL zawiera ciąg zapytania wyszukiwania.Now when you submit a search, the URL contains the search query string. Wyszukiwanie spowoduje również przejście do HttpGet Index metody akcji, nawet jeśli HttpPost Index masz metodę.Searching will also go to the HttpGet Index action method, even if you have a HttpPost Index method.

Okno przeglądarki pokazujące Ciągwyszukiwania = Ghost w adresie URL, a filmy zwrócone, Ghostbusters i Ghostbusters 2 zawierają słowo Ghost

Następujące znaczniki pokazują zmianę form znacznika:The following markup shows the change to the form tag:

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

Dodaj wyszukiwanie według gatunkuAdd Search by genre

Dodaj następujący kod MovieGenreViewModel klasy modeli folderu:Add the following MovieGenreViewModel class to the Models folder:

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 film-gatunek będzie zawierać następujące:The movie-genre view model will contain:

  • Lista filmów.A list of movies.
  • A SelectList zawiera listę gatunku.A SelectList containing the list of genres. Dzięki temu użytkownik może wybrać gatunek z listy.This allows the user to select a genre from the list.
  • MovieGenre, który zawiera wybrany gatunek.MovieGenre, which contains the selected genre.
  • SearchString, który zawiera tekst wprowadzany przez użytkowników w polu tekstowym Wyszukaj.SearchString, which contains the text users enter in the search text box.

Zastąp MoviesController.cs metodę w następującym kodzie: IndexReplace the Index method in MoviesController.cs with the following 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);
}

Poniższy kod jest LINQ zapytaniem pobierającym wszystkie gatunki z bazy danych.The following code is a LINQ query that retrieves all the genres from the database.

// 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 nasze listy wyboru miały zduplikowane gatunek).The SelectList of genres is created by projecting the distinct genres (we don't want our select list to have duplicate genres).

Gdy użytkownik wyszukuje element, wartość wyszukiwania jest zachowywana w polu wyszukiwania.When the user searches for the item, the search value is retained in the search box.

Dodaj wyszukiwanie według gatunku do widoku indeksuAdd search by genre to the Index view

Aktualizacja Index.cshtml znaleziona w widokach/filmach/ w następujący sposób:Update Index.cshtml found in Views/Movies/ as follows:

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

Bada wyrażenie lambda użyte w następującym Pomocniku HTML:Examine the lambda expression used in the following HTML Helper:

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

W poprzednim kodzie DisplayNameFor pomocnik html sprawdza Title właściwość, do której istnieje odwołanie w wyrażeniu lambda, aby określić nazwę wyświetlaną.In the preceding code, the DisplayNameFor HTML Helper inspects the Title property referenced in the lambda expression to determine the display name. Ponieważ wyrażenie lambda jest sprawdzane zamiast oceniania, nie otrzymujesz naruszenia dostępu, modelgdy, model.Movies, lub model.Movies[0]null puste.Since the lambda expression is inspected rather than evaluated, you don't receive an access violation when model, model.Movies, or model.Movies[0] are null or empty. Gdy wyrażenie lambda zostanie obliczone (na przykład @Html.DisplayFor(modelItem => item.Title)), wartości właściwości modelu są oceniane.When the lambda expression is evaluated (for example, @Html.DisplayFor(modelItem => item.Title)), the model's property values are evaluated.

Przetestuj aplikację, wyszukując według gatunku, tytułu filmu i obu tych elementów:Test the app by searching by genre, by movie title, and by both:

Okno przeglądarki z wynikami https://localhost:5001/Movies?MovieGenre=Comedy&SearchString=2