Skip to content

workspace sdk config pages

Andre Lafleur edited this page Oct 8, 2026 · 8 revisions

About configuration 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 SpecificConfiguration and RestrictedConfiguration
  • Custom entity configuration: Adding property pages for custom entity types, storing data in the entity's Xml property

How configuration pages work

The configuration page lifecycle follows these steps:

  1. Registration: The module registers the configuration page with IConfigurationService during load
  2. Entity selection: When a user selects an entity, the system calls the Entity setter with the entity GUID
  3. Visibility: The configuration page sets IsVisible based on whether it handles the selected entity
  4. Refresh: The system calls Refresh() to load the entity configuration into the view
  5. Modification: User changes trigger property updates that set IsDirty = true
  6. Validation: Before saving, the system calls BeforeSave() to validate the configuration
  7. Save: The system calls Save() to persist the configuration to the entity

Registering a configuration page

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.

MVVM pattern

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 ConfigPage class exposes properties that the view binds to
  • View: A WPF UserControl assigned to the View property, bound to the ConfigPage via DataContext

Property change notification

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.

Configuring plugin roles

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

Creating a plugin configuration page

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.

Serializing plugin role configuration

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);
}

Restricted configuration

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.

Configuring custom entities

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.

Creating a custom entity configuration page

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();
    }
}

Serializing custom entity configuration

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);
}

Creating the view

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.

Customizing tab appearance

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.

Grouping and ordering

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.

Multiple configuration pages

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);
    }
}

ConfigPage members

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

Configuration page lifecycle methods

These lifecycle hooks control when the page loads entity state, validates edits, and reacts to visibility changes.

Refresh

Called when the page needs to display the entity's current configuration. This occurs:

  • After the Entity setter 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;
}

Activate and deactivate

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;
}

BeforeSave and Save

Called when the user saves the configuration.

  • BeforeSave: Called first to validate the configuration. Set cancelSaveOperation = true to prevent saving. The beforeUnloading parameter is true when the save is triggered by navigating away from the entity.
  • Save: Called only if BeforeSave did not cancel. Persist the configuration to the entity. The system automatically sets IsDirty = false after 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();
}

See also

Platform SDK

Plugin SDK

Workspace SDK

Media SDK

Macro SDK

Web SDK

Synergis RIO

Media Gateway

Genetec Web Player

Clone this wiki locally