다음을 통해 공유


7부. ASP.NET Core MVC 앱에 검색 추가

참고 항목

이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.

Warning

이 버전의 ASP.NET Core는 더 이상 지원되지 않습니다. 자세한 내용은 .NET 및 .NET Core 지원 정책을 참조 하세요. 현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.

Important

이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.

현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.

작성자: Rick Anderson

이 섹션에서는 장르 또는 이름별로 영화를 검색할 수 있는 Index 작업 메서드에 검색 기능을 추가합니다.

Index 다음 코드로 내부에 Controllers/MoviesController.cs 있는 메서드를 업데이트합니다.

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

Index 작업 메서드의 첫 번째 줄은 영화를 선택하는 LINQ 쿼리를 만듭니다.

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

이 시점에 쿼리는 정의되기만 했으며 데이터베이스에 대해 실행되지는 않았습니다.

searchString 매개 변수에 문자열이 담겨 있으면 해당 검색 문자열의 값으로 필터링하도록 영화 쿼리가 수정됩니다.

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

위의 s => s.Title!.ToUpper().Contains(searchString.ToUpper()) 코드는 람다 식입니다. 람다는 메서드 기반 LINQ 쿼리에서 Where 또는 Contains 메서드(위의 코드에서 사용됨)와 같은 표준 쿼리 연산자 메서드의 인수로 사용됩니다. LINQ 쿼리는 정의될 때 또는 메서드(예: Where, Contains 또는 OrderBy)를 호출하여 수정될 때 실행되지 않습니다. 대신 쿼리 실행이 지연됩니다. 즉, 실제로 결정된 값이 반복되거나 ToListAsync 메서드가 호출될 때까지 식의 계산이 지연된다는 뜻입니다. 지연된 쿼리 실행에 대한 자세한 내용은 쿼리 실행을 참조하세요.

참고 항목

Contains 메서드는 C# 코드가 아닌 데이터베이스에서 실행됩니다. 쿼리에 대한 대/소문자 구분은 데이터베이스 및 데이터 정렬에 따라 달라집니다. SQL Server에서 Contains는 대/소문자를 구분하는 SQL LIKE로 매핑됩니다. 기본 데이터 정렬을 사용하는 SQLite는 쿼리에 따라 대/소문자를 구분하거나 구분하지 않습니다. 대/소문자를 구분하지 않는 SQLite 쿼리를 만드는 방법에 대한 자세한 내용은 다음을 참조하세요.

/Movies/Index으로 이동합니다. 쿼리 문자열(예: ?searchString=Ghost)을 URL에 추가합니다. 필터링된 동영상이 표시됩니다.

인덱스 보기

메서드의 Index 시그니처를 매개 변수로 id id변경하면 매개 변수가 설정된 Program.cs기본 경로에 대한 선택적 {id} 자리 표시자와 일치합니다.

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

매개 변수를 id로, searchString의 모든 항목을 id로 변경합니다.

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

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

id 매개 변수를 사용하는 수정된 Index 메서드는 다음과 같습니다.

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

이제 쿼리 문자열 값 대신 경로 데이터(URL 세그먼트)로 검색 제목을 전달할 수 있습니다.

Url에 추가된 단어 ghost와 두 개의 동영상, Ghostbusters 및 Ghostbusters 2의 반환된 동영상 목록이 있는 인덱스 보기

그러나 사용자가 영화를 검색하려고 할 때마다 URL을 수정하지는 않습니다. 따라서 이제 영화를 필터링하는 데 도움이 되는 UI 요소를 추가할 것입니다. Index 메서드의 서명을 변경하여 경로 바인딩 ID 매개 변수 전달 방법을 테스트하는 경우 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());
}

Views/Movies/Index.cshtml 파일을 열고 아래에 강조 표시된 태그를 추가 <form> 합니다.

@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> 태그는 Form 태그 도우미를 사용하므로 양식을 제출할 경우 필터 문자열은 영화 컨트롤러의 Index 작업에 게시됩니다. 변경 내용을 저장하고 필터를 테스트합니다.

Title 필터 텍스트 상자에 ghost라는 단어를 입력한 Index 보기

예상할 수 있는 것처럼 Index 메서드의 [HttpPost] 오버로드는 없습니다. 이 메서드는 앱의 상태를 변경하지 않고 데이터를 필터링하기만 하기 때문에 필요하지 않습니다.

다음 [HttpPost] Index 메서드를 추가할 수 있습니다.

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

notUsed 매개 변수는 Index 메서드의 오버로드를 만들기 위해서 사용됩니다. 자습서의 뒷부분에서 이에 대해 알아봅니다.

이 메서드를 추가하면 작업 호출자는 [HttpPost] Index 메서드와 일치하고, 아래 이미지와 같이 [HttpPost] Index 메서드가 실행됩니다.

From HttpPost Index: filter on ghost 응답을 보여주는 브라우저 창

그러나 이 [HttpPost] 버전의 Index 메서드를 추가하는 경우에도 이를 모두 구현하는 방법은 제한됩니다. 특정 검색을 책갈피로 설정하거나 동일하게 필터링된 영화 목록을 보기 위해 클릭할 수 있는 링크를 친구에게 보내려고 한다고 가정합니다. HTTP POST 요청의 URL은 GET 요청의 URL(localhost:{PORT}/Movies/Index)과 동일합니다. URL에는 검색 정보가 없습니다. 검색 문자열 정보는 양식 필드 값으로 서버에 전송됩니다. 브라우저 개발자 도구 또는 뛰어난 Fiddler 도구에서 확인할 수 있습니다.

다음 이미지는 네트워크헤더 탭이 선택된 Chrome 브라우저 개발자 도구 보여줍니다.

검색 값이 ghost인 요청 본문을 표시하는 Chrome 브라우저 개발자 도구의 네트워크 및 헤더 탭

양식 데이터를 보려면 네트워크페이로드 탭이 선택됩니다.

양식 데이터를 보여 주는 Chrome 브라우저 개발자 도구의 네트워크 및 페이로드 탭

요청 본문에서 검색 매개 변수 및 XSRF 토큰을 확인할 수 있습니다. 이전 자습서에서 설명한 것처럼 양식 태그 도우미는 XSRF 위조 방지 토큰을 생성합니다. 데이터를 수정하지 않으므로 컨트롤러 메서드에서 토큰 유효성을 검사할 필요도 없습니다.

검색 매개 변수가 URL이 아닌 요청 본문에 있기 때문에 책갈피에 해당 검색 정보를 캡처하거나 다른 사용자와 공유할 수 없습니다. 파일에 있는 태그에 form 요청이 있어야 HTTP GET 한다고 지정하여 이 문제를 해결합니다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">

이제 검색을 제출할 때 URL에는 검색 쿼리 문자열이 포함됩니다. HttpPost Index 메서드가 존재하더라도 검색은 HttpGet Index 작업 메서드로 이동합니다.

URL에서 searchString=ghost 및 Ghostbusters 및 Ghostbusters 2라는 두 개의 반환된 영화를 표시하는 브라우저 창은 단어 ghost를 포함합니다.

장르별 검색 추가

다음 MovieGenreViewModel 클래스를 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; }
}

영화 장르 보기 모델은 다음을 포함합니다.

  • 영화 목록.
  • 장르 목록을 포함한 SelectList 이를 이용해서 사용자는 목록에서 장르를 선택할 수 있습니다.
  • 선택한 장르가 포함된 MovieGenre
  • 사용자가 검색 텍스트 상자에 입력한 텍스트가 포함된 SearchString.

MoviesController.cs에서 Index 메서드를 다음 코드로 바꿉니다.

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

다음 코드는 데이터베이스에서 모든 장르를 검색하는 LINQ 쿼리입니다.

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

장르의 SelectList는 고유한 장르를 프로젝팅하여 생성됩니다(선택 목록에 중복된 장르가 포함되는 것을 원하지 않습니다).

사용자가 항목을 검색하면 검색 상자에 검색 값이 유지됩니다.

인덱스 보기에 장르별 검색 추가

Views/Movies/에 있는 Index.cshtml을 다음과 같이 업데이트합니다.

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

다음 HTML 도우미에서 사용되는 람다 식을 살펴봅니다.

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

이전 코드에서 DisplayNameFor HTML 도우미는 람다 식에서 참조되는 Title 속성을 검사하여 표시 이름을 확인합니다. 람다 식이 평가되지 않고 검사되기 때문에 model, model.Movies 또는 model.Movies[0]null이거나 비어 있더라도 액세스 위반을 수신하지 않습니다. 람다 식이 계산될 경우(예: @Html.DisplayFor(modelItem => item.Title)) 모델의 속성 값이 평가됩니다. model.Movies 다음 !은 null이 아닌 Movies을 선언 하는 데 사용되는 null 용서 연산자입니다.

장르로, 영화 제목으로, 그리고 두 가지 모두로 검색하여 앱을 테스트합니다.

의 https://localhost:5001/Movies?MovieGenre=Comedy&결과를 보여 주는 브라우저 창 SearchString=2

이 섹션에서는 장르 또는 이름별로 영화를 검색할 수 있는 Index 작업 메서드에 검색 기능을 추가합니다.

Index 다음 코드로 내부에 Controllers/MoviesController.cs 있는 메서드를 업데이트합니다.

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

Index 작업 메서드의 첫 번째 줄은 영화를 선택하는 LINQ 쿼리를 만듭니다.

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

이 시점에 쿼리는 정의되기만 했으며 데이터베이스에 대해 실행되지는 않았습니다.

searchString 매개 변수에 문자열이 담겨 있으면 해당 검색 문자열의 값으로 필터링하도록 영화 쿼리가 수정됩니다.

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

위의 s => s.Title!.ToUpper().Contains(searchString.ToUpper()) 코드는 람다 식입니다. 람다는 메서드 기반 LINQ 쿼리에서 Where 또는 Contains 메서드(위의 코드에서 사용됨)와 같은 표준 쿼리 연산자 메서드의 인수로 사용됩니다. LINQ 쿼리는 정의될 때 또는 메서드(예: Where, Contains 또는 OrderBy)를 호출하여 수정될 때 실행되지 않습니다. 대신 쿼리 실행이 지연됩니다. 즉, 실제로 결정된 값이 반복되거나 ToListAsync 메서드가 호출될 때까지 식의 계산이 지연된다는 뜻입니다. 지연된 쿼리 실행에 대한 자세한 내용은 쿼리 실행을 참조하세요.

참고 항목

Contains 메서드는 C# 코드가 아닌 데이터베이스에서 실행됩니다. 쿼리에 대한 대/소문자 구분은 데이터베이스 및 데이터 정렬에 따라 달라집니다. SQL Server에서 Contains는 대/소문자를 구분하는 SQL LIKE로 매핑됩니다. 기본 데이터 정렬을 사용하는 SQLite는 쿼리에 따라 대/소문자를 구분하거나 구분하지 않습니다. 대/소문자를 구분하지 않는 SQLite 쿼리를 만드는 방법에 대한 자세한 내용은 다음을 참조하세요.

/Movies/Index으로 이동합니다. 쿼리 문자열(예: ?searchString=Ghost)을 URL에 추가합니다. 필터링된 동영상이 표시됩니다.

인덱스 보기

메서드의 Index 시그니처를 매개 변수로 id id변경하면 매개 변수가 설정된 Program.cs기본 경로에 대한 선택적 {id} 자리 표시자와 일치합니다.

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

매개 변수를 id로, searchString의 모든 항목을 id로 변경합니다.

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

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

id 매개 변수를 사용하는 수정된 Index 메서드는 다음과 같습니다.

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

이제 쿼리 문자열 값 대신 경로 데이터(URL 세그먼트)로 검색 제목을 전달할 수 있습니다.

Url에 추가된 단어 ghost와 두 개의 동영상, Ghostbusters 및 Ghostbusters 2의 반환된 동영상 목록이 있는 인덱스 보기

그러나 사용자가 영화를 검색하려고 할 때마다 URL을 수정하지는 않습니다. 따라서 이제 영화를 필터링하는 데 도움이 되는 UI 요소를 추가할 것입니다. Index 메서드의 서명을 변경하여 경로 바인딩 ID 매개 변수 전달 방법을 테스트하는 경우 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());
}

Views/Movies/Index.cshtml 파일을 열고 아래에 강조 표시된 태그를 추가 <form> 합니다.

@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> 태그는 Form 태그 도우미를 사용하므로 양식을 제출할 경우 필터 문자열은 영화 컨트롤러의 Index 작업에 게시됩니다. 변경 내용을 저장하고 필터를 테스트합니다.

Title 필터 텍스트 상자에 ghost라는 단어를 입력한 Index 보기

예상할 수 있는 것처럼 Index 메서드의 [HttpPost] 오버로드는 없습니다. 이 메서드는 앱의 상태를 변경하지 않고 데이터를 필터링하기만 하기 때문에 필요하지 않습니다.

다음 [HttpPost] Index 메서드를 추가할 수 있습니다.

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

notUsed 매개 변수는 Index 메서드의 오버로드를 만들기 위해서 사용됩니다. 자습서의 뒷부분에서 이에 대해 알아봅니다.

이 메서드를 추가하면 작업 호출자는 [HttpPost] Index 메서드와 일치하고, 아래 이미지와 같이 [HttpPost] Index 메서드가 실행됩니다.

From HttpPost Index: filter on ghost 응답을 보여주는 브라우저 창

그러나 이 [HttpPost] 버전의 Index 메서드를 추가하는 경우에도 이를 모두 구현하는 방법은 제한됩니다. 특정 검색을 책갈피로 설정하거나 동일하게 필터링된 영화 목록을 보기 위해 클릭할 수 있는 링크를 친구에게 보내려고 한다고 가정합니다. HTTP POST 요청의 URL은 GET 요청의 URL(localhost:{PORT}/Movies/Index)과 동일합니다. URL에는 검색 정보가 없습니다. 검색 문자열 정보는 양식 필드 값으로 서버에 전송됩니다. 브라우저 개발자 도구 또는 뛰어난 Fiddler 도구에서 확인할 수 있습니다. 아래 이미지에서는 Chrome 브라우저 개발자 도구를 보여줍니다.

ghost라는 searchString 값을 가진 요청 본문을 보여주는 Microsoft Edge 개발자 도구의 네트워크 탭

요청 본문에서 검색 매개 변수 및 XSRF 토큰을 확인할 수 있습니다. 이전 자습서에서 설명한 것처럼 양식 태그 도우미는 XSRF 위조 방지 토큰을 생성합니다. 데이터를 수정하지 않으므로 컨트롤러 메서드에서 토큰 유효성을 검사할 필요도 없습니다.

검색 매개 변수가 URL이 아닌 요청 본문에 있기 때문에 책갈피에 해당 검색 정보를 캡처하거나 다른 사용자와 공유할 수 없습니다. 파일에 요청이 있어야 한다고 HTTP GET 지정하여 이 문제를 해결합니다 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">

이제 검색을 제출할 때 URL에는 검색 쿼리 문자열이 포함됩니다. HttpPost Index 메서드가 존재하더라도 검색은 HttpGet Index 작업 메서드로 이동합니다.

URL에서 searchString=ghost 및 Ghostbusters 및 Ghostbusters 2라는 두 개의 반환된 영화를 표시하는 브라우저 창은 단어 ghost를 포함합니다.

다음 표시는 form 태그에 대한 변경 내용을 표시합니다.

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

장르별 검색 추가

다음 MovieGenreViewModel 클래스를 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; }
}

영화 장르 보기 모델은 다음을 포함합니다.

  • 영화 목록.
  • 장르 목록을 포함한 SelectList 이를 이용해서 사용자는 목록에서 장르를 선택할 수 있습니다.
  • 선택한 장르가 포함된 MovieGenre
  • 사용자가 검색 텍스트 상자에 입력한 텍스트가 포함된 SearchString.

MoviesController.cs에서 Index 메서드를 다음 코드로 바꿉니다.

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

다음 코드는 데이터베이스에서 모든 장르를 검색하는 LINQ 쿼리입니다.

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

장르의 SelectList는 고유한 장르를 프로젝팅하여 생성됩니다(선택 목록에 중복된 장르가 포함되는 것을 원하지 않습니다).

사용자가 항목을 검색하면 검색 상자에 검색 값이 유지됩니다.

인덱스 보기에 장르별 검색 추가

Views/Movies/에 있는 Index.cshtml을 다음과 같이 업데이트합니다.

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

다음 HTML 도우미에서 사용되는 람다 식을 살펴봅니다.

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

이전 코드에서 DisplayNameFor HTML 도우미는 람다 식에서 참조되는 Title 속성을 검사하여 표시 이름을 확인합니다. 람다 식이 평가되지 않고 검사되기 때문에 model, model.Movies 또는 model.Movies[0]null이거나 비어 있더라도 액세스 위반을 수신하지 않습니다. 람다 식이 계산될 경우(예: @Html.DisplayFor(modelItem => item.Title)) 모델의 속성 값이 평가됩니다. model.Movies 다음 !은 null이 아닌 Movies을 선언 하는 데 사용되는 null 용서 연산자입니다.

장르로, 영화 제목으로, 그리고 두 가지 모두로 검색하여 앱을 테스트합니다.

의 https://localhost:5001/Movies?MovieGenre=Comedy&결과를 보여 주는 브라우저 창 SearchString=2

이 섹션에서는 장르 또는 이름별로 영화를 검색할 수 있는 Index 작업 메서드에 검색 기능을 추가합니다.

Index 다음 코드로 내부에 Controllers/MoviesController.cs 있는 메서드를 업데이트합니다.

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

Index 작업 메서드의 첫 번째 줄은 영화를 선택하는 LINQ 쿼리를 만듭니다.

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

이 시점에 쿼리는 정의되기만 했으며 데이터베이스에 대해 실행되지는 않았습니다.

searchString 매개 변수에 문자열이 담겨 있으면 해당 검색 문자열의 값으로 필터링하도록 영화 쿼리가 수정됩니다.

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

위의 s => s.Title!.ToUpper().Contains(searchString.ToUpper()) 코드는 람다 식입니다. 람다는 메서드 기반 LINQ 쿼리에서 Where 또는 Contains 메서드(위의 코드에서 사용됨)와 같은 표준 쿼리 연산자 메서드의 인수로 사용됩니다. LINQ 쿼리는 정의될 때 또는 메서드(예: Where, Contains 또는 OrderBy)를 호출하여 수정될 때 실행되지 않습니다. 대신 쿼리 실행이 지연됩니다. 즉, 실제로 결정된 값이 반복되거나 ToListAsync 메서드가 호출될 때까지 식의 계산이 지연된다는 뜻입니다. 지연된 쿼리 실행에 대한 자세한 내용은 쿼리 실행을 참조하세요.

참고 항목

Contains 메서드는 C# 코드가 아닌 데이터베이스에서 실행됩니다. 쿼리에 대한 대/소문자 구분은 데이터베이스 및 데이터 정렬에 따라 달라집니다. SQL Server에서 Contains는 대/소문자를 구분하는 SQL LIKE로 매핑됩니다. 기본 데이터 정렬을 사용하는 SQLite는 쿼리에 따라 대/소문자를 구분하거나 구분하지 않습니다. 대/소문자를 구분하지 않는 SQLite 쿼리를 만드는 방법에 대한 자세한 내용은 다음을 참조하세요.

/Movies/Index으로 이동합니다. 쿼리 문자열(예: ?searchString=Ghost)을 URL에 추가합니다. 필터링된 동영상이 표시됩니다.

인덱스 보기

메서드의 Index 시그니처를 매개 변수로 id id변경하면 매개 변수가 설정된 Program.cs기본 경로에 대한 선택적 {id} 자리 표시자와 일치합니다.

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

매개 변수를 id로, searchString의 모든 항목을 id로 변경합니다.

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

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

id 매개 변수를 사용하는 수정된 Index 메서드는 다음과 같습니다.

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

이제 쿼리 문자열 값 대신 경로 데이터(URL 세그먼트)로 검색 제목을 전달할 수 있습니다.

Url에 추가된 단어 ghost와 두 개의 동영상, Ghostbusters 및 Ghostbusters 2의 반환된 동영상 목록이 있는 인덱스 보기

그러나 사용자가 영화를 검색하려고 할 때마다 URL을 수정하지는 않습니다. 따라서 이제 영화를 필터링하는 데 도움이 되는 UI 요소를 추가할 것입니다. Index 메서드의 서명을 변경하여 경로 바인딩 ID 매개 변수 전달 방법을 테스트하는 경우 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());
}

Views/Movies/Index.cshtml 파일을 열고 아래에 강조 표시된 태그를 추가 <form> 합니다.

@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> 태그는 Form 태그 도우미를 사용하므로 양식을 제출할 경우 필터 문자열은 영화 컨트롤러의 Index 작업에 게시됩니다. 변경 내용을 저장하고 필터를 테스트합니다.

Title 필터 텍스트 상자에 ghost라는 단어를 입력한 Index 보기

예상할 수 있는 것처럼 Index 메서드의 [HttpPost] 오버로드는 없습니다. 이 메서드는 앱의 상태를 변경하지 않고 데이터를 필터링하기만 하기 때문에 필요하지 않습니다.

다음 [HttpPost] Index 메서드를 추가할 수 있습니다.

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

notUsed 매개 변수는 Index 메서드의 오버로드를 만들기 위해서 사용됩니다. 자습서의 뒷부분에서 이에 대해 알아봅니다.

이 메서드를 추가하면 작업 호출자는 [HttpPost] Index 메서드와 일치하고, 아래 이미지와 같이 [HttpPost] Index 메서드가 실행됩니다.

From HttpPost Index: filter on ghost 응답을 보여주는 브라우저 창

그러나 이 [HttpPost] 버전의 Index 메서드를 추가하는 경우에도 이를 모두 구현하는 방법은 제한됩니다. 특정 검색을 책갈피로 설정하거나 동일하게 필터링된 영화 목록을 보기 위해 클릭할 수 있는 링크를 친구에게 보내려고 한다고 가정합니다. HTTP POST 요청의 URL은 GET 요청의 URL(localhost:{PORT}/Movies/Index)과 동일합니다. URL에는 검색 정보가 없습니다. 검색 문자열 정보는 양식 필드 값으로 서버에 전송됩니다. 브라우저 개발자 도구 또는 뛰어난 Fiddler 도구에서 확인할 수 있습니다. 아래 이미지에서는 Chrome 브라우저 개발자 도구를 보여줍니다.

ghost라는 searchString 값을 가진 요청 본문을 보여주는 Microsoft Edge 개발자 도구의 네트워크 탭

요청 본문에서 검색 매개 변수 및 XSRF 토큰을 확인할 수 있습니다. 이전 자습서에서 설명한 것처럼 양식 태그 도우미는 XSRF 위조 방지 토큰을 생성합니다. 데이터를 수정하지 않으므로 컨트롤러 메서드에서 토큰 유효성을 검사할 필요도 없습니다.

검색 매개 변수가 URL이 아닌 요청 본문에 있기 때문에 책갈피에 해당 검색 정보를 캡처하거나 다른 사용자와 공유할 수 없습니다. 파일에 요청이 있어야 한다고 HTTP GET 지정하여 이 문제를 해결합니다 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">

이제 검색을 제출할 때 URL에는 검색 쿼리 문자열이 포함됩니다. HttpPost Index 메서드가 존재하더라도 검색은 HttpGet Index 작업 메서드로 이동합니다.

URL에서 searchString=ghost 및 Ghostbusters 및 Ghostbusters 2라는 두 개의 반환된 영화를 표시하는 브라우저 창은 단어 ghost를 포함합니다.

다음 표시는 form 태그에 대한 변경 내용을 표시합니다.

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

장르별 검색 추가

다음 MovieGenreViewModel 클래스를 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; }
}

영화 장르 보기 모델은 다음을 포함합니다.

  • 영화 목록.
  • 장르 목록을 포함한 SelectList 이를 이용해서 사용자는 목록에서 장르를 선택할 수 있습니다.
  • 선택한 장르가 포함된 MovieGenre
  • 사용자가 검색 텍스트 상자에 입력한 텍스트가 포함된 SearchString.

MoviesController.cs에서 Index 메서드를 다음 코드로 바꿉니다.

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

다음 코드는 데이터베이스에서 모든 장르를 검색하는 LINQ 쿼리입니다.

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

장르의 SelectList는 고유한 장르를 프로젝팅하여 생성됩니다(선택 목록에 중복된 장르가 포함되는 것을 원하지 않습니다).

사용자가 항목을 검색하면 검색 상자에 검색 값이 유지됩니다.

인덱스 보기에 장르별 검색 추가

Views/Movies/에 있는 Index.cshtml을 다음과 같이 업데이트합니다.

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

다음 HTML 도우미에서 사용되는 람다 식을 살펴봅니다.

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

이전 코드에서 DisplayNameFor HTML 도우미는 람다 식에서 참조되는 Title 속성을 검사하여 표시 이름을 확인합니다. 람다 식이 평가되지 않고 검사되기 때문에 model, model.Movies 또는 model.Movies[0]null이거나 비어 있더라도 액세스 위반을 수신하지 않습니다. 람다 식이 계산될 경우(예: @Html.DisplayFor(modelItem => item.Title)) 모델의 속성 값이 평가됩니다. model.Movies 다음 !은 null이 아닌 Movies을 선언 하는 데 사용되는 null 용서 연산자입니다.

장르로, 영화 제목으로, 그리고 두 가지 모두로 검색하여 앱을 테스트합니다.

의 https://localhost:5001/Movies?MovieGenre=Comedy&결과를 보여 주는 브라우저 창 SearchString=2

이 섹션에서는 장르 또는 이름별로 영화를 검색할 수 있는 Index 작업 메서드에 검색 기능을 추가합니다.

Index 다음 코드로 내부에 Controllers/MoviesController.cs 있는 메서드를 업데이트합니다.

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

Index 작업 메서드의 첫 번째 줄은 영화를 선택하는 LINQ 쿼리를 만듭니다.

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

이 시점에 쿼리는 정의되기만 했으며 데이터베이스에 대해 실행되지는 않았습니다.

searchString 매개 변수에 문자열이 담겨 있으면 해당 검색 문자열의 값으로 필터링하도록 영화 쿼리가 수정됩니다.

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

위의 s => s.Title!.Contains(searchString) 코드는 람다 식입니다. 람다는 메서드 기반 LINQ 쿼리에서 Where 또는 Contains 메서드(위의 코드에서 사용됨)와 같은 표준 쿼리 연산자 메서드의 인수로 사용됩니다. LINQ 쿼리는 정의될 때 또는 메서드(예: Where, Contains 또는 OrderBy)를 호출하여 수정될 때 실행되지 않습니다. 대신 쿼리 실행이 지연됩니다. 즉, 실제로 결정된 값이 반복되거나 ToListAsync 메서드가 호출될 때까지 식의 계산이 지연된다는 뜻입니다. 지연된 쿼리 실행에 대한 자세한 내용은 쿼리 실행을 참조하세요.

참고: 메서드는 Contains 위에 표시된 c# 코드가 아니라 데이터베이스에서 실행됩니다. 쿼리에 대한 대/소문자 구분은 데이터베이스 및 데이터 정렬에 따라 달라집니다. SQL Server에서 Contains는 대/소문자를 구분하는 SQL LIKE로 매핑됩니다. SQLite에서 기본 데이터 정렬과 함께 대/소문자를 구분합니다.

/Movies/Index으로 이동합니다. 쿼리 문자열(예: ?searchString=Ghost)을 URL에 추가합니다. 필터링된 동영상이 표시됩니다.

인덱스 보기

메서드의 Index 시그니처를 매개 변수로 id id변경하면 매개 변수가 설정된 Program.cs기본 경로에 대한 선택적 {id} 자리 표시자와 일치합니다.

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

매개 변수를 id로, searchString의 모든 항목을 id로 변경합니다.

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

id 매개 변수를 사용하는 수정된 Index 메서드는 다음과 같습니다.

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

이제 쿼리 문자열 값 대신 경로 데이터(URL 세그먼트)로 검색 제목을 전달할 수 있습니다.

Url에 추가된 단어 ghost와 두 개의 동영상, Ghostbusters 및 Ghostbusters 2의 반환된 동영상 목록이 있는 인덱스 보기

그러나 사용자가 영화를 검색하려고 할 때마다 URL을 수정하지는 않습니다. 따라서 이제 영화를 필터링하는 데 도움이 되는 UI 요소를 추가할 것입니다. Index 메서드의 서명을 변경하여 경로 바인딩 ID 매개 변수 전달 방법을 테스트하는 경우 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 파일을 열고 아래에 강조 표시된 태그를 추가 <form> 합니다.

@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> 태그는 Form 태그 도우미를 사용하므로 양식을 제출할 경우 필터 문자열은 영화 컨트롤러의 Index 작업에 게시됩니다. 변경 내용을 저장하고 필터를 테스트합니다.

Title 필터 텍스트 상자에 ghost라는 단어를 입력한 Index 보기

예상할 수 있는 것처럼 Index 메서드의 [HttpPost] 오버로드는 없습니다. 이 메서드는 앱의 상태를 변경하지 않고 데이터를 필터링하기만 하기 때문에 필요하지 않습니다.

다음 [HttpPost] Index 메서드를 추가할 수 있습니다.

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

notUsed 매개 변수는 Index 메서드의 오버로드를 만들기 위해서 사용됩니다. 자습서의 뒷부분에서 이에 대해 알아봅니다.

이 메서드를 추가하면 작업 호출자는 [HttpPost] Index 메서드와 일치하고, 아래 이미지와 같이 [HttpPost] Index 메서드가 실행됩니다.

From HttpPost Index: filter on ghost 응답을 보여주는 브라우저 창

그러나 이 [HttpPost] 버전의 Index 메서드를 추가하는 경우에도 이를 모두 구현하는 방법은 제한됩니다. 특정 검색을 책갈피로 설정하거나 동일하게 필터링된 영화 목록을 보기 위해 클릭할 수 있는 링크를 친구에게 보내려고 한다고 가정합니다. HTTP POST 요청의 URL은 GET 요청의 URL(localhost:{PORT}/Movies/Index)과 동일합니다. URL에는 검색 정보가 없습니다. 검색 문자열 정보는 양식 필드 값으로 서버에 전송됩니다. 브라우저 개발자 도구 또는 뛰어난 Fiddler 도구에서 확인할 수 있습니다. 아래 이미지에서는 Chrome 브라우저 개발자 도구를 보여줍니다.

ghost라는 searchString 값을 가진 요청 본문을 보여주는 Microsoft Edge 개발자 도구의 네트워크 탭

요청 본문에서 검색 매개 변수 및 XSRF 토큰을 확인할 수 있습니다. 이전 자습서에서 설명한 것처럼 양식 태그 도우미는 XSRF 위조 방지 토큰을 생성합니다. 데이터를 수정하지 않으므로 컨트롤러 메서드에서 토큰 유효성을 검사할 필요도 없습니다.

검색 매개 변수가 URL이 아닌 요청 본문에 있기 때문에 책갈피에 해당 검색 정보를 캡처하거나 다른 사용자와 공유할 수 없습니다. 파일에 요청이 있어야 한다고 HTTP GET 지정하여 이 문제를 해결합니다 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">

이제 검색을 제출할 때 URL에는 검색 쿼리 문자열이 포함됩니다. HttpPost Index 메서드가 존재하더라도 검색은 HttpGet Index 작업 메서드로 이동합니다.

URL에서 searchString=ghost 및 Ghostbusters 및 Ghostbusters 2라는 두 개의 반환된 영화를 표시하는 브라우저 창은 단어 ghost를 포함합니다.

다음 표시는 form 태그에 대한 변경 내용을 표시합니다.

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

장르별 검색 추가

다음 MovieGenreViewModel 클래스를 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; }
    }
}

영화 장르 보기 모델은 다음을 포함합니다.

  • 영화 목록.
  • 장르 목록을 포함한 SelectList 이를 이용해서 사용자는 목록에서 장르를 선택할 수 있습니다.
  • 선택한 장르가 포함된 MovieGenre
  • 사용자가 검색 텍스트 상자에 입력한 텍스트가 포함된 SearchString.

MoviesController.cs에서 Index 메서드를 다음 코드로 바꿉니다.

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

다음 코드는 데이터베이스에서 모든 장르를 검색하는 LINQ 쿼리입니다.

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

장르의 SelectList는 고유한 장르를 프로젝팅하여 생성됩니다(선택 목록에 중복된 장르가 포함되는 것을 원하지 않습니다).

사용자가 항목을 검색하면 검색 상자에 검색 값이 유지됩니다.

인덱스 보기에 장르별 검색 추가

Views/Movies/에 있는 Index.cshtml을 다음과 같이 업데이트합니다.

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

다음 HTML 도우미에서 사용되는 람다 식을 살펴봅니다.

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

이전 코드에서 DisplayNameFor HTML 도우미는 람다 식에서 참조되는 Title 속성을 검사하여 표시 이름을 확인합니다. 람다 식이 평가되지 않고 검사되기 때문에 model, model.Movies 또는 model.Movies[0]null이거나 비어 있더라도 액세스 위반을 수신하지 않습니다. 람다 식이 계산될 경우(예: @Html.DisplayFor(modelItem => item.Title)) 모델의 속성 값이 평가됩니다.

장르로, 영화 제목으로, 그리고 두 가지 모두로 검색하여 앱을 테스트합니다.

의 https://localhost:5001/Movies?MovieGenre=Comedy&결과를 보여 주는 브라우저 창 SearchString=2

이 섹션에서는 장르 또는 이름별로 영화를 검색할 수 있는 Index 작업 메서드에 검색 기능을 추가합니다.

Index 다음 코드로 내부에 Controllers/MoviesController.cs 있는 메서드를 업데이트합니다.

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

Index 작업 메서드의 첫 번째 줄은 영화를 선택하는 LINQ 쿼리를 만듭니다.

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

이 시점에 쿼리는 정의되기만 했으며 데이터베이스에 대해 실행되지는 않았습니다.

searchString 매개 변수에 문자열이 담겨 있으면 해당 검색 문자열의 값으로 필터링하도록 영화 쿼리가 수정됩니다.

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

위의 s => s.Title.Contains() 코드는 람다 식입니다. 람다는 메서드 기반 LINQ 쿼리에서 Where 또는 Contains 메서드(위의 코드에서 사용됨)와 같은 표준 쿼리 연산자 메서드의 인수로 사용됩니다. LINQ 쿼리는 정의될 때 또는 메서드(예: Where, Contains 또는 OrderBy)를 호출하여 수정될 때 실행되지 않습니다. 대신 쿼리 실행이 지연됩니다. 즉, 실제로 결정된 값이 반복되거나 ToListAsync 메서드가 호출될 때까지 식의 계산이 지연된다는 뜻입니다. 지연된 쿼리 실행에 대한 자세한 내용은 쿼리 실행을 참조하세요.

참고: 메서드는 Contains 위에 표시된 c# 코드가 아니라 데이터베이스에서 실행됩니다. 쿼리에 대한 대/소문자 구분은 데이터베이스 및 데이터 정렬에 따라 달라집니다. SQL Server에서 Contains는 대/소문자를 구분하는 SQL LIKE로 매핑됩니다. SQLite에서 기본 데이터 정렬과 함께 대/소문자를 구분합니다.

/Movies/Index으로 이동합니다. 쿼리 문자열(예: ?searchString=Ghost)을 URL에 추가합니다. 필터링된 동영상이 표시됩니다.

인덱스 보기

메서드의 Index 시그니처를 매개 변수로 id id변경하면 매개 변수가 설정된 Startup.cs기본 경로에 대한 선택적 {id} 자리 표시자와 일치합니다.

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

매개 변수를 id로, searchString의 모든 항목을 id로 변경합니다.

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

id 매개 변수를 사용하는 수정된 Index 메서드는 다음과 같습니다.

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

이제 쿼리 문자열 값 대신 경로 데이터(URL 세그먼트)로 검색 제목을 전달할 수 있습니다.

Url에 추가된 단어 ghost와 두 개의 동영상, Ghostbusters 및 Ghostbusters 2의 반환된 동영상 목록이 있는 인덱스 보기

그러나 사용자가 영화를 검색하려고 할 때마다 URL을 수정하지는 않습니다. 따라서 이제 영화를 필터링하는 데 도움이 되는 UI 요소를 추가할 것입니다. Index 메서드의 서명을 변경하여 경로 바인딩 ID 매개 변수 전달 방법을 테스트하는 경우 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 파일을 열고 아래에 강조 표시된 태그를 추가 <form> 합니다.

    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> 태그는 Form 태그 도우미를 사용하므로 양식을 제출할 경우 필터 문자열은 영화 컨트롤러의 Index 작업에 게시됩니다. 변경 내용을 저장하고 필터를 테스트합니다.

Title 필터 텍스트 상자에 ghost라는 단어를 입력한 Index 보기

예상할 수 있는 것처럼 Index 메서드의 [HttpPost] 오버로드는 없습니다. 이 메서드는 앱의 상태를 변경하지 않고 데이터를 필터링하기만 하기 때문에 필요하지 않습니다.

다음 [HttpPost] Index 메서드를 추가할 수 있습니다.

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

notUsed 매개 변수는 Index 메서드의 오버로드를 만들기 위해서 사용됩니다. 자습서의 뒷부분에서 이에 대해 알아봅니다.

이 메서드를 추가하면 작업 호출자는 [HttpPost] Index 메서드와 일치하고, 아래 이미지와 같이 [HttpPost] Index 메서드가 실행됩니다.

From HttpPost Index: filter on ghost 응답을 보여주는 브라우저 창

그러나 이 [HttpPost] 버전의 Index 메서드를 추가하는 경우에도 이를 모두 구현하는 방법은 제한됩니다. 특정 검색을 책갈피로 설정하거나 동일하게 필터링된 영화 목록을 보기 위해 클릭할 수 있는 링크를 친구에게 보내려고 한다고 가정합니다. HTTP POST 요청의 URL은 GET 요청의 URL(localhost:{PORT}/Movies/Index)과 동일합니다. URL에는 검색 정보가 없습니다. 검색 문자열 정보는 양식 필드 값으로 서버에 전송됩니다. 브라우저 개발자 도구 또는 뛰어난 Fiddler 도구에서 확인할 수 있습니다. 아래 이미지에서는 Chrome 브라우저 개발자 도구를 보여줍니다.

ghost라는 searchString 값을 가진 요청 본문을 보여주는 Microsoft Edge 개발자 도구의 네트워크 탭

요청 본문에서 검색 매개 변수 및 XSRF 토큰을 확인할 수 있습니다. 이전 자습서에서 설명한 것처럼 양식 태그 도우미는 XSRF 위조 방지 토큰을 생성합니다. 데이터를 수정하지 않으므로 컨트롤러 메서드에서 토큰 유효성을 검사할 필요도 없습니다.

검색 매개 변수가 URL이 아닌 요청 본문에 있기 때문에 책갈피에 해당 검색 정보를 캡처하거나 다른 사용자와 공유할 수 없습니다. 파일에 요청이 있어야 한다고 HTTP GET 지정하여 이 문제를 해결합니다 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)

이제 검색을 제출할 때 URL에는 검색 쿼리 문자열이 포함됩니다. HttpPost Index 메서드가 존재하더라도 검색은 HttpGet Index 작업 메서드로 이동합니다.

URL에서 searchString=ghost 및 Ghostbusters 및 Ghostbusters 2라는 두 개의 반환된 영화를 표시하는 브라우저 창은 단어 ghost를 포함합니다.

다음 표시는 form 태그에 대한 변경 내용을 표시합니다.

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

장르별 검색 추가

다음 MovieGenreViewModel 클래스를 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; }
    }
}

영화 장르 보기 모델은 다음을 포함합니다.

  • 영화 목록.
  • 장르 목록을 포함한 SelectList 이를 이용해서 사용자는 목록에서 장르를 선택할 수 있습니다.
  • 선택한 장르가 포함된 MovieGenre
  • 사용자가 검색 텍스트 상자에 입력한 텍스트가 포함된 SearchString.

MoviesController.cs에서 Index 메서드를 다음 코드로 바꿉니다.

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

다음 코드는 데이터베이스에서 모든 장르를 검색하는 LINQ 쿼리입니다.

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

장르의 SelectList는 고유한 장르를 프로젝팅하여 생성됩니다(선택 목록에 중복된 장르가 포함되는 것을 원하지 않습니다).

사용자가 항목을 검색하면 검색 상자에 검색 값이 유지됩니다.

인덱스 보기에 장르별 검색 추가

Views/Movies/에 있는 Index.cshtml을 다음과 같이 업데이트합니다.

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

다음 HTML 도우미에서 사용되는 람다 식을 살펴봅니다.

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

이전 코드에서 DisplayNameFor HTML 도우미는 람다 식에서 참조되는 Title 속성을 검사하여 표시 이름을 확인합니다. 람다 식이 평가되지 않고 검사되기 때문에 model, model.Movies 또는 model.Movies[0]null이거나 비어 있더라도 액세스 위반을 수신하지 않습니다. 람다 식이 계산될 경우(예: @Html.DisplayFor(modelItem => item.Title)) 모델의 속성 값이 평가됩니다.

장르로, 영화 제목으로, 그리고 두 가지 모두로 검색하여 앱을 테스트합니다.

의 https://localhost:5001/Movies?MovieGenre=Comedy&결과를 보여 주는 브라우저 창 SearchString=2