# 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

```csharp
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:

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

public interface IMyService
{
    void DoWork();
}
```

Implement the service with constructor injection:

```csharp
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

```csharp
using Ninject.Modules;

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

### Complete startup example

```csharp
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:

```csharp
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

```csharp
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:

```csharp
    [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](https://docs.launchdarkly.com/sdk/features/test-data-sources)*.

## Key implementation points

### MUST use InSingletonScope

```csharp
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

```csharp
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

```csharp
    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

```csharp
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

```csharp
    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:**
```csharp
Bind<ILdClient>()
    .ToMethod(ctx => new LdClient(sdkKey));
```

**Correct:**
```csharp
Bind<ILdClient>()
    .ToMethod(ctx => new LdClient(sdkKey))
    .InSingletonScope();
```

### Blocking application startup

**Incorrect:**
```csharp
var config = Configuration.Builder(sdkKey)
    .StartWaitTime(TimeSpan.FromMinutes(5))
    .Build();
var client = new LdClient(config);
```

**Correct:**
```csharp
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:**
```csharp
var sdkKey = "sdk-12345678-1234-1234-1234-123456789abc";
```

**Correct:**
```csharp
var sdkKey = Environment.GetEnvironmentVariable("LAUNCHDARKLY_SDK_KEY");
```

## Related topics

- [SDK Initialization and Configuration](../../../sdk/preflight-checklist/init-and-config.md)
- [Contexts Best Practices](../../../sdk/contexts/best-practices.md)
- [Resilient SDK Implementation](../../../resilency/sdk-implementation.md)

## Resources

- [LaunchDarkly .NET Server-Side SDK Documentation](https://docs.launchdarkly.com/sdk/server-side/dotnet)
- [Ninject Documentation](http://www.ninject.org/)
- [Dependency Injection in .NET](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection)

<div class="page-metadata"><table class="page-metadata-table" aria-label="Page metadata"><thead><tr><th scope="col">Last modified</th><th scope="col">Last reviewed</th><th scope="col">Review due</th></tr></thead><tbody><tr><td><time datetime="2026-04-29">2026-04-29</time></td><td></td><td></td></tr><tr><td>Last commit: <a href="https://github.com/launchdarkly-labs/ps-flag-book/commit/2e92061">2e92061</a></td><td></td><td></td></tr></tbody></table></div>