部署

参照 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. 算法拾遗[4]——STL用法

    主要bb一下优先队列和字符串吧. 哦还有 bitset. 优先队列 定义很容易: priority_queue<int> pq; 内部是一个堆. 基本操作 pq.top() 取堆顶元素; ...

  2. Coding and Paper Letter(十五)

    资源整理. 1.Nature Climate Change论文"Higher temperatures increase suicide rates in the United States ...

  3. Vue内置组件keep-alive的使用

    本文主要介绍Vue内置组件keep-alive的使用. Vue内置组件keep-alive的使用 keep-alive接收三个props:●include - 字符串或正则表达式.只有名称匹配的组件会 ...

  4. Intellij IDEA 干货分享

    更多视频详情:https://www.bilibili.com/video/av89385013/ Intellij IDEA 真是越用越强大 它总是在我们写代码的时候 不时给我们来个小惊喜 出于对 ...

  5. SpringBoot图文教程8 — SpringBoot集成MBG「代码生成器」

    有天上飞的概念,就要有落地的实现 概念十遍不如代码一遍,朋友,希望你把文中所有的代码案例都敲一遍 先赞后看,养成习惯 SpringBoot 图文教程系列文章目录 SpringBoot图文教程1「概念+ ...

  6. JZOJ 5235. 【NOIP2017模拟8.7A组】好的排列

    5235. [NOIP2017模拟8.7A组]好的排列 (File IO): input:permutation.in output:permutation.out Time Limits: 1000 ...

  7. Pycharm IDE安装及注册激活笔记(1)

    一.Windows 下的安装及激活. 1.首先去Pycharm官网,或者直接输入网址:http://www.jetbrains.com/pycharm/download/#section=window ...

  8. Linux——如何将Red Hat Enterprise Linux 6的语言改为中文?

    第一步,打开终端,输入su -,获取超级用户权限,输入密码. 第二步,输入cd /etc/sysconfig,进入设置目录. 第三步,输入vi i18n,进入到配置文件. 第四步,按 ‘i’键,进入编 ...

  9. VUE四 axios详解

    axios的中文文档写的已经很详细 https://www.kancloud.cn/yunye/axios/234845

  10. Educational Codeforces Round 83 (Rated for Div. 2)A--C

    题意:给出一个边数为n的等边多边形,问是否可以变成m的等边多边形.条件是同一个中心,共用原顶点. 解析:直接n%m==0即可,这样就是平分了.签到题没得说了. #include<iostream ...