Browse Source

Resolve #297: Widget system.

pull/1520/head
Halil İbrahim Kalkan 7 years ago
parent
commit
39b7abf4b4
  1. 234
      docs/en/AspNetCore/Widgets.md
  2. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/Components/Dashboard/DashboardViewComponent.cshtml.cs
  3. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/Components/Dashboard/DashboardViewModel.cs
  4. 31
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/AbpAspNetCoreMvcUiWidgetsModule.cs
  5. 51
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/AbpViewComponentHelper.cs
  6. 46
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetAttribute.cs
  7. 128
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetDefinition.cs
  8. 70
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetDefinitionCollection.cs
  9. 8
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetOptions.cs
  10. 15
      samples/DashboardDemo/src/DashboardDemo.Web/DashboardDemoWebModule.cs
  11. 16
      samples/DashboardDemo/src/DashboardDemo.Web/Pages/Components/MySimpleWidget/MySimpleWidgetViewComponent.cs
  12. 4
      samples/DashboardDemo/src/DashboardDemo.Web/Pages/MyWidgets.cshtml

234
docs/en/AspNetCore/Widgets.md

@ -1,17 +1,17 @@
# 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;
ABP provides a model and infrastructure to create **reusable widgets**. Widget system is an extension to [ASP.NET Core's ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components). Widgets are 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).
* Have **scripts & styles** dependencies for your widget.
* Create **[dashboards](Dashboards.md)** with widgets used inside.
* 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:
As the first step, create a new regular ASP.NET Core View Component:
![widget-basic-files](../images/widget-basic-files.png)
@ -33,6 +33,8 @@ namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
}
````
Inheriting from `AbpViewComponent` is not required. You could inherit from ASP.NET Core's standard `ViewComponent`. `AbpViewComponent` only defines some base useful properties.
**Default.cshtml**:
```xml
@ -44,35 +46,93 @@ namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
### Define the Widget
Second step is to define a widget using the `WidgetOptions` (in the `ConfigureServices` method of your Web module):
Add a `Widget` attribute to the `MySimpleWidgetViewComponent` class to mark this view component as a widget:
````csharp
Configure<WidgetOptions>(options =>
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
{
options.Widgets.Add(
new WidgetDefinition(
"MySimpleWidget", //Unique Widget name
typeof(MySimpleWidgetViewComponent) //Type of the ViewComponent
)
);
});
[Widget]
public class MySimpleWidgetViewComponent : AbpViewComponent
{
public IViewComponentResult Invoke()
{
return View();
}
}
}
````
Widget name should be unique in the application.
## Rendering a Widget
*TODO: This is in development and will probably change!*
Rendering a widget is pretty standard. Use the `Component.InvokeAsync` method in a razor view/page as you do for any view component. Examples:
### Render the Widget
````xml
@await Component.InvokeAsync("MySimpleWidget")
@await Component.InvokeAsync(typeof(MySimpleWidgetViewComponent))
````
Whenever you want to render a widget, you can inject the `IWidgetRenderer` and use the `RenderAsync` method with the unique widget name.
First approach uses the widget name while second approach uses the view component type.
````xml
@inject IWidgetRenderer WidgetRenderer
## Widget Name
Default name of the view components are calculated based on the name of the view component type. If your view component type is `MySimpleWidgetViewComponent` then the widget name will be `MySimpleWidget` (removes `ViewComponent` postfix). This is how ASP.NET Core calculates a view component's name.
To customize widget's name, just use the standard `ViewComponent` attribute of ASP.NET Core:
```csharp
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 will respect to the custom name by handling the widget.
@await WidgetRenderer.RenderAsync(Component, "MySimpleWidget")
> If the view component name and the folder name of the view component don't match, you may need to manually write the view path as done in this example.
### Display Name
You can also define a human-readable, localizable display name for the widget. This display name then can be used on the UI when needed. Display name is optional and can be defined using properties of the `Widget` attribute:
````csharp
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", //Localization key
DisplayNameResource = typeof(DashboardDemoResource) //localization resource
)]
public class MySimpleWidgetViewComponent : AbpViewComponent
{
public IViewComponentResult Invoke()
{
return View();
}
}
}
````
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.
See [the localization document](../Localization.md) to learn about localization resources and keys.
## Style & Script Dependencies
@ -81,4 +141,134 @@ 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.
ABP solves these issues when you properly relate the resources with the widget. You don't care about dependencies of the widget while using it.
### Defining as Simple File Paths
The example widget below adds a style and a script file:
````csharp
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
{
[Widget(
StyleSrcs = new[] { "/Pages/Components/MySimpleWidget/Default.css" },
ScriptSrcs = new[] { "/Pages/Components/MySimpleWidget/Default.js" }
)]
public class MySimpleWidgetViewComponent : AbpViewComponent
{
public IViewComponentResult Invoke()
{
return View();
}
}
}
````
ABP takes account these dependencies and properly adds to the view/page when you use the widget. Style/script files can be **physical or virtual**. It is completely integrated to the [Virtual File System](../Virtual-File-System.md).
### Defining Bundle Contributors
All resources for used widgets in a page are added as a **bundle** (bundled & minified in production if you don't configure otherwise). In addition to adding a simple file, you can take full power of the bundle contributors.
The sample code below does the same with the code above, but defines and uses bundle contributors:
````csharp
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");
}
}
}
````
Bundle contribution system is very powerful. If your widget uses a JavaScript library to render a chart, then you can declare it as a dependency, so the JavaScript library is automatically added to the page if it wasn't added before. In this way, the page using your widget doesn't care about the dependencies.
See the [bundling & minification](Bundling-Minification.md) documentation for more information about that system.
## Authorization
Some widgets may need to be available only for authenticated or authorized users. In this case, use the following properties of the `Widget` attribute:
* `RequiresAuthentication` (`bool`): Set to true to make this widget usable only for authentication users (user have logged in to the application).
* `RequiredPolicies` (`List<string>`): A list of policy names to authorize the user. See [the authorization document](../Authorization.md) for more info about policies.
Example:
````csharp
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();
}
}
}
````
## Widget Options
As alternative to the `Widget` attribute, you can use the `WidgetOptions` to configure widgets:
```csharp
Configure<WidgetOptions>(options =>
{
options.Widgets.Add<MySimpleWidgetViewComponent>();
});
```
Write this into the `ConfigureServices` method of your [module](../Module-Development-Basics.md). All the configuration done with the `Widhet` attribute is also possible with the `WidgetOptions`. Example configuration that adds a style for the widget:
````csharp
Configure<WidgetOptions>(options =>
{
options.Widgets
.Add<MySimpleWidgetViewComponent>()
.WithStyles("/Pages/Components/MySimpleWidget/Default.css");
});
````
> Tip: `WidgetOptions` can also be used to get an existing widget and change its configuration. This is especially useful if you want to modify the configuration of a widget inside a module used by your application. Use `options.Widgets.Find` to get an existing `WidgetDefinition`.

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/Components/Dashboard/DashboardViewComponent.cshtml.cs

@ -23,7 +23,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Dashboards.Components.Dashboard
{
var dashboard = _dashboardOptions.Dashboards.Single(d => d.Name.Equals(dashboardName));
var model = new DashboardViewModel(dashboard, _widgetOptions.Widgets, _globalFilterOptions.GlobalFilters);
var model = new DashboardViewModel(dashboard, _widgetOptions.Widgets.GetAll().ToList(), _globalFilterOptions.GlobalFilters);
return View("~/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/Components/Dashboard/Default.cshtml", model);
}

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/Components/Dashboard/DashboardViewModel.cs

@ -34,7 +34,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Dashboards.Components.Dashboard
public async Task<bool> CheckPermissionsAsync(IAuthorizationService authorizationService, WidgetDefinition widget)
{
foreach (var permission in widget.RequiredPermissions)
foreach (var permission in widget.RequiredPolicies)
{
if (!await authorizationService.IsGrantedAsync(permission))
{

31
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/AbpAspNetCoreMvcUiWidgetsModule.cs

@ -1,4 +1,7 @@
using Microsoft.AspNetCore.Mvc.ViewComponents;
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
@ -13,6 +16,11 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets
)]
public class AbpAspNetCoreMvcUiWidgetsModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
AutoAddWidgets(context.Services);
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddTransient<DefaultViewComponentHelper>();
@ -22,5 +30,26 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets
options.FileSets.AddEmbedded<AbpAspNetCoreMvcUiWidgetsModule>();
});
}
private static void AutoAddWidgets(IServiceCollection services)
{
var widgetTypes = new List<Type>();
services.OnRegistred(context =>
{
if (WidgetAttribute.IsWidget(context.ImplementationType))
{
widgetTypes.Add(context.ImplementationType);
}
});
services.Configure<WidgetOptions>(options =>
{
foreach (var widgetType in widgetTypes)
{
options.Widgets.Add(new WidgetDefinition(widgetType));
}
});
}
}
}

51
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/AbpViewComponentHelper.cs

@ -1,13 +1,16 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.Options;
using Volo.Abp.Authorization;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Users;
namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets
{
@ -16,43 +19,67 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets
{
protected WidgetOptions Options { get; }
protected IPageWidgetManager PageWidgetManager { get; }
protected IAuthorizationService AuthorizationService { get; }
protected ICurrentUser CurrentUser { get; }
protected DefaultViewComponentHelper DefaultViewComponentHelper { get; }
public AbpViewComponentHelper(
DefaultViewComponentHelper defaultViewComponentHelper,
IOptions<WidgetOptions> widgetOptions,
IPageWidgetManager pageWidgetManager)
IPageWidgetManager pageWidgetManager,
IAuthorizationService authorizationService,
ICurrentUser currentUser)
{
DefaultViewComponentHelper = defaultViewComponentHelper;
PageWidgetManager = pageWidgetManager;
AuthorizationService = authorizationService;
CurrentUser = currentUser;
Options = widgetOptions.Value;
}
public Task<IHtmlContent> InvokeAsync(string name, object arguments)
public virtual async Task<IHtmlContent> InvokeAsync(string name, object arguments)
{
var widget = Options.Widgets.FirstOrDefault(w => w.Name == name); //Optimize using a dictionary by name
if (widget != null)
var widget = Options.Widgets.Find(name);
if (widget == null)
{
PageWidgetManager.TryAdd(widget);
return await DefaultViewComponentHelper.InvokeAsync(name, arguments);
}
return DefaultViewComponentHelper.InvokeAsync(name, arguments);
return await InvokeWidgetAsync(arguments, widget);
}
public Task<IHtmlContent> InvokeAsync(Type componentType, object arguments)
public virtual async Task<IHtmlContent> InvokeAsync(Type componentType, object arguments)
{
var widget = Options.Widgets.FirstOrDefault(w => w.ViewComponentType == componentType); //Optimize using a dictionary by type
if (widget != null)
var widget = Options.Widgets.Find(componentType);
if (widget == null)
{
PageWidgetManager.TryAdd(widget);
return await DefaultViewComponentHelper.InvokeAsync(componentType, arguments);
}
return DefaultViewComponentHelper.InvokeAsync(componentType, arguments);
return await InvokeWidgetAsync(arguments, widget);
}
public void Contextualize(ViewContext viewContext)
public virtual void Contextualize(ViewContext viewContext)
{
DefaultViewComponentHelper.Contextualize(viewContext);
}
protected virtual async Task<IHtmlContent> InvokeWidgetAsync(object arguments, WidgetDefinition widget)
{
if (widget.RequiredPolicies.Any())
{
foreach (var requiredPolicy in widget.RequiredPolicies)
{
await AuthorizationService.AuthorizeAsync(requiredPolicy);
}
}
else if (widget.RequiresAuthentication && !CurrentUser.IsAuthenticated)
{
throw new AbpAuthorizationException("Authorization failed! User has not logged in.");
}
PageWidgetManager.TryAdd(widget);
return await DefaultViewComponentHelper.InvokeAsync(widget.ViewComponentType, arguments);
}
}
}

46
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetAttribute.cs

@ -0,0 +1,46 @@
using System;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Mvc;
namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets
{
[AttributeUsage(AttributeTargets.Class)]
public class WidgetAttribute : Attribute
{
[CanBeNull]
public string[] StyleSrcs { get; set; }
[CanBeNull]
public Type[] StyleTypes { get; set; }
[CanBeNull]
public string[] ScriptSrcs { get; set; }
[CanBeNull]
public Type[] ScriptTypes { get; set; }
[CanBeNull]
public string DisplayName { get; set; }
[CanBeNull]
public Type DisplayNameResource { get; set; }
[CanBeNull]
public string[] RequiredPolicies { get; set; }
public bool RequiresAuthentication { get; set; }
public static bool IsWidget(Type type)
{
return type.IsSubclassOf(typeof(ViewComponent)) &&
type.IsDefined(typeof(WidgetAttribute), true);
}
public static WidgetAttribute Get(Type viewComponentType)
{
return viewComponentType.GetCustomAttribute<WidgetAttribute>(true)
?? throw new AbpException($"Given type '{viewComponentType.AssemblyQualifiedName}' does not declare a {typeof(WidgetAttribute).AssemblyQualifiedName}");
}
}
}

128
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetDefinition.cs

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Localization;
namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets
@ -13,6 +15,9 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets
[NotNull]
public string Name { get; }
[NotNull]
public WidgetAttribute WidgetAttribute { get; }
/// <summary>
/// Display name of the widget.
/// </summary>
@ -27,33 +32,130 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets
[NotNull]
public Type ViewComponentType { get; }
[CanBeNull]
public WidgetDimensions DefaultDimensions { get; set; }
[NotNull]
public List<string> RequiredPolicies { get; }
public List<string> RequiredPermissions { get; set; }
/// <summary>
/// Set true to make this Widget available only for authenticated users.
/// This property is not considered if <see cref="RequiredPolicies"/> is already set.
/// </summary>
public bool RequiresAuthentication { get; set; }
[NotNull]
public List<WidgetResourceItem> Styles { get; }
[NotNull]
public List<WidgetResourceItem> Scripts { get; }
[CanBeNull]
public WidgetDimensions DefaultDimensions { get; set; }
public WidgetDefinition(
[NotNull] string name,
[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<string>();
Styles = new List<WidgetResourceItem>();
Scripts = new List<WidgetResourceItem>();
WidgetAttribute = WidgetAttribute.Get(viewComponentType);
Name = GetWidgetName(viewComponentType);
DisplayName = displayName ?? GetDisplayName(WidgetAttribute, Name);
RequiredPolicies = GetRequiredPolicies(WidgetAttribute);
Styles = GetStyles(WidgetAttribute);
Scripts = GetScripts(WidgetAttribute);
}
public WidgetDefinition WithPermission([NotNull] string permissionName)
private static List<WidgetResourceItem> GetStyles(WidgetAttribute widgetAttribute)
{
Check.NotNullOrWhiteSpace(permissionName, nameof(permissionName));
RequiredPermissions.Add(permissionName);
var styles = new List<WidgetResourceItem>();
if (!widgetAttribute.StyleSrcs.IsNullOrEmpty())
{
styles.AddRange(widgetAttribute.StyleSrcs.Select(src => new WidgetResourceItem(src)));
}
if (!widgetAttribute.StyleTypes.IsNullOrEmpty())
{
styles.AddRange(widgetAttribute.StyleTypes.Select(type => new WidgetResourceItem(type)));
}
return styles;
}
private static List<WidgetResourceItem> GetScripts(WidgetAttribute widgetAttribute)
{
var scripts = new List<WidgetResourceItem>();
if (!widgetAttribute.ScriptSrcs.IsNullOrEmpty())
{
scripts.AddRange(widgetAttribute.ScriptSrcs.Select(src => new WidgetResourceItem(src)));
}
if (!widgetAttribute.ScriptTypes.IsNullOrEmpty())
{
scripts.AddRange(widgetAttribute.ScriptTypes.Select(type => new WidgetResourceItem(type)));
}
return scripts;
}
private static List<string> GetRequiredPolicies(WidgetAttribute widgetAttribute)
{
var policies = new List<string>();
if (!widgetAttribute.RequiredPolicies.IsNullOrEmpty())
{
policies.AddRange(widgetAttribute.RequiredPolicies);
}
return policies;
}
private static string GetWidgetName(Type viewComponentType)
{
var viewComponentAttr = viewComponentType
.GetCustomAttributes(typeof(ViewComponentAttribute), true)
.FirstOrDefault() as ViewComponentAttribute;
if (viewComponentAttr?.Name != null)
{
return viewComponentAttr.Name;
}
return viewComponentType.Name.RemovePostFix("ViewComponent");
}
private static ILocalizableString GetDisplayName(WidgetAttribute widgetAttribute, string widgetName)
{
if (widgetAttribute.DisplayName == null)
{
return new FixedLocalizableString(widgetName);
}
if (widgetAttribute.DisplayNameResource == null)
{
return new FixedLocalizableString(widgetAttribute.DisplayName);
}
return new LocalizableString(widgetAttribute.DisplayNameResource, widgetAttribute.DisplayName);
}
public WidgetDefinition WithRequiredPolicies(params string[] policyNames)
{
foreach (var policyName in policyNames)
{
RequiredPolicies.Add(policyName);
}
return this;
}
/// <summary>
/// Set true to make this Widget available only for authenticated users.
/// This value is not considered if <see cref="RequiredPolicies"/> is already set.
/// </summary>
public WidgetDefinition WithRequiresAuthentication(bool value = true)
{
RequiresAuthentication = value;
return this;
}
@ -67,7 +169,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets
{
return WithResources(Styles, files);
}
public WidgetDefinition WithStyles(params Type[] bundleContributorTypes)
{
return WithResources(Styles, bundleContributorTypes);

70
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetDefinitionCollection.cs

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using JetBrains.Annotations;
using Volo.Abp.Localization;
namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets
{
public class WidgetDefinitionCollection
{
private readonly Dictionary<string, WidgetDefinition> _widgetsByName;
private readonly Dictionary<Type, WidgetDefinition> _widgetsByType;
public WidgetDefinitionCollection()
{
_widgetsByName = new Dictionary<string, WidgetDefinition>();
_widgetsByType = new Dictionary<Type, WidgetDefinition>();
}
public void Add(WidgetDefinition widget)
{
var existingWidget = _widgetsByName.GetOrDefault(widget.Name);
if (existingWidget != null)
{
_widgetsByType[existingWidget.ViewComponentType] = widget;
}
_widgetsByName[widget.Name] = widget;
_widgetsByType[widget.ViewComponentType] = widget;
}
public WidgetDefinition Add<TViewComponent>(
[CanBeNull] ILocalizableString displayName = null)
{
return Add(typeof(TViewComponent), displayName);
}
public WidgetDefinition Add(
[NotNull] Type viewComponentType,
[CanBeNull] ILocalizableString displayName = null)
{
var widget = new WidgetDefinition(viewComponentType, displayName);
Add(widget);
return widget;
}
[CanBeNull]
public WidgetDefinition Find(string name)
{
return _widgetsByName.GetOrDefault(name);
}
[CanBeNull]
public WidgetDefinition Find<TViewComponent>()
{
return Find(typeof(TViewComponent));
}
[CanBeNull]
public WidgetDefinition Find(Type viewComponentType)
{
return _widgetsByType.GetOrDefault(viewComponentType);
}
public IReadOnlyCollection<WidgetDefinition> GetAll()
{
return _widgetsByName.Values.ToImmutableArray();
}
}
}

8
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetOptions.cs

@ -1,14 +1,12 @@
using System.Collections.Generic;
namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets
namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets
{
public class WidgetOptions
{
public List<WidgetDefinition> Widgets { get; }
public WidgetDefinitionCollection Widgets { get; }
public WidgetOptions()
{
Widgets = new List<WidgetDefinition>();
Widgets = new WidgetDefinitionCollection();
}
}
}

15
samples/DashboardDemo/src/DashboardDemo.Web/DashboardDemoWebModule.cs

@ -76,7 +76,6 @@ namespace DashboardDemo.Web
ConfigureNavigationServices();
ConfigureAutoApiControllers();
ConfigureSwaggerServices(context.Services);
ConfigureWidgets();
}
private void ConfigureUrls(IConfigurationRoot configuration)
@ -172,20 +171,6 @@ namespace DashboardDemo.Web
);
}
private void ConfigureWidgets()
{
Configure<WidgetOptions>(options =>
{
options.Widgets.Add(
new WidgetDefinition(
"MyCustomNameWidget",
typeof(MySimpleWidgetViewComponent))
.WithStyles("/Pages/Components/MySimpleWidget/Default.css")
.WithScripts("/Pages/Components/MySimpleWidget/Default.js")
);
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();

16
samples/DashboardDemo/src/DashboardDemo.Web/Pages/Components/MySimpleWidget/MySimpleWidgetViewComponent.cs

@ -2,10 +2,14 @@
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
{
[ViewComponent(Name = "MyCustomNameWidget")]
[Widget(
StyleTypes = new[] { typeof(MySimpleWidgetStyleBundleContributor) },
ScriptTypes = new[] { typeof(MySimpleWidgetScriptBundleContributor) }
)]
public class MySimpleWidgetViewComponent : AbpViewComponent
{
public IViewComponentResult Invoke()
@ -21,4 +25,12 @@ namespace DashboardDemo.Web.Pages.Components.MySimpleWidget
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");
}
}
}

4
samples/DashboardDemo/src/DashboardDemo.Web/Pages/MyWidgets.cshtml

@ -4,8 +4,6 @@
@await Component.InvokeAsync(typeof(MySimpleWidgetViewComponent))
<hr />
@await Component.InvokeAsync("MyCustomNameWidget")
@await Component.InvokeAsync("MySimpleWidget")
<hr />
<vc:my-custom-name-widget></vc:my-custom-name-widget>
@*<hr />
@await WidgetRenderer.RenderAsync(Component, "MySimpleWidget")*@

Loading…
Cancel
Save