Skip to content

plugin sdk request manager

Andre Lafleur edited this page Dec 12, 2025 · 1 revision

About plugin request handling

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

Request/Response Architecture

Client Application
    ↓
Sends Request<TRequest>
    ↓
Directory routes to plugin based on PluginGuid
    ↓
RequestManager calls registered handler
    ↓
Handler returns Response<TResponse>
    ↓
Response flows back to client

Key concepts:

  • Request/response communication pattern (RPC-style)
  • Type-safe request and response classes
  • Requests routed by plugin GUID
  • Multiple handlers for different request types
  • Synchronous and asynchronous handlers supported

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.SetResult(new ComplexResponse { Result = result });
        }
        catch (Exception ex)
        {
            completion.SetException(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. Plugin GUID - Specified in client request
  3. Handler registration - Plugin must register handler for type

Multiple plugins can handle the same request type - routing is by PluginGuid.

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 for unexpected errors only
  • Prefer returning error in response for expected errors

Thread Safety

Request handlers are called on the engine thread:

private StatusResponse HandleGetStatus(GetStatusRequest request)
{
    // Safe to access Engine and plugin state
    var role = Engine.GetEntity<Role>(PluginGuid);

    return new StatusResponse
    {
        IsConnected = m_isConnected,
        Configuration = LoadConfiguration()
    };
}

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.SetResult(new LongOperationResponse { ... });
        });
    });
}

Related Guides

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

Clone this wiki locally