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:
Initializedproperty returnsfalse- 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
ILdClientinterface - 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
Initializedalways returnsfalsebecause 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");