Screen injectors let you modify any existing administration screen — without subclassing it. You can add, remove, or replace information, actions, editors, columns, and other UI components on screens that ship with Dynamicweb or that are provided by other add-ins.
Tip
Injector or subclass? Use a screen injector when you want to change a screen you do not own — a Dynamicweb core screen or one from another add-in. Build your own edit, list, or overview screen by subclassing a ScreenBase when the data and screen are yours. You cannot subclass a core screen from a 3rd-party assembly and have Dynamicweb use it in place of the original — injectors are the supported way in.
Injectors are discovered automatically by the AddInManager. Create a class, inherit from the appropriate base, and your changes are applied the next time the screen renders — no registration required.
How it works
Every screen follows this lifecycle when it renders:
- OnBefore — all injectors for the screen type are called. The screen instance and its
Modelare available, but the layout has not been built yet. - GetDefinitionInternal — the screen builds its layout (form, list, overview widgets, infobar, actions, etc.). During this phase the specialized hooks (
OnBuildEditScreen,GetEditor,GetCell,GetScreenActions,GetListItemActions) are called. - OnAfter — all injectors are called again, now receiving the fully built
UiComponentBase content. You can traverse, modify, or replace any part of it.
Injector base classes
Dynamicweb provides three injector base classes, each tailored to a screen category.
ScreenInjector<T>
The general-purpose base class. Use it when you need direct access to the rendered content tree — for example to modify an overview screen's infobar or collapse a form group.
public abstract class ScreenInjector<T> where T : ScreenBase
{
public T? Screen { get; }
public virtual void OnBefore(T screen) { }
public virtual void OnAfter(T screen, UiComponentBase content) { }
}
EditScreenInjector<TScreen, TModel>
Specialized for edit screens. Adds a builder-based API to insert editors and form sections, and a hook to override the editor used for a specific property.
public abstract class EditScreenInjector<TScreen, TModel>
: ScreenInjector<TScreen>
where TScreen : EditScreenBase<TModel>, new()
where TModel : DataViewModelBase, new()
{
public virtual void OnBuildEditScreen(
EditScreenBase<TModel>.EditScreenBuilder builder) { }
public virtual EditorBase? GetEditor(
string propertyName, TModel? model) => null;
public virtual IEnumerable<ActionGroup>? GetScreenActions() => null;
}
The three hooks:
| Hook | What it does | Renders as |
|---|---|---|
OnBuildEditScreen(builder) |
Add editors/groups to existing or new tabs | New fields in the form |
GetEditor(propertyName, model) |
Replace the editor for a specific model property | A custom editor for that field |
GetScreenActions() |
Add action groups to the screen | Entries in the topbar Actions menu |
ListScreenInjector<TScreen, TRowModel>
Specialized for list screens. Lets you customize cell rendering, add toolbar actions, and add per-row context menu actions.
public abstract class ListScreenInjector<TScreen, TRowModel>
: ListScreenInjector<TScreen, DataListViewModel<TRowModel>, TRowModel>
where TScreen : ListScreenBase<DataListViewModel<TRowModel>, TRowModel>
where TRowModel : DataViewModelBase
{
public virtual Cell? GetCell(
string propertyName, TRowModel model) => null;
public virtual IEnumerable<ActionGroup>? GetScreenActions() => null;
public virtual IEnumerable<ActionGroup>? GetListItemActions(
TRowModel model) => null;
}
The three hooks:
| Hook | What it does | Renders as |
|---|---|---|
GetCell(propertyName, model) |
Replace the rendering of a specific column cell | A custom cell for that column |
GetScreenActions() |
Add action groups to the screen | Entries in the topbar Actions menu |
GetListItemActions(model) |
Add actions for a single row | Entries in that row's context menu |
Note
Two-parameter vs three-parameter ListScreenInjector. The two-parameter form above is a convenience wrapper for the common case where the list screen uses the default DataListViewModel<TRowModel>. If the screen you are targeting uses a custom list model (ListScreenBase<TCustomListModel, TRowModel>), inherit from the three-parameter base instead:
public abstract class ListScreenInjector<TScreen, TScreenModel, TRowModel>
where TScreen : ListScreenBase<TScreenModel, TRowModel>
where TScreenModel : DataListViewModel<TRowModel>
where TRowModel : DataViewModelBase
Use the three-parameter form when the target screen carries extra data on its list model (and you want Screen?.Model typed to it) — see the reorder example below, which targets OrderLineListScreen via its OrderLineListDataModel.
Accessing the current record
Inside any injector, Screen?.Model gives you the record the screen is showing — for example Screen?.Model?.Id. It can be null (a create screen has no existing record yet, or the query returned nothing), so always null-check it.
Examples
Modifying the infobar on an overview screen
Overview screens display an InfoBar at the top with key-value information, an optional image, append-components (badges, progress bars), and a primary action. You can modify every part of it.
The InfoBar has this structure:
| Property | Type | Description |
|---|---|---|
Image |
Image? |
Thumbnail shown on the left |
Icon |
Icon? |
Icon shown when no image is set |
Information |
Dictionary<string, InfoValue>? |
Key-value rows displayed in the bar |
AppendComponents |
List<UiComponentBase> |
Additional components (badges, progress, alerts) |
PrimaryAction |
ActionBase? |
Action triggered when clicking the bar |
InfoValue accepts several types through its constructors:
| Type | Rendering |
|---|---|
string |
Plain text |
DateTime? |
Formatted date |
bool |
Checkmark or cross icon |
DisplayBase |
Any UI display component |
(string, ActionBase) |
Text with a clickable action |
The following injector removes two default rows, adds four custom ones using different value types, modifies an existing row, changes the primary action, adds an alert component, and removes the progress bar:
using Dynamicweb.CoreUI.Screens;
using Dynamicweb.CoreUI.Layout;
using Dynamicweb.CoreUI.Displays.Information;
using Dynamicweb.CoreUI.Displays.Widgets;
using Dynamicweb.Products.UI.Screens;
public sealed class CustomProductInfoBarInjector
: ScreenInjector<ProductOverviewScreen>
{
public override void OnAfter(
ProductOverviewScreen screen, UiComponentBase content)
{
if (!content.TryGet<ScreenLayout>(out var layout))
return;
var infoBar = layout.InfoBar;
if (infoBar is null)
return;
// Remove rows by key
infoBar.Information?.Remove("Variants");
infoBar.Information?.Remove("Number");
// Add a string value
infoBar.Information?.Add("Brand", new InfoValue("Acme Corp"));
// Add a DateTime value
infoBar.Information?.Add("Created",
new InfoValue(screen.Model?.CreatedAt));
// Add a bool value — renders as checkmark or cross
infoBar.Information?.Add("Active",
new InfoValue(screen.Model?.Active ?? false));
// Add a string with a navigation action
infoBar.Information?.Add("Category", new InfoValue(
"Electronics",
NavigateScreenAction.To<CategoryOverviewScreen>()
.With(new CategoryByIdQuery { Id = 42 })));
// Overwrite an existing row
if (infoBar.Information is not null)
{
infoBar.Information["Name"] =
new InfoValue($"Custom: {screen.Model?.Name}");
}
// Change the primary action
infoBar.PrimaryAction =
NavigateScreenAction.To<ProductEditScreen>()
.With(new ProductByIdQuery { Id = screen.Model?.Id });
// Append an alert component
infoBar.AppendComponents.Add(new Alert
{
Value = "This product needs review",
Type = AlertType.Warning
});
// Remove the completeness progress bar
infoBar.AppendComponents.RemoveAll(
c => c is ProgressDisplay);
}
}
Note
You can also replace the infobar entirely by assigning a new instance to layout.InfoBar.
Adding fields to an edit screen
Use EditScreenInjector to add editors to an existing edit form. The builder places your components inside a named tab and group.
public sealed class CustomUserFieldsInjector
: EditScreenInjector<UserEditScreen, UserDataModel>
{
public override void OnBuildEditScreen(
EditScreenBase<UserDataModel>.EditScreenBuilder builder)
{
builder.AddComponents("Commerce", "Settings", new[]
{
builder.EditorFor(m => m.CustomerNumber),
builder.EditorFor(m => m.ShopId),
builder.EditorFor(m => m.Currency),
});
}
public override EditorBase? GetEditor(
string propertyName, UserDataModel? model)
{
return propertyName switch
{
nameof(model.ShopId) =>
EditorHelper.GetShopEditor(new[] { ShopType.Shop }),
nameof(model.Currency) =>
EditorHelper.GetCurrencyEditor(),
_ => null
};
}
}
For fields bound to model properties, use builder.EditorFor(m => m.Property). For external or computed data that is not on the model, construct the editor manually and set its Label, Value, and Readonly directly before adding it via builder.AddComponents(...).
Customizing cell rendering on a list screen
Use ListScreenInjector to change how specific columns render, and to add actions to the list toolbar or individual rows.
public sealed class CustomAreaListInjector
: ListScreenInjector<AreaListScreen, AreaDataModel>
{
public override Cell? GetCell(
string propertyName, AreaDataModel model)
{
return propertyName switch
{
nameof(AreaDataModel.EcomCountryCode) =>
Cell.MakeCell(new TextBlock
{
Value = GetCountryName(model.EcomCountryCode)
}),
_ => null
};
}
public override IEnumerable<ActionGroup>? GetListItemActions(
AreaDataModel model)
{
return new List<ActionGroup>
{
new()
{
Nodes =
[
new ActionNode("View details", Icon.Eye,
NavigateScreenAction.To<AreaOverviewScreen>()
.With(new AreaByIdQuery
{ Id = model.Id }))
]
}
};
}
private static string? GetCountryName(string? code)
{
if (string.IsNullOrEmpty(code))
return null;
var country = Ecommerce.Services.Countries
.GetCountry(code);
return country?.GetName(
Ecommerce.Services.Languages
.GetDefaultLanguageId());
}
}
Manipulating form structure in OnAfter
When the specialized hooks are not enough, use OnAfter to walk the rendered component tree directly. This example collapses a form group by default:
public sealed class CollapseAdvancedGroupInjector
: ScreenInjector<DiscountEditScreen>
{
public override void OnAfter(
DiscountEditScreen screen, UiComponentBase content)
{
if (content is not ScreenLayout layout
|| layout.Root is not Form form
|| form.Content is not TabContainer tabContainer)
return;
var groups = tabContainer.Tabs
.SelectMany(t => t.Section.Groups);
foreach (var group in groups)
{
if (string.Equals(group.Name, "Advanced",
StringComparison.OrdinalIgnoreCase))
{
group.Collapsed = true;
}
}
}
}
Reordering data in OnBefore
OnBefore runs before the screen builds its layout. Use it to preprocess or reorder the data model. This injector targets a list screen with a custom list model, so it uses the three-parameter ListScreenInjector:
public sealed class SortOrderLinesInjector
: ListScreenInjector<OrderLineListScreen,
OrderLineListDataModel, OrderLineDataModel>
{
public override void OnBefore(OrderLineListScreen screen)
{
if (screen.Model?.Data is null)
return;
// Ensure parent order lines appear before their children
screen.Model.Data = screen.Model.Data
.OrderBy(line => line.ParentLineId ?? line.Id)
.ThenBy(line => line.ParentLineId is null ? 0 : 1)
.ToList();
}
}
Navigating the content tree
In OnAfter, the content parameter is typically a ScreenLayout. Use the extension methods on UiComponentBase to find components within the tree:
| Method | Returns | Use case |
|---|---|---|
content.TryGet<T>(out var result) |
bool |
Guard clause — find the first component of type T |
content.Get<T>() |
T? |
Get first match or null |
content.Get<T>(predicate) |
T? |
First match satisfying a condition |
content.GetAll<T>() |
IReadOnlyCollection<T> |
All matches in the tree |
content.Has<T>() |
bool |
Check existence without retrieving |
Common traversal starting points:
ScreenLayout
├── InfoBar → layout.InfoBar
├── Alert → layout.Alert
├── Actions → layout.Actions / layout.ContextActionGroups
└── Root → layout.Root
├── Form → layout.Root as Form
│ └── TabContainer → form.Content as TabContainer
│ └── Tab[] → tabContainer.Tabs
│ └── Section → tab.Section
│ └── Group[] → section.Groups
└── List / other component types
Things to keep in mind
- Discovery is automatic. Your injector class is found by
AddInManagerthrough assembly scanning. Make sure your assembly is loaded — no manual registration is needed. - Multiple injectors can target the same screen. They all run in sequence. Do not assume your injector is the only one modifying the content.
- OnBefore has no layout. The
ScreenLayout,InfoBar, and other UI components do not exist yet duringOnBefore. Use it for data preparation only. - OnAfter content is mutable. The
UiComponentBasepassed toOnAfteris the live instance. Changes you make are reflected in the rendered screen. AlertusesValue, notText.Alertinherits its text content asValuefromDisplayBase<string>.- Null-safety matters. Always check for
null— the screen may not have a model (Screen?.Model), the layout may not have an infobar, andInformationdictionary keys may not exist. - Dictionary keys are the display labels. When removing or overwriting entries in
InfoBar.Information, the keys are the labels shown in the UI (e.g.,"Name","Number","Variants"). - The
new()constraint onEditScreenInjector. BothTScreenandTModelmust have a public parameterless constructor — which they do for standard screens andDataViewModelBasesubclasses.