Table of Contents

Services in GQI extensions

From DataMiner 10.5.0 [CU18]/10.6.0 [CU6]/10.6.9 onwards, GQI extensions using the Skyline.DataMiner.Core.GQI.Extensions API can use constructor injection to share service instances across extension instances.

Services are useful when an extension library needs a reusable state or functionality, for example a cache, a client for an external system, or shared helper logic. A service belongs to the extension library worker process in which it is loaded. It is not shared across different extension libraries or across DataMiner Agents.

Important

GQI services require the GQI DxM and the Skyline.DataMiner.Core.GQI.Extensions NuGet package.

Registering a service

Register a service by adding one of the GQI service attributes to a class in the same extension library:

Attribute Scope Default idle expiration
GQIWorkerServiceAttribute One instance shared across all users and all queries in the extension worker process. None
GQISecurityServiceAttribute One instance per security context, shared by users with the same security group combination. 60 minutes
GQIUserServiceAttribute One instance per user within the current security context. 5 minutes

The idle expiration is a sliding timeout that starts when the service instance was last used. Each time the service is used again, the timeout is reset. Set IdleExpirationMinutes to 0 to disable idle expiration.

using System;
using Skyline.DataMiner.Core.GQI.Extensions;

public interface IInventoryCache
{
    string[] GetItems();
}

[GQISecurityService(typeof(IInventoryCache), IdleExpirationMinutes = 15)]
public sealed class InventoryCache : IInventoryCache, IDisposable
{
    private readonly IGQISecurity _security;

    public InventoryCache(IGQISecurity security)
    {
        _security = security;
    }

    public string[] GetItems()
    {
        // Fetch or return cached data for _security.SecurityKey.
        return Array.Empty<string>();
    }

    public void Dispose()
    {
        // Clean up cached data or external resources.
    }
}

When a class is marked with a service attribute, GQI registers it by its concrete type by default. This means that constructor parameters can request that concrete type to inject a service instance. You can also specify a different service type as the injection contract. In that case, the marked class must be assignable to the specified service type. This is typically used to register a service by an interface, for example to decouple extensions from the implementation or to make unit testing easier.

Note

Do not register multiple services for the same service type in one extension library. Duplicate service definitions are ignored.

Service lifetime

A service instance is created when GQI resolves that service for the first time within its scope:

  • A worker service is shared across all users and queries in the extension worker process.
  • A security service is shared within the current security context.
  • A user service is shared for the current user within the current security context.

When a service instance is created, GQI calls its constructor. The constructor can use injection in the same way as an ad hoc data source or custom operator, and it can also inject other services. If a constructor dependency cannot be resolved or the constructor throws an exception, the service instance cannot be created.

A service instance remains available until GQI removes it from its scope. This can happen when the configured idle expiration has elapsed since the service instance was last used, when the scope is no longer valid, or when the extension worker process is cleaned up. The next time the service is requested within that scope, GQI creates a new instance.

If the service class implements IDisposable, GQI calls Dispose when the service instance is removed from its scope. Use Dispose to release resources that are tied to the service instance, such as cached data, subscriptions, or external connections.

Injecting services into an extension

To receive services, add constructor parameters to an ad hoc data source or custom operator. If the extension class does not explicitly declare a constructor, C# provides a public parameterless constructor and GQI uses it. If the extension class has an explicit parameterless public constructor, GQI also uses that constructor for backward compatibility. Otherwise, the extension class must have exactly one public constructor.

Constructor parameters can use any of the supported constructor dependencies.

using System;
using System.Linq;
using Skyline.DataMiner.Core.GQI.Extensions;

[GQIMetaData(Name = "Inventory")]
public sealed class InventoryDataSource : IGQIDataSource
{
    private readonly IInventoryCache _cache;
    private readonly IGQILogger _logger;

    public InventoryDataSource(IInventoryCache cache, IGQILogger logger)
    {
        _cache = cache;
        _logger = logger;
    }

    public GQIColumn[] GetColumns()
    {
        return new GQIColumn[]
        {
            new GQIStringColumn("Item"),
        };
    }

    public GQIPage GetNextPage(GetNextPageInputArgs args)
    {
        var rows = _cache.GetItems()
            .Select(item => new GQIRow(new[] { new GQICell { Value = item } }))
            .ToArray();

        _logger.Information($"Returned {rows.Length} inventory items.");
        return new GQIPage(rows);
    }
}

Constructor injection happens before OnInit is called. If a constructor dependency cannot be resolved or the constructor throws an exception, the extension instance cannot be created.

Injecting services into other services

Services can also use constructor injection to depend on other services in the same extension library. A service class must have exactly one public constructor. If it does not explicitly declare a constructor, the default public parameterless constructor is used.

Service constructor parameters can use any of the supported constructor dependencies, as long as they are available in the service scope.

A service can depend on services from its own scope or from a broader scope:

Service being created Services it can inject
Worker service Worker services
Security service Worker services and security services
User service Worker services, security services, and user services

Services cannot depend on services from a narrower scope. For example, a worker service cannot inject a security or user service, and a security service cannot inject a user service.

If a service constructor dependency cannot be resolved or the constructor throws an exception, the service instance cannot be created.

Retrieving data from DataMiner in services

User-scoped services can inject IGQIDMSInterface or IConnection. GQI provides the same user connection as it would provide if the dependency were injected directly into the extension for that query. This allows a user-scoped service to cache and reuse user-specific data.

Security-scoped services do not support injecting these dependencies yet.

Deferred service injection

Use GQILazy<T> when a dependency is only needed conditionally or only in specific lifecycle methods. The service is resolved the first time the Value property is accessed.

public sealed class InventoryDataSource : IGQIDataSource
{
    private readonly GQILazy<IInventoryCache> _cache;

    public InventoryDataSource(GQILazy<IInventoryCache> cache)
    {
        _cache = cache;
    }

    public GQIPage GetNextPage(GetNextPageInputArgs args)
    {
        var items = _cache.Value.GetItems();

        // Return rows based on the items.
        return new GQIPage(Array.Empty<GQIRow>());
    }

    // Other IGQIDataSource members omitted for brevity.
}

Direct constructor injection should be preferred when the dependency is always needed. Use GQILazy<T> when resolving the dependency can be postponed until it is actually used.

Supported constructor dependencies

The following types can be used as constructor parameters:

Type Provides Can be injected into
IGQILogger A logger for the extension or service. Extensions and services
IGQIFactory Factory methods to create GQI objects. Extensions and services
IGQIDMSInterface Access to the DataMiner System. Extensions and user-scoped services
IConnection A live SLNet connection to the DataMiner System. Extensions and user-scoped services
IGQISecurity The current security context. Extensions, security services, and user services
IGQIUser The user context. Extensions and user services
IGQISession The session context. Extensions
CultureInfo The culture used in the current query session. Extensions
TimeZoneInfo The time zone used in the current query session. Extensions
GQILazy<T> Deferred resolution of a service dependency. Extensions and services
Registered GQI services A service instance registered in the same extension library. Extensions and services, depending on the service scope
Important

Security-scoped and user-scoped services are tied to the current security context. When a user's security context changes, references to existing service instances remain valid but new service instances with the updated security context will be created for new references.

Advanced search syntax