Dela via


Del 7, lägg till sökning i en ASP.NET Core MVC-app

Not

Det här är inte den senaste versionen av den här artikeln. För den nuvarande versionen, se .NET 9-versionen av denna artikel.

Varning

Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i .NET och .NET Core Support Policy. För den aktuella versionen, se den .NET 9-versionen av den här artikeln.

Viktig

Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.

För den aktuella versionen, se .NET 9-versionen av den här artikeln.

Av Rick Anderson

I det här avsnittet lägger du till sökfunktionen i åtgärdsmetoden Index som gör att du kan söka efter filmer efter genre eller namn.

Uppdatera Index-metoden som finns i Controllers/MoviesController.cs med följande kod:

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!.ToUpper().Contains(searchString.ToUpper()));
    }

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

Följande rad i Index-åtgärdsmetoden skapar en LINQ- fråga för att välja filmer:

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

Frågan definieras endast vid denna tidpunkt och har inte körts mot databasen.

Om parametern searchString innehåller en sträng ändras filmfrågan för att filtrera efter värdet för söksträngen:

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

Koden s => s.Title!.ToUpper().Contains(searchString.ToUpper()) ovan är ett Lambda-uttryck. Lambdas används i metodbaserade LINQ frågor som argument till standardmetoder för frågeoperatorer, till exempel Where-metoden eller Contains (används i koden ovan). LINQ-frågor körs inte när de definieras eller när de ändras genom att anropa en metod som Where, Containseller OrderBy. I stället skjuts frågekörningen upp. Det innebär att utvärderingen av ett uttryck fördröjs tills dess realiserade värde faktiskt itereras över eller metoden ToListAsync anropas. För mer information om uppskjuten frågekörning, se Frågekörning.

Anteckning

Metoden Contains körs på databasen, inte i C#-koden. Skiftlägeskänsligheten för frågan beror på databasen och teckenuppsättningen. På SQL Server mappar Contains till SQL LIKE, vilket inte är skiftlägeskänsligt. SQLite med standardsortering är en blandning av skiftlägeskänsliga och skiftlägesokänsliga INberoende på frågan. Information om hur du gör skiftlägesokänsliga SQLite-frågor finns i följande:

Gå till /Movies/Index. Lägg till en frågesträng, till exempel ?searchString=Ghost till URL:en. De filtrerade filmerna visas.

Indexvy

Om du ändrar signaturen för metoden Index så att den har en parameter med namnet idmatchar parametern id den valfria {id} platshållaren för standardvägarna som anges i Program.cs.

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

Ändra parametern till id och ändra alla förekomster av searchString till id.

Föregående Index metod:

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!.ToUpper().Contains(searchString.ToUpper()));
    }

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

Den uppdaterade Index metoden med 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!.ToUpper().Contains(id.ToUpper()));
    }

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

Nu kan du skicka sökrubriken som routningsdata (ett URL-segment) i stället för som ett frågesträngsvärde.

Index-vyn med ordet spöke som lagts till i URL:en och en returnerad filmlista med två filmer, Ghostbusters och Ghostbusters 2

Du kan dock inte förvänta dig att användarna ändrar URL:en varje gång de vill söka efter en film. Så nu ska du lägga till gränssnittselement som hjälper dem att filtrera filmer. Om du har ändrat signaturen för metoden Index för att testa hur du skickar den routningsbundna ID parametern ändrar du tillbaka den så att den tar en parameter med namnet 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!.ToUpper().Contains(searchString.ToUpper()));
    }

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

Öppna filen Views/Movies/Index.cshtml och lägg till <form> markeringen som är markerad nedan:

@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>
        <label>Title: <input type="text" name="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>
<table class="table">

HTML-taggen <form> använder Form Tag Helper , så när du skickar in formuläret postas filtersträngen till Index-åtgärden av filmcontroller. Spara ändringarna och testa sedan filtret.

indexvyn med ordet spöke skrivet i rubrikfilters textruta

Det finns ingen [HttpPost]-överladdning av Index-metoden som du kanske förväntar dig. Du behöver det inte eftersom metoden inte ändrar appens tillstånd, utan bara filtrerar data.

Du kan lägga till följande [HttpPost] Index metod.

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

Parametern notUsed används för att skapa en överlagring för metoden Index. Vi kommer att prata om det senare i handledningen.

Om du lägger till den här metoden matchar åtgärdsanroparen metoden [HttpPost] Index och metoden [HttpPost] Index skulle köras enligt bilden nedan.

webbläsarfönster med programsvaret från HttpPost Index: filtrera på ghost

Men även om du lägger till den här [HttpPost] versionen av Index-metoden finns det en begränsning i hur allt detta har implementerats. Tänk dig att du vill bokmärke en viss sökning eller att du vill skicka en länk till vänner som de kan klicka på för att se samma filtrerade lista över filmer. Observera att URL:en för HTTP POST-begäran är densamma som URL:en för GET-begäran (localhost:{PORT}/Movies/Index) – det finns ingen sökinformation i URL:en. Söksträngsinformationen skickas till servern som ett formulärfältvärde. Du kan kontrollera att med webbläsarens utvecklarverktyg eller det utmärkta Fiddler-verktyget.

Följande bild visar chrome-webbläsarutvecklarverktygen med Network och Rubriker flikar valda:

flikarna Nätverk och Sidhuvuden i Chrome-webbläsarens utvecklarverktyg som visar en begärandetext med värdet searchString för ghost

Flikarna Network och Payload är markerade för att visa formulärdata:

Nätverks- och Payload-flikarna i Chrome-webbläsarens utvecklarverktyg som visar formulärdata

Du kan se sökparametern och XSRF- token i begärandetexten. Observera, som nämndes i föregående handledning, att Form Tag Helper genererar en XSRF- antiforgery-token. Vi ändrar inte data, så vi behöver inte verifiera token i kontrollantmetoden.

Eftersom sökparametern finns i begärandetexten och inte i URL:en kan du inte samla in den sökinformationen för att bokmärka eller dela med andra. Åtgärda detta genom att specificera att begäran ska vara HTTP GET i taggen form som finns i filen Views/Movies/Index.cshtml.

@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>
        <label>Title: <input type="text" name="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>
<table class="table">

Nu när du skickar en sökning innehåller URL:en sökfrågesträngen. Sökningen går också till åtgärdsmetoden HttpGet Index, även om du har en HttpPost Index-metod.

Webbläsarfönster som visar searchString=ghost i URL:en och filmerna som returneras, Ghostbusters och Ghostbusters 2, innehåller ordet ghost

Lägg till sök efter genre

Lägg till följande MovieGenreViewModel-klass i mappen 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; }
}

Visningsmodellen för filmgenren innehåller:

  • En lista över filmer.
  • En SelectList som innehåller listan över genrer. På så sätt kan användaren välja en genre i listan.
  • MovieGenre, som innehåller den valda genren.
  • SearchString, som innehåller texten som användarna anger i söktextrutan.

Ersätt metoden Index i MoviesController.cs med följande kod:

// 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!.ToUpper().Contains(searchString.ToUpper()));
    }

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

Följande kod är en LINQ fråga som hämtar alla genrer från databasen.

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

Den SelectList av genrer skapas genom att projicera distinkta genrer (vi vill inte att vår urvalslista ska ha dubbletter av genrer).

När användaren söker efter objektet behålls sökvärdet i sökrutan.

Lägga till sökning efter genre i indexvyn

Uppdatera Index.cshtml som finns i Views/Movies/ enligt följande:

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

        <label>Title: <input type="text" asp-for="SearchString" /></label>
        <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>

Granska lambda-uttrycket som används i följande HTML-hjälp:

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

I föregående kod inspekterar DisplayNameFor HTML Helpern den Title egenskap som refereras på i lambda-uttrycket för att fastställa visningsnamnet. Eftersom lambda-uttrycket inspekteras i stället för att utvärderas får du ingen åtkomstöverträdelse när model, model.Movieseller model.Movies[0] är null eller tomma. När lambda-uttrycket utvärderas (till exempel @Html.DisplayFor(modelItem => item.Title)) utvärderas modellens egenskapsvärden. ! efter model.Movies är null-förlåtande operatorn, som används för att deklarera att Movies inte är null.

Testa appen genom att söka efter genre, efter filmtitel och efter båda:

Webbläsarfönster som visar resultatet av https://localhost:5001/Movies?MovieGenre=Comedy& SearchString=2

I det här avsnittet lägger du till sökfunktionen i åtgärdsmetoden Index som gör att du kan söka efter filmer efter genre eller namn.

Uppdatera Index-metoden som finns i Controllers/MoviesController.cs med följande kod:

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!.ToUpper().Contains(searchString.ToUpper()));
    }

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

Följande rad i Index-åtgärdsmetoden skapar en LINQ- fråga för att välja filmer:

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

Frågan är endast definierad just nu; den har inte körts mot databasen.

Om parametern searchString innehåller en sträng ändras filmfrågan för att filtrera efter värdet för söksträngen:

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

Koden s => s.Title!.ToUpper().Contains(searchString.ToUpper()) ovan är ett Lambda-uttryck. Lambdas används i metodbaserade LINQ frågor som argument till standardmetoder för frågeoperatorer, till exempel Where-metoden eller Contains (används i koden ovan). LINQ-frågor körs inte när de definieras eller när de ändras genom att anropa en metod som Where, Containseller OrderBy. I stället skjuts frågekörningen upp. Det innebär att utvärderingen av ett uttryck fördröjs tills dess realiserade värde faktiskt itereras över eller metoden ToListAsync anropas. Mer information om uppskjuten frågekörning finns i Frågekörning.

Not

Metoden Contains körs på databasen, inte i C#-koden. Skiftlägeskänsligheten för frågan beror på databasen och sorteringen. På SQL Server mappar Contains till SQL LIKE, vilket är skiftlägesokänsligt. SQLite med standardsortering är en blandning av skiftlägeskänslighet och skiftlägesokänslighet INberoende på frågan. Information om hur du gör skiftlägesokänsliga SQLite-frågor finns i följande:

Gå till /Movies/Index. Lägg till en frågesträng, till exempel ?searchString=Ghost till URL:en. De filtrerade filmerna visas.

Indexvy

Om du ändrar signaturen för metoden Index så att den har en parameter med namnet idmatchar parametern id den valfria {id} platshållaren för standardvägarna som anges i Program.cs.

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

Ändra parametern till id och ändra alla förekomster av searchString till id.

Föregående Index metod:

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!.ToUpper().Contains(searchString.ToUpper()));
    }

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

Den uppdaterade Index metoden med 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!.ToUpper().Contains(id.ToUpper()));
    }

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

Nu kan du skicka sökrubriken som routningsdata (ett URL-segment) i stället för som ett frågesträngsvärde.

Index-vyn med ordet spöke som lagts till i URL:en och en returnerad filmlista med två filmer, Ghostbusters och Ghostbusters 2

Du kan dock inte förvänta dig att användarna ändrar URL:en varje gång de vill söka efter en film. Så nu ska du lägga till gränssnittselement som hjälper dem att filtrera filmer. Om du har ändrat signaturen för metoden Index för att testa hur du skickar den routningsbundna ID parametern ändrar du tillbaka den så att den tar en parameter med namnet 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!.ToUpper().Contains(searchString.ToUpper()));
    }

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

Öppna filen Views/Movies/Index.cshtml och lägg till <form> markeringen som är markerad nedan:

@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>
        <label>Title: <input type="text" name="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>
<table class="table">

HTML-<form>-taggen använder hjälpverktyget formulärtagg, så när du skickar formuläret publiceras filtersträngen till Index åtgärd för filmkontrollanten. Spara ändringarna och testa sedan filtret.

indexvy med ordet spöke skrivet i textrutan rubrikfiltret

Det finns ingen [HttpPost] överlagring av Index metod som du kan förvänta dig. Du behöver det inte eftersom metoden inte ändrar appens tillstånd, utan bara filtrerar data.

Du kan lägga till följande [HttpPost] Index metod.

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

Parametern notUsed används för att skapa en överlagring för metoden Index. Vi pratar om det senare i handledningen.

Om du lägger till den här metoden matchar åtgärdsanroparen metoden [HttpPost] Index och metoden [HttpPost] Index skulle köras enligt bilden nedan.

webbläsarfönster med programsvaret från HttpPost Index: filtrera på ghost

Men även om du lägger till den här [HttpPost] versionen av Index-metoden finns det en begränsning i hur allt detta har implementerats. Tänk dig att du vill bokmärke en viss sökning eller att du vill skicka en länk till vänner som de kan klicka på för att se samma filtrerade lista över filmer. Observera att URL:en för HTTP POST-begäran är densamma som URL:en för GET-begäran (localhost:{PORT}/Movies/Index) – det finns ingen sökinformation i URL:en. Söksträngsinformationen skickas till servern som ett formulärfältvärde. Du kan kontrollera att med webbläsarens utvecklarverktyg eller det utmärkta Fiddler-verktyget. Bilden nedan visar Chrome-webbläsarens utvecklarverktyg:

fliken Nätverk i Microsoft Edge Developer Tools som visar en begärandetext med värdet searchString för ghost

Du kan se sökparametern och XSRF- token i begärandetexten. Observera, som nämnts i den föregående självstudien, att Form Tag Helper genererar en XSRF- antiförfalsknings-token. Vi ändrar inte data, så vi behöver inte verifiera token i kontrollantmetoden.

Eftersom sökparametern finns i begärandetexten och inte i URL:en kan du inte samla in den sökinformationen för att bokmärka eller dela med andra. Åtgärda detta genom att ange att begäran ska HTTP GET hittas i filen Views/Movies/Index.cshtml.

@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>
        <label>Title: <input type="text" name="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>
<table class="table">

Nu när du skickar en sökning innehåller URL:en sökfrågesträngen. Sökningen går också till åtgärdsmetoden HttpGet Index, även om du har en HttpPost Index-metod.

Webbläsarfönster som visar searchString=ghost i URL:en och filmerna som returneras, Ghostbusters och Ghostbusters 2, innehåller ordet ghost

Följande markering visar ändringen av taggen form:

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

Lägg till sök efter genre

Lägg till följande MovieGenreViewModel-klass i mappen 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; }
}

Visningsmodellen för filmgenren innehåller:

  • En lista över filmer.
  • En SelectList som innehåller listan över genrer. På så sätt kan användaren välja en genre i listan.
  • MovieGenre, som innehåller den valda genren.
  • SearchString, som innehåller texten som användarna anger i söktextrutan.

Ersätt metoden Index i MoviesController.cs med följande kod:

// 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!.ToUpper().Contains(searchString.ToUpper()));
    }

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

Följande kod är en LINQ fråga som hämtar alla genrer från databasen.

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

Genrerna i SelectList skapas genom att projicera distinkta genrer (vi vill inte att vår urvalslista ska ha dubletter av genrer).

När användaren söker efter objektet behålls sökvärdet i sökrutan.

Lägga till sökning efter genre i indexvyn

Uppdatera Index.cshtml som finns i Views/Movies/ enligt följande:

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

        <label>Title: <input type="text" asp-for="SearchString" /></label>
        <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>

Granska lambda-uttrycket som används i följande HTML-hjälp:

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

I den föregående koden inspekterar DisplayNameFor HTML-hjälpare den Title-egenskap som refereras i lambda-uttrycket för att fastställa visningsnamnet. Eftersom lambda-uttrycket inspekteras i stället för att utvärderas får du ingen åtkomstöverträdelse när model, model.Movieseller model.Movies[0] är null eller tomma. När lambda-uttrycket utvärderas (till exempel @Html.DisplayFor(modelItem => item.Title)) utvärderas modellens egenskapsvärden. ! efter model.Movies är null-förlåtande operatorn, som används för att deklarera att Movies inte är null.

Testa appen genom att söka efter genre, efter filmtitel och efter båda:

Webbläsarfönster som visar resultatet av https://localhost:5001/Movies?MovieGenre=Comedy& SearchString=2

I det här avsnittet lägger du till sökfunktionen i åtgärdsmetoden Index som gör att du kan söka efter filmer efter genre eller namn.

Uppdatera Index-metoden som finns i Controllers/MoviesController.cs med följande kod:

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!.ToUpper().Contains(searchString.ToUpper()));
    }

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

Följande rad i Index-åtgärdsmetoden skapar en LINQ- fråga för att välja filmer:

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

Förfrågan är endast definierad för tillfället, den har inte körts mot databasen ännu.

Om parametern searchString innehåller en sträng ändras filmfrågan för att filtrera efter värdet för söksträngen:

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

Koden s => s.Title!.ToUpper().Contains(searchString.ToUpper()) ovan är ett Lambda-uttryck. Lambdas används i metodbaserade LINQ frågor som argument till standardmetoder för frågeoperatorer, till exempel Where-metoden eller Contains (används i koden ovan). LINQ-frågor körs inte när de definieras eller när de ändras genom att anropa en metod som Where, Containseller OrderBy. I stället skjuts frågekörningen upp. Det innebär att utvärderingen av ett uttryck fördröjs tills dess realiserade värde faktiskt itereras över eller metoden ToListAsync anropas. För mer information om uppskjuten frågekörning, se Frågekörning.

Not

Metoden Contains körs på databasen, inte i C#-koden. Skiftlägeskänsligheten för frågeparametrarna beror på databasen och sorteringsordningen. På SQL Server mappar Contains till SQL LIKE, vilket är skiftlägesokänsligt. SQLite med standardsortering är en blandning av skiftlägeskänsliga och skiftlägesokänsliga IN, beroende på frågan. Information om hur du gör skiftlägesokänsliga SQLite-frågor finns i följande:

Gå till /Movies/Index. Lägg till en frågesträng, till exempel ?searchString=Ghost till URL:en. De filtrerade filmerna visas.

Indexvy

Om du ändrar signaturen för metoden Index så att den har en parameter med namnet idmatchar parametern id den valfria {id} platshållaren för standardvägarna som anges i Program.cs.

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

Ändra parametern till id och ändra alla förekomster av searchString till id.

Föregående Index metod:

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!.ToUpper().Contains(searchString.ToUpper()));
    }

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

Den uppdaterade Index metoden med 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!.ToUpper().Contains(id.ToUpper()));
    }

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

Nu kan du skicka sökrubriken som routningsdata (ett URL-segment) i stället för som ett frågesträngsvärde.

Index-vyn med ordet spöke som lagts till i URL:en och en returnerad filmlista med två filmer, Ghostbusters och Ghostbusters 2

Du kan dock inte förvänta dig att användarna ändrar URL:en varje gång de vill söka efter en film. Så nu ska du lägga till gränssnittselement som hjälper dem att filtrera filmer. Om du har ändrat signaturen för metoden Index för att testa hur du skickar den routningsbundna ID parametern ändrar du tillbaka den så att den tar en parameter med namnet 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!.ToUpper().Contains(searchString.ToUpper()));
    }

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

Öppna filen Views/Movies/Index.cshtml och lägg till <form> markeringen som är markerad nedan:

@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>
        <label>Title: <input type="text" name="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>
<table class="table">

HTML-<form>-taggen använder Form Tag Helper , så när du skickar formuläret skickas filtersträngen till åtgärden Index i filmkontrollern. Spara ändringarna och testa sedan filtret.

indexvyn där ordet 'ghost' är skrivet i textfältet för Titel-filtret

Det finns ingen [HttpPost] överlagring av Index-metoden som du kan förvänta dig. Du behöver det inte eftersom metoden inte ändrar appens tillstånd, utan bara filtrerar data.

Du kan lägga till följande [HttpPost] Index metod.

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

Parametern notUsed används för att skapa en överlagring för metoden Index. Vi kommer att prata om det senare i handledningen.

Om du lägger till den här metoden matchar åtgärdsanroparen metoden [HttpPost] Index och metoden [HttpPost] Index skulle köras enligt bilden nedan.

webbläsarfönster med programsvaret från HttpPost Index: filtrera på ghost

Men även om du lägger till den här [HttpPost] versionen av Index-metoden finns det en begränsning i hur allt detta har implementerats. Tänk dig att du vill bokmärke en viss sökning eller att du vill skicka en länk till vänner som de kan klicka på för att se samma filtrerade lista över filmer. Observera att URL:en för HTTP POST-begäran är densamma som URL:en för GET-begäran (localhost:{PORT}/Movies/Index) – det finns ingen sökinformation i URL:en. Söksträngsinformationen skickas till servern som ett formulärfältvärde. Du kan kontrollera att med webbläsarens utvecklarverktyg eller det utmärkta Fiddler-verktyget. Bilden nedan visar Chrome-webbläsarens utvecklarverktyg:

fliken Nätverk i Microsoft Edge Developer Tools som visar en begärandetext med värdet searchString för ghost

Du kan se sökparametern och XSRF- token i begärandetexten. Observera, som du nämnde i föregående självstudie, att Form Tag Helper genererar en XSRF- antiforgery-token. Vi ändrar inte data, så vi behöver inte verifiera token i kontrollantmetoden.

Eftersom sökparametern finns i begärandetexten och inte i URL:en kan du inte samla in den sökinformationen för att bokmärka eller dela med andra. Åtgärda detta genom att specificera att begäran blir HTTP GET hittad i filen Views/Movies/Index.cshtml.

@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>
        <label>Title: <input type="text" name="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>
<table class="table">

Nu när du skickar en sökning innehåller URL:en sökfrågesträngen. Sökningen går också till åtgärdsmetoden HttpGet Index, även om du har en HttpPost Index-metod.

Webbläsarfönster som visar searchString=ghost i URL:en och filmerna som returneras, Ghostbusters och Ghostbusters 2, innehåller ordet ghost

Följande markering visar ändringen av taggen form:

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

Lägg till sök efter genre

Lägg till följande MovieGenreViewModel-klass i mappen 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; }
}

Visningsmodellen för filmgenren innehåller:

  • En lista över filmer.
  • En SelectList som innehåller listan över genrer. På så sätt kan användaren välja en genre i listan.
  • MovieGenre, som innehåller den valda genren.
  • SearchString, som innehåller texten som användarna anger i söktextrutan.

Ersätt metoden Index i MoviesController.cs med följande kod:

// 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!.ToUpper().Contains(searchString.ToUpper()));
    }

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

Följande kod är en LINQ fråga som hämtar alla genrer från databasen.

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

Det SelectList av genrer skapas genom att projicera distinkta genrer (vi vill inte att vår urvalslista ska ha dubbla genrer).

När användaren söker efter objektet behålls sökvärdet i sökrutan.

Lägga till sökning efter genre i indexvyn

Uppdatera Index.cshtml som finns i Views/Movies/ enligt följande:

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

        <label>Title: <input type="text" asp-for="SearchString" /></label>
        <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>

Granska lambda-uttrycket som används i följande HTML-hjälp:

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

I föregående kod inspekterar DisplayNameFor HTML-hjälpmedlet Title egenskap som refereras i lambda-uttrycket, för att fastställa visningsnamnet. Eftersom lambda-uttrycket inspekteras i stället för att utvärderas får du ingen åtkomstöverträdelse när model, model.Movieseller model.Movies[0] är null eller tomma. När lambda-uttrycket utvärderas (till exempel @Html.DisplayFor(modelItem => item.Title)) utvärderas modellens egenskapsvärden. ! efter model.Movies är null-förlåtande operatorn, som används för att deklarera att Movies inte är null.

Testa appen genom att söka efter genre, efter filmtitel och efter båda:

Webbläsarfönster som visar resultatet av https://localhost:5001/Movies?MovieGenre=Comedy& SearchString=2

I det här avsnittet lägger du till sökfunktionen i åtgärdsmetoden Index som gör att du kan söka efter filmer efter genre eller namn.

Uppdatera Index-metoden som finns i Controllers/MoviesController.cs med följande kod:

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

Den första raden i Index-åtgärdsmetoden skapar en LINQ- fråga för att välja filmerna:

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

Frågan definieras endast för tillfället, den har inte körts mot databasen.

Om parametern searchString innehåller en sträng ändras filmfrågan för att filtrera efter värdet för söksträngen:

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

Koden s => s.Title!.Contains(searchString) ovan är ett Lambda-uttryck. Lambdas används i metodbaserade LINQ frågor som argument till standardmetoder för frågeoperatorer, till exempel Where-metoden eller Contains (används i koden ovan). LINQ-frågor körs inte när de definieras eller när de ändras genom att anropa en metod som Where, Containseller OrderBy. I stället skjuts frågekörningen upp. Det innebär att utvärderingen av ett uttryck fördröjs tills dess realiserade värde faktiskt itereras över eller metoden ToListAsync anropas. Mer information om uppskjuten frågekörning finns i Frågekörning.

Obs! Metoden Contains körs på databasen, inte i c#-koden som visas ovan. Skiftlägeskänsligheten för frågan beror på databasen och teckenuppsättningen. På SQL Server motsvarar ContainsSQL LIKE, vilket är skiftlägesokänsligt. I SQLite, med standardsortering, är det skiftlägeskänsligt.

Gå till /Movies/Index. Lägg till en frågesträng, till exempel ?searchString=Ghost till URL:en. De filtrerade filmerna visas.

Indexvy

Om du ändrar signaturen för metoden Index så att den har en parameter med namnet idmatchar parametern id den valfria {id} platshållaren för standardvägarna som anges i Program.cs.

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

Ändra parametern till id och ändra alla förekomster av searchString till id.

Föregående Index metod:

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

Den uppdaterade Index metoden med 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());
}

Nu kan du skicka sökrubriken som routningsdata (ett URL-segment) i stället för som ett frågesträngsvärde.

Index-vyn med ordet spöke som lagts till i URL:en och en returnerad filmlista med två filmer, Ghostbusters och Ghostbusters 2

Du kan dock inte förvänta dig att användarna ändrar URL:en varje gång de vill söka efter en film. Så nu ska du lägga till gränssnittselement som hjälper dem att filtrera filmer. Om du har ändrat signaturen för metoden Index för att testa hur du skickar den routningsbundna ID parametern ändrar du tillbaka den så att den tar en parameter med namnet 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());
}

Öppna filen Views/Movies/Index.cshtml och lägg till <form> markeringen som är markerad nedan:

@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>
        <label>Title: <input type="text" name="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>

HTML-<form>-taggen använder Formulärtaggs-hjälparen , så när du skickar formuläret publiceras filtersträngen till Index-åtgärden för filmkontrollanten. Spara ändringarna och testa sedan filtret.

Index-vyn med ordet ghost skrivet i textrutan för rubrikfilter

Det finns ingen [HttpPost] överlag av Index-metoden som du kanske förväntar dig. Du behöver det inte eftersom metoden inte ändrar appens tillstånd, utan bara filtrerar data.

Du kan lägga till följande [HttpPost] Index metod.

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

Parametern notUsed används för att skapa en överlagring för metoden Index. Vi kommer att prata om det senare i självstudien.

Om du lägger till den här metoden matchar åtgärdsanroparen metoden [HttpPost] Index och metoden [HttpPost] Index skulle köras enligt bilden nedan.

webbläsarfönster med programsvaret från HttpPost Index: filtrera på ghost

Men även om du lägger till den här [HttpPost] versionen av Index-metoden finns det en begränsning i hur allt detta har implementerats. Tänk dig att du vill bokmärke en viss sökning eller att du vill skicka en länk till vänner som de kan klicka på för att se samma filtrerade lista över filmer. Observera att URL:en för HTTP POST-begäran är densamma som URL:en för GET-begäran (localhost:{PORT}/Movies/Index) – det finns ingen sökinformation i URL:en. Söksträngsinformationen skickas till servern som ett formulärfältvärde. Du kan kontrollera att med webbläsarens utvecklarverktyg eller det utmärkta Fiddler-verktyget. Bilden nedan visar Chrome-webbläsarens utvecklarverktyg:

fliken Nätverk i Microsoft Edge Developer Tools som visar en begärandetext med värdet searchString för ghost

Du kan se sökparametern och XSRF- token i begärandetexten. Observera, som nämnts i föregående handledning, att Form Tag Helper genererar en XSRF antiförfalskningstoken. Vi ändrar inte data, så vi behöver inte verifiera token i kontrollantmetoden.

Eftersom sökparametern finns i begärandetexten och inte i URL:en kan du inte samla in den sökinformationen för att bokmärka eller dela med andra. Åtgärda detta genom att specificera att begäran HTTP GET ska hittas i filen Views/Movies/Index.cshtml.

@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>
        <label>Title: <input type="text" name="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>
<table class="table">

Nu när du skickar en sökning innehåller URL:en sökfrågesträngen. Sökningen går också till åtgärdsmetoden HttpGet Index, även om du har en HttpPost Index-metod.

Webbläsarfönster som visar searchString=ghost i URL:en och filmerna som returneras, Ghostbusters och Ghostbusters 2, innehåller ordet ghost

Följande markering visar ändringen av taggen form:

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

Lägg till sök efter genre

Lägg till följande MovieGenreViewModel-klass i mappen 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; }
    }
}

Visningsmodellen för filmgenren innehåller:

  • En lista över filmer.
  • En SelectList som innehåller listan över genrer. På så sätt kan användaren välja en genre i listan.
  • MovieGenre, som innehåller den valda genren.
  • SearchString, som innehåller texten som användarna anger i söktextrutan.

Ersätt metoden Index i MoviesController.cs med följande kod:

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

Följande kod är en LINQ fråga som hämtar alla genrer från databasen.

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

Den SelectList av genrer skapas genom att projicera distinkta genrer (vi vill inte att vår urvalslista ska ha dubbletter av genrer).

När användaren söker efter objektet behålls sökvärdet i sökrutan.

Lägga till sökning efter genre i indexvyn

Uppdatera Index.cshtml som finns i Views/Movies/ enligt följande:

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

        <label>Title: <input type="text" asp-for="SearchString" /></label>
        <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>

Granska lambda-uttrycket som används i följande HTML-hjälp:

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

I föregående kod inspekterar DisplayNameFor HTML-hjälpen den Title egenskap som refereras i lambda-uttrycket för att fastställa visningsnamnet. Eftersom lambda-uttrycket inspekteras i stället för att utvärderas får du ingen åtkomstöverträdelse när model, model.Movieseller model.Movies[0] är null eller tomma. När lambda-uttrycket utvärderas (till exempel @Html.DisplayFor(modelItem => item.Title)) utvärderas modellens egenskapsvärden.

Testa appen genom att söka efter genre, efter filmtitel och efter båda:

Webbläsarfönster som visar resultatet av https://localhost:5001/Movies?MovieGenre=Comedy& SearchString=2

I det här avsnittet lägger du till sökfunktionen i åtgärdsmetoden Index som gör att du kan söka efter filmer efter genre eller namn.

Uppdatera Index-metoden som finns i Controllers/MoviesController.cs med följande kod:

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

Den första raden i Index-åtgärdsmetoden skapar en LINQ- fråga för att välja filmerna:

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

Förfrågan är endast definierad vid den här tidpunkten, den har inte körts mot databasen.

Om parametern searchString innehåller en sträng ändras filmfrågan för att filtrera efter värdet för söksträngen:

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

Koden s => s.Title.Contains() ovan är ett Lambda-uttryck. Lambdas används i metodbaserade LINQ frågor som argument till standardmetoder för frågeoperatorer, till exempel Where-metoden eller Contains (används i koden ovan). LINQ-frågor körs inte när de definieras eller när de ändras genom att anropa en metod som Where, Containseller OrderBy. I stället skjuts frågekörningen upp. Det innebär att utvärderingen av ett uttryck fördröjs tills dess realiserade värde faktiskt itereras över eller metoden ToListAsync anropas. För mer information om uppskjuten frågekörning, se Frågekörning.

Obs! Metoden Contains körs på databasen, inte i c#-koden som visas ovan. Skiftlägeskänsligheten för frågan beror på databasen och teckenuppsättningen. På SQL Server mappar Contains till SQL LIKE, vilket är skiftlägesokänsligt. I SQLite, med standardsortering, är det skiftlägeskänsligt.

Gå till /Movies/Index. Lägg till en frågesträng, till exempel ?searchString=Ghost till URL:en. De filtrerade filmerna visas.

Index vy

Om du ändrar signaturen för metoden Index så att den har en parameter med namnet idmatchar parametern id den valfria {id} platshållaren för standardvägarna som anges i Startup.cs.

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

Ändra parametern till id och alla förekomster av searchString ändras till id.

Föregående Index metod:

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

Den uppdaterade Index metoden med 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());
}

Nu kan du skicka sökrubriken som routningsdata (ett URL-segment) i stället för som ett frågesträngsvärde.

Index-vyn med ordet spöke som lagts till i URL:en och en returnerad filmlista med två filmer, Ghostbusters och Ghostbusters 2

Du kan dock inte förvänta dig att användarna ändrar URL:en varje gång de vill söka efter en film. Så nu ska du lägga till gränssnittselement som hjälper dem att filtrera filmer. Om du har ändrat signaturen för metoden Index för att testa hur du skickar den routningsbundna ID parametern ändrar du tillbaka den så att den tar en parameter med namnet 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());
}

Öppna filen Views/Movies/Index.cshtml och lägg till <form> markeringen som är markerad nedan:

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

<h2>Index</h2>

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

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

<table class="table">
    <thead>

HTML-<form>-taggen använder hjälpverktyget för -formulärtagg, så när du skickar formuläret skickas filtersträngen till Index-åtgärden i filmkontrollen. Spara ändringarna och testa sedan filtret.

indexvy med ordet ghost skrivet in i textrutan för Rubrikfilter

Det finns ingen [HttpPost] överlagring av Index metod som du kan förvänta dig. Du behöver det inte eftersom metoden inte ändrar appens tillstånd, utan bara filtrerar data.

Du kan lägga till följande [HttpPost] Index metod.

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

Parametern notUsed används för att skapa en överlagring för metoden Index. Vi kommer att prata om det senare i handledningen.

Om du lägger till den här metoden matchar åtgärdsanroparen metoden [HttpPost] Index och metoden [HttpPost] Index skulle köras enligt bilden nedan.

webbläsarfönster med programsvaret från HttpPost Index: filtrera på ghost

Men även om du lägger till den här [HttpPost] versionen av Index-metoden finns det en begränsning i hur allt detta har implementerats. Tänk dig att du vill bokmärke en viss sökning eller att du vill skicka en länk till vänner som de kan klicka på för att se samma filtrerade lista över filmer. Observera att URL:en för HTTP POST-begäran är densamma som URL:en för GET-begäran (localhost:{PORT}/Movies/Index) – det finns ingen sökinformation i URL:en. Söksträngsinformationen skickas till servern som ett formulärfältvärde. Du kan kontrollera att med webbläsarens utvecklarverktyg eller det utmärkta Fiddler-verktyget. Bilden nedan visar Chrome-webbläsarens utvecklarverktyg:

fliken Nätverk i Microsoft Edge Developer Tools som visar en begärandetext med värdet searchString för ghost

Du kan se sökparametern och XSRF- token i begärandetexten. Observera, som nämnts i föregående handledning, att Form Tag Helper genererar en XSRF- antiforgery-token. Vi ändrar inte data, så vi behöver inte verifiera token i kontrollantmetoden.

Eftersom sökparametern finns i begärandetexten och inte i URL:en kan du inte samla in den sökinformationen för att bokmärka eller dela med andra. Åtgärda detta genom att ange att begäran ska HTTP GET hittas i filen Views/Movies/Index.cshtml.

@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>
        <label>Title: <input type="text" name="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>

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

Nu när du skickar en sökning innehåller URL:en sökfrågesträngen. Sökningen går också till åtgärdsmetoden HttpGet Index, även om du har en HttpPost Index-metod.

Webbläsarfönster som visar searchString=ghost i URL:en och filmerna som returneras, Ghostbusters och Ghostbusters 2, innehåller ordet ghost

Följande markering visar ändringen av taggen form:

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

Lägg till sök efter genre

Lägg till följande MovieGenreViewModel-klass i mappen 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; }
    }
}

Visningsmodellen för filmgenren innehåller:

  • En lista över filmer.
  • En SelectList som innehåller listan över genrer. På så sätt kan användaren välja en genre i listan.
  • MovieGenre, som innehåller den valda genren.
  • SearchString, som innehåller texten som användarna anger i söktextrutan.

Ersätt metoden Index i MoviesController.cs med följande kod:

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

Följande kod är en LINQ fråga som hämtar alla genrer från databasen.

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

Den SelectList av genrer skapas genom att projicera distinkta genrer (vi vill inte att vår urvalslista ska ha dubbletter av genrer).

När användaren söker efter objektet behålls sökvärdet i sökrutan.

Lägga till sökning efter genre i indexvyn

Uppdatera Index.cshtml som finns i Views/Movies/ enligt följande:

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

        <label>Title: <input type="text" asp-for="SearchString" /></label>
        <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>

Granska lambda-uttrycket som används i följande HTML-hjälp:

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

I föregående kod inspekterar DisplayNameFor HTML-hjälpen den Title egenskap som refereras i lambda-uttrycket för att fastställa visningsnamnet. Eftersom lambda-uttrycket inspekteras i stället för att utvärderas får du ingen åtkomstöverträdelse när model, model.Movieseller model.Movies[0] är null eller tomma. När lambda-uttrycket utvärderas (till exempel @Html.DisplayFor(modelItem => item.Title)) utvärderas modellens egenskapsvärden.

Testa appen genom att söka efter genre, efter filmtitel och efter båda:

Webbläsarfönster som visar resultatet av https://localhost:5001/Movies?MovieGenre=Comedy& SearchString=2