Skip to content

plugin sdk configuration

Andre Lafleur edited this page May 10, 2026 · 8 revisions

About plugin configuration

Plugins store configuration in the Role.SpecificConfiguration property as a serialized string (typically JSON).

Configuration architecture

PluginDescriptor.SpecificDefaultConfig
    ↓
Provides default configuration when role is created
    ↓
Role.SpecificConfiguration
    ↓
Stored in Security Center database
    ↓
Plugin reads on startup and monitors for changes

Key points:

  • Configuration is stored per-role instance
  • Multiple instances of same plugin have separate configurations
  • Changes persist across plugin restarts
  • Configuration changes trigger EntitiesInvalidated event
  • Configuration is a string, plugin chooses serialization format

Default configuration

Define defaults in PluginDescriptor.SpecificDefaultConfig:

public class MyPluginDescriptor : PluginDescriptor
{
    public override string SpecificDefaultConfig
    {
        get
        {
            var config = new PluginConfig
            {
                ServerUrl = "http://localhost:8080",
                PollInterval = 30,
                EnableLogging = true,
                Timeout = TimeSpan.FromSeconds(10)
            };
            return JsonConvert.SerializeObject(config);
        }
    }
}

When applied:

  • Applied when plugin role is first created in Config Tool
  • Not applied to existing roles (preserves customization)
  • If SpecificDefaultConfig returns empty string, no default applied

Serialization format:

  • JSON is recommended (human-readable, well-supported)
  • XML is also supported
  • Binary formats work but harder to troubleshoot
  • Must be string-serializable

Reading configuration

Read configuration in OnPluginLoaded():

public class PluginConfig
{
    public string ServerUrl { get; set; }
    public int PollInterval { get; set; }
    public bool EnableLogging { get; set; }
    public TimeSpan Timeout { get; set; }
}

protected override void OnPluginLoaded()
{
    var config = LoadConfiguration();
    ApplyConfiguration(config);
}

private PluginConfig LoadConfiguration()
{
    var role = Engine.GetEntity<Role>(PluginGuid);
    string configJson = role.SpecificConfiguration;
    
    if (string.IsNullOrEmpty(configJson))
    {
        Logger.TraceWarning("No configuration found, using defaults");
        return CreateDefaultConfiguration();
    }
    
    try
    {
        return JsonConvert.DeserializeObject<PluginConfig>(configJson);
    }
    catch (Exception ex)
    {
        Logger.TraceError(ex, "Failed to parse configuration, using defaults");
        return CreateDefaultConfiguration();
    }
}

private void ApplyConfiguration(PluginConfig config)
{
    Logger.TraceInformation($"Applying configuration: ServerUrl={config.ServerUrl}");
    
    m_serverUrl = config.ServerUrl;
    m_pollInterval = config.PollInterval;
    m_enableLogging = config.EnableLogging;
    m_timeout = config.Timeout;
}

Best practices:

  • Always handle missing/invalid configuration gracefully
  • Log configuration values (except secrets)
  • Validate configuration values
  • Fall back to safe defaults on errors

Updating configuration

Plugins can programmatically update their configuration:

private void UpdateConfiguration(PluginConfig config)
{
    Engine.TransactionManager.ExecuteTransaction(() =>
    {
        var role = Engine.GetEntity<Role>(PluginGuid);
        role.SpecificConfiguration = JsonConvert.SerializeObject(config);
    });
    
    Logger.TraceInformation("Configuration updated");
}

When to update:

  • In response to request from config page
  • After auto-discovery of settings
  • To persist runtime state changes
  • When defaults need adjustment

Considerations:

  • Updates trigger EntitiesInvalidated event
  • Other plugin instances will see the change
  • Changes are immediately visible in Config Tool
  • Use transactions to ensure atomicity

Monitoring configuration changes

Subscribe to EntitiesInvalidated to detect configuration changes:

protected override void OnPluginLoaded()
{
    Engine.EntitiesInvalidated += OnEntitiesInvalidated;
    
    // Load initial configuration
    var config = LoadConfiguration();
    ApplyConfiguration(config);
}

private void OnEntitiesInvalidated(object sender, EntitiesInvalidatedEventArgs e)
{
    // Check if our role was updated
    var roleUpdate = e.Entities.FirstOrDefault(u => u.EntityGuid == PluginGuid);
    
    if (roleUpdate != null)
    {
        Logger.TraceDebug("Plugin configuration changed");
        
        // Reload and apply configuration
        var config = LoadConfiguration();
        ApplyConfiguration(config);
    }
}

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        Engine.EntitiesInvalidated -= OnEntitiesInvalidated;
    }
}

When fired:

  • Configuration changed in Config Tool
  • Configuration updated programmatically by SDK client
  • Any other role property changed
  • Occurs on the engine thread (safe to use Engine)

Configuration validation

Validate configuration values:

private PluginConfig LoadConfiguration()
{
    var config = ParseConfiguration();
    
    if (!ValidateConfiguration(config, out string error))
    {
        Logger.TraceError($"Invalid configuration: {error}");
        ModifyPluginState(new PluginStateEntry("Configuration",
            $"Invalid configuration: {error}") { IsError = true });
        return CreateDefaultConfiguration();
    }
    
    return config;
}

private bool ValidateConfiguration(PluginConfig config, out string error)
{
    if (string.IsNullOrWhiteSpace(config.ServerUrl))
    {
        error = "ServerUrl is required";
        return false;
    }
    
    if (!Uri.IsWellFormedUriString(config.ServerUrl, UriKind.Absolute))
    {
        error = "ServerUrl is not a valid URL";
        return false;
    }
    
    if (config.PollInterval < 1 || config.PollInterval > 3600)
    {
        error = "PollInterval must be between 1 and 3600 seconds";
        return false;
    }
    
    if (config.Timeout < TimeSpan.FromSeconds(1))
    {
        error = "Timeout must be at least 1 second";
        return false;
    }
    
    error = null;
    return true;
}

Validation strategies:

  • Validate on load and on change
  • Report errors via ModifyPluginState()
  • Fall back to safe defaults on validation failure
  • Log validation errors
  • Don't crash plugin on invalid config

Secrets and sensitive data

DO NOT store passwords or API keys in SpecificConfiguration - it is stored as plaintext in the database.

For sensitive data, use RestrictedConfiguration instead:

// WRONG - Plaintext password in SpecificConfiguration
public class PluginConfig
{
    public string ServerUrl { get; set; }
    public string Password { get; set; }  // DON'T DO THIS!
}

// CORRECT - Use RestrictedConfiguration for credentials
protected override void OnPluginLoaded()
{
    // Load non-sensitive config from SpecificConfiguration
    var config = LoadConfiguration();
    
    // Load credentials from RestrictedConfiguration
    var role = Engine.GetEntity<Role>(PluginGuid);
    if (role.RestrictedConfiguration.TryGetPrivateValue("Password", out var securePassword))
    {
        // Use secure password
        InitializeConnection(config.ServerUrl, securePassword);
        securePassword.Dispose();
    }
}

See Plugin SDK Restricted Configuration for secure credential storage.

Configuration storage comparison

Storage Purpose Format Security Access
SpecificConfiguration General settings JSON/XML/any string Plaintext Anyone with role access
RestrictedConfiguration.AdminConfigXml Admin config XML Plaintext Plugin or administrators
RestrictedConfiguration (methods) Credentials SecureString Secure Plugin only (read), admin (write)

Best practice: Store non-sensitive configuration in SpecificConfiguration, sensitive credentials in RestrictedConfiguration using GetPrivateValue()/SetPrivateValue() methods.

Configuration migration

Handle configuration schema changes between versions:

private PluginConfig LoadConfiguration()
{
    var role = Engine.GetEntity<Role>(PluginGuid);
    string configJson = role.SpecificConfiguration;
    
    if (string.IsNullOrEmpty(configJson))
        return CreateDefaultConfiguration();
    
    try
    {
        // Try to parse as current version
        var config = JsonConvert.DeserializeObject<PluginConfig>(configJson);
        
        // Check if migration needed
        if (NeedsMigration(config))
        {
            config = MigrateConfiguration(config);
            
            // Save migrated configuration
            Engine.TransactionManager.ExecuteTransaction(() =>
            {
                role.SpecificConfiguration = JsonConvert.SerializeObject(config);
            });
            
            Logger.TraceInformation("Configuration migrated to new format");
        }
        
        return config;
    }
    catch (Exception ex)
    {
        Logger.TraceError(ex, "Configuration parse error");
        return CreateDefaultConfiguration();
    }
}

private bool NeedsMigration(PluginConfig config)
{
    // Check for old property or schema version
    return config.SchemaVersion < 2;
}

private PluginConfig MigrateConfiguration(PluginConfig config)
{
    if (config.SchemaVersion == 1)
    {
        // Migrate v1 to v2
        config.NewProperty = ConvertOldProperty(config.OldProperty);
        config.SchemaVersion = 2;
    }
    
    return config;
}

Migration best practices:

  • Include schema version in configuration
  • Support migrating from any previous version
  • Log migrations

Configuration UI

Custom config pages can read/write configuration via RequestManager:

Plugin side:

protected override void OnPluginLoaded()
{
    // Use Plugin's protected helper methods to register handlers
    AddRequestHandler<GetConfigRequest, PluginConfig>(HandleGetConfig);
    AddRequestHandler<SetConfigRequest, SetConfigResponse>(HandleSetConfig);
}

private PluginConfig HandleGetConfig(GetConfigRequest request)
{
    return LoadConfiguration();
}

private SetConfigResponse HandleSetConfig(SetConfigRequest request)
{
    try
    {
        if (!ValidateConfiguration(request.Config, out string error))
        {
            return new SetConfigResponse { Success = false, Error = error };
        }
        
        UpdateConfiguration(request.Config);
        ApplyConfiguration(request.Config);
        
        return new SetConfigResponse { Success = true };
    }
    catch (Exception ex)
    {
        Logger.TraceError(ex, "Failed to update configuration");
        return new SetConfigResponse 
        { 
            Success = false, 
            Error = ex.Message 
        };
    }
}

For request and response details, see Plugin SDK request manager.

See also

Platform SDK

Plugin SDK

Workspace SDK

Media SDK

Macro SDK

Web SDK

Media Gateway

Genetec Web Player

Clone this wiki locally