(强力推荐!)记录将 DevExpress 的Blazor Demo (Github示例)改造为通用的 Admin 后台,并加入XAF的 安全用户、角色、权限
DevExpress 是一家提供微软技术的配套全面解决方案的软件商,其最新试用产品提供了: Blazor 控件、XAF 开发框架、其中有可单独使用的 以角色为基础的用户授权开权限控制API包。
(1) 演示示例地址: https://demos.devexpress.com/blazor/。参考官方解释 Online Demos https://docs.devexpress.com/Blazor/401058/demos#run-demos-locally
在GitHub 的源代码 :
-
For Blazor Server: https://github.com/DevExpress/Blazor/tree/master/demo/BlazorDemo.ServerSide
-
For Blazor WebAssembly: https://github.com/DevExpress/Blazor/tree/master/demo/BlazorDemo.Wasm
(2) 上述示例没有用户身份验证与角色权限等内容, 但官方有单独的多个身份验证与授权示例:(参考) How to: Use the Integrated Mode of the Security System in Non-XAF Applications
源码地址: https://github.com/DevExpress-Examples/XAF_Security_E4908
其中: Blazor Server App 是 面向 Blazor 服务器端的类型, 地址:https://github.com/DevExpress-Examples/XAF_Security_E4908/tree/master/XPO/ASP.NetCore/Blazor.ServerSide?utm_source=DevExpress&utm_medium=Website&utm_campaign=XAF&utm_content=XAF_Security_NonXAF_Series_6_Blazor_ServerSide
本Blog 记录改造Blazor演示示例的过程, 首先清理 BlazorDemo.ServerSide 项目,然后将 身份验证项目,整合成一个 后台Admin系统,并充分利用 DevExpress 的Blazor 控件。
一、 清理 BlazorDemo.ServerSide 项目
本服务端演示解决方案,实际包含两个项目, 其中启动项目是: BlazorDemo.ServerSide ,引用了库项目: BlazorDemo.ServerSide.Core (默认命令空间,设定为: BlazorDemo)。
1、 启动项目 BlazorDemo.ServerSide, Properties文件夹的(launchSettings.json)要引入,系统隐藏了。
3、ConnectionStrings.json 独立,可以设置数据库连接字符串;
4、Startup.ServerSide.cs + HostingStartupBase.cs , 是 StartUp类,配置服务与中间件,需要清理服务;
清理库Core 项目后,才能开始,主要是清理服务注册。
2个 Startup.XXX 注册了 5个 DBContext,多个 DataProvider ,注释掉(与库项目相关文件一起,参考后续!)。
5、Pages 文件夹下的 _Host.cshtml 文件
注释掉:<environment include="Wasm.AspNetCoreHosted"> 环境组件相关部分。
6、Utils 文件夹下的 DataSourceLoadOptions.cs 保持。
7、Controllers 文件夹 (移动到库项目 / 或 排除)
有 2个控制器,一个用于 NwindController 用于演示 GRID 获得大数据量;另一个 UploadController 用于演示 上传文件。两者都有菜单项演示。
在库项目中,编译时 复制了整个文件夹及文件。(在 项目的 .CSProj 文件中,删除 复制有关的 ItemGroup)
运作: 排除(删除)NwindController 或 UploadController , 或者将整个文件夹移动到 库项目,取消 编译时复制。
8、DataProviders 文件夹 (排除/删除)
在库项目中,也有对应的DataProviders 文件夹(主要是 接口), 并在 HostingStartupBase.cs 注册为服务。
排除/删除,与对应的库项目。
二、 清理 BlazorDemo.ServerSide.Core 项目 (重点)
(一) 全局文件
1、App.razor 、_Imports.razor
2、Startup.Reporting.cs 定义了静态类 ReportingHostingStartup
在启动项目的 Startup.ServerSide.cs 文件的最后 有语句使用其方法:
ReportingHostingStartup.Configure(builder);
3、Utils.cs 工具静态类, 用于获取静态资产及连接字符串。
4、DemoServiceCollectionExtensions.cs 扩展 “服务集合 IServiceCollection”
(1)用于在服务集合中,将 Services 文件夹中定义的 DataService,注册到服务容器中。
(2)配置 文档元数据 IDocumentMetadataCollection
(二) Shared 文件夹
1、MainLayout.razor 是App 选择的默认。 DefaultLayout="@(typeof(MainLayout))
1)注释掉 :Free Trial 连接;
2) 修改版权申明:
<div class="version">版本: @DemoVersion.Version</div>
<div class="copyright">
<span>版权 © 2000-</span><span>@DateTimeNowYear</span>
XXXX 开发
</div>
3)前面启动项目 appsettings.json 可以加入一个 "dxversion": "[X.X]" ,会影响版本号的第四位;
2、NavMenuFactoryComponent.cs 、NavMenu.razor、NavMenuReporting
[ NavMenuFactoryComponent 中,可以依据配置属性(.ShowOnlyReporting),开启不同的菜单,可注释掉开启(NavMenuReporting):
// if (Configuration.ShowOnlyReporting) {
// builder.OpenComponent<NavMenuReporting>(0);
// builder.CloseComponent(); } ]
此项可选,如果保留,将 “NavMenuReporting” 中 菜单项注释掉。
3、排除 Pages 文件夹下的所有 演示页面。
1) NavMenu 关闭所有 菜单项(根、子):
即 DxTreeView 组件中的 <Nodes> 内容: @foreach 整个内容。
将 “NavMenuReporting” 中 菜单项注释掉。即 DxTreeView 中 Nodes 的内容。 且注释掉不注入 DemoReportSource (所有菜单项的数据源!)
2)排除 Pages 文件夹下的所有 演示页面。不留一个
由于 Pages 文件夹是系统保留,所以,根Pages 不能取消。
------------------ 至此,没有菜单与页面了。------------------------
4、关闭与报表相关的功能,涉及以下文件与目录文件夹
Startup.Reporting.cs 、Shared 文件夹\ NavMenuReporting.razor、
Services文件夹 \ ReportFactory.cs + DemoReportStorageWebExtension.cs +ReportingCustomConfigurationProvider.cs
Reports 文件夹 \ 所有演示页面与文件,可以全部删除。
1) 注释掉: _Imports.razor 的 @*@using BlazorDemo.Reports*@
【 2) 排除掉: Shared 文件夹 NavMenuReporting.razor 报表相关页面。------ 如上可以保留。只注释掉菜单。]
3) 排除掉 : Services 文件夹下: ReportFactory.cs (定义 DemoReportSource,接口 IDemoReportSource ,定义 ReportInfo), 同时
排除掉: Services 文件夹下 DemoReportStorageWebExtension.cs , 继续 :DevExpress.XtraReports.Web.Extensions.ReportStorageWebExtension
官方文档 “Create a Blazor Reporting (JavaScript-Based) Application” 有涉及 ReportStorageWebExtension 的应用(报表设计器等)。
排除掉:ReportingCustomConfigurationProvider.cs
4) 排除掉: Startup.Reporting.cs
类StartupFilter 方法ConfigureApp 的 app.UseDevExpressBlazorReporting();
类Startup.Reporting.cs 中 , 类 ReportingHostingStartup 的 静态方法,
//services.AddSingleton<IDemoReportSource, DemoReportSource>();
//services.AddScoped<ReportStorageWebExtension, DemoReportStorageWebExtension>();
同时,注释掉:启动项目 Startup.ServerSide.cs 中的 ReportingHostingStartup.Configure(builder); ----- 其引用了Startup.Reporting.cs。
5) 排除掉:Reports 文件夹(所有报表设计文件)。
------------------ 至此,没有报表了。------------------------
(三) 关闭数据服务,涉及多个文件及文件夹
1、DemoServiceCollectionExtensions.cs 扩展了服务注册(.AddDemoServices),增加了:7 项数据服务 XXDataService;
库项目 Services 文件夹下,含有7项服务的定义,(另外3项 是报表有关,已排除)
services.AddScoped<WeatherForecastService>();
services.AddScoped<ProductsFlatService>(); //;;
services.AddScoped<RentInfoDataService>();//
services.AddScoped<ContosoRetailDataService>(); //
services.AddScoped<NwindDataService>();//
services.AddScoped<IssuesDataService>();//
services.AddScoped<WorldcitiesDataService>();//
2、上述 7服务,引用了 Startup.ServerSide.cs 、HostingStartupBase.cs 中注册的 数据提供者与数据上下文
(保留) services.AddDemoServices(); ---- 数据服务,定义在 DemoServiceCollectionExtensions.cs 中
services.AddDbContextFactory<NorthwindContext>、<IssuesContext>、<WorldcitiesContext>
<RentInfoContext>、<ContosoRetailContext> ---- 数据上下文 ,定义在 启动项目 \ DataProviders \EntityFramework 文件夹下
// 13个接口及实现 ---- 数据提供者 ,12个接口定义在 库项目 \DataProviders 文件夹下,
实现在 启动项目 \ DataProviders 或 \EntityFramework 文件夹下。
services.AddSingleton<IContosoRetailDataProvider, ContosoRetailDataProvider>();
services.AddSingleton<IRentInfoDataProvider, RentInfoDataProvider>();
services.AddSingleton<IProductCategoriesProvider, ProductCategoriesProvider>();
services.AddSingleton<ISalesInfoDataProvider, SalesInfoDataProvider>();
services.AddSingleton<IFinancialSeriesDataProvider, FinancialSeriesDataProvider>();
services.AddSingleton<ICurrencyExchangeDataProvider, UsdJpyDataProvider>();
services.AddSingleton<IUsdJpyCsvFileContentProvider, UsdJpyCsvFileContentProvider>();
services.AddSingleton<IWeatherSummaryCsvFileContentProvider, WeatherSummaryCsvFileContentProvider>();
services.AddSingleton<IWeatherSummaryDataProvider, WeatherSummaryDataProvider>();
services.AddSingleton<IIssuesDataProvider, IssuesDataProvider>();
services.AddSingleton<IWorldcitiesDataProvider, WorldcitiesDataProvider>();
// Editable should be scoped
services.AddScoped<INwindDataProvider, NwindDataProvider>();
services.AddScoped<IProductsFlatProvider, ProductsFlatProvider>();
3、DbContex数据实体定义
定义在 库项目 \Data 文件夹下。
4、数据库与数据文件
定义在 库项目 \DataSources 文件夹下。 共有 7个 db 文件,1个 sqlite3 ,2个 csv 文件。
5、启动项目 Controllers 文件夹下
NwindController (2个文件) 此文件引用了 NorthwindContext 。
---------------------------------------
6、排除或注释过程
注释启动项目的服务注册(DemoServiceCollectionExtensions 7项、Startup 共18项 dbconext、dataprovider);
按序排除 库项目文件夹:Services、DataProviders(也包括启动项目)、Data、DataSources;
排除启动项目 Controllers 文件夹下 2个 NwindController
保留 库项目文件夹 DataProviders 下 IDataProvider.cs 文件,或移动到 Shared 文件夹下,其 DataProviderAccessAreaContainer 引用了接口。
(四) 与 DocumentMetadata 相关内容 (排除)
1、DemoServiceCollectionExtensions.cs 文件中
services.AddDocumentMetadata(ConfigureMetadata);
---- 扩展方法 定义在 DocumentMetadata\ DocumentMetadata.AspNetCore.cs 中。
static void ConfigureMetadata(IServiceProvider sp, IDocumentMetadataCollection metadataCollection) {
sp.GetService<DemoConfiguration>().ConfigureMetadata(metadataCollection); }
2、Configuration 文件夹下 DemoConfiguration.cs 2个方法
public virtual void ConfigureMetadata(IDocumentMetadataCollection metadataCollection)
static void ConfigurePage(IDocumentMetadataCollection metadataCollection, DemoPageBase page, string title, string titleFormat)
3、Shared\ThemeSwitcher文件夹下 ThemeSwitcher.razor
[Inject]
IDocumentMetadataService DocumentMetadataService { get; set; }
DocumentMetadataService.Update( Action<IDocumentMetadataBuilder> )
Update 方法涉及的范围太大,暂保留。可将 DocumentMetadata 文件夹名称 修改为:DocMetadata。
4、另一种选择
ThemeSwitcher 中使用的DocumentMetadataService.Update 方法,主要是选择样式后,更新 Style。
DocumentMetadataService.Update((m) => {
m.StyleSheet("currentThemeCss", Themes.GetThemeCssUrl(theme));
m.StyleSheet("currentThemeCodeCss", Themes.GetThemeCodeCssUrl(theme)); });
关闭(删除0)上述语句,就不能够更新选择的样式。
另一选择就是:手工修改选择的样式,将你想选择的样式所属的Css文件(wwwroot\css\switcher-resources\themes\XXXX),
复制(Copy)到 wwwroot\css\switcher-resources\themes\default 。
如果手工操作,可以排除(删除)Shared\ThemeSwitcher, 以及整个 DocumentMetadata 文件夹及相关内容。
注释 MainLayout.razor 的选择图标,以及ThemeSwitcher 组件;
注释 CommServiceExt.cs 中的 services.AddDocumentMetadata(ConfigureMetadata) 、static void ConfigureMetadata。
注释 DemoConfiguration.cs 中的 public virtual void ConfigureMetadata、static void ConfigurePage。
--------- 这样就没有主题选择了-------------------------
(五) Configuration 与 Shared 文件夹
1、DemoProductInfo
(1) Index.razor 首页有涉及 DemoProductInfo 的内容, 注释掉。
RenderFragment<DemoProductInfo> DemoProduct = (info) => 整个委托;
@foreach(var info in Configuration.Products) .... 整个循环。
(2) 排除 DemoProductInfo.cs
同时注释掉 DemoConfiguration.cs 、DemoConfigurationData.cs 相关的对 类 DemoProductInfo 的引用语句。
(3) 排除 Configuration\DemoFeedback.cs 与 Shared\ DemoFeedbackPanel.razor + DemoFeedbackPanel.razor.css
2、MainLayout.razor 中 Search 与 DemoVersion
(1) 注释 MainLayout.razor 中的
<DemoSearchEditor @bind-SearchString="@SearchString" />
<DemoSearchResultList SearchString="@SearchString" SearchResults="@SearchResults" />
<div class="info-wrapper"> </div> 包含的内容。
注释掉 :@code { } 中的 相关的函数:public string SearchString、SearchResults、UpdateSearchResults()。
(2)Startup.ServerSide.cs 注释: services.AddSingleton<IDemoVersion, DemoVersion>(...) 以及 MainLayout.razor中的注入引用。
(3)相应排除:Shared\ DemoSearchEditor.razor 、DemoSearchResultList.razor、
Configuration \ DemoVersion.cs 、DemoSearchAgregator.cs、DemoSearchHelper.cs、DemoSearchModel.cs、DemoSearchResult.cs
(4)注释掉 DemoConfiguration.cs、DemoConfigurationData.cs 中的引用类
3、MainLayout.razor 中的 DemoModalBackdrop
注释掉 :if(!_isDesktop) {<DemoModalBackdrop Shown="@ModalBackgroundShown" ShownChanged="@ModalBackdropShownChanged" />
排除:DemoModalBackdrop.razor 、DemoMobileContent.razor、DemoMobileContent.razor.css。
4、排除:
DataProviderAccessArea.razor、DataProviderAccessAreaContainer.razor、DataProviderAccessAreaContainer.razor.cs
5、排除: IDataProvider.cs、IFrameLayout.razor
6、排除: Shared 文件夹下:CodeSnippet、OptionComponents、PageNavigation 三个文件夹及文件。
7、特别排除:
Shared 文件夹下 DemoPageSectionComponent.razor、DemoPageSectionParameters.cs、
DemoResizableContent.razor、DemoResizableContent.razor.css
Configuration文件夹下 DemoPage.cs、DemoPageSection.cs、DemoPageSectionCodeFile.cs、DemoPageSectionCodeProcessor.cs
修改调整 DemoConfiguration.cs 及 DemoConfigurationData.cs 对 DemoPage 等的引用与函数;
修改调整 Shared \ DemoBreadcrumbs.razor 对 DemoPage 等的引用与函数。
8、排除 Configuration文件夹下 DemoPageLayout.razor
以及 Shared 文件夹下 Error.razor、_Imports.razor
9、修改 NavMenuFactoryComponent.cs ,仅保留 导航菜单 NavMenu,取消 报表菜单 NavMenuReporting
排除 :NavMenuReporting.razor
10、清理 主题 DemoTheme
排除并删除 : Bootswatch Themes ,保留 DevExpress Themes 以及 Default 四个。
即 排除并删除 wwwroot\css\switcher-resources\themes 文件夹下相关文件。
********************* 最简解决方案 !!! ***********************
三、文件夹名称、默认命令空间名称、项目名称、程序集名称 统一调整(难点)
其中: 文件夹名称、命令空间名称 调整,会涉及很多文件,需要慎重!
(一) 项目名称、程序集名称 调整
库项目名称修改,会影响启动项目中的引用项目的名称。
(二) 项目所属 “文件夹名称” 调整
*、“文件夹名称” 改变,会影响 “文件夹”下引用的资源文件路径,主要是:.CSHtml 、.Razor 文件,具体如下:
1、启动项目的 _Host.cshtml
<link rel="prefetch" href="_content/BlazorDemo/css/scroll-view.css" as="style">
<link rel="stylesheet" href="_content/BlazorDemo/css/dx-demo.css">
<link rel="stylesheet" href="_content/BlazorDemo/css/dx-demo-pages.css">
<script type="text/javascript" src="_content/BlazorDemo/bootstrapper.js" defer></script>
下列几个,引用href 中含有:_content、_framework, AHCorp.ServerSide 是(启动项目的)默认程序集名称。
<link rel="stylesheet" href="_content/DevExpress.Blazor/dx-blazor.css">
<link rel="stylesheet" href="_content/DevExpress.Blazor.RichEdit/dx-richedit.css">
<link rel="preload" href="_framework/blazor.server.js" as="script">
<link href="BlazorDemo.ServerSide.styles.css" rel="stylesheet"> ------ 比较特别,参考:https://blog.csdn.net/xhydongda/article/details/117081960
说明1: _framework _content 来定位 css/js 。 在 Server 的 _Host.cshtml 或 WebAssembly 的 index.html 文件中,可以引用 Blazor 组件库里面的css或js资源。
[ 在 WebAssembly wwwroot/index.html 文件中,你会发现有一行 <script src="_framework/blazor.webassembly.js"></script>,_framework 指向的是编译后生成的 bin\Debug\netX.X\wwwroot\_framework 文件夹。根据你使用的组件,可以向 index.html 添加<link href="_content/Z.Blazor.Diagrams/style.css" rel="stylesheet" />,这里面的 _content 指向引用的组件库 XXBlazor 下面的 wwwroot 目录。]
说明2: BlazorDemo.ServerSide.styles.css 其中“BlazorDemo.ServerSide” 是项目名称,项目编译后的 Obj \XX...\scopedcss\bundle 文件夹下的css文件名。
2、库项目 Shared
BrowserNotSupported.razor :<img class="mt-2 mr-4" src="_content/BlazorDemo/images/Sad.svg"
DemoThemesConfiguration.cs :return $"_content/BlazorDemo/css/switcher-resources/themes/{theme.Name}/bootstrap.min.css";
DxScrollView.razor :Src="_content/BlazorDemo/lib/scroll-view.js"
MainLayout.razor : Src="_content/BlazorDemo/lib/page-helper.js"
(三)默认命令空间名称 调整
1、启动项目的“默认命令空间名称”,保持与项目名称一致
首先,重新命令 启动项目的名称,相应地会 调整启动项目的 程序集名称、默认命令空间名称, 两者保持一致;
然后,将启动项目下,有关文件(.CS)的命令空间名称,相应地进行修改调整,以与项目名称或默认命令空间名称,相一致。
主要文件是:
Program.cs、HostingStartupBase.cs、Startup.ServerSide.cs、\Utils\DataSourceLoadOptions.cs 几个。
2、库项目的 默认命令空间名称 调整
1)Configuration 文件夹
此文件夹下所有.CS 文件的命令空间为:namespace XX默认命令空间.Configuration ,进行相应的调整修改。
2)Shared、Pages 文件夹
Shared文件夹下所有 .CS 文件的根命令空间依据调整后的新“命令空间名称”进行修改。主要是:
DemoScriptLoader.cs、 NavMenuFactoryComponent.cs
3) _Imports.razor、App.razor、CommServiceExt.cs、Utils.cs
_Imports.razor、App.razor ,调整修改 相应引用的命令空间。
CommServiceExt.cs、Utils.cs , 调整修改 类定义的命令空间。
4) 所有文件,如果引用的类的命令空间修改了,相应进行调整。
四、将 Security System 的配置代码集成到上述启动项目
(参考) How to: Use the Integrated Mode of the Security System in Non-XAF Applications
源码地址: https://github.com/DevExpress-Examples/XAF_Security_E4908
其中: Blazor Server App 是 面向 Blazor 服务器端的类型, 地址:https://github.com/DevExpress-Examples/XAF_Security_E4908/tree/master/XPO/ASP.NetCore/Blazor.ServerSide?utm_source=DevExpress&utm_medium=Website&utm_campaign=XAF&utm_content=XAF_Security_NonXAF_Series_6_Blazor_ServerSide
1、将 Security System 的Startup的服务注入,迁移到 库项目
1)安装 DevExpress.ExpressApp.Security.Xpo 包
2)安装 DevExpress.Persistent.BaseImpl 包
3)复制 Helpers 文件夹 到库项目,包括2个文件:SecurityProvider.cs、XpoDataStoreProviderService.cs
4)在CommServiceExt.cs 文件的 AddDemoServices 方法中 增加 “服务注册”
(或者 启动项目的 Startup 的 ConfigureServices 方法中 增加)
services.AddSession();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie( );
services.AddCusXafSecurity().AddCusSecuredTypes(typeof(ECUser), typeof(ECRole));
5) 在启动项目的 Startup 的 Configure 方法中 增加 几个中间件 ,注意与原有中间件的顺序。
app.UseSession();
app.UseDefaultFiles(); //参考补充
......
app.UseRouting();
app.UseAuthentication();
app.UseEndpoints(endpoints => {
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host"); }); //增加
6)复制 到 Pages文件夹: Login.cshtml 、Login.cshtml.cs、LogOut.cshtml、LogOut.cshtml.cs
7)_Imports.razor 中增加:
@using Microsoft.AspNetCore.Components.Authorization
8)依据迁移项目,修改 库项目 App.razor
增加 @inject NavigationManager NavigationManager
代替 <Found/> 内的 <RouteView RouteData="@routeData" DefaultLayout="@(typeof(MainLayout))" /> 为:
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
@{ string returnUrl = "~/" + NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
NavigationManager.NavigateTo($"Login?returnUrl={returnUrl}", forceLoad: true); }
</NotAuthorized>
</AuthorizeRouteView>
代替 <NotFound> 内:
<LayoutView Layout="@(typeof(MainLayout))">
<p>Sorry, there's nothing at this address.</p>
</LayoutView> 为:
<CascadingAuthenticationState>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</CascadingAuthenticationState>