BookStore示例项目---菜单栏UI分析

部署

参照 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视图文件都包含了这么一段脚本:



如此便调用了后端方法生成动态脚本,同时我们可以改造这里的视图,用来定制网站的菜单栏等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代理,但是同时还会去渲染菜单导航栏。


    @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.First, StandardLayouts.Application)

    @(await Component.InvokeAsync())

    
@(await Component.InvokeAsync())
@RenderSection("content_toolbar", false)
@RenderBody()
@await Component.InvokeAsync(typeof(WidgetScriptsViewComponent)) @await RenderSectionAsync("scripts", false) @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.Last, StandardLayouts.Application)

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


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

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

@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Components
@inject IBrandingProvider BrandingProvider
@BrandingProvider.AppName

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)
        {
            
        }
    }
    else
    {
        
    }
}

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

Configure(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>();

        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(options =>
            {
                options.Themes.Add();

                if (options.DefaultThemeName == null)
                {
                    options.DefaultThemeName = BasicTheme.Name;
                }
            });

            // 添加嵌入资源文件
            Configure(options =>
            {
                options.FileSets.AddEmbedded("Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic");
            });

            // 添加工具栏 (多语言)
            Configure(options =>
            {
                options.Contributors.Add(new BasicThemeMainTopToolbarContributor());
            });

            // 样式及脚本捆绑
            Configure(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();

        //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().IsAuthenticated)
        {
            context.Toolbar.Items.Add(new ToolbarItem(typeof(UserMenuViewComponent)));
        }
    }
}

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

public class DefaultLanguageProvider : ILanguageProvider, ITransientDependency
{
    protected AbpLocalizationOptions Options { get; }

    public DefaultLanguageProvider(IOptions options)
    {
        Options = options.Value;
    }

    public Task> GetLanguagesAsync()
    {
        return Task.FromResult((IReadOnlyList)Options.Languages);
    }
}

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

BookStore项目的扩展:

Configure(options =>
{
    options.Resources
        .Get()
        .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 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())
{
     
}

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

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

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

你可能感兴趣的:(BookStore示例项目---菜单栏UI分析)