Skip to content

plugin sdk request manager

Andre Lafleur edited this page Feb 3, 2026 · 5 revisions

About plugin request handling

Plugins can handle custom requests from client applications (custom tasks, config pages, other plugins) using the RequestManager.

Request/Response Architecture

sequenceDiagram
    participant Client as Client application
    participant ClientRM as SDK RequestManager (client)
    participant Directory
    participant PluginRM as SDK RequestManager (plugin)
    participant Handler as Request handler

    Client->>ClientRM: SendRequest TRequest to TResponse
    ClientRM->>ClientRM: Resolve recipient to role owner
    ClientRM->>Directory: Send request
    Directory->>PluginRM: Dispatch request to role
    PluginRM->>Handler: Invoke registered handler
    Handler-->>PluginRM: RequestCompletion TResponse
    PluginRM-->>Directory: Send response
    Directory-->>ClientRM: Return response
    ClientRM-->>Client: TResponse
Loading

Key concepts:

  • Request/response communication pattern (RPC-style)
  • Type-safe request and response classes
  • Requests target a recipient entity GUID
  • Multiple handlers for different request types
  • Synchronous, asynchronous, and completion-based handlers

Use Cases

Custom configuration pages:

  • Page requests current settings
  • Plugin returns configuration
  • Page sends updated settings
  • Plugin validates and applies

Custom tasks:

  • User triggers task in Security Desk
  • Task sends command to plugin
  • Plugin executes operation
  • Returns result to task

Inter-plugin communication:

  • One plugin requests data from another
  • Plugins coordinate actions
  • Share state or resources

Diagnostics and testing:

  • Test connections
  • Query internal state
  • Trigger operations
  • Get statistics

Request Handler Types

Synchronous Handler

Simple function that returns response immediately:

protected override void OnPluginLoaded()
{
    // Use Plugin's protected helper method
    AddRequestHandler<GetStatusRequest, StatusResponse>(HandleGetStatus);
}

private StatusResponse HandleGetStatus(GetStatusRequest request)
{
    return new StatusResponse
    {
        IsConnected = m_isConnected,
        DeviceCount = m_devices.Count,
        Uptime = DateTime.UtcNow - m_startTime
    };
}

When to use:

  • Fast operations (< 100ms)
  • No I/O required
  • Simple data retrieval
  • Immediate results available

Asynchronous Handler (Task-based)

Async method that can await operations:

protected override void OnPluginLoaded()
{
    // Use Plugin's protected helper method
    AddAsyncRequestHandler<TestConnectionRequest, TestConnectionResponse>(
        HandleTestConnectionAsync);
}

private async Task<TestConnectionResponse> HandleTestConnectionAsync(
    TestConnectionRequest request,
    RequestManagerContext context)
{
    try
    {
        var startTime = DateTime.UtcNow;
        await TestConnectionToExternalSystemAsync(
            request.Timeout,
            context.RequestCancellationToken);
        var elapsed = DateTime.UtcNow - startTime;
        
        return new TestConnectionResponse
        {
            Success = true,
            ResponseTime = elapsed
        };
    }
    catch (Exception ex)
    {
        return new TestConnectionResponse
        {
            Success = false,
            Error = ex.Message
        };
    }
}

When to use:

  • Slow operations (> 100ms)
  • I/O operations (database, network)
  • Long-running tasks
  • Need async/await
  • Need cancellation support or user context

RequestManagerContext

The RequestManagerContext parameter provides information about the request origin and supports cancellation:

Properties

public class RequestManagerContext
{
    // Cancellation support
    CancellationToken RequestCancellationToken { get; }
    TimeSpan RequestTimeout { get; }
    
    // Request source information
    Guid SourceApplication { get; }     // Application that sent request
    Guid SourceUser { get; }            // User who initiated request
    string SourceUsername { get; }      // Username of requester
    
    // Request metadata
    DateTime ReceptionTimestamp { get; }              // When request was received (UTC)
    IReadOnlyCollection<Guid> DestinationEntities { get; } // Target entities
}

Usage Examples

Cancellation support:

private async Task<Response> HandleRequestAsync(
    Request request,
    RequestManagerContext context)
{
    // Pass cancellation token to async operations
    var data = await FetchDataAsync(context.RequestCancellationToken);
    
    // Check if cancelled
    if (context.RequestCancellationToken.IsCancellationRequested)
        return new Response { Cancelled = true };
        
    return new Response { Data = data };
}

User-specific logic:

private Response HandleRequest(Request request, RequestManagerContext context)
{
    var user = Engine.GetEntity<User>(context.SourceUser);
    
    Logger.TraceInformation($"Request from {context.SourceUsername} received at {context.ReceptionTimestamp}");
    
    // Apply user-specific permissions or behavior
    if (user.HasPrivilege(MyPrivilege))
    {
        return ProcessRequest(request);
    }
    
    return new Response { Error = "Insufficient privileges" };
}

Timeout handling:

private async Task<Response> HandleRequestAsync(
    Request request,
    RequestManagerContext context)
{
    Logger.TraceDebug($"Request timeout: {context.RequestTimeout}");
    
    try
    {
        // Use the timeout from context
        using var cts = CancellationTokenSource.CreateLinkedTokenSource(
            context.RequestCancellationToken);
        cts.CancelAfter(context.RequestTimeout);
        
        return await ProcessWithTimeoutAsync(request, cts.Token);
    }
    catch (OperationCanceledException)
    {
        return new Response { Error = "Request timed out" };
    }
}

Completion-based Handler

Callback pattern for complex scenarios:

protected override void OnPluginLoaded()
{
    // Use Plugin's protected helper method
    AddRequestHandler<ComplexRequest, ComplexResponse>(HandleComplexRequest);
}

private void HandleComplexRequest(
    ComplexRequest request,
    RequestCompletion<ComplexResponse> completion)
{
    // Process in background
    Task.Run(async () =>
    {
        try
        {
            var result = await ProcessComplexOperationAsync(request);
            completion.SetResponse(new ComplexResponse { Result = result });
        }
        catch (Exception ex)
        {
            completion.SetError(ex);
        }
    });
}

When to use:

  • Need manual control over completion
  • Complex error handling
  • Progress reporting scenarios
  • Legacy async patterns

Registering Handlers

Register in OnPluginLoaded

protected override void OnPluginLoaded()
{
    // Register multiple handlers using Plugin's protected helper methods
    AddRequestHandler<GetConfigRequest, PluginConfig>(HandleGetConfig);
    AddRequestHandler<SetConfigRequest, SetConfigResponse>(HandleSetConfig);
    AddAsyncRequestHandler<TestConnectionRequest, TestConnectionResponse>(
        HandleTestConnectionAsync);
}

Remove in Dispose

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        // Remove handlers using Plugin's protected helper methods
        RemoveRequestHandler<GetConfigRequest, PluginConfig>(HandleGetConfig);
        RemoveRequestHandler<SetConfigRequest, SetConfigResponse>(HandleSetConfig);
        RemoveAsyncRequestHandler<TestConnectionRequest, TestConnectionResponse>(
            HandleTestConnectionAsync);
    }
}

Important

  • Register handlers in OnPluginLoaded()
  • Remove handlers in Dispose()
  • Match handler type when removing (sync vs async)

Request Routing

How Routing Works

Requests are routed by:

  1. Request type - The generic type TRequest
  2. Recipient entity - The GUID passed to SendRequest
  3. Handler registration - Plugin must register handler for type

If the recipient entity is owned by a plugin role, the RequestManager routes the request to the owner role. Multiple plugins can handle the same request type as long as the recipient entity differs.

Request Classes

Define request and response classes:

[Serializable]
public class GetDeviceListRequest
{
    public bool IncludeOffline { get; set; }
    public DateTime Since { get; set; }
}

[Serializable]
public class DeviceListResponse
{
    public List<DeviceInfo> Devices { get; set; }
    public int TotalCount { get; set; }
}

[Serializable]
public class DeviceInfo
{
    public Guid DeviceGuid { get; set; }
    public string Name { get; set; }
    public bool IsOnline { get; set; }
}

Requirements:

  • Must be [Serializable]
  • Must be able to serialize/deserialize (JSON or binary)
  • Keep classes simple (POCOs)
  • Avoid complex object graphs

Common Request Patterns

Configuration Requests

// Get configuration
AddRequestHandler<GetConfigRequest, PluginConfig>(request => LoadConfiguration());

// Set configuration
AddRequestHandler<SetConfigRequest, SetConfigResponse>(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
        };
    }
});

Test Connection

AddAsyncRequestHandler<TestConnectionRequest, TestConnectionResponse>(
    async (request, context) =>
    {
        var sw = Stopwatch.StartNew();

        try
        {
            await ConnectToExternalSystemAsync(
                request.Timeout,
                context.RequestCancellationToken);

            return new TestConnectionResponse
            {
                Success = true,
                Message = "Connection successful",
                ResponseTime = sw.Elapsed
            };
        }
        catch (TimeoutException)
        {
            return new TestConnectionResponse
            {
                Success = false,
                Message = "Connection timeout",
                ResponseTime = sw.Elapsed
            };
        }
        catch (Exception ex)
        {
            return new TestConnectionResponse
            {
                Success = false,
                Message = ex.Message,
                ResponseTime = sw.Elapsed
            };
        }
    });

Execute Command

AddAsyncRequestHandler<ExecuteCommandRequest, CommandResponse>(
    async (request, context) =>
    {
        try
        {
            var result = await ExecuteCommandOnHardwareAsync(
                request.DeviceGuid,
                request.Command,
                request.Parameters,
                context.RequestCancellationToken);

            return new CommandResponse
            {
                Success = true,
                Result = result
            };
        }
        catch (Exception ex)
        {
            Logger.TraceError(ex, "Command execution failed");
            return new CommandResponse
            {
                Success = false,
                Error = ex.Message
            };
        }
    });

Query State

AddRequestHandler<GetDeviceStateRequest, DeviceStateResponse>(request =>
{
    var device = m_devices.FirstOrDefault(d => d.Guid == request.DeviceGuid);

    if (device == null)
    {
        return new DeviceStateResponse { Found = false };
    }

    return new DeviceStateResponse
    {
        Found = true,
        State = device.State,
        LastUpdate = device.LastUpdate,
        Properties = device.GetProperties()
    };
});

Error Handling

Return Error in Response

private SetConfigResponse HandleSetConfig(SetConfigRequest request)
{
    try
    {
        ValidateAndApplyConfig(request.Config);
        return new SetConfigResponse { Success = true };
    }
    catch (ValidationException ex)
    {
        return new SetConfigResponse 
        { 
            Success = false, 
            Error = ex.Message,
            ValidationErrors = ex.Errors
        };
    }
    catch (Exception ex)
    {
        Logger.TraceError(ex, "Configuration update failed");
        return new SetConfigResponse 
        { 
            Success = false, 
            Error = "Internal error occurred"
        };
    }
}

Throw Exception

private StatusResponse HandleGetStatus(GetStatusRequest request)
{
    if (!m_initialized)
    {
        throw new InvalidOperationException("Plugin not initialized");
    }
    
    return new StatusResponse { ... };
}

Exception handling:

  • Exceptions are serialized and sent to client
  • Client receives exception details
  • Use exceptions for unexpected errors
  • Prefer returning error in response for expected errors

Thread Safety

Request handlers run on a background thread, not the engine thread. Use Engine.QueueUpdate() or Engine.QueueUpdateAndWait() when work needs to update entities or other Engine state.

If handler spawns async work:

private void HandleLongOperation(
    LongOperationRequest request,
    RequestCompletion<LongOperationResponse> completion)
{
    Task.Run(async () =>
    {
        var result = await DoLongWorkAsync();

        // Queue back to engine thread for Engine access
        Engine.QueueUpdate(() =>
        {
            UpdateEntityState(result);
            completion.SetResponse(new LongOperationResponse { ... });
        });
    });
}

Federated Action Requests

Plugins can receive requests that cross Security Center federation boundaries using OnSdkFederationActionRequestReceived().

Overview

Federated action requests allow communication between plugins across federated Security Center systems. The request and response use serialized string payloads, giving you flexibility in the data format.

protected virtual SdkFederationActionResponse OnSdkFederationActionRequestReceived(
    SdkFederationActionRequest request)

Returns: null by default. Override to handle requests.

SdkFederationActionRequest

Property Type Description
Request string Serialized request payload
RequestId Guid Unique identifier for this request

SdkFederationActionResponse

Property Type Description
Response string Serialized response payload

Implementation Example

[Serializable]
public class MyFederatedRequest
{
    public string Command { get; set; }
    public Dictionary<string, string> Parameters { get; set; }
}

[Serializable]
public class MyFederatedResponse
{
    public bool Success { get; set; }
    public string Result { get; set; }
    public string Error { get; set; }
}

public class MyPlugin : Plugin
{
    protected override SdkFederationActionResponse OnSdkFederationActionRequestReceived(
        SdkFederationActionRequest request)
    {
        try
        {
            // Deserialize the request
            var federatedRequest = JsonConvert.DeserializeObject<MyFederatedRequest>(
                request.Request);

            Logger.TraceInformation(
                $"Received federated request {request.RequestId}: {federatedRequest.Command}");

            // Process the request
            string result = ProcessCommand(federatedRequest.Command, federatedRequest.Parameters);

            // Create and serialize response
            var response = new MyFederatedResponse
            {
                Success = true,
                Result = result
            };

            return new SdkFederationActionResponse(JsonConvert.SerializeObject(response));
        }
        catch (Exception ex)
        {
            Logger.TraceError(ex, "Failed to process federated request");

            var errorResponse = new MyFederatedResponse
            {
                Success = false,
                Error = ex.Message
            };

            return new SdkFederationActionResponse(JsonConvert.SerializeObject(errorResponse));
        }
    }

    private string ProcessCommand(string command, Dictionary<string, string> parameters)
    {
        // Implement command processing logic
        return $"Processed: {command}";
    }
}

Usage notes:

  • Use JSON or another serialization format for request/response payloads
  • Return null if your plugin does not handle federated requests
  • Handle exceptions and return error responses rather than throwing
  • Works for both federated and non-federated Security Center deployments

See also

Security Center SDK

  • Security Center SDK Developer Guide Overview of the SDK framework and how to build integrations with Security Center.

    • Platform SDK

      • Overview Introduction to the Platform SDK and core concepts.
      • Connecting to Security Center Step-by-step guide for connecting and authenticating with the SDK.
      • SDK Certificates Details certificates, licensing, and connection validation.
      • Referencing SDK Assemblies Best practices for referencing assemblies and resolving them at runtime.
      • SDK Compatibility Guide Understanding backward compatibility and versioning in the SDK.
      • Entity Guide Explains the core entity model, inheritance, and how to work with entities.
      • Entity Cache Guide Describes the engine's local entity cache and synchronization.
      • Transactions Covers batching operations for performance and consistency.
      • Events Subscribing to real-time system events.
      • Actions Sending actions to Security Center.
      • Security Desk Displaying content on monitors, reading tiles, sending tasks, and messaging operators.
      • Custom Events Defining, raising, and subscribing to custom events.
      • ReportManager Querying entities and activity data from Security Center.
      • ReportManager Query Reference Complete reference of query types, parameters, and response formats.
      • Privileges Checking, querying, and setting user privileges.
      • Partitions Entity organization and access control through partitions.
      • Logging How to configure logging, diagnostics, and debug methods.
    • Plugin SDK

    • Workspace SDK

    • Macro SDK

      • Overview How macros work, creating and configuring macro entities, automation, and monitoring.
      • Developer Guide Developing macro code with the UserMacro class and Security Center SDK.

Web SDK Developer Guide

  • Getting Started Setup, authentication, and basic configuration for the Web SDK.
  • Referencing Entities Entity discovery, search capabilities, and parameter formats.
  • Entity Operations CRUD operations, multi-value fields, and method execution.
  • Partitions Managing partitions, entity membership, and user access control.
  • Custom Fields Creating, reading, writing, and filtering custom entity fields.
  • Custom Card Formats Managing custom credential card format definitions.
  • Actions Control operations for doors, cameras, macros, and notifications.
  • Events and Alarms Real-time event monitoring, alarm monitoring, and custom events.
  • Incidents Incident management, creation, and attachment handling.
  • Reports Activity reports, entity queries, and historical data retrieval.
  • Performance Guide Optimization tips and best practices for efficient API usage.
  • Reference Entity GUIDs, EntityType enumeration, and EventType enumeration.
  • Under the Hood Technical architecture, query reflection, and SDK internals.
  • Troubleshooting Common error resolution and debugging techniques.

Media Gateway Developer Guide


Web Player Developer Guide

  • Developer Guide Complete guide to integrating GWP for live and playback video streaming.
  • API Reference Full API documentation with interfaces, methods, properties, and events.
  • Sample Application Comprehensive demo showcasing all GWP features with timeline and PTZ controls.
  • Multiplexing Sample Multi-camera grid demo using a shared WebSocket connection.

Clone this wiki locally