OrchardCore 中的 插件開發/ Shape / DisplayDriver / 視圖擴展 / Razor代碼注入

請注意該文章 僅限於 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視圖。。有點難搞
分析下我們現在得到的信息 :

  1. 第一個入參是一個 強類型 User
  2. 另一個參數 DisplayType:"SummaryAdmin"
  3. _userDisplayManager 的類型爲 IDisplayManager

要使用IDisplayManager<User> ,就要先註冊它對應的 IDisplayType
我們全局查找下 關鍵詞 services.AddScoped<IDisplayDriver<User>
image

然後在查找結果中逐個排查哪個實現中註冊了 SummaryAdmin(最好是結合網頁右鍵查看源碼,根據關鍵代碼定位)
最終定位下來只有 UserDisplayDriver 是比較像的。還有個UserRoleDisplayDriver 看名字就不像。。先不理會
還記得 DisplayType 對應的名字是SummaryAdmin 最終對應的 razor 頁面
User.SummaryAdmin.cshtml
打開看看,裏面有這樣一段

    @if (Model.Actions != null)
    {
        <div>
            @await DisplayAsync(Model.Actions)
        </div>
    }

另外在UserDisplayDriverDisplay 方法中 有這樣一行代碼

Initialize<SummaryAdminUserViewModel>("UserButtons", model => model.User = user).Location("SummaryAdmin", "Actions:1")

再去找 對應的Razor文件 UserButtons.cshtml
好吧 ,總算是找到了,就它

開始我簡單的按照文檔將這個文件複製到我的項目中,然後增加了一段html ,發現並沒有什麼用。。
然後再看上面 的 Initialize 代碼,做了個嘗試

  1. 在我的項目中注入services.AddScoped<IDisplayDriver<User>, MockLoginUserDisplayDriver>();
  2. 將複製過來的 razor文件 重命名爲 UserButtons.MockUserLogin.cshtml
  3. 添加 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>
}

總算是出來了!!

image

原文連接:https://www.cnblogs.com/Qbit/p/17376516.html

總結

核心代碼截個圖。。

image

第一個參數 UserButtons_MockUserLogin 這種格式不是必須的,但是 razor 文件名中的 .替換爲下劃線是必須的
也可以寫成
Initialize<SummaryAdminUserViewModel>("MockUserLoginButtons", model => model.User = user).Location("SummaryAdmin", "Actions:2")
對應razor 文件 MockUserLoginButtons.cshtml
主要是 DisplayDriver 和 Location方法, 第一個 決定渲染類型,第二個參數決定渲染在頁面上的位置
我的這個場景可以認時當在Admin後臺渲染User類型的列表時(SummaryAdmin ) 將此代碼注入到 Actions 位置的 第二個位置

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章