部署

参照 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. fiddler 针对单个接口打断点

    在命令行输入相关指令: 以慕课网为例: 请求前设置断点:bpu 实例: bpu https://www.imooc.com/index/getstarlist 请求 https://www.imooc ...

  2. Proto3:C++ API概览

    包名 说明 google::protobuf Protocol Buffer运行时库核心组件. google::protobuf::io I/O操作辅助类. google::protobuf::uti ...

  3. 手机视频APP将关闭 生态梦成空的三星如何自救?

    生态梦成空的三星如何自救?"> 三星如今的处境,只能用"屋漏偏逢连夜雨"来形容.继营收.利润.智能手机销量等大幅下滑之后,裁员也接踵而来,股价的下跌也自然在情理之中 ...

  4. Pro SQL Server Internal (Dmitri Korotkev)电子书翻译P8-14(12w)

    数据行与数据列 数据库的控件逻辑上分成8KB的页,这些页从0开始,连续排序,对特定的文件ID和页码有借鉴意义.页码编号一定是连续的,当SQL服务器中的数据库文件增加时,新的数据页从最高的页码开始编码. ...

  5. ado.net DataSet

    一.概念 DataSet是ADO.NET的中心概念.可以把DataSet当成内存中的数据库,DataSet是不依赖于数据库的独立数据集合.所谓独立,就是说,即使断开数据链路,或者关闭数据库,DataS ...

  6. python递归用法

    需求:4的阶乘 4*3*2*1计算.通过递归算法,c=4*getnums(4-1),然后调用自己本身的函数,形成递归,就等于3*getnums(3-1),2*getnums(2-1),依次递归调用,最 ...

  7. Glide源码解析一,初始化

    转载请标明出处:https:////www.cnblogs.com/tangZH/p/12409849.html Glide作为一个强大的图片加载框架,已经被android官方使用,所以,明白Glid ...

  8. iOS下的 Fixed BUG

    input 光标位置乱窜 固定式浮层内的输入框光标会发生偏移.即 fixed 定位的容器中输入框光标的位置显示不正确,没有正常地显示在输入框中,而是偏移到了输入框外面 可触发条件 页面body出现滚动 ...

  9. 前端每日实战:102# 视频演示如何用纯 CSS 创作一个小和尚

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/oMmYXp 可交互视频 此视频是可 ...

  10. Codeforces Round #620 (Div. 2)

    Codeforces Round #620 (Div. 2) A. Two Rabbits 题意 两只兔子相向而跳,一只一次跳距离a,另一只一次跳距离b,每次同时跳,问是否可能到同一位置 题解 每次跳 ...