UI Extensibility
How to extend the administration interface.
When it comes to UI extensibility, DW10 is a lot more flexible than DW9 was - you can:
- Create custom areas (like Users and Commerce)
- Create custom area trees – or change an existing one
- Implement custom screens
- Build custom commands, queries & models
Custom Areas
To create a custom area you create a new class in your project and inherit from Dynamicweb.CoreUI.Application.AreaBase. You then implement the constructor and set properties such as Name, Icon, and Sort:
public sealed class FilesArea : AreaBase
{
public FilesArea()
{
Name = "Digital Assets";
Icon = Icon.Folder;
Sort = 30;
}
}
Add it to your solution using the DynamicWeb CLI.
Custom Area Trees
To create custom area trees you first create a class...
Public class CustomSection : NavigationSection<Area>
...then implement the constructor and fill out name and sort:
public sealed class MonitoringSection : NavigationSection<InsightsArea>
{
public MonitoringSection(NavigationContext context) : base(context)
{
Name = "Monitoring";
Sort = 30;
ContextActions.Add(CommonActionsHelper.GetPermissionAction());
}
}
A section, then, is basically a label somewhere in the area tree - to add nodes to a section you need to implement a NavigationNodeProvider and override the default implementation of GetRootNodes()
public sealed class MonitoringNodeProvider : NavigationNodeProvider<MonitoringSection>
{
private const string SystemLogRootPath = "/Files/System/Log";
private static readonly string SystemLogRootFolder = SystemInformation.MapPath(SystemLogRootPath);
public override IEnumerable<NavigationNode> GetRootNodes()
{
var nodes = new List<NavigationNode>();
nodes.Add(new NavigationNode
{
Name = "Reports",
Id = "Reports",
Icon = Icon.FileCheckAlt,
HasSubNodes = true,
ContextActions = GetContextActions(),
});
return nodes;
}
public override IEnumerable<NavigationNode> GetSubNodes(NavigationNodePath parentNodePath)
{
var nodes = new List<NavigationNode>();
var parentNodeId = parentNodePath.First;
if (parentNodeId.Equals(ReportsNodeId, StringComparison.OrdinalIgnoreCase))
{
nodes.AddRange(GetReportNodes());
}
return nodes;
}
private static IEnumerable<NavigationNode> GetReportNodes()
{
var nodes = new List<NavigationNode>();
nodes.Add(new NavigationNode
{
Name = "Scheduled tasks",
Id = "ScheduledTasks",
Icon = Icon.ClockFive,
ContextActions = GetContextActions(),
NodeAction = new NavigateScreenAction<ScheduledTaskListScreen>(new ScheduledTaskAllQuery()),
});
return nodes;
}
private static List<ActionNode> GetContextActions()
{
return new List<ActionNode>
{
new ActionNode { Name = "Permissions", Icon = Icon.Lock, Sort = 10, NodeAction = new AlertAction("HI MOM") },
};
}
}
Add to your solution using the DynamicWeb CLI.
Customized Screens
If you want to build a customized screen you can use one of our built-in ScreenTypes:
- ListScreen
- EditScreen
- OverviewScreen
When you use one of the standard ScreenTypes, you don’t have to worry about the look and feel of your screen, as we’ve taken care of that part – you just need to concentrate about your data.
All of the standard ScreenTypes are located in Dynamicweb.CoreUI.Screens, and contain methods that you need to override to put something on the screen.
In the example below we inherit from ListScreenBase – in this case for the HealthProviderCheckDataModel – and use two overrides to add Name and Columns to the screen:
public sealed class HealthProviderCheckListScreen : ListScreenBase<HealthProviderCheckDataModel>
{
// Override default values
protected override string GetScreenName() => "Health checks";
protected override IEnumerable<ListViewMapping> GetViewMappings()
{
var rowMapping = new RowViewMapping
{
Columns = new System.Collections.Generic.List<ModelMapping>
{
CreateMapping(p => p.State),
CreateMapping(p => p.Name),
CreateMapping(p => p.Description),
CreateMapping(p => p.Count)
}
};
return new ListViewMapping[] { rowMapping };
}
}
Custom Screens
If you want more control over how a screen looks, of if you want to show off a combination of data for which we have no data model, you can build a truly custom screen. This is a bit more difficult than using a built-in ScreenType, but also affords you more freedom.
You must:
- Create a ProjectFile and configure it
- Make sure the DynamicWeb 10 application loads Views from your custom project
- Add content to the custom screen
You can read about each of these steps below.
Creating the custom project
First you should create an empty ASP.NET Core Web App in Visual Studio:
- Create the ProjectFile and toggle RazorSupportForMVC and GenerateEmbeddedFilesManifest
- Add a reference to the Microsoft.AspNetCore.App framework
- Add a package reference to Microsoft.Extensions.FileProvider.Embedded version 6.0.6
Your project file will look something like this:
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="wwwroot/**/*" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="6.0.6" />
</ItemGroup>
</Project>
Add a reference to IRenderBundle
Since this custom project will contain custom views and static files, we need to tell DynamicWeb to look for Views inside this dll when the application is loaded. To do this, you create a class which inherits from Dynamicweb.CoreUI.Rendering.IRenderingBundle
:
public class RenderingBundle : Dynamicweb.CoreUI.Rendering.IRenderingBundle
The class doesn’t need to have any functionality, it just needs to be there.
Add content to the custom screen
The first thing you want to do is create a component – this is where you define what data is available for the screen, as well as set various properties like a header and whether to show our standard search bar, etc. A component should inherit from Dynamicweb.CoreUi.UIComponentBase, but all other content is up to you.
In this example we’re creating a component called ExpressDeliveryWidget which contains a Header and some other data related to shipping and delivery:
- ShippingLimit: Total hours given to ship a parcel
- ShippingComment: A comment field
- RemainingTime: How much time is left to fulfil this shipping request
public sealed class ExpressDeliveryWidget : UiComponentBase
{
public string Header { get; set; } = "";
public int ShippingLimit { get; set; }
public string ShippingComment { get; set; } = "";
public TimeSpan RemainingTime { get; internal set; }
}
When the component has been defined, it’s time to define how it should look. You do this by creating a matching View. To ensure that a custom view is properly loaded, it must be placed in the same hierarchy we use:
Views\Shared\Components\UiComponentWrapper
The name of the a View should be the full name of the Component, so if you have a component called "CustomComponent", and it’s located in the namespace "MyCustomComponent.Components", the full name would be MyCustomComponent.Components.CustomComponent.
In this case our View looks like this:
@using Dynamicweb.CoreUI.Rendering;
@model ExpressDelivery.Components.ExpressDeliveryWidget
<div>
<script src="/ExpressDelivery/js/ExpressDeliveryWidget.js" type="module"></script>
<express-delivery-widget class="h-100" style="border: var(--dw-border);border-radius: 0.5rem;">
<div class="info-card-header">
<div class="flex-fill">
<h6 class="dw-group-header">@Model.Header</h6>
</div>
</div>
<div class="widget-content d-flex justify-content-center">
<div class="list-widget flex-nowrap flex-column w-75">
@if (Model.RemainingTime > TimeSpan.Zero)
{
<div class="flex-row m-3">
@await Component.RenderDynamicwebUiComponent(new Dynamicweb.CoreUI.Displays.Widgets.ProgressWidget((int)Math.Floor(Model.RemainingTime.TotalHours)){ProgressType = Dynamicweb.CoreUI.Displays.Widgets.ProgressWidgetType.Bar, HideSummary = true, OutOf = Model.ShippingLimit})
<span>Hours remaining until shipment: @Model.RemainingTime.ToString("d' days 'hh' hours 'mm' minutes 'ss' seconds'") </span>
</div>
}
<div class="flex-row">
<h3 style="text-align:center">@Model.ShippingComment</h3>
</div>
</div>
</div>
</express-delivery-widget>
</div>
Creating Custom Commands, Queries & Models
When you’re building custom Views you need to fetch and show data to your users, and often you also want your users to do stuff with the data - like edit and save a user, etc. To do so you can use our built-in queries & commands – you can see the built-in types under Settings > Developer > Api Explorer.
When we don’t have standard queries and commands suited to your needs you can build something custom: