项目

小部件

ABP 提供了一个模型和基础设施来创建可重用的小部件。小部件系统是对 ASP.NET Core 的视图组件 的扩展。当您希望实现以下目标时,小部件特别有用:

  • 为小部件拥有脚本和样式依赖项。
  • 使用其中的小部件创建仪表板
  • 在可重用的 模块 中定义小部件。
  • 让小部件与 授权捆绑与压缩 系统协作。

基本小部件定义

创建视图组件

作为第一步,创建一个新的常规 ASP.NET Core 视图组件:

小部件基础文件

MySimpleWidgetViewComponent.cs:

using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;

namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
{
    public class MySimpleWidgetViewComponent : AbpViewComponent
    {
        public IViewComponentResult Invoke()
        {
            return View();
        }
    }
}

AbpViewComponent 继承不是必需的。您可以继承 ASP.NET Core 标准的 ViewComponentAbpViewComponent 只是定义了一些基础有用的属性。

您可以在 Invoke 方法中注入服务并调用,以从服务获取一些数据。您可能需要将 Invoke 方法设置为异步,例如 public async Task<IViewComponentResult> InvokeAsync()。有关所有不同用法,请参阅 ASP.NET Core 的视图组件 文档。

Default.cshtml:

<div class="my-simple-widget">
    <h2>我的简单小部件</h2>
    <p>这是一个简单的小部件!</p>
</div>

定义小部件

MySimpleWidgetViewComponent 类添加 Widget 特性,以将此视图组件标记为一个小部件:

using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Widgets;

namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
{
    [Widget]
    public class MySimpleWidgetViewComponent : AbpViewComponent
    {
        public IViewComponentResult Invoke()
        {
            return View();
        }
    }
}

渲染小部件

渲染小部件是相当标准的操作。像处理任何视图组件一样,在 Razor 视图/页面中使用 Component.InvokeAsync 方法。示例:

@await Component.InvokeAsync("MySimpleWidget")
@await Component.InvokeAsync(typeof(MySimpleWidgetViewComponent))

第一种方法使用小部件名称,而第二种方法使用视图组件类型。

带参数的小部件

ASP.NET Core 的视图组件系统允许您为视图组件接受参数。下面的示例视图组件接受 startDateendDate,并使用这些参数从服务检索数据。

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Widgets;

namespace DashboardDemo.Web.Pages.Shared.Components.CountersWidget
{
    [Widget]
    public class CountersWidgetViewComponent : AbpViewComponent
    {
        private readonly IDashboardAppService _dashboardAppService;

        public CountersWidgetViewComponent(IDashboardAppService dashboardAppService)
        {
            _dashboardAppService = dashboardAppService;
        }

        public async Task<IViewComponentResult> InvokeAsync(
            DateTime startDate, DateTime endDate)
        {
            var result = await _dashboardAppService.GetCountersWidgetAsync(
                new CountersWidgetInputDto
                {
                    StartDate = startDate,
                    EndDate = endDate
                }
            );

            return View(result);
        }
    }
}

现在,您需要传递一个匿名对象来传递参数,如下所示:

@await Component.InvokeAsync("CountersWidget", new
{
    startDate = DateTime.Now.Subtract(TimeSpan.FromDays(7)),
    endDate = DateTime.Now
})

小部件名称

视图组件的默认名称是基于视图组件类型的名称计算的。如果您的视图组件类型是 MySimpleWidgetViewComponent,那么小部件名称将是 MySimpleWidget(移除 ViewComponent 后缀)。这是 ASP.NET Core 计算视图组件名称的方式。

要自定义小部件的名称,只需使用 ASP.NET Core 的标准 ViewComponent 特性:

using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Widgets;

namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
{
    [Widget]
    [ViewComponent(Name = "MyCustomNamedWidget")]
    public class MySimpleWidgetViewComponent : AbpViewComponent
    {
        public IViewComponentResult Invoke()
        {
            return View("~/Pages/Components/MySimpleWidget/Default.cshtml");
        }
    }
}

ABP 将通过处理小部件来尊重自定义名称。

如果视图组件名称与视图组件的文件夹名称不匹配,您可能需要像本示例中那样手动编写视图路径。

显示名称

您还可以为小部件定义一个人类可读的、可本地化的显示名称。这个显示名称可以在需要时在 UI 上使用。显示名称是可选的,可以使用 Widget 特性的属性来定义:

using DashboardDemo.Localization;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Widgets;

namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
{
    [Widget(
        DisplayName = "MySimpleWidgetDisplayName", //本地化键
        DisplayNameResource = typeof(DashboardDemoResource) //本地化资源
        )]
    public class MySimpleWidgetViewComponent : AbpViewComponent
    {
        public IViewComponentResult Invoke()
        {
            return View();
        }
    }
}

有关本地化资源和键的更多信息,请参阅 本地化文档

样式和脚本依赖

当您的小部件拥有脚本和样式文件时,会遇到一些挑战:

  • 任何使用该小部件的页面都应将其脚本和样式文件也包含到页面中。
  • 页面还应关心小部件的依赖库/文件

当您将资源与小部件正确关联时,ABP 可以解决这些问题。在使用小部件时,您无需关心其依赖项。

定义为简单文件路径

下面的示例小部件添加了一个样式文件和一个脚本文件:

using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Widgets;

namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
{
    [Widget(
        StyleFiles = new[] { "/Pages/Components/MySimpleWidget/Default.css" },
        ScriptFiles = new[] { "/Pages/Components/MySimpleWidget/Default.js" }
        )]
    public class MySimpleWidgetViewComponent : AbpViewComponent
    {
        public IViewComponentResult Invoke()
        {
            return View();
        }
    }
}

当您使用小部件时,ABP 会考虑这些依赖项并将其正确添加到视图/页面。样式/脚本文件可以是物理的或虚拟的。它与 虚拟文件系统 完全集成。

定义捆绑贡献者

页面中使用的小部件的所有资源都作为捆绑包添加(在生产环境中,如果没有其他配置,则会进行捆绑和压缩)。除了添加简单文件外,您还可以充分利用捆绑贡献者的强大功能。

下面的示例代码与上面的代码功能相同,但定义并使用了捆绑贡献者:

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Widgets;

namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
{
    [Widget(
        StyleTypes = new []{ typeof(MySimpleWidgetStyleBundleContributor) },
        ScriptTypes = new[]{ typeof(MySimpleWidgetScriptBundleContributor) }
        )]
    public class MySimpleWidgetViewComponent : AbpViewComponent
    {
        public IViewComponentResult Invoke()
        {
            return View();
        }
    }

    public class MySimpleWidgetStyleBundleContributor : BundleContributor
    {
        public override void ConfigureBundle(BundleConfigurationContext context)
        {
            context.Files
              .AddIfNotContains("/Pages/Components/MySimpleWidget/Default.css");
        }
    }

    public class MySimpleWidgetScriptBundleContributor : BundleContributor
    {
        public override void ConfigureBundle(BundleConfigurationContext context)
        {
            context.Files
              .AddIfNotContains("/Pages/Components/MySimpleWidget/Default.js");
        }
    }
}

捆绑贡献系统非常强大。如果您的组件使用一个 JavaScript 库来渲染图表,那么您可以将其声明为一个依赖项,这样如果该 JavaScript 库之前没有被添加,它会自动添加到页面中。这样,使用您的组件的页面就无需关心依赖关系。

有关该系统的更多信息,请参阅 捆绑与压缩 文档。

RefreshUrl

小部件可以设计一个 RefreshUrl,每当小部件需要刷新时就会使用它。如果定义了此属性,小部件会在每次刷新时在服务器端重新渲染(参见下面的 WidgetManager 的刷新 方法)。

[Widget(RefreshUrl = "Widgets/Counters")]
public class CountersWidgetViewComponent : AbpViewComponent
{
    
}

一旦您为小部件定义了 RefreshUrl,就需要提供一个端点来渲染并返回它:

[Route("Widgets")]
public class CountersWidgetController : AbpController
{
    [HttpGet]
    [Route("Counters")]
    public IActionResult Counters(DateTime startDate, DateTime endDate)
    {
        return ViewComponent("CountersWidget", new {startDate, endDate});
    }
}

Widgets/Counters 路由与之前声明的 RefreshUrl 匹配。

小部件可以通过两种方式刷新:第一种方式,当您使用 RefreshUrl 时,它在服务器上重新渲染,并被服务器返回的 HTML 替换。第二种方式,小部件从服务器获取数据(通常是 JSON 对象)并在客户端自行刷新(请参阅小部件 JavaScript API 部分中的刷新方法)。

AutoInitialize

WidgetAttribute 有一个 AutoInitialize 属性(bool 类型),可以设置为在页面准备就绪时以及每当小部件添加到 DOM 时自动初始化小部件。默认值为 false

如果一个小部件被配置为自动初始化,则会自动为其实例创建并初始化一个 WidgetManager(见下文)。当小部件实例不分组且独立工作时(它们不需要一起初始化或刷新),这非常有用。

AutoInitialize 设置为 true 相当于自己编写这样的代码:

$('.abp-widget-wrapper[data-widget-name="MySimpleWidget"]')
    .each(function () {
        var widgetManager = new abp.WidgetManager({
            wrapper: $(this),
        });

        widgetManager.init($(this));
    });

AutoInitialize 也支持通过 AJAX 加载/刷新(稍后添加到 DOM)和/或以嵌套方式使用(一个小部件在另一个小部件内部)的小部件。如果您不需要分组多个小部件并通过单个 WidgetManager 控制,AutoInitialize 是推荐的方法。

JavaScript API

小部件可能需要在客户端渲染和刷新。在这种情况下,您可以使用 ABP 的 WidgetManager 并为小部件定义 API。

WidgetManager

WidgetManager 用于初始化和刷新一个或多个小部件。如下所示创建一个新的 WidgetManager

$(function() {
    var myWidgetManager = new abp.WidgetManager('#MyDashboardWidgetsArea');    
})

MyDashboardWidgetsArea 内部可能包含一个或多个小部件。

在 document.ready 内部使用 WidgetManager(如上所示)是一个好习惯,因为其函数使用 DOM 并且需要 DOM 准备就绪。

WidgetManager.init()

init 方法简单地初始化 WidgetManager,并调用相关小部件的 init 方法(如果它们定义了,请参见下面的小部件 JavaScript API 部分)。

myWidgetManager.init();

WidgetManager.refresh()

refresh 方法刷新与此 WidgetManager 相关的所有小部件:

myWidgetManager.refresh();

WidgetManager 选项

WidgetManager 有一些额外的选项。

筛选表单

如果小部件需要参数/筛选器,那么通常会有一个表单来筛选小部件。在这种情况下,您可以创建一个包含一些表单元素的表单,以及一个包含一些小部件的仪表板区域。示例:

<form method="get" id="MyDashboardFilterForm">
    ...表单元素
</form>

<div id="MyDashboardWidgetsArea" data-widget-filter="#MyDashboardFilterForm">
   ...小部件
</div>

data-widget-filter 属性将表单与小部件关联起来。每当表单提交时,所有小部件都会自动以表单字段作为筛选条件进行刷新。

除了 data-widget-filter 属性,您还可以使用 WidgetManager 构造函数的 filterForm 参数。示例:

var myWidgetManager = new abp.WidgetManager({
    wrapper: '#MyDashboardWidgetsArea',
    filterForm: '#MyDashboardFilterForm'
});
筛选回调

您可能希望在初始化和刷新小部件时更好地控制筛选条件的提供。在这种情况下,您可以使用 filterCallback 选项:

var myWidgetManager = new abp.WidgetManager({
    wrapper: '#MyDashboardWidgetsArea',
    filterCallback: function() {
        return $('#MyDashboardFilterForm').serializeFormToObject();
    }
});

这个例子展示了 filterCallback 的默认实现。您可以返回任何包含字段的 JavaScript 对象。示例:

filterCallback: function() {
    return {
        'startDate': $('#StartDateInput').val(),
        'endDate': $('#EndDateInput').val()
    };
}

返回的筛选器会在 initrefresh 时传递给所有小部件。

小部件 JavaScript API

小部件可以定义一个 JavaScript API,供 WidgetManager 在需要时调用。下面的代码示例可以用来开始为小部件定义 API。

(function () {
    abp.widgets.NewUserStatisticWidget = function ($wrapper) {

        var getFilters = function () {
            return {
                ...
            };
        }

        var refresh = function (filters) {
            ...
        };

        var init = function (filters) {
            ...
        };

        return {
            getFilters: getFilters,
            init: init,
            refresh: refresh
        };
    };
})();

这里的 NewUserStatisticWidget 是小部件的名称。它应与服务器端定义的小部件名称匹配。所有函数都是可选的。

getFilters

如果小部件有内部自定义筛选器,此函数应返回筛选器对象。示例:

var getFilters = function() {
    return {
        frequency: $wrapper.find('.frequency-filter option:selected').val()
    };
}

此方法由 WidgetManager 在构建筛选器时使用。

init

用于在需要时初始化小部件。它有一个筛选器参数,在从服务器获取数据时可以使用。当调用 WidgetManager.init() 函数时,会使用 init 方法。如果小部件在刷新时需要完全重新加载,也会调用它。请参阅 RefreshUrl 小部件选项。

refresh

用于在需要时刷新小部件。它有一个筛选器参数,在从服务器获取数据时可以使用。每当调用 WidgetManager.refresh() 函数时,都会使用 refresh 方法。

授权

某些小部件可能只对经过身份验证或授权的用户可用。在这种情况下,请使用 Widget 特性的以下属性:

  • RequiresAuthentication (bool):设置为 true 可使此小部件仅对已通过身份验证的用户(用户已登录到应用程序)可用。
  • RequiredPolicies (List<string>):授权用户所需的策略名称列表。有关策略的更多信息,请参阅 授权文档

示例:

using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Widgets;

namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
{
    [Widget(RequiredPolicies = new[] { "MyPolicyName" })]
    public class MySimpleWidgetViewComponent : AbpViewComponent
    {
        public IViewComponentResult Invoke()
        {
            return View();
        }
    }
}

WidgetOptions

作为 Widget 特性的替代方案,您可以使用 AbpWidgetOptions 来配置小部件:

Configure<AbpWidgetOptions>(options =>
{
    options.Widgets.Add<MySimpleWidgetViewComponent>();
});

将此代码写入您的 模块ConfigureServices 方法中。使用 Widget 特性完成的所有配置也可以通过 AbpWidgetOptions 实现。为小部件添加样式的配置示例:

Configure<AbpWidgetOptions>(options =>
{
    options.Widgets
        .Add<MySimpleWidgetViewComponent>()
        .WithStyles("/Pages/Components/MySimpleWidget/Default.css");
});

提示:AbpWidgetOptions 也可以用来获取现有小部件并更改其配置。当您想要修改应用程序使用的模块中的小部件配置时,这特别有用。使用 options.Widgets.Find 来获取现有的 WidgetDefinition

另请参阅

在本文档中