Adding Search to an ASP.NET Core MVC app 給程式添加搜索功能 2017-3-7 7 分鐘閱讀時長 作者 本文內容 1.Adding Search by genre 根據音樂流派添加搜索 2.Adding search by genre to the Index ...
Adding Search to an ASP.NET Core MVC app
給程式添加搜索功能
2017-3-7 7 分鐘閱讀時長 作者
本文內容
根據音樂流派添加搜索
2.Adding search by genre to the Index view
在 Index 視圖添加音樂流派搜索功能
In this section you add search capability to the Index action method that lets you search movies by genre or name.
在本節我們將給 Index 方法添加搜索功能,可以搜索音樂流派或音樂的名字。
Update the Index method with the following code:
用下麵的代碼更新 Index 方法:
1 public async Task<IActionResult> Index(string searchString) 2 3 { 4 5 var movies = from m in _context.Movie 6 7 select m; 8 9 10 11 if (!String.IsNullOrEmpty(searchString)) 12 13 { 14 15 movies = movies.Where(s => s.Title.Contains(searchString)); 16 17 } 18 19 20 21 return View(await movies.ToListAsync()); 22 23 }C# code
The first line of the Index action method creates a LINQ query to select the movies:
Index 方法的第一行寫了一個 LINQ 查詢表達式,以查詢出movie數據:
1 var movies = from m in _context.Movie 2 3 select m;C# code
The query is only defined at this point, it has not been run against the database.
這個查詢表達式僅僅在此刻定義了,他是惰性的,不會向資料庫請求執行。
If the searchString parameter contains a string, the movies query is modified to filter on the value of the search string:
searchString 參數包含了一個字元串,movie查詢會去根據這個搜索字元串過濾查詢數據:
1 if (!String.IsNullOrEmpty(id)) 2 3 { 4 5 movies = movies.Where(s => s.Title.Contains(id)); 6 7 }C# code
The s => s.Title.Contains() code above is a Lambda Expression.
上面的 s => s.Title.Contains() 是 Lambda 表達式。
Lambdas are used in method-based LINQqueries as arguments to standard query operator methods such as the Where method or Contains (used in the code above).
Lambda 表達式被用於基於函數為參數的標準操作函數,如: Where 方法或上面的 Contains 方法。
LINQ queries are not executed when they are defined or when they are modified by calling a method such as Where, Contains or OrderBy.
LINQ 查詢表達式在定義的時候不會執行,當他們被 Where, Contains or OrderBy 修改時也不會執行。
Rather, query execution is deferred.
查詢的執行是被延遲的,它是惰性執行的。
That means that the evaluation of an expression is delayed until its realized value is actually iterated over or the ToListAsyncmethod is called.
意思就是說,表達式的求值會被延遲到真正的去遍歷或者調用了ToListAsync 方法,才會開始計算表達式的值。
For more information about deferred query execution, see Query Execution.
查看 Query Execution 以獲取更多關於表達式延遲執行的信息。
Note: The Contains method is run on the database, not in the c# code shown above.
筆記: Contains 方法會被翻譯為sql併在DB中執行,而不是在C#中調用執行。
The case sensitivity on the query depends on the database and the collation.
查詢表達式是依賴於集合或者DB對象的。
On SQL Server, Contains maps to SQL LIKE, which is case insensitive.
在 SQL Server 上,Contains 方法會被智能的映射為 SQL LIKE 。
In SQLlite, with the default collation, it's case sensitive.
在 SQLlite 中,他會智能的預設矯正。
Navigate to /Movies/Index.
在地址欄導航到 /Movies/Index 。
Append a query string such as ?searchString=Ghost to the URL.
在URL後追加一段像 ?searchString=Ghost 的查詢字元串。
The filtered movies are displayed.
過濾後的movie數據就顯示出來了。
If you change the signature of the Index method to have a parameter named id,
如果你更改 Index 的方法簽名,使其參數名字為 id ,
the id parameter will match the optional {id} placeholder for the default routes set in Startup.cs.
id 參數將會預設匹配 Startup.cs 文件中的 {id} 可選項參數的占位符。
1 app.UseMvc(routes => 2 3 { 4 5 routes.MapRoute( 6 7 name: "default", 8 9 template: "{controller=Home}/{action=Index}/{id?}"); 10 11 });C# code
You can quickly rename the searchString parameter to id with the rename command.
你可以使用 rename 命令快速的重命名 searchString 參數為 id 。
Right click on searchString > Rename.
右擊,並選擇 > Rename 菜單。
The rename targets are highlighted.
需要重命名的目標將會全部高亮顯示,如下:
Change the parameter to id and all occurrences of searchString change to id.
改變參數名為 id ,下麵所有出現的 searchString 都會被命名為 id 。
The previous Index method:
前面的 Index 方法,如下:
1 public async Task<IActionResult> Index(string searchString) 2 3 { 4 5 var movies = from m in _context.Movie 6 7 select m; 8 9 10 11 if (!String.IsNullOrEmpty(searchString)) 12 13 { 14 15 movies = movies.Where(s => s.Title.Contains(searchString)); 16 17 } 18 19 20 21 return View(await movies.ToListAsync()); 22 23 }C# code
The updated Index method with id parameter:
更新後的 Index 方法將帶有名為 id 的參數,如下:
1 public async Task<IActionResult> Index(string id) 2 3 { 4 5 var movies = from m in _context.Movie 6 7 select m; 8 9 10 11 if (!String.IsNullOrEmpty(id)) 12 13 { 14 15 movies = movies.Where(s => s.Title.Contains(id)); 16 17 } 18 19 20 21 return View(await movies.ToListAsync()); 22 23 }C# code
You can now pass the search title as route data (a URL segment) instead of as a query string value.
你現在就可以將搜索標題的字元串做為路由數據的一部分而不是一個查詢字元串使用了。
However, you can't expect users to modify the URL every time they want to search for a movie.
然而,你不會期望用戶每次去更改URL,當他們搜索他們想要的電影的時候。
So now you'll add UI to help them filter movies.
因此,你需要增加UI來幫助他們過濾想要的movies。
If you changed the signature of the Index method to test how to pass the route-bound ID parameter, change it back so that it takes a parameter named searchString:
如果你更改了 Index 方法的簽名來測試路由數據綁定,現在把他改回來,如下:
1 public async Task<IActionResult> Index(string searchString) 2 3 { 4 5 var movies = from m in _context.Movie 6 7 select m; 8 9 10 11 if (!String.IsNullOrEmpty(searchString)) 12 13 { 14 15 movies = movies.Where(s => s.Title.Contains(searchString)); 16 17 } 18 19 20 21 return View(await movies.ToListAsync()); 22 23 }C# code
Open the Views/Movies/Index.cshtml file, and add the <form> markup highlighted below:
打開 Views/Movies/Index.cshtml 文件,並且添加 <form> 標簽,如下麵高亮部分所示:
1 ViewData["Title"] = "Index"; 2 3 } 4 5 6 7 <h2>Index</h2> 8 9 10 11 <p> 12 13 <a asp-action="Create">Create New</a> 14 15 </p> 16 17 18 19 <form asp-controller="Movies" asp-action="Index"> 20 21 <p> 22 23 Title: <input type="text" name="SearchString"> 24 25 <input type="submit" value="Filter" /> 26 27 </p> 28 29 </form> 30 31 32 33 <table class="table"> 34 35 <thead>HTML Code
The HTML <form> tag uses the Form Tag Helper, so when you submit the form, the filter string is posted to the Index action of the movies controller.
<form> 標簽使用了 Form Tag Helper ,因此當你提交表單時,過濾字元串被提交到了Index 方法。
Save your changes and then test the filter.
保存你的修改,然後測試搜索功能。
There's no [HttpPost] overload of the Index method as you might expect.
正如你所料,在 Index 方法上沒有 [HttpPost] 特征標記類。
You don't need it, because the method isn't changing the state of the app, just filtering data.
你不需要他,因為這個方法不會變更應用的然和狀態,僅僅是查詢了一些數據。
You could add the following [HttpPost] Index method.
你也可以添加 [HttpPost] ,如下的 Index 方法:
1 [HttpPost] 2 3 public string Index(string searchString, bool notUsed) 4 5 { 6 7 return "From [HttpPost]Index: filter on " + searchString; 8 9 }C# code
The notUsed parameter is used to create an overload for the Index method.
notUsed 參數被用於創建了一個重載的 Index 方法。
We'll talk about that later in the tutorial.
我們將在後面的教程中對它進行講解。
If you add this method, the action invoker would match the [HttpPost] Index method, and the [HttpPost] Index method would run as shown in the image below.
如果你添加這個方法,action 調用器將會匹配 [HttpPost] Index 方法,並且 [HttpPost] Index 將會執行並返回如下圖所示信息:
However, even if you add this [HttpPost] version of the Index method, there's a limitation in how this has all been implemented.
然而,即使你添加了 [HttpPost] 版本的 Index 方法,這也有一些限制,就是你要怎麼來實現。
Imagine that you want to bookmark a particular search or you want to send a link to friends that they can click in order to see the same filtered list of movies.
猜想你想標記一些詳細的搜索,或者你想給朋友發送一個連接,這個連接可以讓他們看到和你一樣的movies檢索結果。
Notice that the URL for the HTTP POST request is the same as the URL for the GET request (localhost:xxxxx/Movies/Index) -- there's no search information in the URL.
註意到 HTTP POST 請求的URL與GET請求的URL完全相同,在URL上沒有檢索字元串的數據。
The search string information is sent to the server as a form field value.
檢索用的字元串被做為表單欄位上的值傳遞給伺服器。
You can verify that with the browser Developer tools or the excellent Fiddler tool.
你可以用瀏覽器開發者工具來證實。
The image below shows the Chrome browser Developer tools:
下圖展示了 Chrome 瀏覽器的開發者工具:
You can see the search parameter and XSRF token in the request body.
你可以在請求體中看到 搜索參數 與 XSRF 令牌。
Note, as mentioned in the previous tutorial, the Form Tag Helper generates an XSRF anti-forgery token.
註意,如前邊兒教程提到的一樣,是 Form Tag Helper 生成了 XSRF 防偽造令牌。
We're not modifying data, so we don't need to validate the token in the controller method.
我們不去修改數據,因此也不需要在控制器中驗證令牌。
Because the search parameter is in the request body and not the URL, you can't capture that search information to bookmark or share with others.
因為查詢參數在請求體中,而不是在URL中,所以你無法捕獲查詢信息添加書簽或分享給其他人。
We'll fix this by specifying the request should be HTTP GET.
我們修複這點只需要指定請求形式為 HTTP GET 即可。
Notice how intelliSense helps us update the markup.
註意vs的智能感知如何幫助我們更新html標記。
Notice the distinctive font in the <form> tag.
註意在 <form> 標簽中的不用的字體顏色。
That distinctive font indicates the tag is supported by Tag Helpers.
不同的字體顏色指明瞭哪些受 Tag Helpers 支持。
Now when you submit a search, the URL contains the search query string.
現在,當你提交一個查詢的時候,URL中就在查詢字元串中包含了查詢參數。
Searching will also go to the HttpGet Index action method, even if you have a HttpPost Index method.
查詢將會直接調用 HttpGet Index ,即使已經存在了一個 HttpPost Index 方法。
The following markup shows the change to the form tag:
下麵的標記展示了 form 標簽的變更:
1 <form asp-controller="Movies" asp-action="Index" method="get">HTML Code
Adding Search by genre
添加根據流派進行搜索的功能
Add the following MovieGenreViewModel class to the Models folder:
在 Models 文件夾下添加 MovieGenreViewModel 類:
1 using Microsoft.AspNetCore.Mvc.Rendering; 2 3 using System.Collections.Generic; 4 5 6 7 namespace MvcMovie.Models 8 9 { 10 11 public class MovieGenreViewModel 12 13 { 14 15 public List<Movie> movies; 16 17 public SelectList genres; 18 19 public string movieGenre { get; set; } 20 21 } 22 23 }C# Code
The movie-genre view model will contain:
movie-genre 視圖將包含:
- A list of movies.
一個電影列表。
- A SelectList containing the list of genres. This will allow the user to select a genre from the list.
SelectList 將包含一系列流派,這將使用戶可以在其中選取流派。
- movieGenre, which contains the selected genre.
movieGenre ,它包含了被選擇的流派。
Replace the Index method in MoviesController.cs with the following code:
使用下麵的代碼替換到 MoviesController.cs 文件中的 Index 方法中:
1 // Requires using Microsoft.AspNetCore.Mvc.Rendering; 2 3 public async Task<IActionResult> Index(string movieGenre, string searchString) 4 5 { 6 7 // Use LINQ to get list of genres. 8 9 IQueryable<string> genreQuery = from m in _context.Movie 10 11 orderby m.Genre 12 13 select m.Genre; 14 15 16 17 var movies = from m in _context.Movie 18 19 select m; 20 21 22 23 if (!String.IsNullOrEmpty(searchString)) 24 25 { 26 27 movies = movies.Where(s => s.Title.Contains(searchString)); 28 29 } 30 31 32 33 if (!String.IsNullOrEmpty(movieGenre)) 34 35 { 36 37 movies = movies.Where(x => x.Genre == movieGenre); 38 39 } 40 41 42 43 var movieGenreVM = new MovieGenreViewModel(); 44 45 movieGenreVM.genres = new SelectList(await genreQuery.Distinct().ToListAsync()); 46 47 movieGenreVM.movies = await movies.ToListAsync(); 48 49 50 51 return View(movieGenreVM); 52 53 }C# Code
The following code is a LINQ query that retrieves all the genres from the database.
下麵是一個 LINQ 查詢,他檢索了資料庫中的所有流派:
1 // Use LINQ to get list of genres. 2 3 IQueryable<string> genreQuery = from m in _context.Movie 4 5 orderby m.Genre 6 7 select m.Genre;C# Code
The SelectList of genres is created by projecting the distinct genres (we don't want our select list to have duplicate genres).
SelectList 由工程創建並用來給流派去重:
1 movieGenreVM.genres = new SelectList(await genreQuery.Distinct().ToListAsync())C# Code
Adding search by genre to the Index view
將流派查詢添加到 Index 視圖上
Update Index.cshtml as follows:
用下麵代碼 更新 Index.cshtml 文件:
1 @model MvcMovie.Models.MovieGenreViewModel 2 3 4 5 @{ 6 7 ViewData["Title"] = "Index"; 8 9 } 10 11 12 13 <h2>Index</h2> 14 15 16 17 <p> 18 19 <a asp-action="Create">Create New</a> 20 21 </p> 22 23 24 25 <form asp-controller="Movies" asp-action="Index" method="get"> 26 27 <p> 28 29 <select asp-for="movieGenre" asp-items="Model.genres"> 30 31 <option value="">All</option> 32 33 </select> 34 35 36 37 Title: <input type="text" name="SearchString"> 38 39 <input type="submit" value="Filter" /> 40 41 </p> 42 43 </form> 44 45 46 47 <table class="table"> 48 49 <thead> 50 51 <tr> 52 53 <th> 54 55 @Html.DisplayNameFor(model => model.movies[0].Title) 56 57 </th> 58 59 <th> 60 61 @Html.DisplayNameFor(model => model.movies[0].ReleaseDate) 62 63 </th> 64 65 <th> 66 67 @Html.DisplayNameFor(model => model.movies[0].Genre) 68 69 </th> 70 71 <th> 72 73 @Html.DisplayNameFor(model => model.movies[0].Price) 74 75 </th> 76 77 <th></th> 78 79 </tr> 80 81 </thead> 82