파트 8: 검색 기능 구현하기
- 본 번역문서의 원문은 Search www.asp.net 입니다.
- 본 번역문서는 ASP.NET MVC 5 (8) : 검색 기능 구현하기 www.taeyo.net 에서도 함께 제공됩니다.
이번 단계에서는 장르나 제목으로 영화를 검색할 수 있도록 Index
액션 메서드에 검색 기능을 추가해보겠습니다.
Index 메서드 및 Index 뷰 변경하기
먼저 MoviesController
클래스의 Index
액션 메서드 코드를 다음과 같이 변경합니다:
public ActionResult Index(string searchString) { var movies = from m in db.Movies select m; if (!String.IsNullOrEmpty(searchString)) { movies = movies.Where(s => s.Title.Contains(searchString)); } return View(movies); }
이 Index
메서드의 첫 번째 코드 부분은 다음과 같이 영화 정보들을 가져오기 위한 LINQ 쿼리를 생성합니다:
var movies = from m in db.Movies select m;
그러나 아직 이 시점까지는 쿼리가 단지 정의만 되었을 뿐, 실제로 데이터베이스를 대상으로 수행된 것은 아니라는 점에 주의하시기 바랍니다.
그런 다음, searchString
매개변수에 검색할 문자열이 담겨 있다면, 다음의 코드를 통해서 검색 문자열 값으로 필터링되도록 movies 쿼리를 변경합니다:
if (!String.IsNullOrEmpty(searchString)) { movies = movies.Where(s => s.Title.Contains(searchString)); }
이 예제 코드에 사용된 s => s.TitleTitle.Contains(searchString)
구문은 람다 식(Lambda Expression)입니다.
람다는 메서드 기반의 LINQ 쿼리에서 이 코드에 사용된 Where 메서드 같은 표준 쿼리 연산자 메서드의 매개변수로 사용됩니다.
LINQ 쿼리는 해당 쿼리를 정의하는 시점이나, Where
및 OrderBy
같은 메서드 호출에 의해서 쿼리가 변경되는 시점에는 실행되지 않습니다.
대신 쿼리의 실제 값이 루프문 등에서 구체적으로 접근되거나, ToList
같은 특정 메서드가 호출될 때까지 표현식의 평가가 미뤄지고 쿼리 실행이 지연됩니다.
가령 이번 예제의 경우, 쿼리가 실제로 실행되는 곳은 Index.cshtml 뷰입니다.
지연된 쿼리 실행(Deferred Query Execution)에 관한 보다 자세한 정보는 쿼리 실행(Query Execution) 문서를 참고하시기 바랍니다.
계속해서 이번에는 Index
뷰를 변경해서 사용자에게 검색 폼을 제공해보도록 하겠습니다.
그 전에, 먼저 예제 응용 프로그램을 실행하고 /Movies/Index 로 이동합니다.
그리고 주소 표시줄의 URL에 ?searchString=ghost
같은 형태의 쿼리 문자열을 추가해보면 필터링 된 영화 목록이 나타나는 것을 확인할 수 있을 것입니다.
한 단계 더 나가서 Index
메서드의 시그니처를 변경해서 매개변수의 이름을 id
로 바꾸면, App_Start\RouteConfig.cs 파일에 다음과 같이 정의된 기본 라우트 설정의 {id}
자리 표시자와 id
매개변수의 이름이 매치되게 됩니다.
{controller}/{action}/{id}
다시 정리해보면 이렇습니다.
우선 본문에서 최초로 변경했던 Index
메서드의 코드는 다음과 같았습니다:
public ActionResult Index(string searchString) { var movies = from m in db.Movies select m; if (!String.IsNullOrEmpty(searchString)) { movies = movies.Where(s => s.Title.Contains(searchString)); } return View(movies); }
이 Index
메서드의 코드를 다시 한 번 변경하면 다음과 비슷한 모습이 됩니다:
public ActionResult Index(string id) { string searchString = id; var movies = from m in db.Movies select m; if (!String.IsNullOrEmpty(searchString)) { movies = movies.Where(s => s.Title.Contains(searchString)); } return View(movies); }
메서드를 이렇게 변경하고 나면 검색할 제목을 쿼리 문자열 값 대신 라우트 데이터(URL 세그먼트)로 전달할 수 있게 됩니다.
그렇지만 그 어떤 개발자도 사용자들이 영화를 검색할 때마다 이렇게 매번 URL을 수정하길 바라지는 않을 것입니다.
따라서 이번에는 사용자들이 영화를 검색할 때 편리하게 사용할 수 있도록 검색용 UI를 추가해보겠습니다.
라우트 기반의 ID 매개변수 전달 방식을 살펴보기 위해서 방금 변경했던 Index
메서드의 시그니처를 다시 원래대로 되돌려서, 본래대로 Index
메서드가 searchString
문자열 매개변수를 받도록 변경합니다:
public ActionResult Index(string searchString) { var movies = from m in db.Movies select m; if (!String.IsNullOrEmpty(searchString)) { movies = movies.Where(s => s.Title.Contains(searchString)); } return View(movies); }
그런 다음, Views\Movies\Index.cshtml 파일을 열고 다음 코드에 강조된 것처럼 @Html.ActionLink("Create New", "Create")
라인 바로 뒤에 폼 마크업을 추가합니다:
@model IEnumerable<MvcMovie.Models.Movie> @{ ViewBag.Title = "Index"; } <h2>Index</h2> <p> @Html.ActionLink("Create New", "Create") @using (Html.BeginForm()) { <p> Title: @Html.TextBox("SearchString") <br /> <input type="submit" value="Filter" /></p> } </p>
이 코드에 사용된 Html.BeginForm
헬퍼 메서드는 여는 <form>
태그를 생성하고, 사용자가 Filter 버튼을 클릭해서 폼을 제출하면 현재 페이지로 폼이 재전송되도록 구성합니다.
참고로 Visual Studio 2013은 뷰 파일의 출력과 편집에 관련된 한 가지 멋진 개선된 기능을 제공해줍니다. 즉 뷰 파일을 열어 놓은 상태에서 응용 프로그램을 실행하면 Visual Studio 2013이 적절한 컨트롤러와 액션 메서드를 호출해서 브라우저에 해당 뷰를 바로 출력해줍니다.
바로 위의 그림에서 볼 수 있는 것처럼 Visual Studio에서 Index 뷰 파일을 열어 놓고, 컨트롤+F5 키나 F5 키를 눌러서 응용 프로그램을 실행한 다음, 영화를 검색해보시기 바랍니다.
이 Index
메서드에 대한 HttpPost
버전의 오버로드는 필요 없습니다.
이 메서드는 데이터를 필터링하기만 할 뿐이고, 응용 프로그램의 어떠한 상태도 변경하지 않기 때문입니다.
물론 그렇다고 해서 다음과 같은 HttpPost Index
메서드를 추가한다고 해서 크게 문제가 되는 것은 아닙니다.
그러면 액션 호출자(Action Invoker)가 HttpPost Index
메서드를 인식해서 실행하게 되고, 그 결과 아래 그림과 같은 결과가 나타날 것입니다.
[HttpPost] public string Index(FormCollection fc, string searchString) { return "<h3>From [HttpPost]Index: " + searchString + "</h3>"; }
그러나 비록 Index
메서드의 HttpPost
버전을 추가해서 코드까지 실제로 모두 구현하더라도 한계가 존재할 수 밖에는 없습니다.
가령, 특정 검색 결과를 북마크 해두거나, 친구에게 링크를 보내서 친구가 그 링크를 클릭했을 때, 필터링 된 동일한 영화 목록이 나타나기를 원한다고 상상해보십시오.
여기서 주목해야 할 점은 HTTP POST 요청의 URL이 기본적인 GET 요청의 URL(localhost:xxxxx/Movies/Index)과 동일하기 때문에, URL에 검색에 관한 정보가 전혀 포함되어 있지 않다는 부분입니다.
검색 문자열 정보가 폼 필드 값의 형태로 서버에 전달되기 때문입니다.
결과적으로 북마크나 친구에게 보낼 URL에 결코 검색 정보를 담을 수가 없다는 결론을 얻게 되는 셈입니다.
이 문제를 해결하려면 BeginForm
메서드의 다른 오버로드를 사용해서, URL에 검색 정보를 추가하고 요청을 Index
메서드의 HttpGet
버전으로 라우트되도록 만드는 것입니다.
매개변수가 지정되지 않은 기존의 BeginForm
메서드를 다음과 같은 마크업으로 변경합니다:
@using (Html.BeginForm("Index", "Movies", FormMethod.Get))
이제 다시 검색을 수행해보면 URL에 검색 쿼리 문자열이 포함되는 것을 확인할 수 있습니다.
이제는 HttpPost Index
메서드가 존재하더라도 HttpGet Index
액션 메서드가 실행됩니다.
장르 검색 추가하기
다음 과정을 진행하기 전에, 먼저 이전 절에서 Index
메서드의 HttpPost
버전을 추가했었다면 이를 제거합니다.
이번에는 사용자들이 장르로 영화 정보를 검색할 수 있는 기능을 추가해보겠습니다.
Index
메서드를 다음의 코드로 대체합니다:
public ActionResult Index(string movieGenre, string searchString) { var GenreLst = new List<string>(); var GenreQry = from d in db.Movies orderby d.Genre select d.Genre; GenreLst.AddRange(GenreQry.Distinct()); ViewBag.movieGenre = new SelectList(GenreLst); var movies = from m in db.Movies 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); } return View(movies); }
이번 버전의 Index
메서드는 movieGenre
라는 이름의 매개변수를 추가로 더 받습니다.
그리고 처음 몇 라인의 코드는 데이터베이스로부터 가져온 영화의 장르들을 저장하는 List
개체를 생성하기 위한 코드입니다.
우선 다음 코드는 데이터베이스에서 모든 장르들을 가져오는 LINQ 쿼리입니다.
var GenreQry = from d in db.Movies orderby d.Genre select d.Genre;
그런 다음, 제네릭 List
컬랙션의 AddRange
메서드를 이용해서 중복되지 않는 모든 장르들을 목록에 추가합니다.
(이 때, Distinct
수정자를 지정하지 않으면 중복된 장르들이 여러 번 추가될 것입니다.
예를 들어서 본문의 예제에서는 "Comedy" 장르가 반복됩니다.)
그리고 이 장르들의 목록을 ViewBag.movieGenre
개체에 저장하는데, MVC 응용 프로그램에서는 영화 장르 같은 카테고리 데이터를 이 예제 코드에서처럼 ViewBag
에 SelectList 개체로 저장해뒀다가, 뷰의 드롭다운 리스트 박스에서 다시 이 카테고리 데이터에 접근하는 것이 일반적인 활용 방법입니다.
다음 코드는 movieGenre
매개변수를 점검하는 방법을 보여주고 있습니다.
이 매개변수가 비어있지 않다면 쿼리에 지정된 장르로 검색된 영화 정보를 필터링하는 제약조건을 다시 한 번 추가합니다.
if (!string.IsNullOrEmpty(movieGenre)) { movies = movies.Where(x => x.Genre == movieGenre); }
이 쿼리는 이미 설명했던 것처럼 Index
액션 메서드가 반환된 이후, 뷰에서 영화들의 목록을 출력하기 위해서 값이 실제로 조회되기 전까지는 데이터베이스를 대상으로 실행되지 않습니다.
Index 뷰에 장르 검색 지원을 위한 마크업 추가하기
계속해서 Views\Movies\Index.cshtml 파일을 연 다음, TextBox
헬퍼 메서드를 호출하기 직전에 다음과 같은 Html.DropDownList
헬퍼 메서드 호출을 추가합니다.
이 작업을 마치고 나면 마크업은 다음과 같을 것입니다:
@model IEnumerable<MvcMovie.Models.Movie> @{ ViewBag.Title = "Index"; } <h2>Index</h2> <p> @Html.ActionLink("Create New", "Create") @using (Html.BeginForm("Index", "Movies", FormMethod.Get)) { <p> Genre: @Html.DropDownList("movieGenre", "All") Title: @Html.TextBox("SearchString") <input type="submit" value="Filter" /> </p> } </p> <table class="table">
이번에 새로 추가된 코드는 다음과 같습니다:
@Html.DropDownList("movieGenre", "All")
이 코드의 첫 번째 매개변수인 "movieGenre"는 DropDownList
헬퍼 메서드가 ViewBag
에서 IEnumerable<SelectListItem>
형식의 값을 조회하기 위해서 검색해야 할 키로, 이 ViewBag
데이터는 액션 메서드의 다음 코드를 통해서 채워진 것입니다:
public ActionResult Index(string movieGenre, string searchString) { var GenreLst = new List<string>(); var GenreQry = from d in db.Movies orderby d.Genre select d.Genre; GenreLst.AddRange(GenreQry.Distinct()); ViewBag.movieGenre = new SelectList(GenreLst); var movies = from m in db.Movies 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); } return View(movies); }
두 번째 매개변수인 "All"은 목록에서 미리 선택되어 있어야 할 항목을 지정합니다. 이 부분의 코드를 다음과 같이 변경할 수도 있습니다:
@Html.DropDownList("movieGenre", "Comedy")
그러면 데이터베이스에 장르가 "Comedy"인 영화들의 정보가 존재하므로, 드롭다운 목록에서 "Comedy" 항목이 미리 선택된 상태로 출력될 것입니다.
반면, 장르가 "All"인 영화는 존재하지 않기 때문에 SelectList
개체에도 "All"에 대한 항목은 존재하지 않으며, 따라서 다른 장르를 선택하지 않고 그대로 재전송을 수행하면 movieGenre
쿼리 문자열 값이 비어 있는 상태가 됩니다.
DropDownList
헬퍼 메서드의 두 번째 인자가 문자열 형식인 경우, 그 값은 자동으로 추가되는 비어 있는 기본 항목에 대한 문구를 나타냅니다
(참고: DropDownList(HtmlHelper, String, String).
즉, 본문의 설명대로 코드를 변경하면 "Comedy"인 항목이 두 번 나타나게 됩니다.
따라서 본문의 원래 의도처럼 "Comedy" 항목이 기본으로 선택되도록 만들려면, Index 액션 메서드에서 ViewBag
에 데이터를 설정하는 부분의 코드를 다음과 같이 수정해야 합니다.
ViewBag.movieGenre = new SelectList(GenreLst, "Comedy");
다시 응용 프로그램을 실행하고 /Movies/Index로 이동한 다음, 장르 검색이나 영화 제목 검색, 그리고 두 가지 모두를 이용한 검색을 테스트 해보십시요.
본문에서는 사용자가 제목과 장르를 이용해서 영화를 검색할 수 있도록 Index 액션 메서드와 Index 뷰를 수정해봤습니다.
다음 단계에서는 Movie
모델에 속성을 추가하는 방법과 자동으로 테스트 데이터베이스를 생성해주는 이니셜라이저(Initializer)의 추가 방법을 살펴보도록 하겠습니다.
이 기사는 2013년 10월 17일에 최초 작성되었습니다.
- 파트 1: ASP.NET MVC 5 시작하기 2015-04-13 08:00
- 파트 2: 컨트롤러 추가하기 2015-04-20 08:00
- 파트 3: 뷰 추가하기 2015-04-27 08:00
- 파트 4: 모델 추가하기 2015-05-04 08:00
- 파트 5: 연결 문자열 생성 및 SQL Server LocalDB 구성하기 2015-05-11 08:00
- 파트 6: 컨트롤러에서 모델 데이터에 접근하기 2015-05-18 08:00
- 파트 7: Edit 메서드와 Edit 뷰 살펴보기 2015-05-25 08:00
- 파트 8: 검색 기능 구현하기 2015-06-01 08:00
- 파트 9: 새로운 필드 추가하기 2015-06-08 08:00
- 파트 10: 유효성 검사 추가하기 2015-06-15 08:00
- 파트 11: Details 메서드 및 Delete 메서드 살펴보기 2015-06-22 08:00