diff --git a/docs/en/AspNetCore/Dashboards.md b/docs/en/AspNetCore/Dashboards.md new file mode 100644 index 0000000000..26b6a77461 --- /dev/null +++ b/docs/en/AspNetCore/Dashboards.md @@ -0,0 +1,3 @@ +# Dashboards + +TODO \ No newline at end of file diff --git a/docs/en/AspNetCore/Widgets.md b/docs/en/AspNetCore/Widgets.md new file mode 100644 index 0000000000..b51ffb901e --- /dev/null +++ b/docs/en/AspNetCore/Widgets.md @@ -0,0 +1,84 @@ +# Widgets + +ABP provides a model and infrastructure to create **reusable widgets**. It relies on [ASP.NET Core's ViewComponent](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components) system, so any view component can be used as a widget. Widget system is especially useful when you want to; + +* Define widgets in reusable **[modules](../Module-Development-Basics.md)**. +* Have **scripts & styles** for your widget. +* Create **[dashboards](Dashboards.md)** with widgets used in (widgets you've built yourself or defined by modules you are using). +* Co-operate widgets with **[authorization](../Authorization.md)** and **[bundling](Bundling-Minification.md)** systems. + +## Basic Widget Definition + +### Create a View Component + +As the first step, create a regular ASP.NET Core View Component: + +![widget-basic-files](../images/widget-basic-files.png) + +**MySimpleWidgetViewComponent.cs**: + +````csharp +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc; + +namespace DashboardDemo.Web.Pages.Components.MySimpleWidget +{ + public class MySimpleWidgetViewComponent : AbpViewComponent + { + public IViewComponentResult Invoke() + { + return View(); + } + } +} +```` + +**Default.cshtml**: + +```xml +
+

My Simple Widget

+

This is a simple widget!

+
+``` + +### Define the Widget + +Second step is to define a widget using the `WidgetOptions` (in the `ConfigureServices` method of your Web module): + +````csharp +Configure(options => +{ + options.Widgets.Add( + new WidgetDefinition( + "MySimpleWidget", //Unique Widget name + typeof(MySimpleWidgetViewComponent) //Type of the ViewComponent + ) + ); +}); +```` + +Widget name should be unique in the application. + +*TODO: This is in development and will probably change!* + +### Render the Widget + +Whenever you want to render a widget, you can inject the `IWidgetRenderer` and use the `RenderAsync` method with the unique widget name. + +````xml +@inject IWidgetRenderer WidgetRenderer + +@await WidgetRenderer.RenderAsync(Component, "MySimpleWidget") +```` + +You could do the same with standard `Component.InvokeAsync` method. The main difference is that `IWidgetRenderer` uses the widget name defined before. The essential benefit of the `WidgetRenderer` comes when your widget has additional resources, like script and style files. + +## Style & Script Dependencies + +There are some challenges when your widget has script and style files; + +* Any page uses the widget should also include the **its script & styles** files into the page. +* The page should also care about **depended libraries/files** of the widget. + +ABP solves all these issues when you properly relate the resources with the widget. You don't care about dependencies of the widget. \ No newline at end of file diff --git a/docs/en/images/widget-basic-files.png b/docs/en/images/widget-basic-files.png new file mode 100644 index 0000000000..bf5f122d42 Binary files /dev/null and b/docs/en/images/widget-basic-files.png differ diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorListExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs similarity index 80% rename from framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorListExtensions.cs rename to framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs index 2e2087435b..8261706431 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorListExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs @@ -1,6 +1,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling { - public static class BundleContributorListExtensions + public static class BundleContributorCollectionExtensions { public static void AddFiles(this BundleContributorCollection contributors, params string[] files) { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml index 6eb41a7546..2e95453185 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml @@ -9,6 +9,8 @@ @using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.PageAlerts @using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Components @using Volo.Abp.AspNetCore.Mvc.UI.Theming +@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetScripts +@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetStyles @using Volo.Abp.MultiTenancy @inject IAbpAntiForgeryManager AbpAntiForgeryManager @inject IBrandingProvider BrandingProvider @@ -39,6 +41,8 @@ @await RenderSectionAsync("styles", false) + @await Component.InvokeAsync(typeof(WidgetStylesViewComponent)) + @await Component.InvokeLayoutHookAsync(LayoutHooks.Head.Last, StandardLayouts.Account) @@ -50,7 +54,7 @@ @if (MultiTenancyOptions.Value.IsEnabled && - (TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(CookieTenantResolveContributor.ContributorName) == true)) + (TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(CookieTenantResolveContributor.ContributorName) == true)) {
@MultiTenancyStringLocalizer["Tenant"]: @@ -80,6 +84,8 @@ @await RenderSectionAsync("scripts", false) + @await Component.InvokeAsync(typeof(WidgetScriptsViewComponent)) + @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.Last, StandardLayouts.Account) \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml index a492b4abfe..9bb54dea09 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml @@ -6,6 +6,8 @@ @using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.PageAlerts @using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Components @using Volo.Abp.AspNetCore.Mvc.UI.Theming +@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetScripts +@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetStyles @inject IAbpAntiForgeryManager AbpAntiForgeryManager @inject IBrandingProvider BrandingProvider @inject IPageLayout PageLayout @@ -41,6 +43,8 @@ + @await Component.InvokeAsync(typeof(WidgetStylesViewComponent)) + @await RenderSectionAsync("styles", false) @await Component.InvokeLayoutHookAsync(LayoutHooks.Head.Last, StandardLayouts.Application) @@ -63,6 +67,8 @@ + @await Component.InvokeAsync(typeof(WidgetScriptsViewComponent)) + @await RenderSectionAsync("scripts", false) @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.Last, StandardLayouts.Application) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Empty.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Empty.cshtml index 62f94f6ede..f4f18202c7 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Empty.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Empty.cshtml @@ -5,6 +5,8 @@ @using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.PageAlerts @using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Components @using Volo.Abp.AspNetCore.Mvc.UI.Theming +@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetScripts +@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetStyles @inject IAbpAntiForgeryManager AbpAntiForgeryManager @inject IBrandingProvider BrandingProvider @inject IPageLayout PageLayout @@ -40,6 +42,8 @@ + @await Component.InvokeAsync(typeof(WidgetStylesViewComponent)) + @await RenderSectionAsync("styles", false) @await Component.InvokeLayoutHookAsync(LayoutHooks.Head.Last, StandardLayouts.Empty) @@ -59,6 +63,8 @@ @await RenderSectionAsync("scripts", false) + @await Component.InvokeAsync(typeof(WidgetScriptsViewComponent)) + @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.Last, StandardLayouts.Empty) \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/AbpAspNetCoreMvcUiThemeSharedModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/AbpAspNetCoreMvcUiThemeSharedModule.cs index fb8619b60a..28ae429028 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/AbpAspNetCoreMvcUiThemeSharedModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/AbpAspNetCoreMvcUiThemeSharedModule.cs @@ -2,6 +2,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Packages; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling; +using Volo.Abp.AspNetCore.Mvc.UI.Widgets; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; @@ -9,7 +10,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared { [DependsOn( typeof(AbpAspNetCoreMvcUiBootstrapModule), - typeof(AbpAspNetCoreMvcUiPackagesModule) + typeof(AbpAspNetCoreMvcUiPackagesModule), + typeof(AbpAspNetCoreMvcUiWidgetsModule) )] public class AbpAspNetCoreMvcUiThemeSharedModule : AbpModule { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj index 5bf8e5fcb1..898d7c1a0d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj @@ -39,6 +39,7 @@ + \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo.Abp.AspNetCore.Mvc.UI.Widgets.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo.Abp.AspNetCore.Mvc.UI.Widgets.csproj index 26634c6270..ae41d65026 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo.Abp.AspNetCore.Mvc.UI.Widgets.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo.Abp.AspNetCore.Mvc.UI.Widgets.csproj @@ -10,7 +10,12 @@ - + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/AbpAspNetCoreMvcUiWidgetsModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/AbpAspNetCoreMvcUiWidgetsModule.cs index cdf1db75cd..8f2d205cad 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/AbpAspNetCoreMvcUiWidgetsModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/AbpAspNetCoreMvcUiWidgetsModule.cs @@ -1,13 +1,22 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets { [DependsOn( - typeof(AbpAspNetCoreMvcUiBootstrapModule) - )] + typeof(AbpAspNetCoreMvcUiBootstrapModule), + typeof(AbpAspNetCoreMvcUiBundlingModule) + )] public class AbpAspNetCoreMvcUiWidgetsModule : AbpModule { - + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/WidgetResourcesViewModel.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/WidgetResourcesViewModel.cs new file mode 100644 index 0000000000..50bb947054 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/WidgetResourcesViewModel.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components +{ + public class WidgetResourcesViewModel + { + public IReadOnlyList Widgets { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/WidgetScripts/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/WidgetScripts/Default.cshtml new file mode 100644 index 0000000000..f81d2ad0f4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/WidgetScripts/Default.cshtml @@ -0,0 +1,21 @@ +@model Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetResourcesViewModel +@if (Model.Widgets.Any()) +{ + var resourceItems = Model.Widgets.SelectMany(w => w.Scripts).ToArray(); + if (resourceItems.Any()) + { + + @foreach (var resourceItem in resourceItems) + { + if (resourceItem.Src != null) + { + + } + else + { + + } + } + + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/WidgetScripts/WidgetScriptsViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/WidgetScripts/WidgetScriptsViewComponent.cs new file mode 100644 index 0000000000..88f7214c7a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/WidgetScripts/WidgetScriptsViewComponent.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetStyles; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetScripts +{ + public class WidgetScriptsViewComponent : AbpViewComponent + { + protected IPageWidgetManager PageWidgetManager { get; } + protected WidgetOptions Options { get; } + + public WidgetScriptsViewComponent( + IPageWidgetManager pageWidgetManager, + IOptions options) + { + PageWidgetManager = pageWidgetManager; + Options = options.Value; + } + + public virtual IViewComponentResult Invoke() + { + return View( + "~/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/WidgetScripts/Default.cshtml", + new WidgetResourcesViewModel + { + Widgets = PageWidgetManager.GetAll() + } + ); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/WidgetStyles/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/WidgetStyles/Default.cshtml new file mode 100644 index 0000000000..c253d2e83d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/WidgetStyles/Default.cshtml @@ -0,0 +1,21 @@ +@model Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetResourcesViewModel +@if (Model.Widgets.Any()) +{ + var resourceItems = Model.Widgets.SelectMany(w => w.Styles).ToArray(); + if (resourceItems.Any()) + { + + @foreach (var resourceItem in resourceItems) + { + if (resourceItem.Src != null) + { + + } + else + { + + } + } + + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/WidgetStyles/WidgetStylesViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/WidgetStyles/WidgetStylesViewComponent.cs new file mode 100644 index 0000000000..1a5b150b09 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/WidgetStyles/WidgetStylesViewComponent.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetStyles +{ + public class WidgetStylesViewComponent : AbpViewComponent + { + protected IPageWidgetManager PageWidgetManager { get; } + protected WidgetOptions Options { get; } + + public WidgetStylesViewComponent( + IPageWidgetManager pageWidgetManager, + IOptions options) + { + PageWidgetManager = pageWidgetManager; + Options = options.Value; + } + + public virtual IViewComponentResult Invoke() + { + return View( + "~/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/WidgetStyles/Default.cshtml", + new WidgetResourcesViewModel + { + Widgets = PageWidgetManager.GetAll() + } + ); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/_ViewImports.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/_ViewImports.cshtml new file mode 100644 index 0000000000..225780c2c2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/Components/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/IPageWidgetManager.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/IPageWidgetManager.cs new file mode 100644 index 0000000000..78ef563d82 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/IPageWidgetManager.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets +{ + public interface IPageWidgetManager + { + bool TryAdd(WidgetDefinition widget); + + IReadOnlyList GetAll(); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/PageWidgetManager.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/PageWidgetManager.cs new file mode 100644 index 0000000000..450bf492cf --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/PageWidgetManager.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.AspNetCore.Http; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets +{ + public class PageWidgetManager : IPageWidgetManager, IScopedDependency + { + public const string HttpContextItemName = "__AbpCurrentWidgets"; + + private readonly IHttpContextAccessor _httpContextAccessor; + + public PageWidgetManager(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public bool TryAdd(WidgetDefinition widget) + { + return GetWidgetList() + .AddIfNotContains(widget); + } + + private List GetWidgetList() + { + var httpContext = _httpContextAccessor.HttpContext; + if (httpContext == null) + { + throw new AbpException($"{typeof(PageWidgetManager).AssemblyQualifiedName} should be used in a web request! Can not get IHttpContextAccessor.HttpContext."); + } + + var widgets = httpContext.Items[HttpContextItemName] as List; + if (widgets == null) + { + widgets = new List(); + httpContext.Items[HttpContextItemName] = widgets; + } + + return widgets; + } + + public IReadOnlyList GetAll() + { + return GetWidgetList().ToImmutableArray(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetDefinition.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetDefinition.cs index b1b404215e..deac09e0c2 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetDefinition.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetDefinition.cs @@ -32,27 +32,85 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets public List RequiredPermissions { get; set; } + public List Styles { get; } + + public List Scripts { get; } + public WidgetDefinition( [NotNull] string name, - [CanBeNull] ILocalizableString displayName, - [NotNull] Type viewComponentType) + [NotNull] Type viewComponentType, + [CanBeNull] ILocalizableString displayName = null) { Name = Check.NotNullOrWhiteSpace(name, nameof(name)); ViewComponentType = Check.NotNull(viewComponentType, nameof(viewComponentType)); DisplayName = displayName ?? new FixedLocalizableString(name); + RequiredPermissions = new List(); + Styles = new List(); + Scripts = new List(); } - public WidgetDefinition AddRequiredPermission(string permissionName) + public WidgetDefinition WithPermission([NotNull] string permissionName) { + Check.NotNullOrWhiteSpace(permissionName, nameof(permissionName)); RequiredPermissions.Add(permissionName); return this; } - public WidgetDefinition SetDefaultDimension(int width, int height) + public WidgetDefinition WithDefaultDimensions(int width, int height) { DefaultDimensions = new WidgetDimensions(width, height); return this; } + + public WidgetDefinition WithStyles(params string[] files) + { + return WithResources(Styles, files); + } + + public WidgetDefinition WithStyles(params Type[] bundleContributorTypes) + { + return WithResources(Styles, bundleContributorTypes); + } + + public WidgetDefinition WithScripts(params string[] files) + { + return WithResources(Scripts, files); + } + + public WidgetDefinition WithScripts(params Type[] bundleContributorTypes) + { + return WithResources(Scripts, bundleContributorTypes); + } + + private WidgetDefinition WithResources(List resourceItems, Type[] bundleContributorTypes) + { + if (bundleContributorTypes.IsNullOrEmpty()) + { + return this; + } + + foreach (var bundleContributorType in bundleContributorTypes) + { + resourceItems.Add(new WidgetResourceItem(bundleContributorType)); + } + + return this; + } + + private WidgetDefinition WithResources(List resourceItems, string[] files) + { + if (files.IsNullOrEmpty()) + { + return this; + } + + foreach (var file in files) + { + resourceItems.Add(new WidgetResourceItem(file)); + } + + return this; + } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetRenderer.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetRenderer.cs index 821e53e853..c6c7b059d8 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetRenderer.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetRenderer.cs @@ -9,16 +9,18 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets public class WidgetRenderer : IWidgetRenderer { private readonly WidgetOptions _widgetOptions; + private readonly IPageWidgetManager _pageWidgetManager; - public WidgetRenderer(IOptions widgetOptions) + public WidgetRenderer(IOptions widgetOptions, IPageWidgetManager pageWidgetManager) { + _pageWidgetManager = pageWidgetManager; _widgetOptions = widgetOptions.Value; } public async Task RenderAsync(IViewComponentHelper componentHelper, string widgetName, object args = null) { var widget = _widgetOptions.Widgets.Single(w=>w.Name.Equals(widgetName)); - + _pageWidgetManager.TryAdd(widget); return await componentHelper.InvokeAsync(widget.ViewComponentType, args ?? new object()); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetResourceItem.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetResourceItem.cs new file mode 100644 index 0000000000..0e5cd195f6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetResourceItem.cs @@ -0,0 +1,24 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets +{ + public class WidgetResourceItem + { + [CanBeNull] + public string Src { get; } + + [CanBeNull] + public Type Type { get; } + + public WidgetResourceItem([NotNull] string src) + { + Src = Check.NotNullOrWhiteSpace(src, nameof(src)); + } + + public WidgetResourceItem([NotNull] Type type) + { + Type = Check.NotNull(type, nameof(type)); + } + } +} \ No newline at end of file