Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Integrating LaunchDarkly .NET SDK with Ninject

Integrate the LaunchDarkly .NET server-side SDK with Ninject dependency injection framework using proper singleton registration and initialization patterns.

Use case

Configure LaunchDarkly SDK as a managed singleton in Ninject-based .NET applications to ensure:

  • Single SDK instance per application
  • Proper initialization before flag evaluations
  • Clean integration with existing dependency injection patterns
  • Testability with mock implementations

Challenge

Incorrect Ninject binding configuration causes common initialization failures:

  • Initialized property returns false
  • All flag evaluations return fallback values
  • SDK creates new instances on each resolution
  • Application blocks on startup waiting for initialization

These occur when the SDK is not registered with singleton scope or when initialization is not properly awaited.

Solution

Register ILdClient as a singleton using Ninject’s .InSingletonScope() and ensure initialization completes during application startup.

Ninject module configuration

using LaunchDarkly.Sdk.Server;
using LaunchDarkly.Sdk.Server.Interfaces;
using Ninject.Modules;
using System;

public class LaunchDarklyModule : NinjectModule
{
    public override void Load()
    {
        Bind<ILdClient>()
            .ToMethod(ctx =>
            {
                var sdkKey = GetSdkKey();

                var config = Configuration.Builder(sdkKey)
                    .StartWaitTime(TimeSpan.FromSeconds(5))
                    .Build();

                var client = new LdClient(config);

                if (!client.Initialized)
                {
                    Console.WriteLine(
                        "LaunchDarkly client did not initialize within timeout. " +
                        "Flag evaluations will use fallback values until connected.");
                }

                return client;
            })
            .InSingletonScope();
    }

    private string GetSdkKey()
    {
        return Environment.GetEnvironmentVariable("LAUNCHDARKLY_SDK_KEY")
            ?? throw new InvalidOperationException(
                "LAUNCHDARKLY_SDK_KEY environment variable not set");
    }
}

Using the SDK in services

Define a service interface and inject ILdClient through the constructor:

using LaunchDarkly.Sdk;
using LaunchDarkly.Sdk.Server.Interfaces;
using System;

public interface IMyService
{
    void DoWork();
}

Implement the service with constructor injection:

public class MyService : IMyService
{
    private readonly ILdClient _ldClient;

    public MyService(ILdClient ldClient)
    {
        _ldClient = ldClient;
    }

    public void DoWork()
    {
        var context = Context.Builder("user-key-123")
            .Name("Example User")
            .Build();

        var showFeature = _ldClient.BoolVariation("my-feature-flag", context, false);

        if (showFeature)
        {
            Console.WriteLine("Feature is enabled");
        }
        else
        {
            Console.WriteLine("Feature is disabled");
        }
    }
}

Service registration

using Ninject.Modules;

public class ServicesModule : NinjectModule
{
    public override void Load()
    {
        Bind<IMyService>().To<MyService>();
    }
}

Complete startup example

using LaunchDarkly.Sdk.Server.Interfaces;
using Ninject;
using System;

public class Program
{
    private static IKernel _kernel;

    public static void Main(string[] args)
    {
        // .NET Framework 4.x does not enable TLS 1.2 by default. If targeting these runtimes,
        // configure the security protocol before initializing the SDK to ensure connectivity
        // to LaunchDarkly's streaming and events endpoints:
        //   ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
        // .NET Core 2.1+ and .NET 5+ enable TLS 1.2 by default; no additional configuration
        // is required.
        _kernel = new StandardKernel(
            new LaunchDarklyModule(),
            new ServicesModule()
        );

        var ldClient = _kernel.Get<ILdClient>();
        Console.WriteLine($"LaunchDarkly SDK initialized: {ldClient.Initialized}");

        if (!ldClient.Initialized)
        {
            Console.WriteLine(
                "WARNING: LaunchDarkly SDK not fully initialized. " +
                "Flag evaluations will use fallback values until connection establishes.");
        }

        RunApplication();

        Cleanup();
    }

    private static void RunApplication()
    {
        var service = _kernel.Get<IMyService>();
        service.DoWork();
    }

    private static void Cleanup()
    {
        _kernel.Dispose();
    }
}

Testing with test data sources

Use LaunchDarkly’s test data source to control flag values in tests without mocking:

using LaunchDarkly.Sdk.Server;
using LaunchDarkly.Sdk.Server.Integrations;
using LaunchDarkly.Sdk.Server.Interfaces;
using Ninject.Modules;

public class TestLaunchDarklyModule : NinjectModule
{
    private readonly TestData _testData;

    public TestLaunchDarklyModule()
    {
        _testData = TestData.DataSource();
    }

    public TestLaunchDarklyModule(TestData testData)
    {
        _testData = testData;
    }

    public override void Load()
    {
        Bind<ILdClient>()
            .ToMethod(ctx =>
            {
                var config = Configuration.Builder("test-sdk-key")
                    .DataSource(_testData)
                    .Events(Components.NoEvents)
                    .Build();

                return new LdClient(config);
            })
            .InSingletonScope();

        Bind<TestData>().ToConstant(_testData);
    }
}

Unit test example

using LaunchDarkly.Sdk;
using LaunchDarkly.Sdk.Server.Integrations;
using LaunchDarkly.Sdk.Server.Interfaces;
using Ninject;
using Xunit;

public class MyServiceTests
{
    [Fact]
    public void DoWork_WhenFeatureEnabled_PrintsEnabledMessage()
    {
        var testData = TestData.DataSource();
        testData.Update(testData.Flag("my-feature-flag").On(true));

        using var kernel = new StandardKernel(
            new TestLaunchDarklyModule(testData),
            new ServicesModule()
        );

        var service = kernel.Get<IMyService>();

        service.DoWork();
    }

    [Fact]
    public void DoWork_WhenFeatureDisabled_PrintsDisabledMessage()
    {
        var testData = TestData.DataSource();
        testData.Update(testData.Flag("my-feature-flag").On(false));

        using var kernel = new StandardKernel(
            new TestLaunchDarklyModule(testData),
            new ServicesModule()
        );

        var service = kernel.Get<IMyService>();

        service.DoWork();
    }

Testing with targeting rules

Test data sources support full targeting functionality:

    [Fact]
    public void DoWork_WithContextTargeting_ReturnsExpectedValue()
    {
        var testData = TestData.DataSource();

        testData.Update(testData.Flag("my-feature-flag")
            .On(true)
            .VariationForKey(ContextKind.Default, "user-key-123", true)
            .FallthroughVariation(false));

        using var kernel = new StandardKernel(
            new TestLaunchDarklyModule(testData),
            new ServicesModule()
        );

        var service = kernel.Get<IMyService>();
        service.DoWork();
    }

Why use test data sources instead of mocks:

  • No need to implement the full ILdClient interface
  • Supports all SDK features including targeting, rules, and prerequisites
  • Closer to production behavior than mocks
  • Easier to maintain as SDK evolves
  • No events sent to LaunchDarkly during tests

To learn more about test data sources, read Test data sources.

Key implementation points

MUST use InSingletonScope

Bind<ILdClient>()
    .ToMethod(ctx => { /* initialization */ })
    .InSingletonScope();

Without .InSingletonScope(), Ninject creates a new SDK instance for each resolution, causing:

  • Multiple concurrent connections to LaunchDarkly
  • Initialized always returns false because each instance starts initialization from scratch
  • Increased resource usage and connection overhead

MUST configure initialization timeout

var config = Configuration.Builder(sdkKey)
    .StartWaitTime(TimeSpan.FromSeconds(5))
    .Build();

Set a reasonable timeout based on your application type:

  • Web applications: 1-3 seconds
  • Background services: 3-5 seconds
  • Console applications: 5-10 seconds

MUST load SDK key from configuration

    private string GetSdkKey()
    {
        return Environment.GetEnvironmentVariable("LAUNCHDARKLY_SDK_KEY")
            ?? throw new InvalidOperationException(
                "LAUNCHDARKLY_SDK_KEY environment variable not set");
    }

Never hardcode SDK keys. Use:

  • Environment variables
  • Configuration files
  • Secret management systems
  • Azure Key Vault
  • AWS Secrets Manager

SHOULD check initialization status

var client = new LdClient(config);

if (!client.Initialized)
{
    Console.WriteLine(
        "LaunchDarkly client did not initialize within timeout. " +
        "Using fallback values until connected.");
}

The StartWaitTime configuration handles the timeout automatically. The constructor blocks until either the SDK connects successfully or the timeout elapses. After timeout, the SDK continues connecting in the background while your application proceeds with fallback values.

SHOULD dispose on shutdown

    private static void Cleanup()
    {
        _kernel.Dispose();
    }

Disposing the Ninject kernel disposes all singleton bindings, including the LdClient. This ensures:

  • Pending events are flushed
  • Connections are closed gracefully
  • Resources are released properly

Common mistakes

Creating new instance on each call

Incorrect:

Bind<ILdClient>()
    .ToMethod(ctx => new LdClient(sdkKey));

Correct:

Bind<ILdClient>()
    .ToMethod(ctx => new LdClient(sdkKey))
    .InSingletonScope();

Blocking application startup

Incorrect:

var config = Configuration.Builder(sdkKey)
    .StartWaitTime(TimeSpan.FromMinutes(5))
    .Build();
var client = new LdClient(config);

Correct:

var config = Configuration.Builder(sdkKey)
    .StartWaitTime(TimeSpan.FromSeconds(5))
    .Build();
var client = new LdClient(config);

if (!client.Initialized)
{
    Console.WriteLine("Continuing with fallback values");
}

The StartWaitTime automatically controls how long the constructor blocks. Set it to a reasonable timeout based on your application type. Do not use DataSourceStatusProvider.WaitFor after construction. StartWaitTime handles all the waiting.

Hardcoding SDK keys

Incorrect:

var sdkKey = "sdk-12345678-1234-1234-1234-123456789abc";

Correct:

var sdkKey = Environment.GetEnvironmentVariable("LAUNCHDARKLY_SDK_KEY");

Resources