請注意該文章 僅限於 OrchardCore項目中的 DisplayDriver 擴展機制,ASP.NET CORE MVC 自身並沒有對應功能,如果需要可以將相關的OrchardCore模塊添加到項目中也可以實現相應功能
背景
最近一個功能需求,需要使用其它用戶模擬身份,所以計劃在用戶列表頁面 擴展 按鈕組功能
那麼開始看代碼,找 用戶列表的源碼, 當然也可以右鍵查看頁面元素根據關鍵詞到源代碼裏搜索,
但這裏爲了研究插件擴展機制如何使用(不含底層實現,感興趣可以去研究源代碼)
源碼定位 (這一段有點強行關聯的意思。。可以直接看總結)
首先定位到打開用戶列表 它對應的路由地址是 Admin/Users/Index
源碼位置:OrchardCore\src\OrchardCore.Modules\OrchardCore.Users\Controllers\AdminController.cs
找到控制器,它返回的是
public async Task<ActionResult> Index([ModelBinder(BinderType = typeof(UserFilterEngineModelBinder), Name = "q")] QueryFilterResult<User> queryFilterResult, PagerParameters pagerParameters)
{
//....其它代碼
var shapeViewModel = await _shapeFactory.CreateAsync<UsersIndexViewModel>("UsersAdminList", viewModel =>
{
viewModel.Users = userEntries;
viewModel.Pager = pagerShape;
viewModel.Options = options;
viewModel.Header = header;
});
return View(shapeViewModel);
}
對應的視圖 文件 OrchardCore\src\OrchardCore.Modules\OrchardCore.Users\Views\Admin\Index.cshtml
這裏只有寥寥幾行代碼。。
@model UsersIndexViewModel
<script asp-name="bootstrap-select" depends-on="admin" at="Foot"></script>
<zone Name="Title"><h1>@RenderTitleSegments(T["Users"])</h1></zone>
<form asp-action="Index" method="post" class="no-multisubmit">
@await DisplayAsync(Model)
</form>
接下來怎麼走?貌似跟不下去了啊,再回去看看,遺漏了什麼
回到控制器代碼 ,注意其返回值 ,它調用了下面的方法生產的一個ViewModel
await _shapeFactory.CreateAsync<UsersIndexViewModel>("UsersAdminList", viewModel =>...
注意到 第一個參數 "UsersAdminList" , 此時先要理解一點:OC中約定的所有Shape 對應的Razor文件都是在 Views 文件夾的根目錄下
此時應該去找 對應這個名稱的視圖文件UsersAdminList.cshtml
負責渲染列表部分的代碼如下
@if (Model.Users.Count > 0)
{
@foreach (var entry in Model.Users)
{
<li class="list-group-item">
<div class="form-check float-start">
<input type="checkbox" class="form-check-input" value="@entry.UserId" name="itemIds" id="[email protected]">
<label class="form-check-label" for="[email protected]"></label>
</div>
@await DisplayAsync(entry.Shape)
</li>
}
}
else
{
<li class="list-group-item">
<div class="alert alert-info mb-0">
@T["No results found."]
</div>
</li>
}
這裏又來了個Shape~ 各種代碼跟蹤又走不下去了?
肯定有線索。。
Razor 的循環中遍歷的是Model的Users 屬性,我們看看 Users屬性是怎麼來的
再回到 Index 代碼
有這麼幾行
var userEntries = new List<UserEntry>();
foreach (var user in results)
{
userEntries.Add(new UserEntry
{
UserId = user.UserId,
Shape = await _userDisplayManager.BuildDisplayAsync(user, updater: _updateModelAccessor.ModelUpdater, displayType: "SummaryAdmin")
});
}
BuildDisplayAsync 是一個泛型方法,第一個參數接收的類型是 User
那麼此時燒腦的事情來了。。。根據一個類型去查找它的Razor視圖。。有點難搞
分析下我們現在得到的信息 :
- 第一個入參是一個 強類型
User
- 另一個參數 DisplayType:"SummaryAdmin"
- _userDisplayManager 的類型爲 IDisplayManager
要使用IDisplayManager<User>
,就要先註冊它對應的 IDisplayType
我們全局查找下 關鍵詞 services.AddScoped<IDisplayDriver<User>
然後在查找結果中逐個排查哪個實現中註冊了 SummaryAdmin
(最好是結合網頁右鍵查看源碼,根據關鍵代碼定位)
最終定位下來只有 UserDisplayDriver
是比較像的。還有個UserRoleDisplayDriver
看名字就不像。。先不理會
還記得 DisplayType 對應的名字是SummaryAdmin
最終對應的 razor 頁面
User.SummaryAdmin.cshtml
打開看看,裏面有這樣一段
@if (Model.Actions != null)
{
<div>
@await DisplayAsync(Model.Actions)
</div>
}
另外在UserDisplayDriver
的 Display
方法中 有這樣一行代碼
Initialize<SummaryAdminUserViewModel>("UserButtons", model => model.User = user).Location("SummaryAdmin", "Actions:1")
再去找 對應的Razor文件 UserButtons.cshtml
好吧 ,總算是找到了,就它
開始我簡單的按照文檔將這個文件複製到我的項目中,然後增加了一段html ,發現並沒有什麼用。。
然後再看上面 的 Initialize 代碼,做了個嘗試
- 在我的項目中注入
services.AddScoped<IDisplayDriver<User>, MockLoginUserDisplayDriver>();
- 將複製過來的 razor文件 重命名爲
UserButtons.MockUserLogin.cshtml
- 添加 MockLoginUserDisplayDriver
public class MockLoginUserDisplayDriver : DisplayDriver<User>
{
public override IDisplayResult Display(User user)
{
return Combine(
Initialize<SummaryAdminUserViewModel>("UserButtons_MockUserLogin", model => model.User = user).Location("SummaryAdmin", "Actions:2")
);
}
}
然後運行。果然生效了,只不過代碼渲染了兩次,修改 UserButtons.MockUserLogin.cshtml
代碼
@if (!isCurrentUser && await AuthorizationService.AuthorizeAsync(User, PermissionsProvider.MockUserLoginPermission, Model.User))
{
<a asp-action="MockLogin" asp-controller="MockUserLogin" asp-route-id="@Model.User.UserId" class="btn btn-secondary btn-sm">@T["Mock Login"]</a>
}
總算是出來了!!
原文連接:https://www.cnblogs.com/Qbit/p/17376516.html
總結
核心代碼截個圖。。
第一個參數 UserButtons_MockUserLogin 這種格式不是必須的,但是 razor 文件名中的 .替換爲下劃線是必須的
也可以寫成
Initialize<SummaryAdminUserViewModel>("MockUserLoginButtons", model => model.User = user).Location("SummaryAdmin", "Actions:2")
對應razor 文件 MockUserLoginButtons.cshtml
主要是 DisplayDriver
我的這個場景可以認時當在Admin後臺渲染User類型的列表時(SummaryAdmin ) 將此代碼注入到 Actions 位置的 第二個位置