Repository navigation
workspace sdk config pages
Configuration pages extend the entity configuration interface in Config Tool. When a user selects an entity in the entity browser, the system displays configuration pages registered for that entity type. Custom configuration pages let you add entity-specific settings that integrate with the standard configuration workflow.
Configuration pages can target any entity type: cameras, doors, cardholders, areas, roles, custom entities, and more. Almost every plugin includes at least one configuration page to manage its settings.
The two most common use cases are:
-
Plugin configuration: Adding settings pages for plugin roles, storing data in
SpecificConfigurationandRestrictedConfiguration -
Custom entity configuration: Adding property pages for custom entity types, storing data in the entity's
Xmlproperty
The configuration page lifecycle follows these steps:
-
Registration: The module registers the configuration page with
IConfigurationServiceduring load -
Entity selection: When a user selects an entity, the system calls the
Entitysetter with the entity GUID -
Visibility: The configuration page sets
IsVisiblebased on whether it handles the selected entity -
Refresh: The system calls
Refresh()to load the entity configuration into the view -
Modification: User changes trigger property updates that set
IsDirty = true -
Validation: Before saving, the system calls
BeforeSave()to validate the configuration -
Save: The system calls
Save()to persist the configuration to the entity
Register configuration pages with IConfigurationService in the module's Load method.
public class SampleModule : Module
{
private CustomConfigPage m_configPage;
public override void Load()
{
if (Workspace.ApplicationType is ApplicationType.ConfigTool)
{
m_configPage = new CustomConfigPage();
m_configPage.Initialize(Workspace);
Workspace.Services.Get<IConfigurationService>().Register(m_configPage);
}
}
public override void Unload()
{
if (m_configPage != null)
{
Workspace.Services.Get<IConfigurationService>().Unregister(m_configPage);
m_configPage = null;
}
}
}Configuration pages are only available in Config Tool.
Configuration pages use the Model-View-ViewModel (MVVM) pattern. The ConfigPage class is both the page container and the view model, implementing INotifyPropertyChanged through the inherited OnPropertyChanged method.
The pattern works as follows:
- Model: The entity being configured (role, custom entity, and so on) and its serialized configuration data
-
ViewModel: The
ConfigPageclass exposes properties that the view binds to -
View: A WPF
UserControlassigned to theViewproperty, bound to theConfigPageviaDataContext
When a property changes, call OnPropertyChanged to notify the view, then set IsDirty = true to enable the Save button.
private int m_port;
public int Port
{
get => m_port;
set
{
if (m_port != value)
{
m_port = value;
OnPropertyChanged(nameof(Port));
IsDirty = true;
}
}
}A helper method simplifies this pattern. Define this method in your ConfigPage class:
private bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(storage, value))
{
return false;
}
storage = value;
OnPropertyChanged(propertyName);
return true;
}
public int Port
{
get => m_port;
set
{
if (SetProperty(ref m_port, value))
{
IsDirty = true;
}
}
}The SetProperty method is not inherited from ConfigPage. You must implement it yourself or use a base class that provides it.
Plugin configuration pages target the EntityType.Role entity type and filter by the plugin's SubType GUID. The SubType is the unique identifier assigned to your plugin when it was created. Configuration data is stored in two locations:
- SpecificConfiguration: Non-sensitive settings serialized as JSON or XML
- RestrictedConfiguration: Sensitive data like passwords and API keys, stored encrypted
Important
Configuration pages must have a public parameterless constructor. The system uses Activator.CreateInstance to instantiate configuration pages.
internal class PluginConfigPage : ConfigPage
{
private readonly RoleConfiguration m_configuration = new();
private Role m_role;
private IPAddress m_ipAddress;
private int m_port;
private SecureString m_password;
protected override EntityType EntityType => EntityType.Role;
protected override Guid Entity
{
set
{
if (Workspace.Sdk.GetEntity(value) is Role role
&& role.SubType == PluginTypes.SamplePlugin)
{
m_role = role;
IsVisible = true;
}
else
{
IsVisible = false;
}
}
}
public IPAddress IPAddress
{
get => m_ipAddress;
set
{
if (SetProperty(ref m_ipAddress, value))
{
IsDirty = true;
}
}
}
public int Port
{
get => m_port;
set
{
if (SetProperty(ref m_port, value))
{
IsDirty = true;
}
}
}
public SecureString Password
{
get => m_password;
set
{
if (SetProperty(ref m_password, value))
{
IsDirty = true;
}
}
}
protected override void Initialize()
{
View = new PluginConfigPageView { DataContext = this };
}
protected override void Refresh()
{
m_configuration.Load(m_role.SpecificConfiguration);
IPAddress = m_configuration.IPAddress;
Port = m_configuration.Port;
IsDirty = false;
}
protected override void Save()
{
m_configuration.IPAddress = IPAddress;
m_configuration.Port = Port;
Workspace.Sdk.TransactionManager.ExecuteTransaction(() =>
{
m_role.SpecificConfiguration = m_configuration.Serialize();
if (m_password != null)
{
m_role.RestrictedConfiguration.SetPrivateValue("Password", m_password);
m_password = null;
}
});
}
}The TransactionManager.ExecuteTransaction() method wraps entity modifications in a transaction that automatically commits on success or rolls back on failure. Use it when modifying multiple entity properties to ensure atomic updates.
Create a configuration class to serialize non-sensitive settings to JSON.
public class RoleConfiguration
{
private IPAddress m_ipAddress = IPAddress.Loopback;
private int m_port = 5660;
[JsonIgnore]
public IPAddress IPAddress
{
get => m_ipAddress;
set => m_ipAddress = value;
}
[JsonProperty(nameof(IPAddress))]
private string IPAddressString
{
get => m_ipAddress.ToString();
set
{
if (IPAddress.TryParse(value, out var ipAddress))
{
m_ipAddress = ipAddress;
}
}
}
public int Port
{
get => m_port;
set => m_port = value;
}
public void Load(string data)
{
if (!string.IsNullOrEmpty(data))
{
JsonConvert.PopulateObject(data, this);
}
}
public string Serialize() => JsonConvert.SerializeObject(this);
}Use RestrictedConfiguration for sensitive data that must be encrypted at rest. A configuration page can write private values with SetPrivateValue, but only the server-side plugin that owns the role can read them.
| Method | Description |
|---|---|
SetPrivateValue(string key, SecureString value) |
Stores an encrypted value. Requires the EditRoleProperties privilege for the target role. Use this from ConfigPage. |
GetPrivateValue(string key) |
Retrieves a decrypted value. Only callable from the server-side plugin. |
TryGetPrivateValue(string key, out SecureString value) |
Attempts to retrieve a value without throwing if the key does not exist. Only callable from the server-side plugin. |
DeletePrivateValue(string key) |
Removes a stored value. Requires the EditRoleProperties privilege for the target role. |
Important
From a ConfigPage, you can only write private values using SetPrivateValue. Reading private values with GetPrivateValue or TryGetPrivateValue is restricted to the server-side plugin that owns the role.
Custom entity configuration pages target EntityType.CustomEntity and filter by the custom entity type identifier. Configuration data is stored in the entity's Xml property.
Replace YOUR-CUSTOM-ENTITY-TYPE-ID with the GUID of your custom entity type descriptor.
internal class CustomEntityConfigPage : ConfigPage
{
private static readonly Guid CustomEntityTypeId =
new Guid("YOUR-CUSTOM-ENTITY-TYPE-ID");
private CustomEntity m_customEntity;
private string m_location;
private DateTime m_installationDate;
protected override EntityType EntityType => EntityType.CustomEntity;
protected override Guid Entity
{
set
{
if (Workspace.Sdk.GetEntity(value) is CustomEntity entity
&& entity.CustomEntityType == CustomEntityTypeId)
{
m_customEntity = entity;
IsVisible = true;
}
else
{
IsVisible = false;
}
}
}
public string Location
{
get => m_location;
set
{
if (SetProperty(ref m_location, value))
{
IsDirty = true;
}
}
}
public DateTime InstallationDate
{
get => m_installationDate;
set
{
if (SetProperty(ref m_installationDate, value))
{
IsDirty = true;
}
}
}
protected override void Initialize()
{
View = new CustomEntityConfigPageView { DataContext = this };
}
protected override void Refresh()
{
var config = EntityConfiguration.Deserialize(m_customEntity.Xml);
Location = config.Location;
InstallationDate = config.InstallationDate;
IsDirty = false;
}
protected override void BeforeSave(bool beforeUnloading, out bool cancelSaveOperation)
{
cancelSaveOperation = string.IsNullOrEmpty(Location);
}
protected override void Save()
{
m_customEntity.Xml = new EntityConfiguration
{
Location = Location,
InstallationDate = InstallationDate
}.Serialize();
}
}The Xml property is a string that accepts any serialization format. Create a data class to serialize configuration to JSON.
public class EntityConfiguration
{
public string Location { get; set; }
public DateTime InstallationDate { get; set; }
public static EntityConfiguration Deserialize(string data)
{
return string.IsNullOrEmpty(data)
? new EntityConfiguration()
: JsonConvert.DeserializeObject<EntityConfiguration>(data);
}
public string Serialize() => JsonConvert.SerializeObject(this);
}Create a WPF UserControl for the configuration page view. Bind controls to properties on the configuration page using two-way data binding. Use Security Center styles for consistent appearance.
<UserControl x:Class="Genetec.Dap.CodeSamples.PluginConfigPageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label
Grid.Row="0"
Grid.Column="0"
Margin="4"
HorizontalAlignment="Right"
Content="IP Address"
Style="{Styles {x:Static LabelStyles.Intro}}" />
<IPAddressTextBox
Grid.Row="0"
Grid.Column="1"
Margin="4"
HorizontalAlignment="Left"
Value="{Binding IPAddress, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Label
Grid.Row="1"
Grid.Column="0"
Margin="4"
HorizontalAlignment="Right"
Content="Port"
Style="{Styles {x:Static LabelStyles.Intro}}" />
<NumericUpDown
Grid.Row="1"
Grid.Column="1"
MinWidth="75"
Margin="4"
HorizontalAlignment="Left"
Minimum="1"
Maximum="65535"
Value="{Binding Port, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</UserControl>The Styles markup extension and controls like IPAddressTextBox, NumericUpDown, and DateTimePicker are provided by Security Center and automatically match the application theme.
Assign the view in the Initialize() method and set the DataContext to the configuration page instance.
protected override void Initialize()
{
View = new PluginConfigPageView { DataContext = this };
}The Initialize() method is called once when you call page.Initialize(Workspace) during registration. Create and assign your view here rather than in the constructor.
Override the Name and Image properties to customize how the configuration tab appears in Config Tool.
protected override string Name => "Connection Settings";
protected override ImageSource Image => new BitmapImage(
new Uri("pack://application:,,,/YourAssembly;component/Resources/Settings.png"));The defaults are "Properties" for the name and a standard properties icon.
Configuration pages with the same Category GUID are grouped together under a single tab. When a user clicks the grouped tab, the individual pages appear as sub-tabs within that group.
public override Guid Category => SystemCategories.Analytics;
protected override void Initialize()
{
Priority = 100;
}The SystemCategories class provides built-in category GUIDs:
-
SystemCategories.Analytics- Analytics-related configuration -
SystemCategories.VideoAnalytics- Video analytics configuration
When you register a configuration page with a matching category, the system automatically groups it with other pages that share the same category. Pages without a category (or with Guid.Empty) appear as standalone tabs.
Use Priority to control the display order. Lower priority values appear first in the tab order.
You can register multiple configuration pages for the same entity type. Each page appears as a separate tab in Config Tool.
public override void Load()
{
if (Workspace.ApplicationType is ApplicationType.ConfigTool)
{
var generalPage = new GeneralConfigPage();
generalPage.Initialize(Workspace);
var advancedPage = new AdvancedConfigPage();
advancedPage.Initialize(Workspace);
var service = Workspace.Services.Get<IConfigurationService>();
service.Register(generalPage);
service.Register(advancedPage);
}
}| Member | Type | Description |
|---|---|---|
EntityType |
Property (abstract) | Gets the entity type this configuration page handles |
Entity |
Property (abstract) | Sets the GUID of the entity being configured |
Name |
Property (virtual) | Gets the display name shown in the configuration tab |
Image |
Property (virtual) | Gets the icon shown in the configuration tab |
Category |
Property (virtual) | Gets the category GUID for grouping configuration pages |
IsDirty |
Property | Gets or sets whether the configuration has unsaved changes |
IsVisible |
Property | Gets or sets whether the configuration page is visible |
Priority |
Property | Gets or sets the display order priority |
View |
Property | Gets or sets the WPF view displayed in the configuration page |
Initialize() |
Method (virtual) | Called once to initialize the configuration page and create the view |
Refresh() |
Method (virtual) | Called to load the entity configuration into the view |
Save() |
Method (virtual) | Called to persist the configuration to the entity |
BeforeSave() |
Method (virtual) | Called to validate before saving; set cancelSaveOperation to prevent save |
Activate() |
Method (virtual) | Called when the configuration page becomes visible |
Deactivate() |
Method (virtual) | Called when the configuration page becomes hidden |
OnPropertyChanged() |
Method | Raises property change notification for data binding |
These lifecycle hooks control when the page loads entity state, validates edits, and reacts to visibility changes.
Called when the page needs to display the entity's current configuration. This occurs:
- After the
Entitysetter when loading a new entity - When the user discards unsaved changes
Load the entity's configuration data and populate the view properties. Set IsDirty = false at the end to indicate no pending changes.
protected override void Refresh()
{
var config = EntityConfiguration.Deserialize(m_customEntity.Xml);
Location = config.Location;
IsDirty = false;
}Called when the user switches between configuration tabs.
- Activate: Called when the user switches TO this tab. Use this to start event subscriptions, refresh dynamic data, or initialize resources that should only be active when the page is visible.
- Deactivate: Called when the user switches AWAY from this tab. Use this to stop subscriptions, release resources, or pause updates.
protected override void Activate()
{
Workspace.Sdk.EventReceived += OnEventReceived;
}
protected override void Deactivate()
{
Workspace.Sdk.EventReceived -= OnEventReceived;
}Called when the user saves the configuration.
-
BeforeSave: Called first to validate the configuration. Set
cancelSaveOperation = trueto prevent saving. ThebeforeUnloadingparameter istruewhen the save is triggered by navigating away from the entity. -
Save: Called only if
BeforeSavedid not cancel. Persist the configuration to the entity. The system automatically setsIsDirty = falseafter a successful save.
protected override void BeforeSave(bool beforeUnloading, out bool cancelSaveOperation)
{
cancelSaveOperation = string.IsNullOrEmpty(Location);
}
protected override void Save()
{
m_customEntity.Xml = new EntityConfiguration
{
Location = Location
}.Serialize();
}- Overview
- Connecting to Security Center
- SDK certificates
- Referencing SDK assemblies
- ASP.NET hosting
- SDK compatibility
- Migrating to modern .NET
- Entities
- Entity cache
- Entity loading, cache, and query options
- Detecting entity changes
- Entity change event reference
- Transactions
- Security Center events
- Actions
- Security Desk
- Custom events
- Event-to-actions
- Managing macros
- ReportManager
- ReportManager query reference
- Access control raw events
- DownloadAllRelatedData and StrictResults
- Privileges
- Partitions
- Entity write restrictions
- About custom fields
- About video
- About cameras
- Enrolling a video unit
- Ingesting third-party video
- Archiver and auxiliary archiver roles
- Archive transfer
- About access control
- About cardholders and credentials
- About doors, areas, elevators, and access points
- About access rules and schedules
- About access control units and interface modules
- Enrolling an access control unit
- Door templates
- Visitors
- Mobile credentials
- About threat levels
- About alarms
- About incidents
- Maps
- Logging
- Overview
- Localization
- Certificates
- Lifecycle
- Threading
- State management
- Configuration
- Restricted configuration
- Events
- Queries
- Request manager
- Database
- Entity ownership
- Engine extensions
- Entity mappings
- Server management
- Custom privileges
- Custom entity types
- Resolving non-SDK assemblies
- Deploying plugins
- Modern .NET support
- Overview
- Localization
- Certificates
- Creating modules
- Tasks
- Pages
- Components
- Tile extensions
- Services
- Contextual actions
- Custom actions
- Options extensions
- Configuration pages
- Monitors
- Shared components
- Commands
- Extending events
- Map extensions
- Timeline providers
- Image extractors
- Credential encoders
- Credential readers
- Cardholder fields extractors
- Badge printers
- Content builders
- Dashboard widgets
- Incidents
- Logon providers
- Pinnable content builders
- Custom report pages
- Overview
- Getting started
- Media file formats
- MediaPlayer
- VideoSourceFilter
- MediaExporter
- MediaFile
- G64 converters
- FileCryptingManager
- PlaybackSequenceQuerier
- PlaybackStreamReader
- OverlayFactory
- PtzCoordinatesManager
- AudioTransmitter
- AudioRecorder
- AnalogMonitorController
- Camera blocking
- Overview
- Getting started
- Referencing entities
- Entity operations
- About access control in the Web SDK
- About video in the Web SDK
- Users and user groups
- Partitions
- Custom fields
- Custom card formats
- Actions
- Events and alarms
- Incidents
- Reports
- Tasks
- Macros
- Custom entity types
- System endpoints
- Performance guide
- Reference
- Endpoints
- Constants
- Enumerations
- Under the hood
- Troubleshooting