作者: Rick Anderson
翻譯: 婁宇(Lyrics)
校對: 高嵩
章節:
介紹視圖組件
視圖組件是 ASP.NET Core MVC 中的新特性,與局部視圖相似,但是它們更加的強大。視圖組件不使用模型綁定,只取決於調用它時所提供的數據。視圖組件有以下特點:
- 渲染一個塊,而不是整個響應
- 在控制器和視圖之間同樣包含了關注點分離和可測試性帶來的好處
- 可以擁有參數和業務邏輯
- 通常從布局頁調用
視圖組件可以用在任何需要重復邏輯且對局部視圖來說過於復雜的情況,比如:
- 動態導航菜單
- 標簽雲 (需要從數據庫查詢時)
- 登錄面板
- 購物車
- 最近發表的文章
- 一個典型博客的側邊欄內容
- 會在所有頁面渲染的登錄面板,根據用戶登錄狀態顯示登錄或者注銷
一個 視圖組件 包含兩個部分,類(通常派生自 ViewComponent
)和它返回的結果(通常是一個視圖)。類似控制器,視圖組件可以是 POCO 類型,但是大部分開發者想要使用派生自 ViewComponent
的方法和屬性。
創建視圖組件
這個章節包含創建視圖組件的高級需求。在稍后的文章中,我們將詳細地檢查每一個步驟,並創建一個視圖組件。
視圖組件類
一個視圖組件類可以由以下任何一個方式創建:
- 派生自
ViewComponent
- 使用
[ViewComponent]
特性裝飾一個類,或者這個類的派生類。 - 創建一個類,並以 ViewComponent 作為后綴。
如同控制器一樣,視圖組件必須是公開的,非嵌套的,以及非抽象的類。視圖組件名稱是類名並去掉“ViewComponent”后綴。也可以通過 ViewComponentAttribute.Name 屬性進行明確的指定。
一個視圖組件類:
視圖組件方法
視圖組件在 InvokeAsync
方法中中定義邏輯,並返回 [IViewComponentResultIViewComponentResult。參數直接來自視圖組件的調用,而不是來自模型綁定。視圖組件從來不直接處理請求。通常視圖組件初始化模型並通過調用 View方法傳遞它到視圖。總之,視圖組件方法有以下特點:
- 定義一個
InvokeAsync
方法並返回IViewComponentResult
- 通常初始化模型並通過調用ViewComponent View方法傳遞它到視圖
- 參數來自調用方法,而不是 HTTP,沒有模型綁定
- 不可直接作為 HTTP 終結點,它們從你的代碼中調用(通常在視圖中)。視圖組件從不處理請求
- 重載的簽名,而不是當前 HTTP 請求中的任何細節
視圖搜索路徑
運行時對視圖的搜索路徑如下:
- Views/<controller_name>/Components/<view_component_name>/<view_name>
- Views/Shared/Components/<view_component_name>/<view_name>
視圖組件默認的視圖名是 Default,意味着通常你的視圖文件會命名為 Default.cshtml。當你創建視圖組件結果或者調用 View
方法的時候,你可以指定不同的視圖名。
我們建議你命名視圖文件為 Default.cshtml 並且使用 Views/Shared/Components/<view_component_name>/<view_name> 路徑。在這個例子中使用的 PriorityList
視圖組件使用了 Views/Shared/Components/PriorityList/Default.cshtml 這個路徑。
調用視圖組件
要使用視圖組件,從視圖中調用 @Component.InvokeAsync("視圖組件名", <匿名類型參數>)
。參數將傳遞給 InvokeAsync
方法。在文章中開發的 PriorityList
視圖組件被 Views/Todo/Index.cshtml 視圖文件調用。在下面,使用了兩個參數調用 InvokeAsync
方法:
@await Component.InvokeAsync("PriorityList", new { maxPriority = 2, isDone = false })
從控制器直接調用視圖組件
視圖組件通常從視圖中調用,但是你也可以從控制器方法中直接調用。當視圖組件沒有像控制器一樣定義終結點時,你可以簡單實現一個控制器的 Action ,並使用一個 ViewComponentResult作為返回內容。
在這例子中,視圖組件通過控制器直接調用:
public IActionResult IndexVC()
{
return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
}
演練:創建一個簡單的視圖組件
下載,生成並測試啟動代碼。這是一個簡單的項目,使用一個 Todo
控制器來顯示 Todo 項列表。
添加一個視圖組件類
創建一個 ViewComponents 文件夾並添加下面的 PriorityListViewComponent
類。
using Microsoft.AspNet.Mvc;
using Microsoft.Data.Entity;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ViewComponentSample.Models;
namespace ViewComponentSample.ViewComponents
{
public class PriorityListViewComponent : ViewComponent
{
private readonly ToDoContext db;
public PriorityListViewComponent(ToDoContext context)
{
db = context;
}
public async Task<IViewComponentResult> InvokeAsync(
int maxPriority, bool isDone)
{
var items = await GetItemsAsync(maxPriority, isDone);
return View(items);
}
private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
{
return db.ToDo.Where(x => x.IsDone == isDone &&
x.Priority <= maxPriority).ToListAsync();
}
}
}
代碼注釋:
- 視圖組件類可以被放在項目中 任何 文件夾內。
- 因為類命名為
PriorityListViewComponent
,以 ViewComponent 作為后綴結束,在運行時會從視圖中使用 "PriorityList" 字符串來引用組件類。我會在后面詳細解釋。 [ViewComponent]
特性可以改變被用來引用視圖組件的名字。比如,我們可以命名類為XYZ
,然后應用ViewComponent
特性:
[ViewComponent(Name = "PriorityList")]
public class XYZ : ViewComponent
- 上面的
[ViewComponent]
特性告知視圖組件選擇器在尋找與組件相關的視圖時使用名字PriorityList
,並且在從視圖中引用組件類時使用 "PriorityList" 字符串。我會在后面詳細解釋。 - 組件使用依賴注入使得數據上下文可用。
InvokeAsync
暴露一個可以在視圖中調用的方法,並且它可以接受任意數量的參數。InvokeAsync
方法返回沒有完成並優先級小於等於maxPriority
的ToDo
項的集合。
創建視圖組件 Razor 視圖
- 創建 Views/Shared/Components 文件夾。這個文件夾 必須 命名為 Components。
- 創建 Views/Shared/Components/PriorityList 文件夾。這個文件夾必須和視圖組件類名字匹配,或者是類名去掉后綴(如果我們遵循了約定並且使用 ViewComponent 作為類名后綴)。如果你使用
ViewComponent
特性,類名需要匹配特性中指定的名字。 - 創建一個 Views/Shared/Components/PriorityList/Default.cshtml Razor 視圖。
@model IEnumerable<ViewComponentSample.Models.TodoItem>
<h3>Priority Items</h3>
<ul>
@foreach (var todo in Model)
{
<li>@todo.Name</li>
}
</ul>
Razor 視圖取一組 TodoItem
並顯示它們。如果視圖組件的 InvokeAsync
方法沒有傳遞視圖名(就像我們例子中),按照約定會使用 Default 作為視圖名。在教程的后面部分,我會告訴你如何傳遞視圖的名稱。為特定控制器重寫默認的樣式,添加一個視圖到特定控制器的視圖文件夾(比如 Views/Todo/Components/PriorityList/Default.cshtml)。
如果視圖組件是特定控制器的,你可以添加到特定控制器文件夾(Views/Todo/Components/PriorityList/Default.cshtml)。
- 在 Views/Todo/index.cshtml 文件底部添加一個
div
包含調用 PriorityList 組件:
}
</table>
<div >
@await Component.InvokeAsync("PriorityList", new { maxPriority = 2, isDone = false })
</div>
標記 @Component.InvokeAsync
展示了調用視圖組件的語法。第一個參數是我們想要調用的組件名。隨后的參數傳遞給組件。InvokeAsync
可以接受任意數量的參數。
下面的圖片顯示了 Priority 項:
你也可以從控制器直接調用視圖組件:
public IActionResult IndexVC()
{
return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
}
指定視圖名
一個復雜的視圖組件在某些條件下可能需要指定非默認的視圖。下面的代碼展示如何從 InvokeAsync
方法中指定 "PVC" 視圖。更新 PriorityListViewComponent
類中的 InvokeAsync
方法。
public async Task<IViewComponentResult> InvokeAsync(int maxPriority, bool isDone)
{
string MyView = "Default"; // 手動高亮
// If asking for all completed tasks, render with the "PVC" view. // 手動高亮
if (maxPriority > 3 && isDone == true) // 手動高亮
{ // 手動高亮
MyView = "PVC"; // 手動高亮
} // 手動高亮
var items = await GetItemsAsync(maxPriority, isDone);
return View(MyView, items);
}
復制 Views/Shared/Components/PriorityList/Default.cshtml 文件到一個視圖中並命名為 Views/Shared/Components/PriorityList/PVC.cshtml。添加一個標題到 PVC 視圖來表明正在使用此視圖。
@model IEnumerable<ViewComponentSample.Models.TodoItem>
<h2> PVC Named Priority Component View</h2> // 手動高亮
<h4>@ViewBag.PriorityMessage</h4>
<ul>
@foreach (var todo in Model)
{
<li>@todo.Name</li>
}
</ul>
更新 Views/TodoList/Index.cshtml
</table>
<div>
@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })
</div>
運行應用程序並驗證 PVC 視圖。
如果 PVC 視圖沒有渲染,請驗證你是否使用 4 或者更高 priority 參數來調用視圖組件。
檢查視圖路徑
-
改變 priority 參數到 3 或者更低,使得不返回優先級視圖。
-
暫時重命名 Views/Todo/Components/PriorityList/Default.cshtml 為 Temp.cshtml。
-
測試應用程序,你將得到以下錯誤:
An unhandled exception occurred while processing the request.
InvalidOperationException: The view 'Components/PriorityList/Default'
was not found. The following locations were searched:
/Views/ToDo/Components/PriorityList/Default.cshtml
/Views/Shared/Components/PriorityList/Default.cshtml.
Microsoft.AspNetCore.Mvc.ViewEngines.ViewEngineResult.EnsureSuccessful() -
復制 Views/Shared/Components/PriorityList/Default.cshtml 到 Views/Todo/Components/PriorityList/Default.cshtml。
-
添加一些標記到 Todo 視圖組件視圖來表明視圖是來自 Todo 文件夾。
-
測試 非共享 組件視圖。
避免魔法字符串
如果你想編譯時安全你可以用類名替換硬編碼視圖組件名。創建視圖組件不以 "ViewComponent" 作為后綴:
using Microsoft.AspNet.Mvc;
using Microsoft.Data.Entity;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ViewComponentSample.Models;
namespace ViewComponentSample.ViewComponents
{
public class PriorityList : ViewComponent // 手動高亮
{
private readonly ToDoContext db;
public PriorityList(ToDoContext context) // 手動高亮
{
db = context;
}
public async Task<IViewComponentResult> InvokeAsync(
int maxPriority, bool isDone)
{
var items = await GetItemsAsync(maxPriority, isDone);
return View(items);
}
private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
{
return db.ToDo.Where(x => x.IsDone == isDone &&
x.Priority <= maxPriority).ToListAsync();
}
}
}
添加一個 using
語句到你的 Razor 視圖文件並使用 nameof
操作符:
@using ViewComponentSample.Models
@using ViewComponentSample.ViewComponents
@model IEnumerable<TodoItem>
<h2>ToDo nameof</h2>
<!-- Markup removed for brevity. -->
}
</table>
<div>
@await Component.InvokeAsync(nameof(PriorityList), new { maxPriority = 4, isDone = true })
</div>