From 26ac9806d3517002de9c68bf50b90ddc7c06a05d Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Wed, 17 Jun 2026 10:13:36 +0200 Subject: [PATCH 1/8] Add docs for .NET SDK hosting integration + some corrections in other SDK references --- website/docs/sdk-reference/android.mdx | 4 +- website/docs/sdk-reference/cpp.mdx | 4 +- website/docs/sdk-reference/dart.mdx | 4 +- website/docs/sdk-reference/dotnet.mdx | 1020 +--------- .../docs/sdk-reference/dotnet/_template.mdx | 1790 +++++++++++++++++ .../sdk-reference/dotnet/generic-host.mdx | 16 + website/docs/sdk-reference/elixir.mdx | 4 +- website/docs/sdk-reference/ios.mdx | 8 +- website/docs/sdk-reference/java.mdx | 4 +- website/docs/sdk-reference/js-ssr.mdx | 8 +- website/docs/sdk-reference/js.mdx | 8 +- website/docs/sdk-reference/js/_template.mdx | 24 +- website/docs/sdk-reference/kotlin.mdx | 8 +- website/docs/sdk-reference/node.mdx | 8 +- website/docs/sdk-reference/python.mdx | 4 +- website/docs/sdk-reference/ruby.mdx | 4 +- website/docs/sdk-reference/rust.mdx | 4 +- website/docs/sdk-reference/unreal.mdx | 4 +- website/sidebars.ts | 10 +- website/src/pages/index.js | 8 +- .../version-V1/sdk-reference/android.mdx | 4 +- .../version-V1/sdk-reference/cpp.mdx | 4 +- .../version-V1/sdk-reference/dart.mdx | 4 +- .../version-V1/sdk-reference/dotnet.mdx | 4 +- .../version-V1/sdk-reference/elixir.mdx | 4 +- .../version-V1/sdk-reference/ios.mdx | 4 +- .../version-V1/sdk-reference/java.mdx | 4 +- .../version-V1/sdk-reference/js-ssr.mdx | 8 +- .../version-V1/sdk-reference/js.mdx | 8 +- .../version-V1/sdk-reference/kotlin.mdx | 4 +- .../version-V1/sdk-reference/node.mdx | 8 +- .../version-V1/sdk-reference/python.mdx | 4 +- .../version-V1/sdk-reference/ruby.mdx | 4 +- .../version-V1/sdk-reference/rust.mdx | 4 +- .../version-V1/sdk-reference/unreal.mdx | 4 +- 35 files changed, 1943 insertions(+), 1073 deletions(-) create mode 100644 website/docs/sdk-reference/dotnet/_template.mdx create mode 100644 website/docs/sdk-reference/dotnet/generic-host.mdx diff --git a/website/docs/sdk-reference/android.mdx b/website/docs/sdk-reference/android.mdx index 2e3f2eeb1..fd56cbc90 100644 --- a/website/docs/sdk-reference/android.mdx +++ b/website/docs/sdk-reference/android.mdx @@ -416,7 +416,9 @@ ConfigCatClient client = ConfigCatClient.get("#YOUR-SDK-KEY#", options -> { client.forceRefresh(); ``` -> `getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +:::caution +`getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +::: ## Hooks diff --git a/website/docs/sdk-reference/cpp.mdx b/website/docs/sdk-reference/cpp.mdx index 8d9944e68..3d426dfd5 100644 --- a/website/docs/sdk-reference/cpp.mdx +++ b/website/docs/sdk-reference/cpp.mdx @@ -371,7 +371,9 @@ auto client = ConfigCatClient::get("#YOUR-SDK-KEY#", &options); client->forceRefresh(); ``` -> `getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +:::caution +`getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +::: ## Hooks diff --git a/website/docs/sdk-reference/dart.mdx b/website/docs/sdk-reference/dart.mdx index ef881ab93..65de1eae4 100644 --- a/website/docs/sdk-reference/dart.mdx +++ b/website/docs/sdk-reference/dart.mdx @@ -416,7 +416,9 @@ final client = ConfigCatClient.get( client.forceRefresh(); ``` -> `getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +:::caution +`getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +::: ## Hooks diff --git a/website/docs/sdk-reference/dotnet.mdx b/website/docs/sdk-reference/dotnet.mdx index 8ec75f70a..bdc5c5c4a 100644 --- a/website/docs/sdk-reference/dotnet.mdx +++ b/website/docs/sdk-reference/dotnet.mdx @@ -4,1023 +4,13 @@ title: .NET SDK Reference description: ConfigCat .NET SDK Reference. This is a step-by-step guide on how to use feature flags in your .NET application. --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; +import DotNetSdkReferenceTemplate, { getAdjustedToc } from "./dotnet/\_template.mdx"; + export const NetSchema = require('@site/src/schema-markup/sdk-reference/net.json'); -[![Star on GitHub](https://img.shields.io/github/stars/configcat/.net-sdk.svg?style=social)](https://github.com/configcat/.net-sdk/stargazers) -[![Build status](https://github.com/configcat/.net-sdk/actions/workflows/dotnet-sdk-ci.yml/badge.svg?branch=master)](https://github.com/configcat/.net-sdk/actions/workflows/dotnet-sdk-ci.yml) -[![NuGet Version](https://img.shields.io/nuget/v/ConfigCat.Client)](https://www.nuget.org/packages/ConfigCat.Client/) -[![Sonar Coverage](https://img.shields.io/sonar/coverage/net-sdk?logo=SonarCloud&server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/project/overview?id=net-sdk) -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=net-sdk&metric=alert_status)](https://sonarcloud.io/dashboard?id=net-sdk) - -ConfigCat .NET SDK on GitHub - -## Getting started - -### 1. Install _ConfigCat SDK_ [NuGet package](https://www.nuget.org/packages/ConfigCat.Client) - - - - -```powershell -Install-Package ConfigCat.Client -``` - - - - -``` -dotnet add package ConfigCat.Client -``` - - - - -:::info -To get the _ConfigCat SDK_ up and running in Unity, please refer to [this guide](./unity.mdx). -::: - -### 2. Import package - -```csharp -using ConfigCat.Client; -``` - -### 3. Create the _ConfigCat_ client with your _SDK Key_ - -```csharp -var client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); -``` - -### 4. Get your setting value - -```csharp -var isMyAwesomeFeatureEnabled = await client.GetValueAsync("isMyAwesomeFeatureEnabled", false); -if (isMyAwesomeFeatureEnabled) -{ - doTheNewThing(); -} -else -{ - doTheOldThing(); -} -``` - -The _ConfigCat SDK_ also offers a synchronous API for feature flag evaluation. Read more [here](#snapshots-and-non-blocking-synchronous-feature-flag-evaluation). - -### 5. Dispose the _ConfigCat_ client - -You can safely dispose all clients at once or individually and release all associated resources on application exit. - -```csharp -ConfigCatClient.DisposeAll(); // disposes all clients -// -or- -client.Dispose(); // disposes a specific client -``` - -## Creating the _ConfigCat Client_ - -_ConfigCat Client_ is responsible for: - -- managing the communication between your application and ConfigCat servers. -- caching your setting values and feature flags. -- serving values quickly in a failsafe way. - -`ConfigCatClient.Get(sdkKey: "")` returns a client with default options. - -### Customizing the _ConfigCat Client_ - -To customize the SDK's behavior, you can pass an additional `Action` parameter to the `Get()` static -factory method where the `ConfigCatClientOptions` class is used to set up the _ConfigCat Client_. - -```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => -{ - options.PollingMode = PollingModes.ManualPoll; - options.Logger = new ConsoleLogger(LogLevel.Info); -}); -``` - -These are the available options on the `ConfigCatClientOptions` class: - -| Properties | Description | Default | -| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -| `PollingMode` | Optional, sets the polling mode for the client. [More about polling modes](#polling-modes). | `PollingModes.AutoPoll()` | -| `ConfigFetcher` | Optional, [`IConfigCatConfigFetcher`](https://github.com/configcat/.net-sdk/blob/master/src/ConfigCatClient/ConfigService/IConfigCatConfigFetcher.cs) instance for downloading a config. | [`HttpClientConfigFetcher`](https://github.com/configcat/.net-sdk/blob/master/src/ConfigCatClient/ConfigService/HttpClientConfigFetcher.cs) | -| `ConfigCache` | Optional, [`IConfigCatCache`](https://github.com/configcat/.net-sdk/blob/master/src/ConfigCatClient/Cache/IConfigCatCache.cs) instance for caching the downloaded config. | [`InMemoryConfigCache`](https://github.com/configcat/.net-sdk/blob/master/src/ConfigCatClient/Cache/InMemoryConfigCache.cs) | -| `Logger` | Optional, [`IConfigCatLogger`](https://github.com/configcat/.net-sdk/blob/master/src/ConfigCatClient/Logging/IConfigCatLogger.cs) instance for tracing. | [`ConsoleLogger`](https://github.com/configcat/.net-sdk/blob/master/src/ConfigCatClient/Logging/ConsoleLogger.cs) (with WARNING level) | -| `LogFilter` | Optional, sets a custom log filter. [More about log filtering](#log-filtering). | `null` (none) | -| `BaseUrl` | Optional, sets the CDN base url (forward proxy, dedicated subscription) from where the SDK will download the config JSON. | | -| `Proxy` | Optional, [`IWebProxy`](https://learn.microsoft.com/en-us/dotnet/api/system.net.iwebproxy) instance that provides settings for routing HTTP requests made by the SDK through an HTTP, HTTPS, SOCKS, etc. proxy. [More about the proxy settings](#using-configcat-behind-a-proxy). | | -| `HttpTimeout` | Optional, sets the underlying HTTP client's timeout. [More about the HTTP timeout](#http-timeout). | `TimeSpan.FromSeconds(30)` | -| `FlagOverrides` | Optional, sets the local feature flag & setting overrides. [More about feature flag overrides](#flag-overrides). | | -| `DataGovernance` | Optional, describes the location of your feature flag and setting data within the ConfigCat CDN. This parameter needs to be in sync with your Data Governance preferences. [More about Data Governance](../advanced/data-governance.mdx). Available options: `Global`, `EuOnly` | `Global` | -| `DefaultUser` | Optional, sets the default user. [More about default user](#default-user). | `null` (none) | -| `Offline` | Optional, determines whether the client should be initialized to offline mode. [More about offline mode](#online--offline-mode). | `false` | - -Via the events provided by `ConfigCatClientOptions` you can also subscribe to the hooks (events) at the time of initialization. [More about hooks](#hooks). - -For example: - -```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => -{ - options.ClientReady += (s, e) => - { - var keys = ((IConfigCatClient)s).Snapshot().GetAllKeys(); - Console.WriteLine("Client is ready! Number of available feature flags: " + keys.Count); - }; -}); -``` - -:::info -You can acquire singleton client instances for your SDK keys using the `ConfigCatClient.Get(sdkKey: "")` static factory method. -(However, please keep in mind that subsequent calls to `ConfigCatClient.Get()` with the _same SDK Key_ return a _shared_ client instance, which was set up by the first call.) - -You can close all open clients at once using the `ConfigCatClient.DisposeAll()` method or do it individually using the `client.Dispose()` method. -::: - -## Anatomy of `GetValueAsync()` - -| Parameters | Description | -| -------------- | ------------------------------------------------------------------------------------------------------------ | -| `key` | **REQUIRED.** The key of a specific setting or feature flag. Set on _ConfigCat Dashboard_ for each setting. | -| `defaultValue` | **REQUIRED.** This value will be returned in case of an error. | -| `user` | Optional, _User Object_. Essential when using Targeting. [Read more about Targeting.](../targeting/targeting-overview.mdx) | - -```csharp -User userObject = new User("#UNIQUE-USER-IDENTIFIER#"); // Optional User Object -var value = await client.GetValueAsync("keyOfMyFeatureFlag", false, userObject); -``` - -:::caution -It is important to provide an argument for the `defaultValue` parameter, specifically for the `T` generic type parameter, -that matches the type of the feature flag or setting you are evaluating. Please refer to the following table for the corresponding types. -::: - -### Setting type mapping {/* #setting-type-mapping */} - -| Setting Kind | Type parameter `T` | -| -------------- | --------------------------------- | -| On/Off Toggle | `bool` / `bool?` | -| Text | `string` / `string?` | -| Whole Number | `int` / `int?` / `long` / `long?` | -| Decimal Number | `double` / `double?` | - -In addition to the types mentioned above, you also have the option to provide `object` or `object?` for the type parameter regardless of the setting kind. -However, this approach is not recommended as it may involve [boxing](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/types/boxing-and-unboxing). - -It's important to note that providing any other type for the type parameter will result in an `ArgumentException`. - -If you specify an allowed type but it mismatches the setting kind, an error message will be logged and `defaultValue` will be returned. - -When relying on type inference and not explicitly specifying the type parameter, be mindful of potential type mismatch issues, especially with number types. -For example, `client.GetValueAsync("keyOfMyDecimalSetting", 0)` will return `defaultValue` (`0`) instead of the actual value of the decimal setting because -the compiler infers the type as `int` instead of `double`, that is, the call is equivalent to `client.GetValueAsync("keyOfMyDecimalSetting", 0)`, -which is a type mismatch. - -To correctly evaluate a decimal setting, you should use: - -```csharp -var value = await client.GetValueAsync("keyOfMyDecimalSetting", 0.0); -// -or- -var value = await client.GetValueAsync("keyOfMyDecimalSetting", 0d); -// -or- -var value = await client.GetValueAsync("keyOfMyDecimalSetting", 0); -``` - -## Anatomy of `GetValueDetailsAsync()` - -`GetValueDetailsAsync()` is similar to `GetValueAsync()` but instead of returning the evaluated value only, it provides more detailed information about the evaluation result. - -| Parameters | Description | -| -------------- | ------------------------------------------------------------------------------------------------------------ | -| `key` | **REQUIRED.** The key of a specific setting or feature flag. Set on _ConfigCat Dashboard_ for each setting. | -| `defaultValue` | **REQUIRED.** This value will be returned in case of an error. | -| `user` | Optional, _User Object_. Essential when using Targeting. [Read more about Targeting.](../targeting/targeting-overview.mdx) | - -```csharp -User userObject = new User("#UNIQUE-USER-IDENTIFIER#"); // Optional User Object -var details = await client.GetValueDetailsAsync("keyOfMyFeatureFlag", false, userObject); -``` - -:::caution -It is important to provide an argument for the `defaultValue` parameter, specifically for the `T` generic type parameter, -that matches the type of the feature flag or setting you are evaluating. Please refer to [this table](#setting-type-mapping) for the corresponding types. -::: - -The `details` result contains the following information: - -| Field | Type | Description | -| --------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------- | -| `Key` | `string` | The key of the evaluated feature flag or setting. | -| `Value` | `bool` / `string` / `int` / `double` | The evaluated value of the feature flag or setting. | -| `User` | `User` | The User Object used for the evaluation. | -| `IsDefaultValue` | `bool` | True when the default value passed to `GetValueDetailsAsync()` is returned due to an error. | -| `ErrorCode` | `EvaluationErrorCode` | In case of an error, this property contains a code that identifies the reason for the error. | -| `ErrorMessage` | `string` | In case of an error, this property contains the error message. | -| `ErrorException` | `Exception` | In case of an error, this property contains the related exception object (if any). | -| `MatchedTargetingRule` | `ITargetingRule` | The Targeting Rule (if any) that matched during the evaluation and was used to return the evaluated value. | -| `MatchedPercentageOption` | `IPercentageOption` | The Percentage Option (if any) that was used to select the evaluated value. | -| `FetchTime` | `DateTime` | The last download time (UTC) of the current config. | - -## User Object - -The [User Object](../targeting/user-object.mdx) is essential if you'd like to use ConfigCat's [Targeting](../targeting/targeting-overview.mdx) feature. - -```csharp -User userObject = new User("#UNIQUE-USER-IDENTIFIER#"); -``` - -```csharp -User userObject = new User("john@example.com"); -``` - -| Parameters | Description | -| ---------- | ------------------------------------------------------------------------------------------------------------------------------- | -| `Id` | **REQUIRED.** Unique identifier of a user in your application. Can be any `string` value, even an email address. | -| `Email` | Optional parameter for easier Targeting Rule definitions. | -| `Country` | Optional parameter for easier Targeting Rule definitions. | -| `Custom` | Optional dictionary for custom attributes of a user for advanced Targeting Rule definitions. E.g. User role, Subscription type. | - -```csharp -User userObject = new User("#UNIQUE-USER-IDENTIFIER#") -{ - Email = "john@example.com", - Country = "United Kingdom", - Custom = - { - ["SubscriptionType"] = "Pro", - ["UserRole"] = "Admin" - } -}; -``` - -The `Custom` dictionary also allows attribute values other than `string` values: - -```csharp -User userObject = new User("#UNIQUE-USER-IDENTIFIER#") -{ - Custom = - { - ["Rating"] = 4.5, - ["RegisteredAt"] = DateTimeOffset.Parse("2023-11-22 12:34:56 +00:00", CultureInfo.InvariantCulture), - ["Roles"] = new[] { "Role1", "Role2" } - } -}; -``` - -### User Object Attribute Types - -All comparators support `string` values as User Object attribute (in some cases they need to be provided in a specific format though, see below), but some of them also support other types of values. It depends on the comparator how the values will be handled. The following rules apply: - -**Text-based comparators** (EQUALS, IS ONE OF, etc.) -* accept `string` values, -* all other values are automatically converted to `string` (a warning will be logged but evaluation will continue as normal). - -**SemVer-based comparators** (IS ONE OF, <, >=, etc.) -* accept `string` values containing a properly formatted, valid semver value, -* all other values are considered invalid (a warning will be logged and the currently evaluated Targeting Rule will be skipped). - -**Number-based comparators** (=, <, >=, etc.) -* accept `double` values and all other numeric values which can safely be converted to `double`, -* accept `string` values containing a properly formatted, valid `double` value, -* all other values are considered invalid (a warning will be logged and the currently evaluated Targeting Rule will be skipped). - -**Date time-based comparators** (BEFORE / AFTER) -* accept `DateTime` or `DateTimeOffset` values, which are automatically converted to a second-based Unix timestamp, -* accept `double` values representing a second-based Unix timestamp and all other numeric values which can safely be converted to `double`, -* accept `string` values containing a properly formatted, valid `double` value, -* all other values are considered invalid (a warning will be logged and the currently evaluated Targeting Rule will be skipped). - -**String array-based comparators** (ARRAY CONTAINS ANY OF / ARRAY NOT CONTAINS ANY OF) -* accept arrays of `string`, -* accept `string` values containing a valid JSON string which can be deserialized to an array of `string`, -* all other values are considered invalid (a warning will be logged and the currently evaluated Targeting Rule will be skipped). - -### Default user - -It's possible to set a default User Object that will be used on feature flag and setting evaluation. It can be useful when your application has a single user only or rarely switches users. - -You can set the default User Object either on SDK initialization: - -```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => - options.DefaultUser = new User(identifier: "john@example.com")); -``` - -...or using the `SetDefaultUser()` method of the `ConfigCatClient` object: - -```csharp -client.SetDefaultUser(new User(identifier: "john@example.com")); -``` - -Whenever the evaluation methods like `GetValueAsync()`, `GetValueDetailsAsync()`, etc. are called without an explicit `user` parameter, the SDK will automatically use the default user as a User Object. - -```csharp -var user = new User(identifier: "john@example.com"); -client.SetDefaultUser(user); - -// The default user will be used in the evaluation process. -var value = await client.GetValueAsync(key: "keyOfMyFeatureFlag", defaultValue: false); -``` - -When a `user` parameter is passed to the evaluation methods, it takes precedence over the default user. - -```csharp -var user = new User(identifier: "john@example.com"); -client.SetDefaultUser(user); - -var otherUser = new User(identifier: "brian@example.com"); - -// otherUser will be used in the evaluation process. -var value = await client.GetValueAsync(key: "keyOfMyFeatureFlag", defaultValue: false, user: otherUser); -``` - -You can also remove the default user by doing the following: - -```csharp -client.ClearDefaultUser(); -``` - -## Polling Modes - -The _ConfigCat SDK_ supports 3 different polling strategies to fetch feature flags and settings from the ConfigCat CDN. Once the latest data is downloaded, it is stored in the cache, then calls to `GetValueAsync()` use the cached data to evaluate feature flags and settings. With the following polling modes, you can customize the SDK to best fit to your application's lifecycle. -[More about polling modes.](../advanced/caching.mdx) - -### Auto polling (default) - -The _ConfigCat SDK_ downloads the latest config data from the ConfigCat CDN automatically every 60 seconds and stores it in the cache. - -Use the `pollInterval` option parameter to change the polling interval. - -```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => -{ - options.PollingMode = PollingModes.AutoPoll(pollInterval: TimeSpan.FromSeconds(95)); -}); -``` - -Available options: - -| Option Parameter | Description | Default | -| ----------------- | --------------------------------------------------------------------------------------------------- | ------- | -| `pollInterval` | Polling interval. | 60s | -| `maxInitWaitTime` | Maximum waiting time between the client initialization and the first config acquisition. | 5s | - -### Lazy loading - -When calling `GetValueAsync()`, the _ConfigCat SDK_ downloads the latest config data from the ConfigCat CDN only if it is not already present in the cache, or if the cache has expired. In this case `GetValueAsync()` will return the setting value after the cache is updated. - -Use `cacheTimeToLive` parameter to manage configuration lifetime. - -```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => -{ - options.PollingMode = PollingModes.LazyLoad(cacheTimeToLive: TimeSpan.FromSeconds(600)); -}); -``` - -Available options: - -| Option Parameter | Description | Default | -| ----------------- | ----------- | ------- | -| `cacheTimeToLive` | Cache TTL. | 60s | - -### Manual polling - -Manual polling gives you full control over when the config data is downloaded from the ConfigCat CDN. The _ConfigCat SDK_ will not download it automatically. Calling `ForceRefreshAsync()` is your application's responsibility. - -```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => -{ - options.PollingMode = PollingModes.ManualPoll; -}); - -await client.ForceRefreshAsync(); -``` - -> `GetValueAsync()` returns `defaultValue` if the cache is empty. Call `ForceRefreshAsync()` to update the cache. - -```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => -{ - options.PollingMode = PollingModes.ManualPoll; -}); - -Console.WriteLine(await client.GetValueAsync("keyOfMyTextSetting", "my default value")); // console: "my default value" -await client.ForceRefreshAsync(); -Console.WriteLine(await client.GetValueAsync("keyOfMyTextSetting", "my default value")); // console: "value from server" -``` - -## Hooks - -The SDK provides several hooks (events), by means of which you can get notified of its actions. -Via the following events you can subscribe to particular events raised by the _ConfigCat_ client: - -- `event EventHandler ClientReady`: This event is raised when the client reaches the ready state, i.e. completes initialization. - * If Lazy Loading or Manual Polling is used, it's considered ready right after the initial sync with the external cache (if any) completes. - * If Auto Polling is used, the ready state is reached as soon as - * the initial sync with the external cache yields up-to-date config data, - * otherwise, if the client is online (i.e. HTTP requests are allowed), the first config fetch operation completes (regardless of success or failure), - * or the time specified via Auto Polling's `maxInitWaitTime` option has passed. - - Reaching the ready state usually means the client is ready to evaluate feature flags and settings. - However, please note that this is not guaranteed. In case of initialization failure or timeout, the internal cache - may be empty or expired even after the ready state is reported. You can verify this by checking the `CacheState` property of the event arguments. -- `event EventHandler ConfigFetched`: This event is raised each time the client attempts to refresh the cached config by - fetching the latest version from the ConfigCat CDN. It is raised not only when `ForceRefreshAsync` is called but also - when the refresh is initiated by the client automatically. Thus, this event allows you to observe potential network issues that occur under the hood. -- `event EventHandler ConfigChanged`: This event is raised first when the client's internal cache gets populated. - Afterwards, it is raised again each time the internally cached config is updated to a newer version, either as a result of synchronization - with the external cache, or as a result of fetching a newer version from the ConfigCat CDN. -- `event EventHandler FlagEvaluated`: This event is raised each time the client evaluates a feature flag or setting. - The event provides the same evaluation details that you would get from [`GetValueDetailsAsync()`](#anatomy-of-getvaluedetailsasync). -- `event EventHandler Error`: This event is raised when an error occurs within the client. - -You can subscribe to these events either on initialization: - -```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => -{ - options.PollingMode = PollingModes.ManualPoll; - options.FlagEvaluated += (s, e) => { /* handle the event */ }; -}); -``` - -...or directly on the `ConfigCatClient` instance: - -```csharp -client.FlagEvaluated += (s, e) => { /* handle the event */ }; -``` - -:::caution -Some events (e.g. `ClientReady`, `ConfigChanged` and `Error`) may be raised before `ConfigCatClient.Get` returns. -This means you may miss them unless you subscribe on initialization. - -However, even if you do, there's another gotcha: it's not safe to use the outer `client` variable in your event handler -because it may not yet be assigned when the handler is called. Instead, you can safely access the client instance -via the sender parameter like `var client = (IConfigCatClient)s;`. -::: - -## Snapshots and non-blocking synchronous feature flag evaluation - -Currently, the _ConfigCat_ client provides both asynchronous and synchronous methods for evaluating feature flags and settings. -However, depending on the setup, the synchronous methods may block the executing thread for longer periods of time -(e.g. when downloading config data from the ConfigCat CDN servers), which can lead to an unresponsive application. -To prevent such issues, the problematic methods have been deprecated and are going to be removed in a future major version. - -As an alternative, since v9.2.0, the .NET SDK provides a way to synchronously evaluate feature flags and settings -as a non-blocking operation, via _snapshots_. - -Using the `Snapshot()` method, you can capture the current state of the _ConfigCat_ client (including the latest downloaded config data) -and use the resulting snapshot object to synchronously evaluate feature flags and settings based on the captured state: - -```csharp -using var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", - options => options.PollingMode = PollingModes.AutoPoll()); - -// Wait for the client to initialize. -await client.WaitForReadyAsync(); - -var snapshot = client.Snapshot(); - -var user = new User("#UNIQUE-USER-IDENTIFIER#"); -foreach (var key in snapshot.GetAllKeys()) -{ - var value = snapshot.GetValue(key, default(object), user); - Console.WriteLine($"{key}: {value}"); -} -``` - -Creating a snapshot is a cheap operation. This is possible because snapshots capture the client's internal (in-memory) cache. -No attempt is made to refresh the internal cache, even if it's empty or expired. - -:::caution -Please note that creating and using a snapshot -* won't trigger a sync with the external cache when working with [shared caching](../advanced/caching.mdx#shared-cache), -* won't fetch the latest config data from the ConfigCat CDN when the internally cached config data is empty or expired. -::: - -For the above reasons, it's recommended to use snapshots in conjunction with the Auto Polling mode, where the SDK automatically -updates the internal cache in the background. (For other polling modes, you'll need to manually initiate a cache refresh -by calling `ForceRefreshAsync`.) - -Because of this behavior, it's important to make sure that the client has completed initialization and populated its internal cache -before creating snapshots. Otherwise the snapshot's evaluation methods won't have the data to do actual evaluation, -but will just return the default value you pass to them. Which behavior is usually not what you want in your application. - -In Auto Polling mode, you can use the `WaitForReadyAsync` method to wait for the latest config data to become available locally. -This is an asynchronous operation, which completes as soon as the client reaches the ready state, i.e. completes initialization -(or the time specified via the `maxInitWaitTime` option passes). - -(Please note that this doesn't apply to other polling modes. In those cases, the client doesn't contact the ConfigCat CDN -during initialization, so the ready state is reached as soon as the first sync with the external cache completes.) - -Typically, you call `WaitForReadyAsync` and wait for its completion only once, in the initialization phase of your application. - -:::caution -Reaching the ready state usually means the client is ready to evaluate feature flags and settings. -However, please note that this is not guaranteed. In case of initialization failure or timeout, the internal cache -may be empty or expired even after the ready state is reported. You can verify this by checking the return value. -::: - -```csharp -var clientCacheState = await client.WaitForReadyAsync(); -if (clientCacheState == ClientCacheState.NoFlagData) -{ - // Handle initialization failure (see below). - Console.WriteLine("ConfigCat client failed to obtain the config data during initialization."); -} -``` - -You have the following options to handle unsuccessful initialization: -* If it's acceptable for your application to start up and use the default values passed to the evaluation methods, - you may log some warning (or skip the check altogether as the client will log warnings anyway), and let the application continue. -* Otherwise, you need to either terminate the application or continue waiting. The latter is an option because the client - might be able to obtain the config data later, in the case of a transient problem like some temporary network issue. - However, the _ConfigCat SDK_ doesn't provide out-of-the-box support for this case currently. You can implement this logic by - subscribing to the `ConfigChanged` hook and waiting for the first event. - -## Online / Offline mode - -In cases where you want to prevent the SDK from making HTTP calls, you can switch it to offline mode: - -```csharp -client.SetOffline(); -``` - -In offline mode, the SDK won't initiate HTTP requests and will work only from its cache. - -To switch the SDK back to online mode, do the following: - -```csharp -client.SetOnline(); -``` - -Using the `client.IsOffline` property you can check whether the SDK is in offline mode. - -## Flag Overrides - -With flag overrides you can overwrite the feature flags & settings downloaded from the ConfigCat CDN with local values. -Moreover, you can specify how the overrides should apply over the downloaded values. The following 3 behaviours are supported: - -- **Local only** (`OverrideBehaviour.LocalOnly`): When evaluating values, the SDK will not use feature flags & settings from the ConfigCat CDN, but it will use all feature flags & settings that are loaded from local-override sources. - -- **Local over remote** (`OverrideBehaviour.LocalOverRemote`): When evaluating values, the SDK will use all feature flags & settings that are downloaded from the ConfigCat CDN, plus all feature flags & settings that are loaded from local-override sources. If a feature flag or a setting is defined both in the downloaded and the local-override source then the local-override version will take precedence. - -- **Remote over local** (`OverrideBehaviour.RemoteOverLocal`): When evaluating values, the SDK will use all feature flags & settings that are downloaded from the ConfigCat CDN, plus all feature flags & settings that are loaded from local-override sources. If a feature flag or a setting is defined both in the downloaded and the local-override source then the downloaded version will take precedence. - -You can load your feature flag & setting overrides from a file or from a simple `Dictionary` structure. - -### JSON File - -The SDK can load your feature flag & setting overrides from a file. -You can also specify whether the file should be reloaded when it gets modified. - -#### File - -```csharp -IConfigCatClient client = ConfigCatClient.Get("localhost", options => -{ - options.FlagOverrides = FlagOverrides.LocalFile( - "path/to/local_flags.json", // path to the file - true, // reload the file when it gets modified - OverrideBehaviour.LocalOnly - ); -}); -``` - -#### JSON File Structure - -The SDK supports 2 types of JSON structures to describe feature flags & settings. - -##### 1. Simple (key-value) structure - -```json -{ - "flags": { - "enabledFeature": true, - "disabledFeature": false, - "intSetting": 5, - "doubleSetting": 3.14, - "stringSetting": "test" - } -} -``` - -##### 2. Complex (full-featured) structure - -This is the same format that the SDK downloads from the ConfigCat CDN. -It allows the usage of all features that are available on the ConfigCat Dashboard. - -You can download your current config JSON from ConfigCat's CDN and use it as a baseline. - -A convenient way to get the config JSON for a specific SDK Key is to install the [ConfigCat CLI](https://github.com/configcat/cli) tool -and execute the following command: - -```bash -configcat config-json get -f v6 -p {YOUR-SDK-KEY} > config.json -``` - -(Depending on your [Data Governance](../advanced/data-governance.mdx) settings, you may need to add the `--eu` switch.) - -Alternatively, you can download the config JSON manually, based on your [Data Governance](../advanced/data-governance.mdx) settings: - -- GLOBAL: `https://cdn-global.configcat.com/configuration-files/{YOUR-SDK-KEY}/config_v6.json` -- EU: `https://cdn-eu.configcat.com/configuration-files/{YOUR-SDK-KEY}/config_v6.json` - -```json -{ - "p": { - // hash salt, required only when confidential text comparator(s) are used - "s": "80xCU/SlDz1lCiWFaxIBjyJeJecWjq46T4eu6GtozkM=" - }, - "s": [ // array of segments - { - "n": "Beta Users", // segment name - "r": [ // array of User Conditions (there is a logical AND relation between the elements) - { - "a": "Email", // comparison attribute - "c": 0, // comparator (see below) - "l": [ // comparison value (see below) - "john@example.com", "jane@example.com" - ] - } - ] - } - ], - "f": { // key-value map of feature flags & settings - "isFeatureEnabled": { // key of a particular flag / setting - "t": 0, // setting type, possible values: - // 0 -> on/off setting (feature flag) - // 1 -> text setting - // 2 -> whole number setting - // 3 -> decimal number setting - "r": [ // array of Targeting Rules (there is a logical OR relation between the elements) - { - "c": [ // array of conditions (there is a logical AND relation between the elements) - { - "u": { // User Condition - "a": "Email", // comparison attribute - "c": 2, // comparator, possible values and required comparison value types: - // 0 -> IS ONE OF (cleartext) + string array comparison value ("l") - // 1 -> IS NOT ONE OF (cleartext) + string array comparison value ("l") - // 2 -> CONTAINS ANY OF (cleartext) + string array comparison value ("l") - // 3 -> NOT CONTAINS ANY OF (cleartext) + string array comparison value ("l") - // 4 -> IS ONE OF (semver) + semver string array comparison value ("l") - // 5 -> IS NOT ONE OF (semver) + semver string array comparison value ("l") - // 6 -> < (semver) + semver string comparison value ("s") - // 7 -> <= (semver + semver string comparison value ("s") - // 8 -> > (semver) + semver string comparison value ("s") - // 9 -> >= (semver + semver string comparison value ("s") - // 10 -> = (number) + number comparison value ("d") - // 11 -> <> (number + number comparison value ("d") - // 12 -> < (number) + number comparison value ("d") - // 13 -> <= (number + number comparison value ("d") - // 14 -> > (number) + number comparison value ("d") - // 15 -> >= (number) + number comparison value ("d") - // 16 -> IS ONE OF (hashed) + string array comparison value ("l") - // 17 -> IS NOT ONE OF (hashed) + string array comparison value ("l") - // 18 -> BEFORE (UTC datetime) + second-based Unix timestamp number comparison value ("d") - // 19 -> AFTER (UTC datetime) + second-based Unix timestamp number comparison value ("d") - // 20 -> EQUALS (hashed) + string comparison value ("s") - // 21 -> NOT EQUALS (hashed) + string comparison value ("s") - // 22 -> STARTS WITH ANY OF (hashed) + string array comparison value ("l") - // 23 -> NOT STARTS WITH ANY OF (hashed) + string array comparison value ("l") - // 24 -> ENDS WITH ANY OF (hashed) + string array comparison value ("l") - // 25 -> NOT ENDS WITH ANY OF (hashed) + string array comparison value ("l") - // 26 -> ARRAY CONTAINS ANY OF (hashed) + string array comparison value ("l") - // 27 -> ARRAY NOT CONTAINS ANY OF (hashed) + string array comparison value ("l") - // 28 -> EQUALS (cleartext) + string comparison value ("s") - // 29 -> NOT EQUALS (cleartext) + string comparison value ("s") - // 30 -> STARTS WITH ANY OF (cleartext) + string array comparison value ("l") - // 31 -> NOT STARTS WITH ANY OF (cleartext) + string array comparison value ("l") - // 32 -> ENDS WITH ANY OF (cleartext) + string array comparison value ("l") - // 33 -> NOT ENDS WITH ANY OF (cleartext + string array comparison value ("l") - // 34 -> ARRAY CONTAINS ANY OF (cleartext) + string array comparison value ("l") - // 35 -> ARRAY NOT CONTAINS ANY OF (cleartext) + string array comparison value ("l") - "l": [ // comparison value - depending on the comparator, another type of value may need - // to be specified (see above): - // "s": string - // "d": number - "@example.com" - ] - } - }, - { - "p": { // Flag Condition (Prerequisite) - "f": "mainIntFlag", // key of prerequisite flag - "c": 0, // comparator, possible values: 0 -> EQUALS, 1 -> NOT EQUALS - "v": { // comparison value (value's type must match the prerequisite flag's type) - "i": 42 - } - } - }, - { - "s": { // Segment Condition - "s": 0, // segment index, a valid index into the top-level segment array ("s") - "c": 1 // comparator, possible values: 0 -> IS IN SEGMENT, 1 -> IS NOT IN SEGMENT - } - } - ], - "s": { // alternatively, an array of Percentage Options ("p", see below) can also be specified - "v": { // the value served when the rule is selected during evaluation - "b": true - }, - "i": "bcfb84a7" - } - } - ], - "p": [ // array of Percentage Options - { - "p": 10, // % value - "v": { // the value served when the Percentage Option is selected during evaluation - "b": true - }, - "i": "bcfb84a7" - }, - { - "p": 90, - "v": { - "b": false - }, - "i": "bddac6ae" - } - ], - "v": { // fallback value, served when none of the Targeting Rules match, - // no Percentage Options are defined or evaluation of these is not possible - "b": false // depending on the setting type, another type of value may need to be specified: - // text setting -> "s": string - // whole number setting -> "i": number - // decimal number setting -> "d": number - }, - "i": "430bded3" // variation id (for analytical purposes) - } - } -} -``` - -For a more comprehensive specification of the config JSON v6 format, you may refer to [this JSON schema document](https://github.com/configcat/config-json/blob/main/V6/config.schema.json). - -### Dictionary - -You can set up the SDK to load your feature flag & setting overrides from a `Dictionary`. - -```csharp -var dictionary = new Dictionary -{ - {"enabledFeature", true}, - {"disabledFeature", false}, - {"intSetting", 5}, - {"doubleSetting", 3.14}, - {"stringSetting", "test"}, -}; - -IConfigCatClient client = ConfigCatClient.Get("localhost", options => -{ - options.FlagOverrides = FlagOverrides.LocalDictionary(dictionary, OverrideBehaviour.LocalOnly); -}); -``` - -## Logging - -### Setting log level - -```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); - -client.LogLevel = LogLevel.Info; -``` - -Available log levels: - -| Level | Description | -| ------- | ------------------------------------------------------- | -| Off | Nothing is logged. | -| Error | Only error level events are logged. | -| Warning | Default. Errors and Warnings are logged. | -| Info | Errors, Warnings and feature flag evaluation is logged. | -| Debug | All of the above plus debug info is logged. | - -Info level logging helps to inspect the feature flag evaluation process: - -```bash -ConfigCat.INFO [5000] Evaluating 'isPOCFeatureEnabled' for User '{"Identifier":"","Email":"configcat@example.com","Country":"US","SubscriptionType":"Pro","Role":"Admin","version":"1.0.0"}' - Evaluating targeting rules and applying the first match if any: - - IF User.Email CONTAINS ANY OF ['@something.com'] THEN 'False' => no match - - IF User.Email CONTAINS ANY OF ['@example.com'] THEN 'True' => MATCH, applying rule - Returning 'True'. -``` - -### Custom logger implementation - -By default, the SDK logs to [the console's standard output](https://learn.microsoft.com/en-us/dotnet/api/system.console.out) but it also allows you to inject any custom logger implementation via the `ConfigCatClientOptions.Logger` property. - -Sample code on how to create a basic file logger implementation for ConfigCat client: See Sample Code - -Another sample which shows how to implement an adapter to [the built-in logging framework](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging) of .NET Core/.NET 5+: See Sample Code - -### Log Filtering - -You can define a custom log filter by providing a callback function via the `ConfigCatClientOptions.LogFilter` property. The callback will be called by the _ConfigCat SDK_ each time a log event occurs (and the event passes the minimum log level specified by the `IConfigCatLogger.LogLevel` property). That is, the callback allows you to filter log events by `level`, `eventId`, `message` or `exception`. The formatted message string can be obtained via `message.InvariantFormattedMessage`. -If the callback function returns `true`, the event will be logged, otherwise it will be skipped. - -```cs -// Filter out events with id 1001 from the log. -LogFilterCallback logFilter = (LogLevel level, LogEventId eventId, ref FormattableLogMessage message, Exception? exception) => eventId != 1001; - -var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => options.LogFilter = logFilter); -``` - -:::caution -Please make sure that your log filter logic doesn't perform heavy computation and doesn't block the executing thread. A complex or incorrectly implemented log filter can degrade the performance of the SDK. -::: - -## `GetAllKeysAsync()` - -You can get the keys for all available feature flags and settings by calling the `GetAllKeysAsync()` method of the `ConfigCatClient`. - -```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); -IEnumerable keys = await client.GetAllKeysAsync(); -``` - -## `GetAllValuesAsync()` - -Evaluates and returns the values of all feature flags and settings. Passing a [User Object](#user-object) is optional. - -```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); -IDictionary settingValues = await client.GetAllValuesAsync(); - -// invoke with User Object -User userObject = new User("#UNIQUE-USER-IDENTIFIER#"); -IDictionary settingValuesTargeting = await client.GetAllValuesAsync(userObject); -``` - -## `GetAllValueDetailsAsync()` - -Evaluates and returns the values along with evaluation details of all feature flags and settings. Passing a [User Object](#user-object) is optional. - -```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); -IReadOnlyList settingValues = await client.GetAllValueDetailsAsync(); - -// invoke with User Object -User userObject = new User("435170f4-8a8b-4b67-a723-505ac7cdea92"); -IReadOnlyList settingValuesTargeting = await client.GetAllValueDetailsAsync(userObject); -``` - -## Using custom cache implementation - -The _ConfigCat SDK_ stores the downloaded config data in a local cache to minimize network traffic and enhance client performance. -If you prefer to use your own cache solution, such as an external or distributed cache in your system, -you can implement the [`IConfigCatCache`](https://github.com/configcat/.net-sdk/blob/master/src/ConfigCatClient/Cache/IConfigCatCache.cs) interface -and set the `ConfigCache` parameter in the setup callback of `ConfigCatClient.Get`. -This allows you to seamlessly integrate ConfigCat with your existing caching infrastructure. - -```csharp -public class MyCustomCache : IConfigCatCache -{ - public string? Get(string key) - { - /* insert your synchronous cache read logic here */ - } - - public Task GetAsync(string key, CancellationToken cancellationToken = default) - { - /* insert your asynchronous cache read logic here */ - } - - public void Set(string key, string value) - { - /* insert your synchronous cache write logic here */ - } - - public Task SetAsync(string key, string value, CancellationToken cancellationToken = default) - { - /* insert your asynchronous cache write logic here */ - } -} -``` - -then - -```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => -{ - options.ConfigCache = new MyCustomCache() -}); -``` - -:::info -The .NET SDK supports *shared caching*. You can read more about this feature and the required minimum SDK versions [here](../advanced/caching.mdx#shared-cache). -::: - -## Using ConfigCat behind a proxy {/* #using-configcat-behind-a-proxy */} - -Provide your own network credentials (username/password) and proxy server settings (proxy server/port) by setting the -`Proxy` property in the setup callback of `ConfigCatClient.Get`. - -```csharp -var myProxySettings = new WebProxy(proxyHost, proxyPort) -{ - UseDefaultCredentials = false, - Credentials = new NetworkCredential(proxyUserName, proxyPassword) -}; - -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => -{ - options.Proxy = myProxySettings; -}); -``` - -## HTTP Timeout - -You can set the maximum wait time for a ConfigCat HTTP response. - -```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => -{ - options.HttpTimeout = TimeSpan.FromSeconds(10); -}); -``` - -The default timeout is 30 seconds. - -## Platform compatibility - -The _ConfigCat SDK_ supports all the widespread .NET JIT runtimes, everything that implements [.NET Standard 2.0](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0)+ and supports TLS 1.2 should work. -Starting with v9.3.0, it can also be used in applications that employ [trimmed self-contained](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-self-contained) or various [ahead-of-time (AOT) compilation](https://en.wikipedia.org/wiki/Ahead-of-time_compilation) deployment models. - -Based on our tests, the SDK is compatible with the following runtimes/deployment models: -* .NET Framework 4.5+ (including Ngen) -* .NET Core 3.1, .NET 5+ (including Crossgen2/ReadyToRun and Native AOT) -* Mono 5.10+ -* .NET for Android (formerly known as Xamarin.Android) -* .NET for iOS (formerly known as Xamarin.iOS) -* Unity 2021.3+ (Mono JIT) -* Unity 2021.3+ (IL2CPP)* -* Universal Windows Platform 10.0.16299.0+ (.NET Native)** -* WebAssembly (Mono AOT/Emscripten, also known as wasm-tools) - -*Unity WebGL also works but needs a bit of extra effort: you will need to enable WebGL compatibility by calling the `ConfigCatClient.PlatformCompatibilityOptions.EnableUnityWebGLCompatibility` method. For more details, see [Sample Scripts](https://github.com/configcat/.net-sdk/tree/master/samples/UnityWebGL).
-**To make the SDK work in Release builds on UWP, you will need to add `` to your application's [.rd.xml](https://learn.microsoft.com/en-us/windows/uwp/dotnet-native/runtime-directives-rd-xml-configuration-file-reference) file. See also [this discussion](https://github.com/dotnet/runtime/issues/29912#issuecomment-638471351). - -:::info -We strive to provide an extensive support for the various .NET runtimes and versions. If you still encounter an issue with the SDK on some platform, please open a [GitHub issue](https://github.com/configcat/.net-sdk/issues/new/choose) or [contact support](https://configcat.com/support). -::: - -## Troubleshooting - -When the _ConfigCat SDK_ does not work as expected in your application, please check for the following potential problems: - -- **Symptom:** Instead of the actual value, the default one is constantly returned by `GetValueAsync()` and - the log contains the following message (provided that the client is set up to log error level events as described [here](#logging)): - "Secure connection could not be established. Please make sure that your application is enabled to use TLS 1.2+." - - **Problem:** ConfigCat CDN servers require TLS 1.2 or newer security protocol for communication. - As for allowed security protocols, please keep in mind that newer .NET runtimes rely on operating system settings, - older versions, however, may need additional setup to make secure communication with the CDN servers work. - - | Runtime Version | Default Protocols | - | -------------------------------------------- | ---------------------- | - | .NET Framework 4.5 and earlier | SSL 3.0, TLS 1.0 | - | .NET Framework 4.6 | TLS 1.0, 1.1, 1.2, 1.3 | - | .NET Framework 4.7+, .NET Core 1.0+, .NET 5+ | System (OS) Defaults | - - As shown in the table above, if your application runs on .NET Framework 4.5, by default it will fail to establish a connection to the CDN servers. - Read [this](https://stackoverflow.com/a/58195987/8656352) for more details. - - **Solution**: The best solution to the problem is to upgrade your application to target a newer runtime but in case that is not possible, you can use the following workaround: - - ```csharp - ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; - ``` - - (Place this code at the startup of your application, **before** any instances of `ConfigCatClient` is created.) - -## Sample Applications - -Check out our Sample Applications how they use the _ConfigCat SDK_: - -- Sample Console App -- Sample Multi-page Web App (ASP.NET Core MVC) -- Sample Single-page Web App (ASP.NET Core Blazor WebAssembly) -- Sample Mobile/Windows Store App (.NET MAUI) - -## Guides - -See the following guides on how to use ConfigCat's .NET SDK: - -- .NET 6 -- ASP.NET Core - -## Look under the hood + + -- ConfigCat .NET SDK on GitHub -- ConfigCat .NET SDK on nuget.org +export const toc = getAdjustedToc(); diff --git a/website/docs/sdk-reference/dotnet/_template.mdx b/website/docs/sdk-reference/dotnet/_template.mdx new file mode 100644 index 000000000..70684e368 --- /dev/null +++ b/website/docs/sdk-reference/dotnet/_template.mdx @@ -0,0 +1,1790 @@ +import Link from '@docusaurus/Link'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import If from '@site/src/components/If'; + +{/** + * @typedef Props + * @property {("generic-host")} [platform] + */} + +export const getAdjustedToc = (platform) => { + // Workaround that removes hidden sections from the table of contents. See also: + // * https://docusaurus.io/docs/markdown-features/toc#customizing-table-of-contents-generation + // * github.com/facebook/docusaurus/issues/6201 + + let adjustedToc; + if (platform === "generic-host") { + adjustedToc = removeSection(adjustedToc, toc, "3-create-the-configcat-client-with-your-sdk-key"); + adjustedToc = removeSection(adjustedToc, toc, "4-get-your-setting-value"); + adjustedToc = removeSection(adjustedToc, toc, "5-dispose-the-configcat-client"); + adjustedToc = removeSection(adjustedToc, toc, "creating-the-configcat-client"); + } else { + adjustedToc = removeSection(adjustedToc, toc, "3-register-the-configcat-client-with-your-sdk-key"); + adjustedToc = removeSection(adjustedToc, toc, "4-initialize-the-configcat-client"); + adjustedToc = removeSection(adjustedToc, toc, "5-obtain-the-configcat-client"); + adjustedToc = removeSection(adjustedToc, toc, "6-get-your-setting-value"); + adjustedToc = removeSection(adjustedToc, toc, "about-the-configcat-client"); + } + return adjustedToc ?? toc; + + function removeSection(adjustedToc, toc, sectionId) { + const index = (adjustedToc ?? toc).findIndex(item => item.id === sectionId); + return index >= 0 + ? (adjustedToc ??= [...toc], adjustedToc.splice(index, 1), adjustedToc) + : adjustedToc; + } +}; + +[![Star on GitHub](https://img.shields.io/github/stars/configcat/.net-sdk.svg?style=social)](https://github.com/configcat/.net-sdk/stargazers) +[![Build status](https://github.com/configcat/.net-sdk/actions/workflows/dotnet-sdk-ci.yml/badge.svg?branch=master)](https://github.com/configcat/.net-sdk/actions/workflows/dotnet-sdk-ci.yml) +[![NuGet Version](https://img.shields.io/nuget/v/ConfigCat.Client)](https://www.nuget.org/packages/ConfigCat.Client/) +[![Sonar Coverage](https://img.shields.io/sonar/coverage/net-sdk?logo=SonarCloud&server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/project/overview?id=net-sdk) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=net-sdk&metric=alert_status)](https://sonarcloud.io/dashboard?id=net-sdk) + + + + :::info + The ConfigCat SDK for .NET can be used in all types of .NET applications. + + However, for ASP.NET Core applications and other modern .NET applications built on .NET Generic Host or .NET's + standard dependency injection, ConfigCat offers [an alternative, more streamlined integration experience](./generic-host). + ::: + + + + + + :::info + As an alternative to using the [core ConfigCat SDK for .NET](../../dotnet), this approach offers a more streamlined + integration for the following types of .NET applications: + - [ASP.NET Core](https://dotnet.microsoft.com/en-us/apps/aspnet) + - [Blazor](https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor) + - [MAUI](https://dotnet.microsoft.com/en-us/apps/maui) + - [Worker Services](https://learn.microsoft.com/en-us/dotnet/core/extensions/workers) + - Any other application built on [.NET Generic Host](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host) + or [.NET's standard dependency injection](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection/overview) (`Microsoft.Extensions.DependencyInjection`). + ::: + + + +ConfigCat .NET SDK on GitHub + +## Getting started + +### 1. Install _ConfigCat SDK_ NuGet package + + + + + + + First, install the [NuGet package](https://www.nuget.org/packages/ConfigCat.Client): + + ``` + dotnet add package ConfigCat.Client + ``` + + + + + First, install the [NuGet package](https://www.nuget.org/packages/ConfigCat.Client): + + ```powershell + Install-Package ConfigCat.Client + ``` + + + + + :::info + To get the _ConfigCat SDK_ up and running in Unity, please refer to [this guide](../unity.mdx). + ::: + + + + + + + + + First, install the [integration NuGet package](https://www.nuget.org/packages/ConfigCat.Extensions.Hosting), + which builds on top of the [core ConfigCat SDK package](https://www.nuget.org/packages/ConfigCat.Client): + + ``` + dotnet add package ConfigCat.Extensions.Hosting + ``` + + + + + First, install the [integration NuGet package](https://www.nuget.org/packages/ConfigCat.Extensions.Hosting), + which builds on top of the [core ConfigCat SDK package](https://www.nuget.org/packages/ConfigCat.Client): + + ```powershell + Install-Package ConfigCat.Extensions.Hosting + ``` + + + + + + +### 2. Import package + + + +```csharp +using ConfigCat.Client; +``` + + + + + +```csharp +using ConfigCat.Client; +using ConfigCat.Extensions.Hosting; +``` + + + + + + ### 3. Create the _ConfigCat_ client with your _SDK Key_ + + ```csharp + var client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); + ``` + + ### 4. Get your setting value + + ```csharp + var isMyAwesomeFeatureEnabled = await client.GetValueAsync("isMyAwesomeFeatureEnabled", false); + if (isMyAwesomeFeatureEnabled) + { + doTheNewThing(); + } + else + { + doTheOldThing(); + } + ``` + + The _ConfigCat SDK_ also offers a synchronous API for feature flag evaluation. Read more [here](#snapshots-and-non-blocking-synchronous-feature-flag-evaluation). + + ### 5. Dispose the _ConfigCat_ client + + You can safely dispose all clients at once or individually and release all associated resources on application exit. + + ```csharp + ConfigCatClient.DisposeAll(); // disposes all clients + // -or- + client.Dispose(); // disposes a specific client + ``` + + + + + ### 3. Register the _ConfigCat_ client with your _SDK Key_ + + + + + If your application uses the [modern, linear, property-based configuration style](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host?tabs=appbuilder#host-builder-options), + i.e., if it performs setup via a host builder implementing [IHostApplicationBuilder](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostapplicationbuilder) + (e.g., ASP.NET Core's [WebApplicationBuilder](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.webapplicationbuilder) or + MAUI's [MauiAppBuilder](https://learn.microsoft.com/en-us/dotnet/api/microsoft.maui.hosting.mauiappbuilder)), + add this to your application setup: + + ```csharp + builder.UseConfigCat(); + ``` + + Then register the default _ConfigCat client_ (more specifically, the `IConfigCatClient` service) in the DI container by + specifying your SDK Key in `appsettings.json` (or via other application configuration, such as environment variables): + + ```json + { + "ConfigCat": { + "DefaultClient": { + "SdkKey": "#YOUR-SDK-KEY#", + } + } + } + ``` + + Alternatively, it is possible to register the client by code: + + ```csharp + var configCatBuilder = builder.UseConfigCat(); + configCatBuilder.AddDefaultClient(options => + { + options.SdkKey = "#YOUR-SDK-KEY#"; + }); + ``` + + + + + If your application uses the [traditional, callback-based approach](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host?tabs=appbuilder#host-builder-options), + i.e., if it performs setup via a host builder implementing [IHostBuilder](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostbuilder), + add this to your application setup: + + ```csharp + builder.ConfigureConfigCat(); + ``` + + Then register the default _ConfigCat client_ (more specifically, the `IConfigCatClient` service) in the DI container by + specifying your SDK Key in `appsettings.json` (or via other application configuration, such as environment variables): + + ```json + { + "ConfigCat": { + "DefaultClient": { + "SdkKey": "#YOUR-SDK-KEY#", + } + } + } + ``` + + Alternatively, it is possible to register the client by code: + + ```csharp + builder.ConfigureConfigCat(configCatBuilder => + { + configCatBuilder.AddDefaultClient(options => + { + options.SdkKey = "#YOUR-SDK-KEY#"; + }); + }); + ``` + + + + + + If your application performs setup via a host builder not compatible with [.NET Generic Host](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host) + (e.g., Blazor's [WebAssemblyHostBuilder](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.webassembly.hosting.webassemblyhostbuilder)), + or manually builds a DI container using [ServiceCollection](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicecollection), + add this to your application setup: + + ```csharp + services.AddConfigCat(configuration); + ``` + + Then register the default _ConfigCat client_ (more specifically, the `IConfigCatClient` service) in the DI container by + specifying your SDK Key in `appsettings.json` (or via other application configuration, such as environment variables): + + ```json + { + "ConfigCat": { + "DefaultClient": { + "SdkKey": "#YOUR-SDK-KEY#", + } + } + } + ``` + + Alternatively, it is possible to register the client by code: + + ```csharp + services.AddConfigCat(configCatBuilder => + { + configCatBuilder.AddDefaultClient(options => + { + options.SdkKey = "#YOUR-SDK-KEY#"; + }); + }); + ``` + + Or, if you need both application configuration and setup by code: + + ```csharp + services.AddConfigCat(configuration, configCatBuilder => { /* ... */ }); + ``` + + + + If your application needs to use multiple ConfigCat configs, it is possible to register additional named clients as follows: + + ```json + { + "ConfigCat": { + "NamedClients": { + "secondary": { + "SdkKey": "#YOUR-SECONDARY-SDK-KEY#" + } + } + } + } + ``` + + Alternatively, by code: + + ```csharp + configCatBuilder.AddNamedClient("secondary", options => + { + options.SdkKey = "#YOUR-SECONDARY-SDK-KEY#"; + }); + ``` + + ### 4. Initialize the _ConfigCat_ client + + Although not necessarily needed, it is highly recommended to initialize the registered client(s) at application startup + to surface errors caused by potential misconfiguration (e.g., invalid/mistyped SDK Keys) early. In addition, + [synchronous feature flag evaluation](#snapshots-and-non-blocking-synchronous-feature-flag-evaluation) also requires + the clients to reach the ready state at startup. + + The SDK offers the following initialization modes: + - `ConfigCatInitMode.DoNotWaitForClientReady`: Ensures that the registered clients are created by resolving them + from the DI container but does not wait for the clients to reach the ready state. + This is the default mode, which is sufficient if you intend to use the [asynchronous API for feature flag evaluation](#anatomy-of-getvalueasync). + - `ConfigCatInitMode.WaitForClientReady`: Ensures that the registered clients are created by resolving them + from the DI container and waits for all clients to reach the ready state. (Plus, the `throwOnFailure` parameter allows + you to control how to proceed when any of the clients fail to initialize.) Choose this mode (along with [Auto Polling](#auto-polling-default)) + if you want to use the [synchronous API for feature flag evaluation](#snapshots-and-non-blocking-synchronous-feature-flag-evaluation). + + One way to specify the initialization mode is application configuration: + + ```json + { + "ConfigCat": { + "Init": { + "Mode": "WaitForClientReady", + "ThrowOnFailure": false + } + } + } + ``` + + Alternatively, by code: + + ```csharp + configCatBuilder.UseInitMode(new ConfigCatInitMode.WaitForClientReady(throwOnFailure: false)); + ``` + + The `UseConfigCat` and `ConfigureConfigCat` methods automatically ensure that the registered client(s) are initialized + at startup if your application host supports [hosted services](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services) + (e.g., ASP.NET Core's host does, but MAUI's host [does not at the moment](https://github.com/dotnet/maui/issues/2244)). + + In other cases, you need to perform initialization manually, in the startup phase of your application, + after the DI container is built. E.g.: + + ```csharp + var app = builder.Build(); + + await app.Services.GetRequiredService().InitializeAsync(); + ``` + + ### 5. Obtain the _ConfigCat_ client + + Resolve the singleton `IConfigCatClient` service from the DI container via constructor injection, method injection + (using the `FromServices` attribute) or by `ServiceProvider.GetRequiredService()`: + + ```csharp + var client = app.Services.GetRequiredService(); + ``` + + Named clients can be resolved similarly, using the `FromKeyedServices` attribute or by + `ServiceProvider.GetRequiredKeyedService()`. Use the client name you specified as the service key. + + ### 6. Get your setting value + + ```csharp + var isMyAwesomeFeatureEnabled = await client.GetValueAsync("isMyAwesomeFeatureEnabled", false); + if (isMyAwesomeFeatureEnabled) + { + doTheNewThing(); + } + else + { + doTheOldThing(); + } + ``` + + The _ConfigCat SDK_ also offers a synchronous API for feature flag evaluation. Read more [here](#snapshots-and-non-blocking-synchronous-feature-flag-evaluation). + + + + + + ## Creating the _ConfigCat Client_ + + + + + + ## About the _ConfigCat Client_ + + + +_ConfigCat Client_ is responsible for: + +- managing the communication between your application and ConfigCat servers. +- caching your setting values and feature flags. +- serving values quickly in a failsafe way. + + + +`ConfigCatClient.Get(sdkKey: "")` returns a client with default options. + + + +### Customizing the _ConfigCat Client_ + + + + To customize the client's behavior, you can pass an additional `Action` parameter to the `Get()` static + factory method where the `ConfigCatClientOptions` class is used to set up the _ConfigCat Client_. + + ```csharp + IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + { + options.PollingMode = PollingModes.AutoPoll(maxInitWaitTime: TimeSpan.FromSeconds(10)); + options.Logger = new ConsoleLogger(LogLevel.Info); + }); + ``` + + + + + + To customize the client's behavior, you can specify additional settings in `appsettings.json` (or via other + application configuration, such as environment variables): + + ```json + { + "ConfigCat": { + "DefaultClient": { + "SdkKey": "#YOUR-SDK-KEY#", + "Polling": { + "Mode": "AutoPoll", + "MaxInitWaitTime": "00:00:10" + } + } + } + } + ``` + + Alternatively, you can specify additional settings by code (using the `AddDefaultClient` and `AddNamedClient` methods): + + ```csharp + configCatBuilder.AddDefaultClient(options => + { + options.SdkKey = "#YOUR-SDK-KEY#"; + options.PollingMode = PollingModes.AutoPoll(maxInitWaitTime: TimeSpan.FromSeconds(10)); + }); + ``` + + :::info + If you add a client by code that is already defined in the application configuration (e.g. `appsettings.json`), + the settings from the configuration and the settings made by code will be merged, with the latter taking precedence. + ::: + + + +These are the available options on the `ConfigCatClientOptions` class: + +| Properties | Description | Default | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| `PollingMode` | Optional, sets the polling mode for the client. [More about polling modes](#polling-modes). | `PollingModes.AutoPoll()` | +| `ConfigFetcher` | Optional, [`IConfigCatConfigFetcher`](https://github.com/configcat/.net-sdk/blob/master/src/ConfigCatClient/ConfigService/IConfigCatConfigFetcher.cs) instance for downloading a config.{props.platform === "generic-host" ? <> (Can only be set by code.) : null} | [`HttpClientConfigFetcher`](https://github.com/configcat/.net-sdk/blob/master/src/ConfigCatClient/ConfigService/HttpClientConfigFetcher.cs) | +| `ConfigCache` | Optional, [`IConfigCatCache`](https://github.com/configcat/.net-sdk/blob/master/src/ConfigCatClient/Cache/IConfigCatCache.cs) instance for caching the downloaded config.{props.platform === "generic-host" ? <> (Can only be set by code.) : null} | [`InMemoryConfigCache`](https://github.com/configcat/.net-sdk/blob/master/src/ConfigCatClient/Cache/InMemoryConfigCache.cs) | +| `Logger` | Optional, [`IConfigCatLogger`](https://github.com/configcat/.net-sdk/blob/master/src/ConfigCatClient/Logging/IConfigCatLogger.cs) instance for tracing.{props.platform === "generic-host" ? <> (Can only be set by code.) : null} | {props.platform === "generic-host" ? ConfigCatToMSLoggerAdapter : <>ConsoleLogger (with WARNING level)} | +| `LogFilter` | Optional, sets a custom log filter. [More about log filtering](#log-filtering).{props.platform === "generic-host" ? <> (Can only be set by code.) : null} | `null` (none) | +| `BaseUrl` | Optional, sets the CDN base url (forward proxy, dedicated subscription) from where the SDK will download the config JSON. | | +| `Proxy` | Optional, [`IWebProxy`](https://learn.microsoft.com/en-us/dotnet/api/system.net.iwebproxy) instance that provides settings for routing HTTP requests made by the SDK through an HTTP, HTTPS, SOCKS, etc. proxy. [More about the proxy settings](#using-configcat-behind-a-proxy). (Applies only if the `ConfigFetcher` option is not set.) | | +| `HttpTimeout` | Optional, sets the underlying HTTP client's timeout. [More about the HTTP timeout](#http-timeout). (Applies only if the `ConfigFetcher` option is not set.) | `TimeSpan.FromSeconds(30)` | +| `FlagOverrides` | Optional, sets the local feature flag & setting overrides. [More about feature flag overrides](#flag-overrides).{props.platform === "generic-host" ? <> (Can only be set by code.) : null} | | +| `DataGovernance` | Optional, describes the location of your feature flag and setting data within the ConfigCat CDN. This parameter needs to be in sync with your Data Governance preferences. [More about Data Governance](../../advanced/data-governance.mdx). Available options: `Global`, `EuOnly` | `Global` | +| `DefaultUser` | Optional, sets the default user. [More about default user](#default-user).{props.platform === "generic-host" ? <> (Can only be set by code.) : null} | `null` (none) | +| `Offline` | Optional, determines whether the client should be initialized to offline mode. [More about offline mode](#online--offline-mode). | `false` | + +Via the events provided by `ConfigCatClientOptions` you can also subscribe to the hooks (events) at the time of initialization. [More about hooks](#hooks). + +For example: + + + + ```csharp + IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + { + options.ClientReady += (s, e) => + { + var keys = ((IConfigCatClient)s).Snapshot().GetAllKeys(); + Console.WriteLine("Client is ready! Number of available feature flags: " + keys.Count); + }; + }); + ``` + + :::info + You can acquire singleton client instances for your SDK keys using the `ConfigCatClient.Get(sdkKey: "")` static factory method. + (However, please keep in mind that subsequent calls to `ConfigCatClient.Get()` with the _same SDK Key_ return a _shared_ client instance, which was set up by the first call.) + + You can close all open clients at once using the `ConfigCatClient.DisposeAll()` method or do it individually using the `client.Dispose()` method. + ::: + + + + + + ```csharp + configCatBuilder.AddDefaultClient(options => + { + options.SdkKey = "#YOUR-SDK-KEY#"; + options.ClientReady += (s, e) => + { + var keys = ((IConfigCatClient)s).Snapshot().GetAllKeys(); + Console.WriteLine("Client is ready! Number of available feature flags: " + keys.Count); + }; + }); + ``` + + + +## Anatomy of `GetValueAsync()` + +| Parameters | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------ | +| `key` | **REQUIRED.** The key of a specific setting or feature flag. Set on _ConfigCat Dashboard_ for each setting. | +| `defaultValue` | **REQUIRED.** This value will be returned in case of an error. | +| `user` | Optional, _User Object_. Essential when using Targeting. [Read more about Targeting.](../../targeting/targeting-overview.mdx) | + +```csharp +User userObject = new User("#UNIQUE-USER-IDENTIFIER#"); // Optional User Object +var value = await client.GetValueAsync("keyOfMyFeatureFlag", false, userObject); +``` + +:::caution +It is important to provide an argument for the `defaultValue` parameter, specifically for the `T` generic type parameter, +that matches the type of the feature flag or setting you are evaluating. Please refer to the following table for the corresponding types. +::: + +### Setting type mapping {/* #setting-type-mapping */} + +| Setting Kind | Type parameter `T` | +| -------------- | --------------------------------- | +| On/Off Toggle | `bool` / `bool?` | +| Text | `string` / `string?` | +| Whole Number | `int` / `int?` / `long` / `long?` | +| Decimal Number | `double` / `double?` | + +In addition to the types mentioned above, you also have the option to provide `object` or `object?` for the type parameter regardless of the setting kind. +However, this approach is not recommended as it may involve [boxing](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/types/boxing-and-unboxing). + +It's important to note that providing any other type for the type parameter will result in an `ArgumentException`. + +If you specify an allowed type but it mismatches the setting kind, an error message will be logged and `defaultValue` will be returned. + +When relying on type inference and not explicitly specifying the type parameter, be mindful of potential type mismatch issues, especially with number types. +For example, `client.GetValueAsync("keyOfMyDecimalSetting", 0)` will return `defaultValue` (`0`) instead of the actual value of the decimal setting because +the compiler infers the type as `int` instead of `double`, that is, the call is equivalent to `client.GetValueAsync("keyOfMyDecimalSetting", 0)`, +which is a type mismatch. + +To correctly evaluate a decimal setting, you should use: + +```csharp +var value = await client.GetValueAsync("keyOfMyDecimalSetting", 0.0); +// -or- +var value = await client.GetValueAsync("keyOfMyDecimalSetting", 0d); +// -or- +var value = await client.GetValueAsync("keyOfMyDecimalSetting", 0); +``` + +## Anatomy of `GetValueDetailsAsync()` + +`GetValueDetailsAsync()` is similar to `GetValueAsync()` but instead of returning the evaluated value only, it provides more detailed information about the evaluation result. + +| Parameters | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------ | +| `key` | **REQUIRED.** The key of a specific setting or feature flag. Set on _ConfigCat Dashboard_ for each setting. | +| `defaultValue` | **REQUIRED.** This value will be returned in case of an error. | +| `user` | Optional, _User Object_. Essential when using Targeting. [Read more about Targeting.](../../targeting/targeting-overview.mdx) | + +```csharp +User userObject = new User("#UNIQUE-USER-IDENTIFIER#"); // Optional User Object +var details = await client.GetValueDetailsAsync("keyOfMyFeatureFlag", false, userObject); +``` + +:::caution +It is important to provide an argument for the `defaultValue` parameter, specifically for the `T` generic type parameter, +that matches the type of the feature flag or setting you are evaluating. Please refer to [this table](#setting-type-mapping) for the corresponding types. +::: + +The `details` result contains the following information: + +| Field | Type | Description | +| --------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------- | +| `Key` | `string` | The key of the evaluated feature flag or setting. | +| `Value` | `bool` / `string` / `int` / `double` | The evaluated value of the feature flag or setting. | +| `User` | `User` | The User Object used for the evaluation. | +| `IsDefaultValue` | `bool` | True when the default value passed to `GetValueDetailsAsync()` is returned due to an error. | +| `ErrorCode` | `EvaluationErrorCode` | In case of an error, this property contains a code that identifies the reason for the error. | +| `ErrorMessage` | `string` | In case of an error, this property contains the error message. | +| `ErrorException` | `Exception` | In case of an error, this property contains the related exception object (if any). | +| `MatchedTargetingRule` | `ITargetingRule` | The Targeting Rule (if any) that matched during the evaluation and was used to return the evaluated value. | +| `MatchedPercentageOption` | `IPercentageOption` | The Percentage Option (if any) that was used to select the evaluated value. | +| `FetchTime` | `DateTime` | The last download time (UTC) of the current config. | + +## User Object + +The [User Object](../../targeting/user-object.mdx) is essential if you'd like to use ConfigCat's [Targeting](../../targeting/targeting-overview.mdx) feature. + +```csharp +User userObject = new User("#UNIQUE-USER-IDENTIFIER#"); +``` + +```csharp +User userObject = new User("john@example.com"); +``` + +| Parameters | Description | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `Id` | **REQUIRED.** Unique identifier of a user in your application. Can be any `string` value, even an email address. | +| `Email` | Optional parameter for easier Targeting Rule definitions. | +| `Country` | Optional parameter for easier Targeting Rule definitions. | +| `Custom` | Optional dictionary for custom attributes of a user for advanced Targeting Rule definitions. E.g. User role, Subscription type. | + +```csharp +User userObject = new User("#UNIQUE-USER-IDENTIFIER#") +{ + Email = "john@example.com", + Country = "United Kingdom", + Custom = + { + ["SubscriptionType"] = "Pro", + ["UserRole"] = "Admin" + } +}; +``` + +The `Custom` dictionary also allows attribute values other than `string` values: + +```csharp +User userObject = new User("#UNIQUE-USER-IDENTIFIER#") +{ + Custom = + { + ["Rating"] = 4.5, + ["RegisteredAt"] = DateTimeOffset.Parse("2023-11-22 12:34:56 +00:00", CultureInfo.InvariantCulture), + ["Roles"] = new[] { "Role1", "Role2" } + } +}; +``` + +### User Object Attribute Types + +All comparators support `string` values as User Object attribute (in some cases they need to be provided in a specific format though, see below), but some of them also support other types of values. It depends on the comparator how the values will be handled. The following rules apply: + +**Text-based comparators** (EQUALS, IS ONE OF, etc.) +* accept `string` values, +* all other values are automatically converted to `string` (a warning will be logged but evaluation will continue as normal). + +**SemVer-based comparators** (IS ONE OF, <, >=, etc.) +* accept `string` values containing a properly formatted, valid semver value, +* all other values are considered invalid (a warning will be logged and the currently evaluated Targeting Rule will be skipped). + +**Number-based comparators** (=, <, >=, etc.) +* accept `double` values and all other numeric values which can safely be converted to `double`, +* accept `string` values containing a properly formatted, valid `double` value, +* all other values are considered invalid (a warning will be logged and the currently evaluated Targeting Rule will be skipped). + +**Date time-based comparators** (BEFORE / AFTER) +* accept `DateTime` or `DateTimeOffset` values, which are automatically converted to a second-based Unix timestamp, +* accept `double` values representing a second-based Unix timestamp and all other numeric values which can safely be converted to `double`, +* accept `string` values containing a properly formatted, valid `double` value, +* all other values are considered invalid (a warning will be logged and the currently evaluated Targeting Rule will be skipped). + +**String array-based comparators** (ARRAY CONTAINS ANY OF / ARRAY NOT CONTAINS ANY OF) +* accept arrays of `string`, +* accept `string` values containing a valid JSON string which can be deserialized to an array of `string`, +* all other values are considered invalid (a warning will be logged and the currently evaluated Targeting Rule will be skipped). + +### Default user + +It's possible to set a default User Object that will be used on feature flag and setting evaluation. It can be useful when your application has a single user only or rarely switches users. + +You can set the default User Object either on SDK initialization: + + + + ```csharp + IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + options.DefaultUser = new User(identifier: "john@example.com")); + ``` + + + + + + ```csharp + configCatBuilder.AddDefaultClient(options => + { + options.SdkKey = "#YOUR-SDK-KEY#"; + options.DefaultUser = new User(identifier: "john@example.com")); + }); + ``` + + + +...or using the `SetDefaultUser()` method of the `ConfigCatClient` object: + +```csharp +client.SetDefaultUser(new User(identifier: "john@example.com")); +``` + +Whenever the evaluation methods like `GetValueAsync()`, `GetValueDetailsAsync()`, etc. are called without an explicit `user` parameter, the SDK will automatically use the default user as a User Object. + +```csharp +var user = new User(identifier: "john@example.com"); +client.SetDefaultUser(user); + +// The default user will be used in the evaluation process. +var value = await client.GetValueAsync(key: "keyOfMyFeatureFlag", defaultValue: false); +``` + +When a `user` parameter is passed to the evaluation methods, it takes precedence over the default user. + +```csharp +var user = new User(identifier: "john@example.com"); +client.SetDefaultUser(user); + +var otherUser = new User(identifier: "brian@example.com"); + +// otherUser will be used in the evaluation process. +var value = await client.GetValueAsync(key: "keyOfMyFeatureFlag", defaultValue: false, user: otherUser); +``` + +You can also remove the default user by doing the following: + +```csharp +client.ClearDefaultUser(); +``` + +## Polling Modes + +The _ConfigCat SDK_ supports 3 different polling strategies to fetch feature flags and settings from the ConfigCat CDN. Once the latest data is downloaded, it is stored in the cache, then calls to `GetValueAsync()` use the cached data to evaluate feature flags and settings. With the following polling modes, you can customize the SDK to best fit to your application's lifecycle. +[More about polling modes.](../../advanced/caching.mdx) + +### Auto polling (default) + +The _ConfigCat SDK_ downloads the latest config data from the ConfigCat CDN automatically every 60 seconds and stores it in the cache. + +Use the {props.platform === "generic-host" ? <>PollInterval option or : null}`pollInterval` parameter to change the polling interval. + + + + ```csharp + IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + { + options.PollingMode = PollingModes.AutoPoll(pollInterval: TimeSpan.FromSeconds(95)); + }); + ``` + + + + + + ```json + { + "ConfigCat": { + "DefaultClient": { + "SdkKey": "#YOUR-SDK-KEY#", + "Polling": { + "Mode": "AutoPoll", + "PollInterval": "00:01:35" + } + } + } + } + ``` + + Alternatively, by code: + + ```csharp + configCatBuilder.AddDefaultClient(options => + { + options.SdkKey = "#YOUR-SDK-KEY#"; + options.PollingMode = PollingModes.AutoPoll(pollInterval: TimeSpan.FromSeconds(95)); + }); + ``` + + + +Available options: + +| Option Parameter | Description | Default | +| ----------------- | --------------------------------------------------------------------------------------------------- | ------- | +| `pollInterval` | Polling interval. | 60s | +| `maxInitWaitTime` | Maximum waiting time between the client initialization and the first config acquisition. | 5s | + +### Lazy loading + +When calling `GetValueAsync()`, the _ConfigCat SDK_ downloads the latest config data from the ConfigCat CDN only if it is not already present in the cache, or if the cache has expired. In this case `GetValueAsync()` will return the setting value after the cache is updated. + +Use the {props.platform === "generic-host" ? <>CacheTimeToLive option or : null}`cacheTimeToLive` parameter to set cache lifetime. + + + + ```csharp + IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + { + options.PollingMode = PollingModes.LazyLoad(cacheTimeToLive: TimeSpan.FromSeconds(600)); + }); + ``` + + + + + + ```json + { + "ConfigCat": { + "DefaultClient": { + "SdkKey": "#YOUR-SDK-KEY#", + "Polling": { + "Mode": "LazyLoad", + "CacheTimeToLive": "00:10:00" + } + } + } + } + ``` + + Alternatively, by code: + + ```csharp + configCatBuilder.AddDefaultClient(options => + { + options.SdkKey = "#YOUR-SDK-KEY#"; + options.PollingMode = PollingModes.LazyLoad(cacheTimeToLive: TimeSpan.FromSeconds(600)); + }); + ``` + + + +Available options: + +| Option Parameter | Description | Default | +| ----------------- | ----------- | ------- | +| `cacheTimeToLive` | Cache TTL. | 60s | + +### Manual polling + +Manual polling gives you full control over when the config data is downloaded from the ConfigCat CDN. The _ConfigCat SDK_ will not download it automatically. Calling `ForceRefreshAsync()` is your application's responsibility. + + + + ```csharp + IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + { + options.PollingMode = PollingModes.ManualPoll; + }); + ``` + + + + + + ```json + { + "ConfigCat": { + "DefaultClient": { + "SdkKey": "#YOUR-SDK-KEY#", + "Polling": { + "Mode": "ManualPoll" + } + } + } + } + ``` + + Alternatively, by code: + + ```csharp + configCatBuilder.AddDefaultClient(options => + { + options.SdkKey = "#YOUR-SDK-KEY#"; + options.PollingMode = PollingModes.ManualPoll; + }); + ``` + + + + :::caution + `GetValueAsync()` returns `defaultValue` if the cache is empty, so + {props.platform === "generic-host" + ? <>once the DI container is built and the IConfigCatClient service is resolved + : <>after creating the client}, + be sure to call `ForceRefreshAsync()` to update the cache. + ::: + +```csharp +Console.WriteLine(await client.GetValueAsync("keyOfMyTextSetting", "my default value")); // console: "my default value" +await client.ForceRefreshAsync(); +Console.WriteLine(await client.GetValueAsync("keyOfMyTextSetting", "my default value")); // console: "value from server" +``` + +## Hooks + +The SDK provides several hooks (events), by means of which you can get notified of its actions. +Via the following events you can subscribe to particular events raised by the _ConfigCat_ client: + +- `event EventHandler ClientReady`: This event is raised when the client reaches the ready state, i.e. completes initialization. + * If Lazy Loading or Manual Polling is used, it's considered ready right after the initial sync with the external cache (if any) completes. + * If Auto Polling is used, the ready state is reached as soon as + * the initial sync with the external cache yields up-to-date config data, + * otherwise, if the client is online (i.e. HTTP requests are allowed), the first config fetch operation completes (regardless of success or failure), + * or the time specified via Auto Polling's `maxInitWaitTime` option has passed. + + Reaching the ready state usually means the client is ready to evaluate feature flags and settings. + However, please note that this is not guaranteed. In case of initialization failure or timeout, the internal cache + may be empty or expired even after the ready state is reported. You can verify this by checking the `CacheState` property of the event arguments. +- `event EventHandler ConfigFetched`: This event is raised each time the client attempts to refresh the cached config by + fetching the latest version from the ConfigCat CDN. It is raised not only when `ForceRefreshAsync` is called but also + when the refresh is initiated by the client automatically. Thus, this event allows you to observe potential network issues that occur under the hood. +- `event EventHandler ConfigChanged`: This event is raised first when the client's internal cache gets populated. + Afterwards, it is raised again each time the internally cached config is updated to a newer version, either as a result of synchronization + with the external cache, or as a result of fetching a newer version from the ConfigCat CDN. +- `event EventHandler FlagEvaluated`: This event is raised each time the client evaluates a feature flag or setting. + The event provides the same evaluation details that you would get from [`GetValueDetailsAsync()`](#anatomy-of-getvaluedetailsasync). +- `event EventHandler Error`: This event is raised when an error occurs within the client. + +You can subscribe to these events either on initialization: + + + + ```csharp + IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + { + options.PollingMode = PollingModes.ManualPoll; + options.FlagEvaluated += (s, e) => { /* handle the event */ }; + }); + ``` + + + + + + ```csharp + configCatBuilder.AddDefaultClient(options => + { + options.SdkKey = "#YOUR-SDK-KEY#"; + options.PollingMode = PollingModes.ManualPoll; + options.FlagEvaluated += (s, e) => { /* handle the event */ }; + }); + ``` + + + +...or directly on the `ConfigCatClient` instance: + +```csharp +client.FlagEvaluated += (s, e) => { /* handle the event */ }; +``` + + + + :::caution + Some events (e.g. `ClientReady`, `ConfigChanged` and `Error`) may be raised before `ConfigCatClient.Get` returns. + This means you may miss them unless you subscribe on initialization. + + However, even if you do, there's another gotcha: it's not safe to use the outer `client` variable in your event handler + because it may not yet be assigned when the handler is called. Instead, you can safely access the client instance + via the sender parameter like `var client = (IConfigCatClient)s;`. + ::: + + + +## Snapshots and non-blocking synchronous feature flag evaluation + +The _ConfigCat_ client doesn't directly provide synchronous methods for evaluating feature flags and settings because +such synchronous methods could block the executing thread for longer periods of time (e.g. when downloading config data +from the ConfigCat CDN servers), which could lead to an unresponsive application. + +However, there can be circumstances where synchronous evaluation is preferable, thus, since v9.2.0, the .NET SDK provides a way +to synchronously evaluate feature flags and settings as a non-blocking operation, via _snapshots_. + + + + Using the `Snapshot()` method, you can capture the current state of the _ConfigCat_ client (including the latest downloaded config data) + and use the resulting snapshot object to synchronously evaluate feature flags and settings based on the captured state: + + ```csharp + using var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", + options => options.PollingMode = PollingModes.AutoPoll()); + + // Wait for the client to initialize. + await client.WaitForReadyAsync(); + + var snapshot = client.Snapshot(); + + var user = new User("#UNIQUE-USER-IDENTIFIER#"); + foreach (var key in snapshot.GetAllKeys()) + { + var value = snapshot.GetValue(key, default(object), user); + Console.WriteLine($"{key}: {value}"); + } + ``` + + + + + + The typical setup for non-blocking synchronous feature flag evaluation looks like this: + + ```json + { + "ConfigCat": { + "DefaultClient": { + "SdkKey": "#YOUR-SDK-KEY#", + "Polling": { + "Mode": "AutoPoll" + } + }, + "Init": { + "Mode": "WaitForClientReady", + "ThrowOnFailure": false + } + } + } + ``` + + Alternatively, by code: + + ```csharp + configCatBuilder.AddDefaultClient(options => + { + options.SdkKey = "#YOUR-SDK-KEY#"; + options.PollingMode = PollingModes.AutoPoll(); + }); + configCatBuilder.UseInitMode(new ConfigCatInitMode.WaitForClientReady(throwOnFailure: false)); + ``` + + Then, in the startup phase of your application, ensure that the registered client(s) are initialized: + - If your application host supports [hosted services](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services), + this will happen automatically. + - In other cases, you need to perform initialization manually, in the startup phase of your application, + after the DI container is built. E.g.: + + ```csharp + var app = builder.Build(); + + await app.Services.GetRequiredService().InitializeAsync(); + ``` + + :::caution + Reaching the ready state usually means the client is ready to evaluate feature flags and settings. + However, please note that this is not guaranteed. In case of initialization failure or timeout, the internal cache + may be empty or expired even after the ready state is reported. You can use the `ThrowOnFailure` option or + `throwOnFailure` parameter to control how to proceed in such cases. + ::: + + From this point on, you can call the `Snapshot()` method of the `IConfigCatClient` service to capture the + current state of the _ConfigCat_ client (including the latest downloaded config data) and use the resulting snapshot + object to synchronously evaluate feature flags and settings based on the captured state: + + ```csharp + var snapshot = client.Snapshot(); + + var user = new User("#UNIQUE-USER-IDENTIFIER#"); + foreach (var key in snapshot.GetAllKeys()) + { + var value = snapshot.GetValue(key, default(object), user); + Console.WriteLine($"{key}: {value}"); + } + ``` + + :::tip + In ASP.NET Core applications, creating one snapshot per request is usually sufficient. To support this, the SDK offers + a shortcut: you can obtain a snapshot for the current request by injecting the scoped `IConfigCatClientSnapshot` service + directly. + ::: + + + +Creating a snapshot is a cheap operation. This is possible because snapshots capture the client's internal (in-memory) cache. +No attempt is made to refresh the internal cache, even if it's empty or expired. + +:::caution +Please note that creating and using a snapshot +* won't trigger synchronization with the external cache when working with [shared caching](../../advanced/caching.mdx#shared-cache), +* won't fetch the latest config data from the ConfigCat CDN when the internally cached config data is empty or expired. +::: + +For the above reasons, it's recommended to use snapshots in conjunction with the Auto Polling mode, where the SDK automatically +updates the internal cache in the background. (For other polling modes, you'll need to manually initiate a cache refresh +by calling `ForceRefreshAsync`.) + + + + Because of this behavior, it's important to make sure that the client has completed initialization and populated its internal cache + before creating snapshots. Otherwise the snapshot's evaluation methods won't have the data to do actual evaluation, + but will just return the default value you pass to them. Which behavior is usually not what you want in your application. + + In Auto Polling mode, you can use the `WaitForReadyAsync` method to wait for the latest config data to become available locally. + This is an asynchronous operation, which completes as soon as the client reaches the ready state, i.e. completes initialization + (or the time specified via the `maxInitWaitTime` option passes). + + (Please note that this doesn't apply to other polling modes. In those cases, the client doesn't contact the ConfigCat CDN + during initialization, so the ready state is reached as soon as the first sync with the external cache completes.) + + Typically, you call `WaitForReadyAsync` and wait for its completion only once, in the startup phase of your application. + + :::caution + Reaching the ready state usually means the client is ready to evaluate feature flags and settings. + However, please note that this is not guaranteed. In case of initialization failure or timeout, the internal cache + may be empty or expired even after the ready state is reported. You can verify this by checking the return value. + ::: + + ```csharp + var clientCacheState = await client.WaitForReadyAsync(); + if (clientCacheState == ClientCacheState.NoFlagData) + { + // Handle initialization failure (see below). + Console.WriteLine("ConfigCat client failed to obtain the config data during initialization."); + } + ``` + + You have the following options to handle unsuccessful initialization: + * If it's acceptable for your application to start up and use the default values passed to the evaluation methods, + you may log some warning (or skip the check altogether as the client will log warnings anyway), and let the application continue. + * Otherwise, you need to either terminate the application or continue waiting. The latter is an option because the client + might be able to obtain the config data later, in the case of a transient problem like some temporary network issue. + However, the _ConfigCat SDK_ doesn't provide out-of-the-box support for this case currently. You can implement this logic by + subscribing to the `ConfigChanged` hook and waiting for the first event. + + + +## Online / Offline mode + +In cases where you want to prevent the SDK from making HTTP calls, you can switch it to offline mode: + +```csharp +client.SetOffline(); +``` + +In offline mode, the SDK won't initiate HTTP requests and will work only from its cache. + +To switch the SDK back to online mode, do the following: + +```csharp +client.SetOnline(); +``` + +Using the `client.IsOffline` property you can check whether the SDK is in offline mode. + +## Flag Overrides + +With flag overrides you can overwrite the feature flags & settings downloaded from the ConfigCat CDN with local values. +Moreover, you can specify how the overrides should apply over the downloaded values. The following 3 behaviours are supported: + +- **Local only** (`OverrideBehaviour.LocalOnly`): When evaluating values, the SDK will not use feature flags & settings from the ConfigCat CDN, but it will use all feature flags & settings that are loaded from local-override sources. + +- **Local over remote** (`OverrideBehaviour.LocalOverRemote`): When evaluating values, the SDK will use all feature flags & settings that are downloaded from the ConfigCat CDN, plus all feature flags & settings that are loaded from local-override sources. If a feature flag or a setting is defined both in the downloaded and the local-override source then the local-override version will take precedence. + +- **Remote over local** (`OverrideBehaviour.RemoteOverLocal`): When evaluating values, the SDK will use all feature flags & settings that are downloaded from the ConfigCat CDN, plus all feature flags & settings that are loaded from local-override sources. If a feature flag or a setting is defined both in the downloaded and the local-override source then the downloaded version will take precedence. + +You can load your feature flag & setting overrides from a file or from a simple `Dictionary` structure. + +### JSON File + +The SDK can load your feature flag & setting overrides from a file. +You can also specify whether the file should be reloaded when it gets modified. + +#### File + + + + ```csharp + IConfigCatClient client = ConfigCatClient.Get("localhost", options => + { + options.FlagOverrides = FlagOverrides.LocalFile( + "path/to/local_flags.json", // path to the file + true, // reload the file when it gets modified + OverrideBehaviour.LocalOnly + ); + }); + ``` + + + + + + ```csharp + configCatBuilder.AddDefaultClient(options => + { + options.SdkKey = "localhost"; + options.FlagOverrides = FlagOverrides.LocalFile( + "path/to/local_flags.json", // path to the file + true, // reload the file when it gets modified + OverrideBehaviour.LocalOnly + ); + }); + ``` + + + +#### JSON File Structure + +The SDK supports 2 types of JSON structures to describe feature flags & settings. + +##### 1. Simple (key-value) structure + +```json +{ + "flags": { + "enabledFeature": true, + "disabledFeature": false, + "intSetting": 5, + "doubleSetting": 3.14, + "stringSetting": "test" + } +} +``` + +##### 2. Complex (full-featured) structure + +This is the same format that the SDK downloads from the ConfigCat CDN. +It allows the usage of all features that are available on the ConfigCat Dashboard. + +You can download your current config JSON from ConfigCat's CDN and use it as a baseline. + +A convenient way to get the config JSON for a specific SDK Key is to install the [ConfigCat CLI](https://github.com/configcat/cli) tool +and execute the following command: + +```bash +configcat config-json get -f v6 -p {YOUR-SDK-KEY} > config.json +``` + +(Depending on your [Data Governance](../../advanced/data-governance.mdx) settings, you may need to add the `--eu` switch.) + +Alternatively, you can download the config JSON manually, based on your [Data Governance](../../advanced/data-governance.mdx) settings: + +- GLOBAL: `https://cdn-global.configcat.com/configuration-files/{YOUR-SDK-KEY}/config_v6.json` +- EU: `https://cdn-eu.configcat.com/configuration-files/{YOUR-SDK-KEY}/config_v6.json` + +```json +{ + "p": { + // hash salt, required only when confidential text comparator(s) are used + "s": "80xCU/SlDz1lCiWFaxIBjyJeJecWjq46T4eu6GtozkM=" + }, + "s": [ // array of segments + { + "n": "Beta Users", // segment name + "r": [ // array of User Conditions (there is a logical AND relation between the elements) + { + "a": "Email", // comparison attribute + "c": 0, // comparator (see below) + "l": [ // comparison value (see below) + "john@example.com", "jane@example.com" + ] + } + ] + } + ], + "f": { // key-value map of feature flags & settings + "isFeatureEnabled": { // key of a particular flag / setting + "t": 0, // setting type, possible values: + // 0 -> on/off setting (feature flag) + // 1 -> text setting + // 2 -> whole number setting + // 3 -> decimal number setting + "r": [ // array of Targeting Rules (there is a logical OR relation between the elements) + { + "c": [ // array of conditions (there is a logical AND relation between the elements) + { + "u": { // User Condition + "a": "Email", // comparison attribute + "c": 2, // comparator, possible values and required comparison value types: + // 0 -> IS ONE OF (cleartext) + string array comparison value ("l") + // 1 -> IS NOT ONE OF (cleartext) + string array comparison value ("l") + // 2 -> CONTAINS ANY OF (cleartext) + string array comparison value ("l") + // 3 -> NOT CONTAINS ANY OF (cleartext) + string array comparison value ("l") + // 4 -> IS ONE OF (semver) + semver string array comparison value ("l") + // 5 -> IS NOT ONE OF (semver) + semver string array comparison value ("l") + // 6 -> < (semver) + semver string comparison value ("s") + // 7 -> <= (semver + semver string comparison value ("s") + // 8 -> > (semver) + semver string comparison value ("s") + // 9 -> >= (semver + semver string comparison value ("s") + // 10 -> = (number) + number comparison value ("d") + // 11 -> <> (number + number comparison value ("d") + // 12 -> < (number) + number comparison value ("d") + // 13 -> <= (number + number comparison value ("d") + // 14 -> > (number) + number comparison value ("d") + // 15 -> >= (number) + number comparison value ("d") + // 16 -> IS ONE OF (hashed) + string array comparison value ("l") + // 17 -> IS NOT ONE OF (hashed) + string array comparison value ("l") + // 18 -> BEFORE (UTC datetime) + second-based Unix timestamp number comparison value ("d") + // 19 -> AFTER (UTC datetime) + second-based Unix timestamp number comparison value ("d") + // 20 -> EQUALS (hashed) + string comparison value ("s") + // 21 -> NOT EQUALS (hashed) + string comparison value ("s") + // 22 -> STARTS WITH ANY OF (hashed) + string array comparison value ("l") + // 23 -> NOT STARTS WITH ANY OF (hashed) + string array comparison value ("l") + // 24 -> ENDS WITH ANY OF (hashed) + string array comparison value ("l") + // 25 -> NOT ENDS WITH ANY OF (hashed) + string array comparison value ("l") + // 26 -> ARRAY CONTAINS ANY OF (hashed) + string array comparison value ("l") + // 27 -> ARRAY NOT CONTAINS ANY OF (hashed) + string array comparison value ("l") + // 28 -> EQUALS (cleartext) + string comparison value ("s") + // 29 -> NOT EQUALS (cleartext) + string comparison value ("s") + // 30 -> STARTS WITH ANY OF (cleartext) + string array comparison value ("l") + // 31 -> NOT STARTS WITH ANY OF (cleartext) + string array comparison value ("l") + // 32 -> ENDS WITH ANY OF (cleartext) + string array comparison value ("l") + // 33 -> NOT ENDS WITH ANY OF (cleartext + string array comparison value ("l") + // 34 -> ARRAY CONTAINS ANY OF (cleartext) + string array comparison value ("l") + // 35 -> ARRAY NOT CONTAINS ANY OF (cleartext) + string array comparison value ("l") + "l": [ // comparison value - depending on the comparator, another type of value may need + // to be specified (see above): + // "s": string + // "d": number + "@example.com" + ] + } + }, + { + "p": { // Flag Condition (Prerequisite) + "f": "mainIntFlag", // key of prerequisite flag + "c": 0, // comparator, possible values: 0 -> EQUALS, 1 -> NOT EQUALS + "v": { // comparison value (value's type must match the prerequisite flag's type) + "i": 42 + } + } + }, + { + "s": { // Segment Condition + "s": 0, // segment index, a valid index into the top-level segment array ("s") + "c": 1 // comparator, possible values: 0 -> IS IN SEGMENT, 1 -> IS NOT IN SEGMENT + } + } + ], + "s": { // alternatively, an array of Percentage Options ("p", see below) can also be specified + "v": { // the value served when the rule is selected during evaluation + "b": true + }, + "i": "bcfb84a7" + } + } + ], + "p": [ // array of Percentage Options + { + "p": 10, // % value + "v": { // the value served when the Percentage Option is selected during evaluation + "b": true + }, + "i": "bcfb84a7" + }, + { + "p": 90, + "v": { + "b": false + }, + "i": "bddac6ae" + } + ], + "v": { // fallback value, served when none of the Targeting Rules match, + // no Percentage Options are defined or evaluation of these is not possible + "b": false // depending on the setting type, another type of value may need to be specified: + // text setting -> "s": string + // whole number setting -> "i": number + // decimal number setting -> "d": number + }, + "i": "430bded3" // variation id (for analytical purposes) + } + } +} +``` + +For a more comprehensive specification of the config JSON v6 format, you may refer to [this JSON schema document](https://github.com/configcat/config-json/blob/main/V6/config.schema.json). + +### Dictionary + +You can set up the SDK to load your feature flag & setting overrides from a `Dictionary`. + + + + ```csharp + var dictionary = new Dictionary + { + {"enabledFeature", true}, + {"disabledFeature", false}, + {"intSetting", 5}, + {"doubleSetting", 3.14}, + {"stringSetting", "test"}, + }; + + IConfigCatClient client = ConfigCatClient.Get("localhost", options => + { + options.FlagOverrides = FlagOverrides.LocalDictionary(dictionary, OverrideBehaviour.LocalOnly); + }); + ``` + + + + + + ```csharp + var dictionary = new Dictionary + { + {"enabledFeature", true}, + {"disabledFeature", false}, + {"intSetting", 5}, + {"doubleSetting", 3.14}, + {"stringSetting", "test"}, + }; + + configCatBuilder.AddDefaultClient(options => + { + options.SdkKey = "localhost"; + options.FlagOverrides = FlagOverrides.LocalDictionary(dictionary, OverrideBehaviour.LocalOnly); + }); + ``` + + + +## Logging + +### Setting log level + + + + ```csharp + IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); + + client.LogLevel = LogLevel.Info; + ``` + + Available log levels: + + | Level | Description | + | ------- | ------------------------------------------------------- | + | Off | Nothing is logged. | + | Error | Only error level events are logged. | + | Warning | Default. Errors and Warnings are logged. | + | Info | Errors, Warnings and feature flag evaluation is logged. | + | Debug | All of the above plus debug info is logged. | + + Info level logging helps to inspect the feature flag evaluation process: + + ```bash + ConfigCat.INFO [5000] Evaluating 'isPOCFeatureEnabled' for User '{"Identifier":"","Email":"configcat@example.com","Country":"US","SubscriptionType":"Pro","Role":"Admin","version":"1.0.0"}' + Evaluating targeting rules and applying the first match if any: + - IF User.Email CONTAINS ANY OF ['@something.com'] THEN 'False' => no match + - IF User.Email CONTAINS ANY OF ['@example.com'] THEN 'True' => MATCH, applying rule + Returning 'True'. + ``` + + + + + +The SDK automatically configures the clients to integrate with `Microsoft.Extensions.Logging`, the [the built-in logging framework](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging) +of .NET Core/.NET 5+. Accordingly, you can set log levels using the standard configuration approach described [here](https://learn.microsoft.com/en-us/dotnet/core/extensions/logging/overview#configure-logging). +E.g., via `appsettings.json`: + +```json +{ + "Logging": { + "LogLevel": { + "ConfigCat.Client": "Information", + "Default": "Information" + } + } +} +``` + +If you have multiple clients, you can define more specific log level filters using +- the `ConfigCat.Client.ConfigCatClient^` category name for the default client and +- the `ConfigCat.Client.ConfigCatClient[#CLIENT‑NAME#]` category name for named clients. + + + +### Custom logger implementation + +By default, the SDK {props.platform === "generic-host" ? <>forwards logs to the the built-in logging framework (and even supports structured logging) : <>logs to the console's standard output} +but it also allows you to inject any custom logger implementation via the `ConfigCatClientOptions.Logger` property. + +See [this sample code](https://github.com/configcat/.net-sdk/blob/master/samples/FileLoggerSample.cs) on how to create a basic file logger implementation for ConfigCat client. + +### Log Filtering + +You can define an {props.platform === "generic-host" ? <>additional : <>custom} log filter by providing a callback function via the `ConfigCatClientOptions.LogFilter` property. +The callback will be called by the _ConfigCat SDK_ each time a log event occurs {!props.platform ? <>(and the event passes the minimum log level specified by the `IConfigCatLogger.LogLevel` property) : null}. +That is, the callback allows you to filter log events by `level`, `eventId`, `message` or `exception`. The formatted message string can be obtained via `message.InvariantFormattedMessage`. +If the callback function returns `true`, the event will be logged, otherwise it will be skipped. + + + + ```csharp + // Filter out events with id 1001 from the log. + LogFilterCallback logFilter = (LogLevel level, LogEventId eventId, ref FormattableLogMessage message, Exception? exception) => eventId != 1001; + + var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => options.LogFilter = logFilter); + ``` + + + + + + ```csharp + // Filter out events with id 1001 from the log. + LogFilterCallback logFilter = (LogLevel level, LogEventId eventId, ref FormattableLogMessage message, Exception? exception) => eventId != 1001; + + configCatBuilder.AddDefaultClient(options => + { + options.SdkKey = "#YOUR-SDK-KEY#"; + options.LogFilter = logFilter; + }); + ``` + + + +:::caution +Please make sure that your log filter logic doesn't perform heavy computation and doesn't block the executing thread. A complex or incorrectly implemented log filter can degrade the performance of the SDK. +::: + +## `GetAllKeysAsync()` + +You can get the keys for all available feature flags and settings by calling the `GetAllKeysAsync()` method. + +```csharp +IEnumerable keys = await client.GetAllKeysAsync(); +``` + +## `GetAllValuesAsync()` + +Evaluates and returns the values of all feature flags and settings. Passing a [User Object](#user-object) is optional. + +```csharp +IDictionary settingValues = await client.GetAllValuesAsync(); + +// invoke with User Object +User userObject = new User("#UNIQUE-USER-IDENTIFIER#"); +IDictionary settingValuesTargeting = await client.GetAllValuesAsync(userObject); +``` + +## `GetAllValueDetailsAsync()` + +Evaluates and returns the values along with evaluation details of all feature flags and settings. Passing a [User Object](#user-object) is optional. + +```csharp +IReadOnlyList settingValues = await client.GetAllValueDetailsAsync(); + +// invoke with User Object +User userObject = new User("435170f4-8a8b-4b67-a723-505ac7cdea92"); +IReadOnlyList settingValuesTargeting = await client.GetAllValueDetailsAsync(userObject); +``` + +## Using custom cache implementation + +The _ConfigCat SDK_ stores the downloaded config data in a local cache to minimize network traffic and enhance client performance. +If you prefer to use your own cache solution, such as an external or distributed cache in your system, +you can implement the [`IConfigCatCache`](https://github.com/configcat/.net-sdk/blob/master/src/ConfigCatClient/Cache/IConfigCatCache.cs) interface +and set the `ConfigCache` parameter in the setup callback of `ConfigCatClient.Get`. +This allows you to seamlessly integrate ConfigCat with your existing caching infrastructure. + +```csharp +public class MyCustomCache : IConfigCatCache +{ + public string? Get(string key) + { + /* insert your synchronous cache read logic here */ + } + + public Task GetAsync(string key, CancellationToken cancellationToken = default) + { + /* insert your asynchronous cache read logic here */ + } + + public void Set(string key, string value) + { + /* insert your synchronous cache write logic here */ + } + + public Task SetAsync(string key, string value, CancellationToken cancellationToken = default) + { + /* insert your asynchronous cache write logic here */ + } +} +``` + +then + + + + ```csharp + IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + { + options.ConfigCache = new MyCustomCache(); + }); + ``` + + + + + + ```csharp + configCatBuilder.AddDefaultClient(options => + { + options.SdkKey = "#YOUR-SDK-KEY#"; + options.ConfigCache = new MyCustomCache(); + }); + ``` + + + +:::info +The .NET SDK supports *shared caching*. You can read more about this feature and the required minimum SDK versions [here](../../advanced/caching.mdx#shared-cache). +::: + +## Using ConfigCat behind a proxy {/* #using-configcat-behind-a-proxy */} + +Provide your own network credentials (username/password) and proxy server settings (proxy server/port) by setting the +`Proxy` property in the setup callback of `ConfigCatClient.Get`. + +:::caution +This setting does not apply if you configure the client to use a custom config fetcher via the `ConfigFetcher` option. +::: + + + + ```csharp + var myProxySettings = new WebProxy(proxyHost, proxyPort) + { + UseDefaultCredentials = false, + Credentials = new NetworkCredential(proxyUserName, proxyPassword) + }; + + IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + { + options.Proxy = myProxySettings; + }); + ``` + + + + + + ```json + { + "ConfigCat": { + "DefaultClient": { + "SdkKey": "#YOUR-SDK-KEY#", + "Proxy": "#PROXY-URL#" + } + } + } + ``` + + Alternatively, by code: + + ```csharp + var myProxySettings = new WebProxy(proxyHost, proxyPort) + { + UseDefaultCredentials = false, + Credentials = new NetworkCredential(proxyUserName, proxyPassword) + }; + + configCatBuilder.AddDefaultClient(options => + { + options.SdkKey = "#YOUR-SDK-KEY#"; + options.Proxy = myProxySettings; + }); + ``` + + + +## HTTP Timeout + +You can set the maximum wait time for a ConfigCat HTTP response. + +:::caution +This setting does not apply if you configure the client to use a custom config fetcher via the `ConfigFetcher` option. +::: + + + + ```csharp + IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + { + options.HttpTimeout = TimeSpan.FromSeconds(10); + }); + ``` + + + + + + ```json + { + "ConfigCat": { + "DefaultClient": { + "SdkKey": "#YOUR-SDK-KEY#", + "HttpTimeout": "00:00:10" + } + } + } + ``` + + Alternatively, by code: + + ```csharp + configCatBuilder.AddDefaultClient(options => + { + options.SdkKey = "#YOUR-SDK-KEY#"; + options.HttpTimeout = TimeSpan.FromSeconds(10); + }); + ``` + + + +The default timeout is 30 seconds. + +## Platform compatibility + +The _ConfigCat SDK_ supports all the widespread .NET JIT runtimes, everything that implements [.NET Standard 2.0](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0)+ and supports TLS 1.2 should work. +Starting with v9.3.0, it can also be used in applications that employ [trimmed self-contained](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-self-contained) or various [ahead-of-time (AOT) compilation](https://en.wikipedia.org/wiki/Ahead-of-time_compilation) deployment models. + +Based on our tests, the SDK is compatible with the following runtimes/deployment models: +* .NET Framework 4.6.2+ (including Ngen) +* .NET Core 3.1, .NET 5+ (including Crossgen2/ReadyToRun and Native AOT) +* Mono 5.10+ +* .NET for Android (formerly known as Xamarin.Android) +* .NET for iOS (formerly known as Xamarin.iOS) +* Unity 2021.3+ (Mono JIT) +* Unity 2021.3+ (IL2CPP)* +* Universal Windows Platform 10.0.16299.0+ (.NET Native)** +* WebAssembly (Mono AOT/Emscripten, also known as wasm-tools) + +*Unity WebGL also works but needs a bit of extra effort: you will need to enable WebGL compatibility by calling the `ConfigCatClient.PlatformCompatibilityOptions.EnableUnityWebGLCompatibility` method. For more details, see [Sample Scripts](https://github.com/configcat/.net-sdk/tree/master/samples/UnityWebGL).
+**To make the SDK work in Release builds on UWP, you will need to add `` to your application's [.rd.xml](https://learn.microsoft.com/en-us/windows/uwp/dotnet-native/runtime-directives-rd-xml-configuration-file-reference) file. See also [this discussion](https://github.com/dotnet/runtime/issues/29912#issuecomment-638471351). + +:::info +We strive to provide an extensive support for the various .NET runtimes and versions. If you still encounter an issue with the SDK on some platform, please open a [GitHub issue](https://github.com/configcat/.net-sdk/issues/new/choose) or [contact support](https://configcat.com/support). +::: + +## Sample Applications + +Check out our Sample Applications how they use the _ConfigCat SDK_: + +- Sample Console App +- Sample Multi-page Web App (ASP.NET Core MVC) +- Sample Single-page Web App (ASP.NET Core Blazor WebAssembly) +- Sample Mobile/Windows Store App (.NET MAUI) +- Sample Windows Service (.NET Generic Host) + +## Guides + +See the following guides on how to use ConfigCat's .NET SDK: + +- Feature Flags in a .NET 6 Application +- Using ConfigCat's Feature Flags in an ASP.NET Core Application +- Using ConfigCat Feature Flags in ASP.NET Core Web API +- How to Implement A/B Testing in .NET? +- Feature Flags in Microservices and Serverless Architecture (AWS Lambda and .NET) + +## Look under the hood + +- ConfigCat .NET SDK on GitHub +- ConfigCat .NET SDK on nuget.org diff --git a/website/docs/sdk-reference/dotnet/generic-host.mdx b/website/docs/sdk-reference/dotnet/generic-host.mdx new file mode 100644 index 000000000..33d2ddd81 --- /dev/null +++ b/website/docs/sdk-reference/dotnet/generic-host.mdx @@ -0,0 +1,16 @@ +--- +id: 'generic-host' +title: Using the ConfigCat SDK in DI‑Based .NET Applications +description: Using the ConfigCat SDK in DI-based .NET applications. This is a step-by-step guide on how to use feature flags in your ASP.NET Core applications and other modern .NET applications built on .NET Generic Host or .NET's standard dependency injection. +--- + +import DotNetSdkReferenceTemplate, { getAdjustedToc } from "./\_template.mdx"; + +{/* TODO: schema */} + +export const platform = "generic-host"; + + + + +export const toc = getAdjustedToc(platform); \ No newline at end of file diff --git a/website/docs/sdk-reference/elixir.mdx b/website/docs/sdk-reference/elixir.mdx index c5c0739b2..84874c2df 100644 --- a/website/docs/sdk-reference/elixir.mdx +++ b/website/docs/sdk-reference/elixir.mdx @@ -294,7 +294,9 @@ Manual polling gives you full control over when the config data is downloaded fr ConfigCat.force_refresh() ``` -> `get_value()` returns `default_value` if the cache is empty. Call `force_refresh()` to update the cache. +:::caution +`get_value()` returns `default_value` if the cache is empty. Call `force_refresh()` to update the cache. +::: ```elixir value = ConfigCat.get_value("key", "my default value") # Returns "my default value" diff --git a/website/docs/sdk-reference/ios.mdx b/website/docs/sdk-reference/ios.mdx index bd0002c3e..1ace52bfa 100644 --- a/website/docs/sdk-reference/ios.mdx +++ b/website/docs/sdk-reference/ios.mdx @@ -887,7 +887,9 @@ ConfigCatClient* client = [ConfigCatClient getWithSdkKey:@"#YOUR-SDK-KEY#" -> `getValue()` returns `defaultValue` if the cache is empty. Call `refresh()` to update the cache. +:::caution +`getValue()` returns `defaultValue` if the cache is empty. Call `refresh()` to update the cache. +::: ## Hooks @@ -1026,7 +1028,7 @@ No attempt is made to refresh the internal cache, even if it's empty or expired. :::caution Please note that creating and using a snapshot -* won't trigger a sync with the external cache when working with [shared caching](../advanced/caching.mdx#shared-cache), +* won't trigger synchronization with the external cache when working with [shared caching](../advanced/caching.mdx#shared-cache), * won't fetch the latest config data from the ConfigCat CDN when the internally cached config data is empty or expired. ::: @@ -1045,7 +1047,7 @@ This is an asynchronous operation, which completes as soon as the client reaches (Please note that this doesn't apply to other polling modes. In those cases, the client doesn't contact the ConfigCat CDN during initialization, so the ready state is reached as soon as the first sync with the external cache completes.) -Typically, you call `waitForReady` and wait for its completion only once, in the initialization phase of your application. +Typically, you call `waitForReady` and wait for its completion only once, in the startup phase of your application. (In Objective-C, `waitForReady` is not available. As an alternative, you can subscribe to the `onReady` hook and wait for the first event.) diff --git a/website/docs/sdk-reference/java.mdx b/website/docs/sdk-reference/java.mdx index eb8ac743d..1e0d12697 100644 --- a/website/docs/sdk-reference/java.mdx +++ b/website/docs/sdk-reference/java.mdx @@ -417,7 +417,9 @@ ConfigCatClient client = ConfigCatClient.get("#YOUR-SDK-KEY#", options ->{ client.forceRefresh(); ``` -> `getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +:::caution +`getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +::: ## Hooks diff --git a/website/docs/sdk-reference/js-ssr.mdx b/website/docs/sdk-reference/js-ssr.mdx index 1ffe018d5..fa99dc4b6 100644 --- a/website/docs/sdk-reference/js-ssr.mdx +++ b/website/docs/sdk-reference/js-ssr.mdx @@ -459,7 +459,9 @@ let value = await configCatClient.getValueAsync( console.log(value); ``` -> `getValueAsync()` returns `defaultValue` if the cache is empty. Call `forceRefreshAsync()` to update the cache. +:::caution +`getValueAsync()` returns `defaultValue` if the cache is empty. Call `forceRefreshAsync()` to update the cache. +::: ```js const configCatClient = configcat.getClient( @@ -561,7 +563,7 @@ No attempt is made to refresh the internal cache, even if it's empty or expired. :::caution Please note that creating and using a snapshot -* won't trigger a sync with the external cache when working with [shared caching](../advanced/caching.mdx#shared-cache), +* won't trigger synchronization with the external cache when working with [shared caching](../advanced/caching.mdx#shared-cache), * won't fetch the latest config data from the ConfigCat CDN when the internally cached config data is empty or expired. ::: @@ -580,7 +582,7 @@ This is an asynchronous operation, which completes as soon as the client reaches (Please note that this doesn't apply to other polling modes. In those cases, the client doesn't contact the ConfigCat CDN during initialization, so the ready state is reached as soon as the first sync with the external cache completes.) -Typically, you call `waitForReady` and wait for its completion only once, in the initialization phase of your application. +Typically, you call `waitForReady` and wait for its completion only once, in the startup phase of your application. :::caution Reaching the ready state usually means the client is ready to evaluate feature flags and settings. diff --git a/website/docs/sdk-reference/js.mdx b/website/docs/sdk-reference/js.mdx index 44889cf16..24fd1083c 100644 --- a/website/docs/sdk-reference/js.mdx +++ b/website/docs/sdk-reference/js.mdx @@ -477,7 +477,9 @@ let value = await configCatClient.getValueAsync( console.log(value); ``` -> `getValueAsync()` returns `defaultValue` if the cache is empty. Call `forceRefreshAsync()` to update the cache. +:::caution +`getValueAsync()` returns `defaultValue` if the cache is empty. Call `forceRefreshAsync()` to update the cache. +::: ```js const configCatClient = configcat.getClient( @@ -579,7 +581,7 @@ No attempt is made to refresh the internal cache, even if it's empty or expired. :::caution Please note that creating and using a snapshot -* won't trigger a sync with the external cache when working with [shared caching](../advanced/caching.mdx#shared-cache), +* won't trigger synchronization with the external cache when working with [shared caching](../advanced/caching.mdx#shared-cache), * won't fetch the latest config data from the ConfigCat CDN when the internally cached config data is empty or expired. ::: @@ -598,7 +600,7 @@ This is an asynchronous operation, which completes as soon as the client reaches (Please note that this doesn't apply to other polling modes. In those cases, the client doesn't contact the ConfigCat CDN during initialization, so the ready state is reached as soon as the first sync with the external cache completes.) -Typically, you call `waitForReady` and wait for its completion only once, in the initialization phase of your application. +Typically, you call `waitForReady` and wait for its completion only once, in the startup phase of your application. :::caution Reaching the ready state usually means the client is ready to evaluate feature flags and settings. diff --git a/website/docs/sdk-reference/js/_template.mdx b/website/docs/sdk-reference/js/_template.mdx index 33a333930..1d9c3435d 100644 --- a/website/docs/sdk-reference/js/_template.mdx +++ b/website/docs/sdk-reference/js/_template.mdx @@ -79,12 +79,12 @@ export const getAdjustedToc = (platform) => { This SDK supersedes the legacy [JavaScript SDK](../../js) and [JavaScript (SSR) SDK](../../js-ssr). It is suitable for the following types of browser applications: - * Static HTML websites - * Server-side rendered Multi-Page Applications (MPA), e.g. PHP, ASP.NET Core Razor Pages, Spring MVC, etc. - * Client-side rendered Single-Page Applications (SPA), e.g. Angular, React, Vue.js, etc. - * Server-side rendered Single-Page Applications (SSR), e.g. Angular SSR, Next.js, Nuxt, etc. - * Prerendered Single/Multi-Page Applications (SSG), e.g. Angular SSG, Vue.js SSG, Astro, etc. - * Web Workers + - Static HTML websites + - Server-side rendered Multi-Page Applications (MPA), e.g. PHP, ASP.NET Core Razor Pages, Spring MVC, etc. + - Client-side rendered Single-Page Applications (SPA), e.g. Angular, React, Vue.js, etc. + - Server-side rendered Single-Page Applications (SSR), e.g. Angular SSR, Next.js, Nuxt, etc. + - Prerendered Single/Multi-Page Applications (SSG), e.g. Angular SSG, Vue.js SSG, Astro, etc. + - Web Workers ::: @@ -125,7 +125,7 @@ export const canImportFromCdn = (platform) => platform === "browser" || platform - First install the [NPM package](https://npmjs.com/package/@configcat/sdk): + First, install the [NPM package](https://npmjs.com/package/@configcat/sdk): ```bash npm i @configcat/sdk @@ -205,7 +205,7 @@ export const canImportFromCdn = (platform) => platform === "browser" || platform - First install the [NPM package](https://npmjs.com/package/@configcat/sdk): + First, install the [NPM package](https://npmjs.com/package/@configcat/sdk): ```bash npm i @configcat/sdk @@ -758,7 +758,9 @@ const value = await configCatClient.getValueAsync( console.log(value); ``` -> `getValueAsync()` returns `defaultValue` if the cache is empty. Call `forceRefreshAsync()` to update the cache. +:::caution +`getValueAsync()` returns `defaultValue` if the cache is empty. Call `forceRefreshAsync()` to update the cache. +::: ```js const configCatClient = configcat.getClient( @@ -872,7 +874,7 @@ No attempt is made to refresh the internal cache, even if it's empty or expired. :::caution Please note that creating and using a snapshot -* won't trigger a sync with the external cache when working with [shared caching](../../advanced/caching.mdx#shared-cache), +* won't trigger synchronization with the external cache when working with [shared caching](../../advanced/caching.mdx#shared-cache), * won't fetch the latest config data from the ConfigCat CDN when the internally cached config data is empty or expired. ::: @@ -891,7 +893,7 @@ This is an asynchronous operation, which completes as soon as the client reaches (Please note that this doesn't apply to other polling modes. In those cases, the client doesn't contact the ConfigCat CDN during initialization, so the ready state is reached as soon as the first sync with the external cache completes.) -Typically, you call `waitForReady` and wait for its completion only once, in the initialization phase of your application. +Typically, you call `waitForReady` and wait for its completion only once, in the startup phase of your application. :::caution Reaching the ready state usually means the client is ready to evaluate feature flags and settings. diff --git a/website/docs/sdk-reference/kotlin.mdx b/website/docs/sdk-reference/kotlin.mdx index ff184c718..320305add 100644 --- a/website/docs/sdk-reference/kotlin.mdx +++ b/website/docs/sdk-reference/kotlin.mdx @@ -395,7 +395,9 @@ val client = ConfigCatClient("#YOUR-SDK-KEY#") { client.forceRefresh() ``` -> `getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +:::caution +`getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +::: ## Hooks @@ -471,7 +473,7 @@ No attempt is made to refresh the internal cache, even if it's empty or expired. :::caution Please note that creating and using a snapshot -* won't trigger a sync with the external cache when working with [shared caching](../advanced/caching.mdx#shared-cache), +* won't trigger synchronization with the external cache when working with [shared caching](../advanced/caching.mdx#shared-cache), * won't fetch the latest config data from the ConfigCat CDN when the internally cached config data is empty or expired. ::: @@ -490,7 +492,7 @@ This is an asynchronous operation, which completes as soon as the client reaches (Please note that this doesn't apply to other polling modes. In those cases, the client doesn't contact the ConfigCat CDN during initialization, so the ready state is reached as soon as the first sync with the external cache completes.) -Typically, you call `waitForReady` and wait for its completion only once, in the initialization phase of your application. +Typically, you call `waitForReady` and wait for its completion only once, in the startup phase of your application. :::caution Reaching the ready state usually means the client is ready to evaluate feature flags and settings. diff --git a/website/docs/sdk-reference/node.mdx b/website/docs/sdk-reference/node.mdx index c8ef318e3..3f988493a 100644 --- a/website/docs/sdk-reference/node.mdx +++ b/website/docs/sdk-reference/node.mdx @@ -460,7 +460,9 @@ let value = await configCatClient.getValueAsync( console.log(value); ``` -> `getValueAsync()` returns `defaultValue` if the cache is empty. Call `forceRefreshAsync()` to update the cache. +:::caution +`getValueAsync()` returns `defaultValue` if the cache is empty. Call `forceRefreshAsync()` to update the cache. +::: ```js const configCatClient = configcat.getClient( @@ -562,7 +564,7 @@ No attempt is made to refresh the internal cache, even if it's empty or expired. :::caution Please note that creating and using a snapshot -* won't trigger a sync with the external cache when working with [shared caching](../advanced/caching.mdx#shared-cache), +* won't trigger synchronization with the external cache when working with [shared caching](../advanced/caching.mdx#shared-cache), * won't fetch the latest config data from the ConfigCat CDN when the internally cached config data is empty or expired. ::: @@ -581,7 +583,7 @@ This is an asynchronous operation, which completes as soon as the client reaches (Please note that this doesn't apply to other polling modes. In those cases, the client doesn't contact the ConfigCat CDN during initialization, so the ready state is reached as soon as the first sync with the external cache completes.) -Typically, you call `waitForReady` and wait for its completion only once, in the initialization phase of your application. +Typically, you call `waitForReady` and wait for its completion only once, in the startup phase of your application. :::caution Reaching the ready state usually means the client is ready to evaluate feature flags and settings. diff --git a/website/docs/sdk-reference/python.mdx b/website/docs/sdk-reference/python.mdx index 6ee0bef78..cc0aa30de 100644 --- a/website/docs/sdk-reference/python.mdx +++ b/website/docs/sdk-reference/python.mdx @@ -331,7 +331,9 @@ client = configcatclient.get('#YOUR-SDK-KEY#', client.force_refresh() ``` -> `get_value()` returns `default_value` if the cache is empty. Call `force_refresh()` to update the cache. +:::caution +`get_value()` returns `default_value` if the cache is empty. Call `force_refresh()` to update the cache. +::: ```python client = configcatclient.get('#YOUR-SDK-KEY#', diff --git a/website/docs/sdk-reference/ruby.mdx b/website/docs/sdk-reference/ruby.mdx index c366e705c..f6145fc47 100644 --- a/website/docs/sdk-reference/ruby.mdx +++ b/website/docs/sdk-reference/ruby.mdx @@ -322,7 +322,9 @@ client = ConfigCat.get('#YOUR-SDK-KEY#', client.force_refresh ``` -> `get_value` returns `default_value` if the cache is empty. Call `force_refresh` to update the cache. +:::caution +`get_value` returns `default_value` if the cache is empty. Call `force_refresh` to update the cache. +::: ```ruby client = ConfigCat.get('#YOUR-SDK-KEY#', diff --git a/website/docs/sdk-reference/rust.mdx b/website/docs/sdk-reference/rust.mdx index b7a7ef4fb..8eb0ca49a 100644 --- a/website/docs/sdk-reference/rust.mdx +++ b/website/docs/sdk-reference/rust.mdx @@ -352,7 +352,9 @@ let client = Client::builder("#YOUR-SDK-KEY#") _ = client.refresh().await; ``` -> `get_value()` returns `default` if the cache is empty. Call `refresh()` to update the cache. +:::caution +`get_value()` returns `default` if the cache is empty. Call `refresh()` to update the cache. +::: ## Online / Offline mode diff --git a/website/docs/sdk-reference/unreal.mdx b/website/docs/sdk-reference/unreal.mdx index adb26dbe9..41a1b1762 100644 --- a/website/docs/sdk-reference/unreal.mdx +++ b/website/docs/sdk-reference/unreal.mdx @@ -371,7 +371,9 @@ ConfigCat->ForceRefresh(); -> `GetValue()` returns `DefaultValue` if the cache is empty. Call `ForceRefresh()` to update the cache. +:::caution +`GetValue()` returns `DefaultValue` if the cache is empty. Call `ForceRefresh()` to update the cache. +::: ## Delegates diff --git a/website/sidebars.ts b/website/sidebars.ts index 276932779..38b92a346 100644 --- a/website/sidebars.ts +++ b/website/sidebars.ts @@ -260,7 +260,15 @@ const sdks: SidebarConfig = [ className: 'icon sdk-icon', link: { type: 'doc', id: 'sdk-reference/overview' }, items: [ - { type: 'doc', id: 'sdk-reference/dotnet', label: '.NET' }, + { + label: '.NET', + type: 'category', + link: { type: 'doc', id: 'sdk-reference/dotnet' }, + collapsed: false, + items: [ + { type: 'doc', id: 'sdk-reference/dotnet/generic-host', label: 'ASP.NET Core & DI‑Based Apps' }, + ], + }, { type: 'doc', id: 'sdk-reference/android', label: 'Android (Java)' }, { type: 'doc', id: 'sdk-reference/cpp', label: 'C++' }, { type: 'doc', id: 'sdk-reference/dart', label: 'Dart (Flutter)' }, diff --git a/website/src/pages/index.js b/website/src/pages/index.js index f1c133cfb..79cc0e35a 100644 --- a/website/src/pages/index.js +++ b/website/src/pages/index.js @@ -109,7 +109,13 @@ const features = [ title: 'SDK References', // This list should be in alphabetical order description: <>Let's do some coding., links: [ - { url: 'sdk-reference/dotnet', title: '.NET' }, + { + url: 'sdk-reference/dotnet', + title: '.NET', + items: [ + { url: 'sdk-reference/dotnet/generic-host', title: 'ASP.NET Core & DI‑Based Apps' }, + ], + }, { url: 'sdk-reference/android', title: 'Android (Java)' }, { url: 'sdk-reference/cpp', title: 'C++' }, { url: 'sdk-reference/dart', title: 'Dart (Flutter)' }, diff --git a/website/versioned_docs/version-V1/sdk-reference/android.mdx b/website/versioned_docs/version-V1/sdk-reference/android.mdx index 6c08dc772..b55103b7e 100644 --- a/website/versioned_docs/version-V1/sdk-reference/android.mdx +++ b/website/versioned_docs/version-V1/sdk-reference/android.mdx @@ -378,7 +378,9 @@ ConfigCatClient client = ConfigCatClient.get("#YOUR-SDK-KEY#", options -> { client.forceRefresh(); ``` -> `getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +:::caution +`getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +::: ## Hooks diff --git a/website/versioned_docs/version-V1/sdk-reference/cpp.mdx b/website/versioned_docs/version-V1/sdk-reference/cpp.mdx index 298e16267..6bd801d60 100644 --- a/website/versioned_docs/version-V1/sdk-reference/cpp.mdx +++ b/website/versioned_docs/version-V1/sdk-reference/cpp.mdx @@ -309,7 +309,9 @@ auto client = ConfigCatClient::get("#YOUR-SDK-KEY#", &options); client->forceRefresh(); ``` -> `getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +:::caution +`getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +::: ## Hooks diff --git a/website/versioned_docs/version-V1/sdk-reference/dart.mdx b/website/versioned_docs/version-V1/sdk-reference/dart.mdx index d927da281..dfea31c34 100644 --- a/website/versioned_docs/version-V1/sdk-reference/dart.mdx +++ b/website/versioned_docs/version-V1/sdk-reference/dart.mdx @@ -342,7 +342,9 @@ final client = ConfigCatClient.get( client.forceRefresh(); ``` -> `getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +:::caution +`getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +::: ## Hooks diff --git a/website/versioned_docs/version-V1/sdk-reference/dotnet.mdx b/website/versioned_docs/version-V1/sdk-reference/dotnet.mdx index ddbc429dd..9248d4f32 100644 --- a/website/versioned_docs/version-V1/sdk-reference/dotnet.mdx +++ b/website/versioned_docs/version-V1/sdk-reference/dotnet.mdx @@ -368,7 +368,9 @@ IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => client.ForceRefresh(); ``` -> `GetValue()` returns `defaultValue` if the cache is empty. Call `ForceRefresh()` to update the cache. +:::caution +`GetValue()` returns `defaultValue` if the cache is empty. Call `ForceRefresh()` to update the cache. +::: ```csharp IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => diff --git a/website/versioned_docs/version-V1/sdk-reference/elixir.mdx b/website/versioned_docs/version-V1/sdk-reference/elixir.mdx index 1d497b61e..555fe0753 100644 --- a/website/versioned_docs/version-V1/sdk-reference/elixir.mdx +++ b/website/versioned_docs/version-V1/sdk-reference/elixir.mdx @@ -259,7 +259,9 @@ Manual polling gives you full control over when the config data is downloaded fr ConfigCat.force_refresh() ``` -> `get_value()` returns `default_value` if the cache is empty. Call `force_refresh()` to update the cache. +:::caution +`get_value()` returns `default_value` if the cache is empty. Call `force_refresh()` to update the cache. +::: ```elixir value = ConfigCat.get_value("key", "my default value") # Returns "my default value" diff --git a/website/versioned_docs/version-V1/sdk-reference/ios.mdx b/website/versioned_docs/version-V1/sdk-reference/ios.mdx index e070727a1..87aacc6e9 100644 --- a/website/versioned_docs/version-V1/sdk-reference/ios.mdx +++ b/website/versioned_docs/version-V1/sdk-reference/ios.mdx @@ -860,7 +860,9 @@ ConfigCatClient* client = [ConfigCatClient getWithSdkKey:@"#YOUR-SDK-KEY#" -> `getValue()` returns `defaultValue` if the cache is empty. Call `refresh()` to update the cache. +:::caution +`getValue()` returns `defaultValue` if the cache is empty. Call `refresh()` to update the cache. +::: ## Hooks diff --git a/website/versioned_docs/version-V1/sdk-reference/java.mdx b/website/versioned_docs/version-V1/sdk-reference/java.mdx index 142d1c73e..5d64da869 100644 --- a/website/versioned_docs/version-V1/sdk-reference/java.mdx +++ b/website/versioned_docs/version-V1/sdk-reference/java.mdx @@ -378,7 +378,9 @@ ConfigCatClient client = ConfigCatClient.get("#YOUR-SDK-KEY#", options ->{ client.forceRefresh(); ``` -> `getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +:::caution +`getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +::: ## Hooks diff --git a/website/versioned_docs/version-V1/sdk-reference/js-ssr.mdx b/website/versioned_docs/version-V1/sdk-reference/js-ssr.mdx index f267f377c..8c58dda7b 100644 --- a/website/versioned_docs/version-V1/sdk-reference/js-ssr.mdx +++ b/website/versioned_docs/version-V1/sdk-reference/js-ssr.mdx @@ -408,7 +408,9 @@ let value = await configCatClient.getValueAsync( console.log(value); ``` -> `getValueAsync()` returns `defaultValue` if the cache is empty. Call `forceRefreshAsync()` to update the cache. +:::caution +`getValueAsync()` returns `defaultValue` if the cache is empty. Call `forceRefreshAsync()` to update the cache. +::: ```js const configCatClient = configcat.getClient( @@ -499,7 +501,7 @@ No attempt is made to refresh the internal cache, even if it's empty or expired. :::caution Please note that creating and using a snapshot -* won't trigger a sync with the external cache when working with [shared caching](../advanced/caching.mdx#shared-cache), +* won't trigger synchronization with the external cache when working with [shared caching](../advanced/caching.mdx#shared-cache), * won't fetch the latest config data from the ConfigCat CDN when the internally cached config data is empty or expired. ::: @@ -518,7 +520,7 @@ This is an asynchronous operation, which completes as soon as the client reaches (Please note that this doesn't apply to other polling modes. In those cases, the client doesn't contact the ConfigCat CDN during initialization, so the ready state is reached as soon as the first sync with the external cache completes.) -Typically, you call `waitForReady` and wait for its completion only once, in the initialization phase of your application. +Typically, you call `waitForReady` and wait for its completion only once, in the startup phase of your application. :::caution Reaching the ready state usually means the client is ready to evaluate feature flags and settings. diff --git a/website/versioned_docs/version-V1/sdk-reference/js.mdx b/website/versioned_docs/version-V1/sdk-reference/js.mdx index 926ba9379..3cc1c4138 100644 --- a/website/versioned_docs/version-V1/sdk-reference/js.mdx +++ b/website/versioned_docs/version-V1/sdk-reference/js.mdx @@ -430,7 +430,9 @@ let value = await configCatClient.getValueAsync( console.log(value); ``` -> `getValueAsync()` returns `defaultValue` if the cache is empty. Call `forceRefreshAsync()` to update the cache. +:::caution +`getValueAsync()` returns `defaultValue` if the cache is empty. Call `forceRefreshAsync()` to update the cache. +::: ```js const configCatClient = configcat.getClient( @@ -521,7 +523,7 @@ No attempt is made to refresh the internal cache, even if it's empty or expired. :::caution Please note that creating and using a snapshot -* won't trigger a sync with the external cache when working with [shared caching](../advanced/caching.mdx#shared-cache), +* won't trigger synchronization with the external cache when working with [shared caching](../advanced/caching.mdx#shared-cache), * won't fetch the latest config data from the ConfigCat CDN when the internally cached config data is empty or expired. ::: @@ -540,7 +542,7 @@ This is an asynchronous operation, which completes as soon as the client reaches (Please note that this doesn't apply to other polling modes. In those cases, the client doesn't contact the ConfigCat CDN during initialization, so the ready state is reached as soon as the first sync with the external cache completes.) -Typically, you call `waitForReady` and wait for its completion only once, in the initialization phase of your application. +Typically, you call `waitForReady` and wait for its completion only once, in the startup phase of your application. :::caution Reaching the ready state usually means the client is ready to evaluate feature flags and settings. diff --git a/website/versioned_docs/version-V1/sdk-reference/kotlin.mdx b/website/versioned_docs/version-V1/sdk-reference/kotlin.mdx index b55c3f0e0..667828555 100644 --- a/website/versioned_docs/version-V1/sdk-reference/kotlin.mdx +++ b/website/versioned_docs/version-V1/sdk-reference/kotlin.mdx @@ -321,7 +321,9 @@ val client = ConfigCatClient("#YOUR-SDK-KEY#") { client.forceRefresh() ``` -> `getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +:::caution +`getValue()` returns `defaultValue` if the cache is empty. Call `forceRefresh()` to update the cache. +::: ## Hooks diff --git a/website/versioned_docs/version-V1/sdk-reference/node.mdx b/website/versioned_docs/version-V1/sdk-reference/node.mdx index acb2a1a8c..6caf685c2 100644 --- a/website/versioned_docs/version-V1/sdk-reference/node.mdx +++ b/website/versioned_docs/version-V1/sdk-reference/node.mdx @@ -413,7 +413,9 @@ let value = await configCatClient.getValueAsync( console.log(value); ``` -> `getValueAsync()` returns `defaultValue` if the cache is empty. Call `forceRefreshAsync()` to update the cache. +:::caution +`getValueAsync()` returns `defaultValue` if the cache is empty. Call `forceRefreshAsync()` to update the cache. +::: ```js const configCatClient = configcat.getClient( @@ -504,7 +506,7 @@ No attempt is made to refresh the internal cache, even if it's empty or expired. :::caution Please note that creating and using a snapshot -* won't trigger a sync with the external cache when working with [shared caching](../advanced/caching.mdx#shared-cache), +* won't trigger synchronization with the external cache when working with [shared caching](../advanced/caching.mdx#shared-cache), * won't fetch the latest config data from the ConfigCat CDN when the internally cached config data is empty or expired. ::: @@ -523,7 +525,7 @@ This is an asynchronous operation, which completes as soon as the client reaches (Please note that this doesn't apply to other polling modes. In those cases, the client doesn't contact the ConfigCat CDN during initialization, so the ready state is reached as soon as the first sync with the external cache completes.) -Typically, you call `waitForReady` and wait for its completion only once, in the initialization phase of your application. +Typically, you call `waitForReady` and wait for its completion only once, in the startup phase of your application. :::caution Reaching the ready state usually means the client is ready to evaluate feature flags and settings. diff --git a/website/versioned_docs/version-V1/sdk-reference/python.mdx b/website/versioned_docs/version-V1/sdk-reference/python.mdx index c35589f76..812bd4dc5 100644 --- a/website/versioned_docs/version-V1/sdk-reference/python.mdx +++ b/website/versioned_docs/version-V1/sdk-reference/python.mdx @@ -294,7 +294,9 @@ client = configcatclient.get('#YOUR-SDK-KEY#', client.force_refresh() ``` -> `get_value()` returns `default_value` if the cache is empty. Call `force_refresh()` to update the cache. +:::caution +`get_value()` returns `default_value` if the cache is empty. Call `force_refresh()` to update the cache. +::: ```python client = configcatclient.get('#YOUR-SDK-KEY#', diff --git a/website/versioned_docs/version-V1/sdk-reference/ruby.mdx b/website/versioned_docs/version-V1/sdk-reference/ruby.mdx index 6d95c2b77..e5c575d8d 100644 --- a/website/versioned_docs/version-V1/sdk-reference/ruby.mdx +++ b/website/versioned_docs/version-V1/sdk-reference/ruby.mdx @@ -287,7 +287,9 @@ client = ConfigCat.get('#YOUR-SDK-KEY#', client.force_refresh ``` -> `get_value` returns `default_value` if the cache is empty. Call `force_refresh` to update the cache. +:::caution +`get_value` returns `default_value` if the cache is empty. Call `force_refresh` to update the cache. +::: ```ruby client = ConfigCat.get('#YOUR-SDK-KEY#', diff --git a/website/versioned_docs/version-V1/sdk-reference/rust.mdx b/website/versioned_docs/version-V1/sdk-reference/rust.mdx index 1bfb08843..3b801c3d1 100644 --- a/website/versioned_docs/version-V1/sdk-reference/rust.mdx +++ b/website/versioned_docs/version-V1/sdk-reference/rust.mdx @@ -352,7 +352,9 @@ let client = Client::builder("#YOUR-SDK-KEY#") _ = client.refresh().await; ``` -> `get_value()` returns `default` if the cache is empty. Call `refresh()` to update the cache. +:::caution +`get_value()` returns `default` if the cache is empty. Call `refresh()` to update the cache. +::: ## Online / Offline mode diff --git a/website/versioned_docs/version-V1/sdk-reference/unreal.mdx b/website/versioned_docs/version-V1/sdk-reference/unreal.mdx index 1b9559223..b44aa78ca 100644 --- a/website/versioned_docs/version-V1/sdk-reference/unreal.mdx +++ b/website/versioned_docs/version-V1/sdk-reference/unreal.mdx @@ -369,7 +369,9 @@ ConfigCat->ForceRefresh(); -> `GetValue()` returns `DefaultValue` if the cache is empty. Call `ForceRefresh()` to update the cache. +:::caution +`GetValue()` returns `DefaultValue` if the cache is empty. Call `ForceRefresh()` to update the cache. +::: ## Delegates From 0e41db528d7986e63762f2b37e956a9055c4e604 Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Fri, 19 Jun 2026 08:58:00 +0200 Subject: [PATCH 2/8] Add docs for custom flag override data source --- .../docs/sdk-reference/dotnet/_template.mdx | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/website/docs/sdk-reference/dotnet/_template.mdx b/website/docs/sdk-reference/dotnet/_template.mdx index 70684e368..982ba3b6e 100644 --- a/website/docs/sdk-reference/dotnet/_template.mdx +++ b/website/docs/sdk-reference/dotnet/_template.mdx @@ -1443,6 +1443,60 @@ You can set up the SDK to load your feature flag & setting overrides from a `Dic +### Custom data source implementation + +You can create a custom flag override data source by implementing `IOverrideDataSource`. + +The SDK provides the `Setting.FromValue()` method to create `Setting` objects from simple `bool`, `string`, `int` and `double` +values. In case you need complex (full-featured) flag overrides, you can use the `Config.Deserialize()` method to obtain `Setting` objects +from a config JSON conforming to the [config JSON v6 format](https://github.com/configcat/config-json/blob/main/V6/config.schema.json). + +```csharp +class MyCustomOverrideDataSource : IOverrideDataSource +{ + private IReadOnlyDictionary settings; + + public MyCustomOverrideDataSource(string configJson) + { + this.settings = Config.Deserialize(configJson).Settings; + } + + public IReadOnlyDictionary GetOverrides() + { + return this.settings; + } +} +``` + +Then configure the client to use the `MyCustomOverrideDataSource` implementation: + + + + ```csharp + var flagOverrideDataSource = new MyCustomOverrideDataSource("{ \"f\": { ... } }"); + + IConfigCatClient client = ConfigCatClient.Get("localhost", options => + { + options.FlagOverrides = new FlagOverrides(flagOverrideDataSource, OverrideBehaviour.LocalOnly); + }); + ``` + + + + + + ```csharp + var flagOverrideDataSource = new MyCustomOverrideDataSource("{ \"f\": { ... } }"); + + configCatBuilder.AddDefaultClient(options => + { + options.SdkKey = "localhost"; + options.FlagOverrides = new FlagOverrides(flagOverrideDataSource, OverrideBehaviour.LocalOnly); + }); + ``` + + + ## Logging ### Setting log level From b95b5977fc21b796fb378ee6385d18605aa85abe Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Fri, 19 Jun 2026 09:02:10 +0200 Subject: [PATCH 3/8] Update docs for custom config cache --- .../docs/sdk-reference/dotnet/_template.mdx | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/website/docs/sdk-reference/dotnet/_template.mdx b/website/docs/sdk-reference/dotnet/_template.mdx index 982ba3b6e..95eadccd3 100644 --- a/website/docs/sdk-reference/dotnet/_template.mdx +++ b/website/docs/sdk-reference/dotnet/_template.mdx @@ -1641,24 +1641,14 @@ This allows you to seamlessly integrate ConfigCat with your existing caching inf ```csharp public class MyCustomCache : IConfigCatCache { - public string? Get(string key) + public ValueTask GetAsync(string key, CancellationToken cancellationToken = default) { - /* insert your synchronous cache read logic here */ + /* insert your cache read logic here */ } - public Task GetAsync(string key, CancellationToken cancellationToken = default) + public ValueTask SetAsync(string key, string value, CancellationToken cancellationToken = default) { - /* insert your asynchronous cache read logic here */ - } - - public void Set(string key, string value) - { - /* insert your synchronous cache write logic here */ - } - - public Task SetAsync(string key, string value, CancellationToken cancellationToken = default) - { - /* insert your asynchronous cache write logic here */ + /* insert your cache write logic here */ } } ``` From 35c31a48345e423413abc970916ec5b3d5f69a7a Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Fri, 19 Jun 2026 09:09:32 +0200 Subject: [PATCH 4/8] Update docs for allowed user attribute types --- website/docs/sdk-reference/dotnet/_template.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/docs/sdk-reference/dotnet/_template.mdx b/website/docs/sdk-reference/dotnet/_template.mdx index 95eadccd3..52bda257a 100644 --- a/website/docs/sdk-reference/dotnet/_template.mdx +++ b/website/docs/sdk-reference/dotnet/_template.mdx @@ -682,6 +682,7 @@ All comparators support `string` values as User Object attribute (in some cases * all other values are automatically converted to `string` (a warning will be logged but evaluation will continue as normal). **SemVer-based comparators** (IS ONE OF, <, >=, etc.) +* accept `SemVersion` values containing a preparsed semver value, * accept `string` values containing a properly formatted, valid semver value, * all other values are considered invalid (a warning will be logged and the currently evaluated Targeting Rule will be skipped). @@ -697,7 +698,7 @@ All comparators support `string` values as User Object attribute (in some cases * all other values are considered invalid (a warning will be logged and the currently evaluated Targeting Rule will be skipped). **String array-based comparators** (ARRAY CONTAINS ANY OF / ARRAY NOT CONTAINS ANY OF) -* accept arrays of `string`, +* accept arrays of `string`, `IReadOnlyList` or `IList`, * accept `string` values containing a valid JSON string which can be deserialized to an array of `string`, * all other values are considered invalid (a warning will be logged and the currently evaluated Targeting Rule will be skipped). From f36641ae7818dc83d0671243978256d0176bcfea Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Fri, 19 Jun 2026 09:22:55 +0200 Subject: [PATCH 5/8] Use var consistently in code examples --- .../docs/sdk-reference/dotnet/_template.mdx | 54 ++++++------ .../version-V1/sdk-reference/dotnet.mdx | 82 +++++++++---------- 2 files changed, 68 insertions(+), 68 deletions(-) diff --git a/website/docs/sdk-reference/dotnet/_template.mdx b/website/docs/sdk-reference/dotnet/_template.mdx index 52bda257a..1059be439 100644 --- a/website/docs/sdk-reference/dotnet/_template.mdx +++ b/website/docs/sdk-reference/dotnet/_template.mdx @@ -439,7 +439,7 @@ _ConfigCat Client_ is responsible for: factory method where the `ConfigCatClientOptions` class is used to set up the _ConfigCat Client_. ```csharp - IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => { options.PollingMode = PollingModes.AutoPoll(maxInitWaitTime: TimeSpan.FromSeconds(10)); options.Logger = new ConsoleLogger(LogLevel.Info); @@ -508,7 +508,7 @@ For example: ```csharp - IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => { options.ClientReady += (s, e) => { @@ -552,7 +552,7 @@ For example: | `user` | Optional, _User Object_. Essential when using Targeting. [Read more about Targeting.](../../targeting/targeting-overview.mdx) | ```csharp -User userObject = new User("#UNIQUE-USER-IDENTIFIER#"); // Optional User Object +var userObject = new User("#UNIQUE-USER-IDENTIFIER#"); // Optional User Object var value = await client.GetValueAsync("keyOfMyFeatureFlag", false, userObject); ``` @@ -603,7 +603,7 @@ var value = await client.GetValueAsync("keyOfMyDecimalSetting", 0); | `user` | Optional, _User Object_. Essential when using Targeting. [Read more about Targeting.](../../targeting/targeting-overview.mdx) | ```csharp -User userObject = new User("#UNIQUE-USER-IDENTIFIER#"); // Optional User Object +var userObject = new User("#UNIQUE-USER-IDENTIFIER#"); // Optional User Object var details = await client.GetValueDetailsAsync("keyOfMyFeatureFlag", false, userObject); ``` @@ -632,11 +632,11 @@ The `details` result contains the following information: The [User Object](../../targeting/user-object.mdx) is essential if you'd like to use ConfigCat's [Targeting](../../targeting/targeting-overview.mdx) feature. ```csharp -User userObject = new User("#UNIQUE-USER-IDENTIFIER#"); +var userObject = new User("#UNIQUE-USER-IDENTIFIER#"); ``` ```csharp -User userObject = new User("john@example.com"); +var userObject = new User("john@example.com"); ``` | Parameters | Description | @@ -647,7 +647,7 @@ User userObject = new User("john@example.com"); | `Custom` | Optional dictionary for custom attributes of a user for advanced Targeting Rule definitions. E.g. User role, Subscription type. | ```csharp -User userObject = new User("#UNIQUE-USER-IDENTIFIER#") +var userObject = new User("#UNIQUE-USER-IDENTIFIER#") { Email = "john@example.com", Country = "United Kingdom", @@ -662,7 +662,7 @@ User userObject = new User("#UNIQUE-USER-IDENTIFIER#") The `Custom` dictionary also allows attribute values other than `string` values: ```csharp -User userObject = new User("#UNIQUE-USER-IDENTIFIER#") +var userObject = new User("#UNIQUE-USER-IDENTIFIER#") { Custom = { @@ -711,7 +711,7 @@ You can set the default User Object either on SDK initialization: ```csharp - IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => options.DefaultUser = new User(identifier: "john@example.com")); ``` @@ -777,7 +777,7 @@ Use the {props.platform === "generic-host" ? <>PollInterval option ```csharp - IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => { options.PollingMode = PollingModes.AutoPoll(pollInterval: TimeSpan.FromSeconds(95)); }); @@ -829,7 +829,7 @@ Use the {props.platform === "generic-host" ? <>CacheTimeToLive opti ```csharp - IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => { options.PollingMode = PollingModes.LazyLoad(cacheTimeToLive: TimeSpan.FromSeconds(600)); }); @@ -878,7 +878,7 @@ Manual polling gives you full control over when the config data is downloaded fr ```csharp - IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => { options.PollingMode = PollingModes.ManualPoll; }); @@ -957,7 +957,7 @@ You can subscribe to these events either on initialization: ```csharp - IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => { options.PollingMode = PollingModes.ManualPoll; options.FlagEvaluated += (s, e) => { /* handle the event */ }; @@ -1199,7 +1199,7 @@ You can also specify whether the file should be reloaded when it gets modified. ```csharp - IConfigCatClient client = ConfigCatClient.Get("localhost", options => + var client = ConfigCatClient.Get("localhost", options => { options.FlagOverrides = FlagOverrides.LocalFile( "path/to/local_flags.json", // path to the file @@ -1415,7 +1415,7 @@ You can set up the SDK to load your feature flag & setting overrides from a `Dic {"stringSetting", "test"}, }; - IConfigCatClient client = ConfigCatClient.Get("localhost", options => + var client = ConfigCatClient.Get("localhost", options => { options.FlagOverrides = FlagOverrides.LocalDictionary(dictionary, OverrideBehaviour.LocalOnly); }); @@ -1476,7 +1476,7 @@ Then configure the client to use the `MyCustomOverrideDataSource` implementation ```csharp var flagOverrideDataSource = new MyCustomOverrideDataSource("{ \"f\": { ... } }"); - IConfigCatClient client = ConfigCatClient.Get("localhost", options => + var client = ConfigCatClient.Get("localhost", options => { options.FlagOverrides = new FlagOverrides(flagOverrideDataSource, OverrideBehaviour.LocalOnly); }); @@ -1505,7 +1505,7 @@ Then configure the client to use the `MyCustomOverrideDataSource` implementation ```csharp - IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); + var client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); client.LogLevel = LogLevel.Info; ``` @@ -1604,7 +1604,7 @@ Please make sure that your log filter logic doesn't perform heavy computation an You can get the keys for all available feature flags and settings by calling the `GetAllKeysAsync()` method. ```csharp -IEnumerable keys = await client.GetAllKeysAsync(); +var keys = await client.GetAllKeysAsync(); ``` ## `GetAllValuesAsync()` @@ -1612,11 +1612,11 @@ IEnumerable keys = await client.GetAllKeysAsync(); Evaluates and returns the values of all feature flags and settings. Passing a [User Object](#user-object) is optional. ```csharp -IDictionary settingValues = await client.GetAllValuesAsync(); +var settingValues = await client.GetAllValuesAsync(); // invoke with User Object -User userObject = new User("#UNIQUE-USER-IDENTIFIER#"); -IDictionary settingValuesTargeting = await client.GetAllValuesAsync(userObject); +var userObject = new User("#UNIQUE-USER-IDENTIFIER#"); +var settingValuesTargeting = await client.GetAllValuesAsync(userObject); ``` ## `GetAllValueDetailsAsync()` @@ -1624,11 +1624,11 @@ IDictionary settingValuesTargeting = await client.GetAllValuesAs Evaluates and returns the values along with evaluation details of all feature flags and settings. Passing a [User Object](#user-object) is optional. ```csharp -IReadOnlyList settingValues = await client.GetAllValueDetailsAsync(); +var settingValues = await client.GetAllValueDetailsAsync(); // invoke with User Object -User userObject = new User("435170f4-8a8b-4b67-a723-505ac7cdea92"); -IReadOnlyList settingValuesTargeting = await client.GetAllValueDetailsAsync(userObject); +var userObject = new User("435170f4-8a8b-4b67-a723-505ac7cdea92"); +var settingValuesTargeting = await client.GetAllValueDetailsAsync(userObject); ``` ## Using custom cache implementation @@ -1659,7 +1659,7 @@ then ```csharp - IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => { options.ConfigCache = new MyCustomCache(); }); @@ -1701,7 +1701,7 @@ This setting does not apply if you configure the client to use a custom config f Credentials = new NetworkCredential(proxyUserName, proxyPassword) }; - IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => { options.Proxy = myProxySettings; }); @@ -1751,7 +1751,7 @@ This setting does not apply if you configure the client to use a custom config f ```csharp - IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => + var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => { options.HttpTimeout = TimeSpan.FromSeconds(10); }); diff --git a/website/versioned_docs/version-V1/sdk-reference/dotnet.mdx b/website/versioned_docs/version-V1/sdk-reference/dotnet.mdx index 9248d4f32..0dfbd95b8 100644 --- a/website/versioned_docs/version-V1/sdk-reference/dotnet.mdx +++ b/website/versioned_docs/version-V1/sdk-reference/dotnet.mdx @@ -101,7 +101,7 @@ To customize the SDK's behavior, you can pass an additional `Action +var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => { options.PollingMode = PollingModes.ManualPoll; options.Logger = new ConsoleLogger(LogLevel.Info); @@ -128,7 +128,7 @@ Via the events provided by `ConfigCatClientOptions` you can also subscribe to th For example: ```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => +var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => { options.ClientReady += (s, e) => Debug.WriteLine("Client is ready!"); }); @@ -150,12 +150,12 @@ You can close all open clients at once using the `ConfigCatClient.DisposeAll()` | `user` | Optional, _User Object_. Essential when using Targeting. [Read more about Targeting.](../advanced/targeting.mdx) | ```csharp -User userObject = new User("#UNIQUE-USER-IDENTIFIER#"); // Optional User Object +var userObject = new User("#UNIQUE-USER-IDENTIFIER#"); // Optional User Object var value = client.GetValue("keyOfMyFeatureFlag", false, userObject); ``` ```csharp -User userObject = new User("#UNIQUE-USER-IDENTIFIER#"); // Optional User Object +var userObject = new User("#UNIQUE-USER-IDENTIFIER#"); // Optional User Object var value = await client.GetValueAsync("keyOfMyFeatureFlag", false, userObject); ``` @@ -206,12 +206,12 @@ var value = client.GetValue("keyOfMyDecimalSetting", 0); | `user` | Optional, _User Object_. Essential when using Targeting. [Read more about Targeting.](../advanced/targeting.mdx) | ```csharp -User userObject = new User("#UNIQUE-USER-IDENTIFIER#"); // Optional User Object +var userObject = new User("#UNIQUE-USER-IDENTIFIER#"); // Optional User Object var details = client.GetValueDetails("keyOfMyFeatureFlag", false, userObject); ``` ```csharp -User userObject = new User("#UNIQUE-USER-IDENTIFIER#"); // Optional User Object +var userObject = new User("#UNIQUE-USER-IDENTIFIER#"); // Optional User Object var details = await client.GetValueDetailsAsync("keyOfMyFeatureFlag", false, userObject); ``` @@ -239,11 +239,11 @@ The `details` result contains the following information: The [User Object](../advanced/user-object.mdx) is essential if you'd like to use ConfigCat's [Targeting](../advanced/targeting.mdx) feature. ```csharp -User userObject = new User("#UNIQUE-USER-IDENTIFIER#"); +var userObject = new User("#UNIQUE-USER-IDENTIFIER#"); ``` ```csharp -User userObject = new User("john@example.com"); +var userObject = new User("john@example.com"); ``` | Parameters | Description | @@ -254,7 +254,7 @@ User userObject = new User("john@example.com"); | `Custom` | Optional dictionary for custom attributes of a user for advanced Targeting Rule definitions. E.g. User role, Subscription type. | ```csharp -User userObject = new User("#UNIQUE-USER-IDENTIFIER#") +var userObject = new User("#UNIQUE-USER-IDENTIFIER#") { Email = "john@example.com", Country = "United Kingdom", @@ -273,7 +273,7 @@ It's possible to set a default User Object that will be used on feature flag and You can set the default User Object either on SDK initialization: ```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => +var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => options.DefaultUser = new User(identifier: "john@example.com")); ``` @@ -323,7 +323,7 @@ The _ConfigCat SDK_ downloads the latest config data from the ConfigCat CDN auto Use the `pollInterval` option parameter to change the polling interval. ```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => +var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => { options.PollingMode = PollingModes.AutoPoll(pollInterval: TimeSpan.FromSeconds(95)); }); @@ -343,7 +343,7 @@ When calling `GetValue()` or `GetValueAsync()`, the _ConfigCat SDK_ downloads th Use `cacheTimeToLive` parameter to manage configuration lifetime. ```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => +var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => { options.PollingMode = PollingModes.LazyLoad(cacheTimeToLive: TimeSpan.FromSeconds(600)); }); @@ -360,7 +360,7 @@ Available options: Manual polling gives you full control over when the config data is downloaded from the ConfigCat CDN. The _ConfigCat SDK_ will not download it automatically. Calling `ForceRefresh()` is your application's responsibility. ```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => +var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => { options.PollingMode = PollingModes.ManualPoll; }); @@ -373,7 +373,7 @@ client.ForceRefresh(); ::: ```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => +var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => { options.PollingMode = PollingModes.ManualPoll; }); @@ -397,7 +397,7 @@ Via the following events you can subscribe to particular events raised by the cl You can subscribe to these events either on initialization: ```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => +var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => { options.PollingMode = PollingModes.ManualPoll; options.FlagEvaluated += (s, e) => { /* handle the event */ }; @@ -449,7 +449,7 @@ You can also specify whether the file should be reloaded when it gets modified. #### File ```csharp -IConfigCatClient client = ConfigCatClient.Get("localhost", options => +var client = ConfigCatClient.Get("localhost", options => { options.FlagOverrides = FlagOverrides.LocalFile( "path/to/local_flags.json", // path to the file @@ -565,7 +565,7 @@ var dictionary = new Dictionary {"stringSetting", "test"}, }; -IConfigCatClient client = ConfigCatClient.Get("localhost", options => +var client = ConfigCatClient.Get("localhost", options => { options.FlagOverrides = FlagOverrides.LocalDictionary(dictionary, OverrideBehaviour.LocalOnly); }); @@ -576,7 +576,7 @@ IConfigCatClient client = ConfigCatClient.Get("localhost", options => ### Setting log level ```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); +var client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); client.LogLevel = LogLevel.Info; ``` @@ -610,13 +610,13 @@ Another sample which shows how to implement an adapter to [the built-in logging You can get the keys for all available feature flags and settings by calling the `GetAllKeys()` or `GetAllKeysAsync()` method of the `ConfigCatClient`. ```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); -IEnumerable keys = client.GetAllKeys(); +var client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); +var keys = client.GetAllKeys(); ``` ```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); -IEnumerable keys = await client.GetAllKeysAsync(); +var client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); +var keys = await client.GetAllKeysAsync(); ``` ## `GetAllValues()`, `GetAllValuesAsync()` @@ -624,21 +624,21 @@ IEnumerable keys = await client.GetAllKeysAsync(); Evaluates and returns the values of all feature flags and settings. Passing a [User Object](#user-object) is optional. ```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); -IDictionary settingValues = client.GetAllValues(); +var client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); +var settingValues = client.GetAllValues(); // invoke with User Object -User userObject = new User("#UNIQUE-USER-IDENTIFIER#"); -IDictionary settingValuesTargeting = client.GetAllValues(userObject); +var userObject = new User("#UNIQUE-USER-IDENTIFIER#"); +var settingValuesTargeting = client.GetAllValues(userObject); ``` ```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); -IDictionary settingValues = await client.GetAllValuesAsync(); +var client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); +var settingValues = await client.GetAllValuesAsync(); // invoke with User Object -User userObject = new User("#UNIQUE-USER-IDENTIFIER#"); -IDictionary settingValuesTargeting = await client.GetAllValuesAsync(userObject); +var userObject = new User("#UNIQUE-USER-IDENTIFIER#"); +var settingValuesTargeting = await client.GetAllValuesAsync(userObject); ``` ## `GetAllValueDetails()`, `GetAllValueDetailsAsync()` @@ -646,21 +646,21 @@ IDictionary settingValuesTargeting = await client.GetAllValuesAs Evaluates and returns the values along with evaluation details of all feature flags and settings. Passing a [User Object](#user-object) is optional. ```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); -IReadOnlyList settingValues = client.GetAllValueDetails(); +var client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); +var settingValues = client.GetAllValueDetails(); // invoke with User Object -User userObject = new User("435170f4-8a8b-4b67-a723-505ac7cdea92"); -IReadOnlyList settingValuesTargeting = client.GetAllValueDetails(userObject); +var userObject = new User("435170f4-8a8b-4b67-a723-505ac7cdea92"); +var settingValuesTargeting = client.GetAllValueDetails(userObject); ``` ```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); -IReadOnlyList settingValues = await client.GetAllValueDetailsAsync(); +var client = ConfigCatClient.Get("#YOUR-SDK-KEY#"); +var settingValues = await client.GetAllValueDetailsAsync(); // invoke with User Object -User userObject = new User("435170f4-8a8b-4b67-a723-505ac7cdea92"); -IReadOnlyList settingValuesTargeting = await client.GetAllValueDetailsAsync(userObject); +var userObject = new User("435170f4-8a8b-4b67-a723-505ac7cdea92"); +var settingValuesTargeting = await client.GetAllValueDetailsAsync(userObject); ``` ## Using custom cache implementation @@ -699,7 +699,7 @@ public class MyCustomCache : IConfigCatCache then ```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => +var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => { options.ConfigCache = new MyCustomCache() }); @@ -723,7 +723,7 @@ var myProxySettings = new WebProxy(proxyHost, proxyPort) var myHttpClientHandler = new HttpClientHandler { Proxy = myProxySettings }; -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => +var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => { options.HttpClientHandler = myHttpClientHandler; }); @@ -734,7 +734,7 @@ IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => You can set the maximum wait time for a ConfigCat HTTP response. ```csharp -IConfigCatClient client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => +var client = ConfigCatClient.Get("#YOUR-SDK-KEY#", options => { options.HttpTimeout = TimeSpan.FromSeconds(10); }); From 42c6f5efb8fbd0f7e1b17d48e1c1b83cb8b77e13 Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Fri, 19 Jun 2026 09:42:54 +0200 Subject: [PATCH 6/8] Update dependency list in Unity reference --- website/docs/sdk-reference/unity.mdx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/website/docs/sdk-reference/unity.mdx b/website/docs/sdk-reference/unity.mdx index 38e6870ff..49cc9ddc2 100644 --- a/website/docs/sdk-reference/unity.mdx +++ b/website/docs/sdk-reference/unity.mdx @@ -23,14 +23,16 @@ However, compared to an ordinary .NET application, the installation process is d Since NuGet packages cannot be referenced in Unity projects directly, the SDK's assembly file (`ConfigCat.Client.dll`) and its dependencies must be added manually. So, as a first step, you will need to get the assembly .dll files for `netstandard2.0` from the following packages: -* [ConfigCat.Client v9.3.2+](https://www.nuget.org/packages/ConfigCat.Client) -* [Microsoft.Bcl.AsyncInterfaces v6.0.0](https://www.nuget.org/packages/Microsoft.Bcl.AsyncInterfaces/6.0.0) +* [ConfigCat.Client v10.0.0+](https://www.nuget.org/packages/ConfigCat.Client) +* [Microsoft.Bcl.AsyncInterfaces v8.0.0](https://www.nuget.org/packages/Microsoft.Bcl.AsyncInterfaces/8.0.0) * [System.Buffers v4.5.1](https://www.nuget.org/packages/System.Buffers/4.5.1) -* [System.Memory v4.5.4](https://www.nuget.org/packages/System.Memory/4.5.4) -* [System.Numerics.Vectors v4.5.0](https://www.nuget.org/packages/System.Numerics.Vectors/4.5.0) +* [System.Memory v4.5.5](https://www.nuget.org/packages/System.Memory/4.5.5) +* [System.Numerics.Vectors v4.4.0](https://www.nuget.org/packages/System.Numerics.Vectors/4.4.0) * [System.Runtime.CompilerServices.Unsafe v6.0.0](https://www.nuget.org/packages/System.Runtime.CompilerServices.Unsafe/6.0.0) -* [System.Text.Encodings.Web v6.0.0](https://www.nuget.org/packages/System.Text.Encodings.Web/6.0.0) -* [System.Text.Json v6.0.10](https://www.nuget.org/packages/System.Text.Json/6.0.10) +* [System.Text.Encodings.Web v8.0.0](https://www.nuget.org/packages/System.Text.Encodings.Web/8.0.0) +* [System.Text.Json v8.0.5](https://www.nuget.org/packages/System.Text.Json/8.0.5) +* [System.Text.RegularExpressions v4.3.1](https://www.nuget.org/packages/System.Text.RegularExpressions/4.3.1) +* [System.Threading v4.3.0](https://www.nuget.org/packages/System.Threading/4.3.0) * [System.Threading.Tasks.Extensions v4.5.4](https://www.nuget.org/packages/System.Threading.Tasks.Extensions/4.5.4) You can do this by downloading these packages and extracting the .dll files manually from the .nupkg files (which are actually plain ZIP files). From 4b68b421869a7bf024b2f9db5f426354f797bec5 Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Fri, 19 Jun 2026 10:56:35 +0200 Subject: [PATCH 7/8] Add schema markup --- .../sdk-reference/dotnet/generic-host.mdx | 4 +- .../sdk-reference/dotnet/generic-host.json | 74 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 website/src/schema-markup/sdk-reference/dotnet/generic-host.json diff --git a/website/docs/sdk-reference/dotnet/generic-host.mdx b/website/docs/sdk-reference/dotnet/generic-host.mdx index 33d2ddd81..0259a93ba 100644 --- a/website/docs/sdk-reference/dotnet/generic-host.mdx +++ b/website/docs/sdk-reference/dotnet/generic-host.mdx @@ -6,7 +6,9 @@ description: Using the ConfigCat SDK in DI-based .NET applications. This is a st import DotNetSdkReferenceTemplate, { getAdjustedToc } from "./\_template.mdx"; -{/* TODO: schema */} +export const NetGenericHostSchema = require('@site/src/schema-markup/sdk-reference/dotnet/generic-host.json'); + + export const platform = "generic-host"; diff --git a/website/src/schema-markup/sdk-reference/dotnet/generic-host.json b/website/src/schema-markup/sdk-reference/dotnet/generic-host.json new file mode 100644 index 000000000..2c18525f1 --- /dev/null +++ b/website/src/schema-markup/sdk-reference/dotnet/generic-host.json @@ -0,0 +1,74 @@ +{ + "@context": "https://schema.org/", + "@type": "HowTo", + "name": "How to use feature flags in ASP.NET Core and other DI-based .NET applications?", + "description": "How to use feature flags ASP.NET Core and other DI-based .NET applications using ConfigCat Feature Flags. https://configcat.com", + "image": "https://configcat.com/images/shared/home.png", + "totalTime": "PT10M", + "estimatedCost": { + "@type": "MonetaryAmount", + "currency": "USD", + "value": "0" + }, + "tool": { + "@type": "HowToTool", + "name": "ConfigCat Feature Flags - https://configcat.com" + }, + "supply": { + "@type": "HowToSupply", + "name": "ConfigCat", + "image": "https://configcat.com/images/shared/home.png", + "url": "https://configcat.com" + }, + "step": [ + { + "@type": "HowToStep", + "text": "Sign up for a free ConfigCat account", + "name": "Registration", + "url": "https://app.configcat.com/auth/signup", + "image": "https://configcat.com/images/shared/home.png" + }, + { + "@type": "HowToStep", + "text": "Install ConfigCat SDK NuGet package", + "name": "Installation", + "url": "https://configcat.com/docs/sdk-reference/dotnet/generic-host/#1-install-configcat-sdk-nuget-package", + "image": "https://configcat.com/images/shared/home.png" + }, + { + "@type": "HowToStep", + "text": "Import package", + "name": "Import", + "url": "https://configcat.com/docs/sdk-reference/dotnet/generic-host/#2-import-package", + "image": "https://configcat.com/images/shared/home.png" + }, + { + "@type": "HowToStep", + "text": "Register the *ConfigCat* client with your *SDK Key*", + "name": "ServiceRegistrationIntoDI", + "url": "https://configcat.com/docs/sdk-reference/dotnet/generic-host/#3-register-the-configcat-client-with-your-sdk-key", + "image": "https://configcat.com/images/shared/home.png" + }, + { + "@type": "HowToStep", + "text": "Initialize the *ConfigCat* client", + "name": "Initialization", + "url": "https://configcat.com/docs/sdk-reference/dotnet/generic-host/#4-initialize-the-configcat-client", + "image": "https://configcat.com/images/shared/home.png" + }, + { + "@type": "HowToStep", + "text": "Obtain the *ConfigCat* client", + "name": "ServiceResolutionFromDI", + "url": "https://configcat.com/docs/sdk-reference/dotnet/generic-host/#5-obtain-the-configcat-client", + "image": "https://configcat.com/images/shared/home.png" + }, + { + "@type": "HowToStep", + "text": "Get your setting value", + "name": "Evaluation", + "url": "https://configcat.com/docs/sdk-reference/dotnet/generic-host/#6-get-your-setting-value", + "image": "https://configcat.com/images/shared/home.png" + } + ] +} From 408a801afd3c3d7da933c376132413aed12cbf31 Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Fri, 3 Jul 2026 09:36:44 +0200 Subject: [PATCH 8/8] Minor correction --- website/docs/sdk-reference/dotnet/_template.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/website/docs/sdk-reference/dotnet/_template.mdx b/website/docs/sdk-reference/dotnet/_template.mdx index 1059be439..f6c3eb881 100644 --- a/website/docs/sdk-reference/dotnet/_template.mdx +++ b/website/docs/sdk-reference/dotnet/_template.mdx @@ -62,7 +62,7 @@ export const getAdjustedToc = (platform) => { - [Blazor](https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor) - [MAUI](https://dotnet.microsoft.com/en-us/apps/maui) - [Worker Services](https://learn.microsoft.com/en-us/dotnet/core/extensions/workers) - - Any other application built on [.NET Generic Host](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host) + - Any other application built on [.NET Generic Host](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host) (`Microsoft.Extensions.Hosting`) or [.NET's standard dependency injection](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection/overview) (`Microsoft.Extensions.DependencyInjection`). ::: @@ -380,7 +380,7 @@ using ConfigCat.Extensions.Hosting; ### 5. Obtain the _ConfigCat_ client Resolve the singleton `IConfigCatClient` service from the DI container via constructor injection, method injection - (using the `FromServices` attribute) or by `ServiceProvider.GetRequiredService()`: + (using the `FromServices` attribute), etc., or by `ServiceProvider.GetRequiredService()`. E.g.: ```csharp var client = app.Services.GetRequiredService(); @@ -1814,10 +1814,10 @@ We strive to provide an extensive support for the various .NET runtimes and vers Check out our Sample Applications how they use the _ConfigCat SDK_: - Sample Console App -- Sample Multi-page Web App (ASP.NET Core MVC) -- Sample Single-page Web App (ASP.NET Core Blazor WebAssembly) +- Sample Multi-Page Web App (ASP.NET Core MVC) +- Sample Single-Page Web App (ASP.NET Core Blazor WebAssembly) - Sample Mobile/Windows Store App (.NET MAUI) -- Sample Windows Service (.NET Generic Host) +- Sample Windows Service (Worker Service built on .NET Generic Host) ## Guides