部署

参照 ABP示例项目BookStore搭建部署

项目解构

1)、动态脚本代理

启动项目时,默认会调用两个接口

/Abp/ApplicationConfigurationScript
/Abp/ServiceProxyScript

ServiceProxyScript会解析项目路由,动态生成api路径。此两个接口封装在了Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic程序集中。一旦引用该程序集便会自动调用接口。

1.1)、虚拟文件系统

说到虚拟文件系统,先要了解 嵌入资源文件。简而言之,就是以程序调用的形式访问文件。对于虚拟文件系统的了解,可以参考:

基于ASP.NET Core的模块化设计: 虚拟文件系统

ABP虚拟文件系统(VirtualFileSystem)实例------定制菜单栏显示用户姓名

1.2)、小结

上面说到的动态脚本代理是如何调用的?在模块 Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared 中有一类cshtml,它是嵌入式资源文件,以Page\Account文件夹下_ViewStart.cshtml为例:

@using Volo.Abp.AspNetCore.Mvc.UI.Theming
@inject IThemeManager ThemeManager
@{
Layout = ThemeManager.CurrentTheme.GetApplicationLayout();
}

在这里调用Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic中的GetApplicationLayout方法:

public virtual string GetLayout(string name, bool fallbackToDefault = true)
{
switch (name)
{
case StandardLayouts.Application:
return "~/Themes/Basic/Layouts/Application.cshtml";
case StandardLayouts.Account:
return "~/Themes/Basic/Layouts/Account.cshtml";
case StandardLayouts.Empty:
return "~/Themes/Basic/Layouts/Empty.cshtml";
default:
return fallbackToDefault ? "~/Themes/Basic/Layouts/Application.cshtml" : null;
}
}

而这三个cshtml视图文件都包含了这么一段脚本:

<script src="~/Abp/ApplicationConfigurationScript"></script>
<script src="~/Abp/ServiceProxyScript"></script>

如此便调用了后端方法生成动态脚本,同时我们可以改造这里的视图,用来定制网站的菜单栏等UI界面。

2)、UI界面菜单栏分析

2.1)、ABP UI界面单测项目分析

ABP简单菜单栏分析,项目源码:https://github.com/abpframework/abp/tree/dev/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo

如图:

由上面得知,开始调用layout下的视图文件,用以加载动态js代理,但是同时还会去渲染菜单导航栏。

<body class="abp-application-layout bg-light">
@await Component.InvokeLayoutHookAsync(LayoutHooks.Body.First, StandardLayouts.Application) @(await Component.InvokeAsync<MainNavbarViewComponent>()) <div class="@containerClass">
@(await Component.InvokeAsync<PageAlertsViewComponent>())
<div id="AbpContentToolbar">
<div class="text-right mb-2">
@RenderSection("content_toolbar", false)
</div>
</div>
@RenderBody()
</div> <abp-script-bundle name="@BasicThemeBundles.Scripts.Global" /> <script src="~/Abp/ApplicationConfigurationScript"></script>
<script src="~/Abp/ServiceProxyScript"></script> @await Component.InvokeAsync(typeof(WidgetScriptsViewComponent)) @await RenderSectionAsync("scripts", false) @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.Last, StandardLayouts.Application)
</body>

MainNavbarViewComponent类会加载一个视图,此视图渲染整个导航栏。

<nav class="navbar navbar-expand-md navbar-dark bg-dark shadow-sm flex-column flex-md-row mb-4" id="main-navbar" style="min-height: 4rem;">
<div class="container">
@(await Component.InvokeAsync<MainNavbarBrandViewComponent>())
<button class="navbar-toggler" type="button" data-toggle="collapse"
data-target="#main-navbar-collapse" aria-controls="main-navbar-collapse"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="main-navbar-collapse">
<ul class="navbar-nav mx-auto">
@(await Component.InvokeAsync<MainNavbarMenuViewComponent>())
</ul>
<ul class="navbar-nav">
@(await Component.InvokeAsync<MainNavbarToolbarViewComponent>())
</ul>
</div>
</div>
</nav>

2.2)、BookStore示例项目应用的UI扩展点

在上面的代码中,涉及到了两个类:MainNavbarBrandViewComponentMainNavbarMenuViewComponent。如此这里便有两个扩展点,首先就是IBrandingProvider接口。在MainNavbarBrandViewComponent源码中会这么调用该接口:

@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Components
@inject IBrandingProvider BrandingProvider
<a class="navbar-brand" href="~/">@BrandingProvider.AppName</a>

ABP源码有一个继承自该接口的默认类:

public class DefaultBrandingProvider : IBrandingProvider, ITransientDependency
{
public virtual string AppName => "MyApplication"; public virtual string LogoUrl => null;
}

BookStore项目中的扩展点:

namespace Acme.BookStore.Web
{
[Dependency(ReplaceServices = true)]
public class BookStoreBrandingProvider : DefaultBrandingProvider
{
public override string AppName => "BookStore";
}
}

MainNavbarMenuViewComponent类源码中会调用一个视图:

@using Volo.Abp.UI.Navigation
@model ApplicationMenu
@foreach (var menuItem in Model.Items)
{
var elementId = string.IsNullOrEmpty(menuItem.ElementId) ? string.Empty : $"id=\"{menuItem.ElementId}\"";
var cssClass = string.IsNullOrEmpty(menuItem.CssClass) ? string.Empty : menuItem.CssClass;
var disabled = menuItem.IsDisabled ? "disabled" : string.Empty;
if (menuItem.IsLeaf)
{
if (menuItem.Url != null)
{
<li class="nav-item @cssClass @disabled" @elementId>
<a class="nav-link" href="@(menuItem.Url ?? "#")">
@if (menuItem.Icon != null)
{
if (menuItem.Icon.StartsWith("fa"))
{
<i class="@menuItem.Icon"></i>
}
}
@menuItem.DisplayName
</a>
</li>
}
}
else
{
<li class="nav-item">
<div class="dropdown">
<a class="nav-link dropdown-toggle" href="#" id="Menu_@(menuItem.Name)" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@if (menuItem.Icon != null)
{
if (menuItem.Icon.StartsWith("fa"))
{
<i class="@menuItem.Icon"></i>
}
}
@menuItem.DisplayName
</a>
<div class="dropdown-menu border-0 shadow-sm" aria-labelledby="Menu_@(menuItem.Name)">
@foreach (var childMenuItem in menuItem.Items)
{
@await Html.PartialAsync("~/Themes/Basic/Components/Menu/_MenuItem.cshtml", childMenuItem)
}
</div>
</div>
</li>
}
}

在这里就会显示菜单栏及其子菜单。那么这么的扩展点在哪里呢?在模块类中有这么一个配置菜单的方法:

Configure<AbpNavigationOptions>(options =>
{
options.MenuContributors.Add(new DefaultMenuContributor());
});

如果我们可以参考DefaultMenuContributor类的实现,扩展自己的菜单。

BookStore示例项目的扩展点:

public class BookStoreMenuContributor : IMenuContributor
{
public async Task ConfigureMenuAsync(MenuConfigurationContext context)
{
if (context.Menu.Name == StandardMenus.Main)
{
await ConfigureMainMenuAsync(context);
}
} // 配置菜单栏的 显示
private async Task ConfigureMainMenuAsync(MenuConfigurationContext context)
{
if (!MultiTenancyConsts.IsEnabled)
{
var administration = context.Menu.GetAdministration();
administration.TryRemoveMenuItem(TenantManagementMenuNames.GroupName);
} var l = context.ServiceProvider.GetRequiredService<IStringLocalizer<BookStoreResource>>(); context.Menu.Items.Insert(0, new ApplicationMenuItem("BookStore.Home", l["Menu:Home"], "/")); context.Menu.AddItem(
new ApplicationMenuItem("BooksStore", l["Menu:BookStore"])
.AddItem(new ApplicationMenuItem("BooksStore.Books", l["Menu:Books"], url: "/Books"))
);
}
}

3)、菜单栏多语言显示

这是ABP示例项目BookStore的菜单栏,前面两个在上面已经有了描述,而多语言的显示是怎么渲染加载出来的呢?

在ABP的源码中,有多个模块专门处理UI界面。其中,有一个基础的模块,就是我们前面提到的

Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic模块。在这里处理基本的一些UI主题界面,比如,菜单栏,工具栏等。

namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic
{
[DependsOn(
typeof(AbpAspNetCoreMvcUiThemeSharedModule),
typeof(AbpAspNetCoreMvcUiMultiTenancyModule)
)]
public class AbpAspNetCoreMvcUiBasicThemeModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// 添加基础 主题
Configure<AbpThemingOptions>(options =>
{
options.Themes.Add<BasicTheme>(); if (options.DefaultThemeName == null)
{
options.DefaultThemeName = BasicTheme.Name;
}
}); // 添加嵌入资源文件
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<AbpAspNetCoreMvcUiBasicThemeModule>("Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic");
}); // 添加工具栏 (多语言)
Configure<AbpToolbarOptions>(options =>
{
options.Contributors.Add(new BasicThemeMainTopToolbarContributor());
}); // 样式及脚本捆绑
Configure<AbpBundlingOptions>(options =>
{
options
.StyleBundles
.Add(BasicThemeBundles.Styles.Global, bundle =>
{
bundle
.AddBaseBundles(StandardBundles.Styles.Global)
.AddContributors(typeof(BasicThemeGlobalStyleContributor));
}); options
.ScriptBundles
.Add(BasicThemeBundles.Scripts.Global, bundle =>
{
bundle
.AddBaseBundles(StandardBundles.Scripts.Global)
.AddContributors(typeof(BasicThemeGlobalScriptContributor));
});
});
}
}
}

我们看看工具栏的处理类BasicThemeMainTopToolbarContributor

public class BasicThemeMainTopToolbarContributor : IToolbarContributor
{
public async Task ConfigureToolbarAsync(IToolbarConfigurationContext context)
{
if (context.Toolbar.Name != StandardToolbars.Main)
{
return;
} if (!(context.Theme is BasicTheme))
{
return;
} var languageProvider = context.ServiceProvider.GetService<ILanguageProvider>(); //TODO: This duplicates GetLanguages() usage. Can we eleminate this?
var languages = await languageProvider.GetLanguagesAsync();
if (languages.Count > 1)
{
context.Toolbar.Items.Add(new ToolbarItem(typeof(LanguageSwitchViewComponent)));
} if (context.ServiceProvider.GetRequiredService<ICurrentUser>().IsAuthenticated)
{
context.Toolbar.Items.Add(new ToolbarItem(typeof(UserMenuViewComponent)));
}
}
}

在这里有一个处理语言转换视图组件LanguageSwitchViewComponent和用户菜单视图组件UserMenuViewComponent。ILanguageProvider接口有一个默认实现类:

public class DefaultLanguageProvider : ILanguageProvider, ITransientDependency
{
protected AbpLocalizationOptions Options { get; } public DefaultLanguageProvider(IOptions<AbpLocalizationOptions> options)
{
Options = options.Value;
} public Task<IReadOnlyList<LanguageInfo>> GetLanguagesAsync()
{
return Task.FromResult((IReadOnlyList<LanguageInfo>)Options.Languages);
}
}

这里的GetLanguagesAsync方法直接返回选项类AbpLocalizationOptions的Languages属性。而ABP开放出来的多语言配置接口就是这个属性,我们将多语言添加到这个属性中,ABP就会加载出来所有的多语言。

BookStore项目的扩展:

Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Get<BookStoreResource>()
.AddBaseTypes(
typeof(AbpUiResource)
); options.Languages.Add(new LanguageInfo("cs", "cs", "Čeština"));
options.Languages.Add(new LanguageInfo("en", "en", "English"));
options.Languages.Add(new LanguageInfo("pt-BR", "pt-BR", "Português"));
options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe"));
options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文"));
});

ABP是如何加载渲染出来视图的呢?有这么一个类LanguageSwitchViewComponent,这个类在上面也有调用,前提就是要在选项类中添加多语言。源码如下:

public class LanguageSwitchViewComponent : AbpViewComponent
{
private readonly ILanguageProvider _languageProvider; public LanguageSwitchViewComponent(ILanguageProvider languageProvider)
{
_languageProvider = languageProvider;
} public async Task<IViewComponentResult> InvokeAsync()
{
var languages = await _languageProvider.GetLanguagesAsync();
var currentLanguage = languages.FindByCulture(
CultureInfo.CurrentCulture.Name,
CultureInfo.CurrentUICulture.Name
); var model = new LanguageSwitchViewComponentModel
{
CurrentLanguage = currentLanguage,
OtherLanguages = languages.Where(l => l != currentLanguage).ToList()
}; return View("~/Themes/Basic/Components/Toolbar/LanguageSwitch/Default.cshtml", model);
}
}

Default.cshtml视图:

@using System.Linq
@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.Toolbar.LanguageSwitch
@model LanguageSwitchViewComponentModel
@if (Model.OtherLanguages.Any())
{
<div class="dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@Model.CurrentLanguage.DisplayName
</a> <div class="dropdown-menu dropdown-menu-right border-0 shadow-sm" aria-labelledby="dropdownMenuLink">
@foreach (var language in Model.OtherLanguages)
{
<a class="dropdown-item" href="/Abp/Languages/Switch?culture=@(language.CultureName)&uiCulture=@(language.UiCultureName)&returnUrl=@Context.Request.Path">@language.DisplayName</a>
}
</div>
</div>
}

还有一个扩展点,也可以通过 扩展 IToolbarContributor 接口。可以参考BasicThemeMainTopToolbarContributor 类。

ABP中处理菜单栏视图主要是在Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic模块中,涉及的文件如下:

如此,BookStore项目的菜单栏UI便分析完了。

BookStore示例项目---菜单栏UI分析的更多相关文章

  1. abp示例项目BookStore搭建部署

    之前部署过BookStore项目,但是换了新电脑也想好好学习下这个示例项目,于是在新电脑上重新拉了Git上的ABP项目代码,一编译生成BookStore项目就报错,可以参考 abp示例项目BookSt ...

  2. andrdoi示例项目SampleSyncAdapter分析

    概述 在sdk目录下有个示例项目SampleSyncAdapter,演示了 用户授权和同步适配器的一些内容,是个学习的很好范例.我读了很久,很多地方没搞明白,先把理解的一些记录下来. 通过学习该示例, ...

  3. 转:Android官方MVP架构示例项目解析

    转自: http://www.infoq.com/cn/articles/android-official-mvp-architecture-sample-project-analysis 作者 吕英 ...

  4. Google官方MVP模式示例项目解析 todo-mvp

    转载请注明出处:http://www.cnblogs.com/cnwutianhao/p/6700668.html 引言:在Google没有给出一套权威的架构实现之前,很多App项目在架构方面都有或多 ...

  5. Reveal UI 分析工具分析手机 App

    上篇文章介绍了: Reveal UI 分析工具简单使用 这里介绍如何使用 Reveal UI 分析工具来进行手机 App UI 界面的分析. 前提准备: (1)已安装 Reveal 的 Mac (2) ...

  6. 针对网站的UI分析

    PM对项目所有功能的把握,特别是UI 最差的UI,体现了团队的组织架构.其次,体现了产品的内部结构.最好,体现了用户的自然需求. 对于几种浏览器分别进行UI分析, (1)360的界面如今看来比较大众化 ...

  7. Sencha Cmd创建Ext JS示例项目

    Sencha提供了免费的Cmd工具,可以用来创建Ext JS项目并提供了一些便利的功能. Sencha也在官方文档中提供了一个示例来演示如何创建一个Sample Login App. 本文就介绍一下这 ...

  8. Spring Boot 示例项目

    Spring Boot 基于注解式开发 maven REST 示例项目    项目地址:https://github.com/windwant/spring-boot-service    项目地址: ...

  9. Aes加密/解密示例项目

    #AesEncrypt:Aes加密/解密示例项目 <br> 附件中的“AesEncrypt.zip”是本项目的exe文件,可直接下载下来运行和查看. *高级加密标准(英语:Advanced ...

随机推荐

  1. this.props

    React中的每一个组件,都包含有一个属性(props),属性主要是从父组件传递给子组件的,在组件内部,我们可以通过this.props获取属性对象

  2. Openstack neutron学习

    最近在学习openstack neutron的东西,记录下自己的一些理解. 网络基础知识 Switches & Vlan交换机的作用是来连接设备,实现互通的.network host之间通过交 ...

  3. JS逆向某网站登录密码分析

    声明: 本文仅供研究学习使用,请勿用于非法用途! 目标网站 aHR0cHM6Ly9hdXRoLmFsaXBheS5jb20vbG9naW4vaW5kZXguaHRt 今日目标网站是某知名支付网站,感觉 ...

  4. 5,Hadoop中的文件

    1,文件结构 · bin:脚本和命令目录. · etc:配置文件目录. · sbin:命令目录,主要包含HDFS和YARN中各类服务的启动和关闭,依赖于bin中的脚本. · share:各个模块编译后 ...

  5. Grafana使用总结

    最近工作需求学习了下grafana,根据创建的几个dashboard简要记录下创建过程. 本次使用了grafana做可视化展示,data source使用的rds是postgresql和时序数据库in ...

  6. CSS3详解:border color

    继续我们的 ,大家觉得怎么样呢?

  7. proxyTable的配置

    在dev环境下面: proxyTable: { '/api': { target: 'http://api.douban.com/v2', //主域名,以前我都写192.168.2.57:80,这里跨 ...

  8. 广告行业中那些趣事系列6:BERT线上化ALBERT优化原理及项目实践(附github)

    摘要:BERT因为效果好和适用范围广两大优点,所以在NLP领域具有里程碑意义.实际项目中主要使用BERT来做文本分类任务,其实就是给文本打标签.因为原生态BERT预训练模型动辄几百兆甚至上千兆的大小, ...

  9. 内网渗透之跨边界传输 - LCX转发

    跨边界转发 端口转发 lcx 流程 目标机 ./lcx -slave 跳板机ip 监听的端口 127.0.0.1 要转发的端口 跳板机(公网) ./lcx -listen 监听的端口 转发给攻击机访问 ...

  10. 软件WEB自动化测试工具之智能元素定位

    江湖一直有着这么一句名言“天下武功,唯快不破".那么在软件测试领域,自然而然我们会想到软件自动化测试.软件自动化测试的实现自然离不开软件自动化测试工具.软件自动化测试工具是软件自动化的载体, ...