第 7 部分,將搜尋新增至 ASP.NET Core MVC 應用程式
注意
這不是這篇文章的最新版本。 如需目前的版本,請參閱 本文的 .NET 9 版本。
警告
不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 如需目前的版本,請參閱 本文的 .NET 9 版本。
在本節中,您會將搜尋功能新增至 Index
動作方法,讓您依據「內容類型」或「名稱」搜尋電影。
使用下列程式碼更新 Controllers/MoviesController.cs
內部找到的 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());
}
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())
程式碼是 Lambda 運算式。 在以方法為基礎的 LINQ 查詢中,Lambda 會作為引數用於標準查詢運算子方法,例如 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。 因此,現在您將新增可協助他們篩選電影的 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>
標記使用表單標記協助程式,因此當您提交表單時,篩選條件字串會張貼至電影控制器的 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
方法會如下列影像所示執行。
不過,即使您新增這個 [HttpPost]
版本的 Index
方法,在如何全部實作此方法方面仍然有其限制。 假設您想要將特定的搜尋加為書籤,或者想要傳送連結給朋友,讓他們可以點選來查看相同的電影篩選清單。 請注意,HTTP POST 與 GET 要求的 URL (localhost:{PORT}/Movies/Index) 相同 -- 在 URL 中沒有搜尋資訊。 搜尋字串資訊會以表單欄位值的形式傳送至伺服器。 您可以使用瀏覽器開發人員工具或絕佳的 Fiddler 工具來進行確認。
下圖會顯示選取 [網络] 和 [頁首] 索引標籤的 Chrome 瀏覽器開發人員工具:
會選取 [網络] 和 [承載] 索引標籤來檢視表單資料:
您可以在要求本文中看到搜尋參數和 XSRF 語彙基元。 請注意,如先前的教學課程所述,表單標記協助程式會產生 XSRF 防偽權杖。 我們不會修改資料,因此不需要驗證控制器方法中的語彙基元。
由於搜尋參數是在要求本文而不是 URL 中,因此您無法擷取該搜尋資訊以加為書籤或與其他人共用。 藉由指定要求應為 Views/Movies/Index.cshtml
檔案中找到的 form
標記 中的 HTTP GET
來修正此問題。
@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
動作方法。
新增依內容類型搜尋
將下列 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
(我們不希望選取清單中有重複的內容類型)。
當使用者搜尋項目時,搜尋的值會保留在搜尋方塊中。
將依內容類型搜尋新增至 Index 檢視
更新在 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 協助程式中使用的 Lambda 運算式:
@Html.DisplayNameFor(model => model.Movies![0].Title)
在上述程式碼中,DisplayNameFor
HTML 協助程式會檢查 Lambda 運算式中參考的 Title
屬性來判斷顯示名稱。 由於 Lambda 運算式是檢查而不是評估,因此您在 model
、model.Movies
或 model.Movies[0]
是 null
或空白時,不會收到存取違規。 在評估 Lambda 運算式時 (例如,@Html.DisplayFor(modelItem => item.Title)
),會評估模型的屬性值。 model.Movies
後面的 !
是 Null 容許運算子,用來宣告 Movies
不是 Null。
依據內容類型、電影標題和這兩者進行搜尋,藉以測試應用程式:
在本節中,您會將搜尋功能新增至 Index
動作方法,讓您依據「內容類型」或「名稱」搜尋電影。
使用下列程式碼更新 Controllers/MoviesController.cs
內部找到的 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());
}
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())
程式碼是 Lambda 運算式。 在以方法為基礎的 LINQ 查詢中,Lambda 會作為引數用於標準查詢運算子方法,例如 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。 因此,現在您將新增可協助他們篩選電影的 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>
標記使用表單標記協助程式,因此當您提交表單時,篩選條件字串會張貼至電影控制器的 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
方法會如下列影像所示執行。
不過,即使您新增這個 [HttpPost]
版本的 Index
方法,在如何全部實作此方法方面仍然有其限制。 假設您想要將特定的搜尋加為書籤,或者想要傳送連結給朋友,讓他們可以點選來查看相同的電影篩選清單。 請注意,HTTP POST 與 GET 要求的 URL (localhost:{PORT}/Movies/Index) 相同 -- 在 URL 中沒有搜尋資訊。 搜尋字串資訊會以表單欄位值的形式傳送至伺服器。 您可以使用瀏覽器開發人員工具或絕佳的 Fiddler 工具來進行確認。 下圖顯示 Chrome 瀏覽器開發人員工具:
您可以在要求本文中看到搜尋參數和 XSRF 語彙基元。 請注意,如先前的教學課程所述,表單標記協助程式會產生 XSRF 防偽權杖。 我們不會修改資料,因此不需要驗證控制器方法中的語彙基元。
由於搜尋參數是在要求本文而不是 URL 中,因此您無法擷取該搜尋資訊以加為書籤或與其他人共用。 藉由指定要求應為 Views/Movies/Index.cshtml
檔案中找到的 HTTP GET
來修正此問題。
@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
動作方法。
下列標記顯示 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
(我們不希望選取清單中有重複的內容類型)。
當使用者搜尋項目時,搜尋的值會保留在搜尋方塊中。
將依內容類型搜尋新增至 Index 檢視
更新在 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 協助程式中使用的 Lambda 運算式:
@Html.DisplayNameFor(model => model.Movies![0].Title)
在上述程式碼中,DisplayNameFor
HTML 協助程式會檢查 Lambda 運算式中參考的 Title
屬性來判斷顯示名稱。 由於 Lambda 運算式是檢查而不是評估,因此您在 model
、model.Movies
或 model.Movies[0]
是 null
或空白時,不會收到存取違規。 在評估 Lambda 運算式時 (例如,@Html.DisplayFor(modelItem => item.Title)
),會評估模型的屬性值。 model.Movies
後面的 !
是 Null 容許運算子,用來宣告 Movies
不是 Null。
依據內容類型、電影標題和這兩者進行搜尋,藉以測試應用程式:
在本節中,您會將搜尋功能新增至 Index
動作方法,讓您依據「內容類型」或「名稱」搜尋電影。
使用下列程式碼更新 Controllers/MoviesController.cs
內部找到的 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());
}
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())
程式碼是 Lambda 運算式。 在以方法為基礎的 LINQ 查詢中,Lambda 會作為引數用於標準查詢運算子方法,例如 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。 因此,現在您將新增可協助他們篩選電影的 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>
標記使用表單標記協助程式,因此當您提交表單時,篩選條件字串會張貼至電影控制器的 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
方法會如下列影像所示執行。
不過,即使您新增這個 [HttpPost]
版本的 Index
方法,在如何全部實作此方法方面仍然有其限制。 假設您想要將特定的搜尋加為書籤,或者想要傳送連結給朋友,讓他們可以點選來查看相同的電影篩選清單。 請注意,HTTP POST 與 GET 要求的 URL (localhost:{PORT}/Movies/Index) 相同 -- 在 URL 中沒有搜尋資訊。 搜尋字串資訊會以表單欄位值的形式傳送至伺服器。 您可以使用瀏覽器開發人員工具或絕佳的 Fiddler 工具來進行確認。 下圖顯示 Chrome 瀏覽器開發人員工具:
您可以在要求本文中看到搜尋參數和 XSRF 語彙基元。 請注意,如先前的教學課程所述,表單標記協助程式會產生 XSRF 防偽權杖。 我們不會修改資料,因此不需要驗證控制器方法中的語彙基元。
由於搜尋參數是在要求本文而不是 URL 中,因此您無法擷取該搜尋資訊以加為書籤或與其他人共用。 藉由指定要求應為 Views/Movies/Index.cshtml
檔案中找到的 HTTP GET
來修正此問題。
@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
動作方法。
下列標記顯示 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
(我們不希望選取清單中有重複的內容類型)。
當使用者搜尋項目時,搜尋的值會保留在搜尋方塊中。
將依內容類型搜尋新增至 Index 檢視
更新在 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 協助程式中使用的 Lambda 運算式:
@Html.DisplayNameFor(model => model.Movies![0].Title)
在上述程式碼中,DisplayNameFor
HTML 協助程式會檢查 Lambda 運算式中參考的 Title
屬性來判斷顯示名稱。 由於 Lambda 運算式是檢查而不是評估,因此您在 model
、model.Movies
或 model.Movies[0]
是 null
或空白時,不會收到存取違規。 在評估 Lambda 運算式時 (例如,@Html.DisplayFor(modelItem => item.Title)
),會評估模型的屬性值。 model.Movies
後面的 !
是 Null 容許運算子,用來宣告 Movies
不是 Null。
依據內容類型、電影標題和這兩者進行搜尋,藉以測試應用程式:
在本節中,您會將搜尋功能新增至 Index
動作方法,讓您依據「內容類型」或「名稱」搜尋電影。
使用下列程式碼更新 Controllers/MoviesController.cs
內部找到的 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());
}
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)
程式碼是 Lambda 運算式。 在以方法為基礎的 LINQ 查詢中,Lambda 會作為引數用於標準查詢運算子方法,例如 Where 方法或 Contains
(用於上述程式碼)。 定義 LINQ 查詢或藉由呼叫像是 Where
、Contains
或 OrderBy
等方法進行修改時,並不會執行查詢。 而是會延後查詢執行。 這是指延遲評估運算式,直到實際反覆運算其實現值或呼叫 ToListAsync
方法為止。 如需延後查詢執行的詳細資訊,請參閱查詢執行。
注意:Contains 方法是在資料庫上執行,而不是在上述 C# 程式碼中執行。 查詢的區分大小寫取決於資料庫和定序。 在 SQL Server 上,Contains
對應至 SQL LIKE,因此不區分大小寫。 而在 SQLlite 中,由於使用預設定序,因此會區分大小寫。
瀏覽至 /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。 因此,現在您將新增可協助他們篩選電影的 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>
標記使用表單標記協助程式,因此當您提交表單時,篩選條件字串會張貼至電影控制器的 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
方法會如下列影像所示執行。
不過,即使您新增這個 [HttpPost]
版本的 Index
方法,在如何全部實作此方法方面仍然有其限制。 假設您想要將特定的搜尋加為書籤,或者想要傳送連結給朋友,讓他們可以點選來查看相同的電影篩選清單。 請注意,HTTP POST 與 GET 要求的 URL (localhost:{PORT}/Movies/Index) 相同 -- 在 URL 中沒有搜尋資訊。 搜尋字串資訊會以表單欄位值的形式傳送至伺服器。 您可以使用瀏覽器開發人員工具或絕佳的 Fiddler 工具來進行確認。 下圖顯示 Chrome 瀏覽器開發人員工具:
您可以在要求本文中看到搜尋參數和 XSRF 語彙基元。 請注意,如先前的教學課程所述,表單標記協助程式會產生 XSRF 防偽權杖。 我們不會修改資料,因此不需要驗證控制器方法中的語彙基元。
由於搜尋參數是在要求本文而不是 URL 中,因此您無法擷取該搜尋資訊以加為書籤或與其他人共用。 藉由指定要求應為 Views/Movies/Index.cshtml
檔案中找到的 HTTP GET
來修正此問題。
@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
動作方法。
下列標記顯示 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
(我們不希望選取清單中有重複的內容類型)。
當使用者搜尋項目時,搜尋的值會保留在搜尋方塊中。
將依內容類型搜尋新增至 Index 檢視
更新在 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 協助程式中使用的 Lambda 運算式:
@Html.DisplayNameFor(model => model.Movies[0].Title)
在上述程式碼中,DisplayNameFor
HTML 協助程式會檢查 Lambda 運算式中參考的 Title
屬性來判斷顯示名稱。 由於 Lambda 運算式是檢查而不是評估,因此您在 model
、model.Movies
或 model.Movies[0]
是 null
或空白時,不會收到存取違規。 在評估 Lambda 運算式時 (例如,@Html.DisplayFor(modelItem => item.Title)
),會評估模型的屬性值。
依據內容類型、電影標題和這兩者進行搜尋,藉以測試應用程式:
在本節中,您會將搜尋功能新增至 Index
動作方法,讓您依據「內容類型」或「名稱」搜尋電影。
使用下列程式碼更新 Controllers/MoviesController.cs
內部找到的 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());
}
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()
程式碼是 Lambda 運算式。 在以方法為基礎的 LINQ 查詢中,Lambda 會作為引數用於標準查詢運算子方法,例如 Where 方法或 Contains
(用於上述程式碼)。 定義 LINQ 查詢或藉由呼叫像是 Where
、Contains
或 OrderBy
等方法進行修改時,並不會執行查詢。 而是會延後查詢執行。 這是指延遲評估運算式,直到實際反覆運算其實現值或呼叫 ToListAsync
方法為止。 如需延後查詢執行的詳細資訊,請參閱查詢執行。
注意:Contains 方法是在資料庫上執行,而不是在上述 C# 程式碼中執行。 查詢的區分大小寫取決於資料庫和定序。 在 SQL Server 上,Contains 對應至 SQL LIKE,因此不區分大小寫。 而在 SQLlite 中,由於使用預設定序,因此會區分大小寫。
瀏覽至 /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。 因此,現在您將新增可協助他們篩選電影的 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>
標記使用表單標記協助程式,因此當您提交表單時,篩選條件字串會張貼至電影控制器的 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
方法會如下列影像所示執行。
不過,即使您新增這個 [HttpPost]
版本的 Index
方法,在如何全部實作此方法方面仍然有其限制。 假設您想要將特定的搜尋加為書籤,或者想要傳送連結給朋友,讓他們可以點選來查看相同的電影篩選清單。 請注意,HTTP POST 與 GET 要求的 URL (localhost:{PORT}/Movies/Index) 相同 -- 在 URL 中沒有搜尋資訊。 搜尋字串資訊會以表單欄位值的形式傳送至伺服器。 您可以使用瀏覽器開發人員工具或絕佳的 Fiddler 工具來進行確認。 下圖顯示 Chrome 瀏覽器開發人員工具:
您可以在要求本文中看到搜尋參數和 XSRF 語彙基元。 請注意,如先前的教學課程所述,表單標記協助程式會產生 XSRF 防偽權杖。 我們不會修改資料,因此不需要驗證控制器方法中的語彙基元。
由於搜尋參數是在要求本文而不是 URL 中,因此您無法擷取該搜尋資訊以加為書籤或與其他人共用。 藉由指定要求應為 Views/Movies/Index.cshtml
檔案中找到的 HTTP GET
來修正此問題。
@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
動作方法。
下列標記顯示 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
(我們不希望選取清單中有重複的內容類型)。
當使用者搜尋項目時,搜尋的值會保留在搜尋方塊中。
將依內容類型搜尋新增至 Index 檢視
更新在 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 協助程式中使用的 Lambda 運算式:
@Html.DisplayNameFor(model => model.Movies[0].Title)
在上述程式碼中,DisplayNameFor
HTML 協助程式會檢查 Lambda 運算式中參考的 Title
屬性來判斷顯示名稱。 由於 Lambda 運算式是檢查而不是評估,因此您在 model
、model.Movies
或 model.Movies[0]
是 null
或空白時,不會收到存取違規。 在評估 Lambda 運算式時 (例如,@Html.DisplayFor(modelItem => item.Title)
),會評估模型的屬性值。
依據內容類型、電影標題和這兩者進行搜尋,藉以測試應用程式: