Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FSSDK-9047] doc: Converted all the files into proper md file format #368

Merged
merged 1 commit into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,12 @@ hidden: false
createdAt: "2019-09-11T13:55:51.015Z"
updatedAt: "2019-09-11T13:58:07.201Z"
---
TEST CHANGE

The C# SDK is distributed through NuGet.

For Windows, to install, run the command `Install-Package Optimizely.SDK` in the Package Manager Console:
[block:code]
{
"codes": [
{
"code": "PM> Install-Package Optimizely.SDK -Version 3.0.0\n\n",
"language": "shell",
"name": "Install the SDK"
}
]
}
[/block]
The package is on NuGet at https://www.nuget.org/packages/Optimizely.SDK. The full source code is at https://github.com/optimizely/csharp-sdk.

```shell
PM> Install-Package Optimizely.SDK -Version 3.0.0
```
The package is on NuGet at https://www.nuget.org/packages/Optimizely.SDK. The full source code is at https://github.com/optimizely/csharp-sdk.
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,13 @@ hidden: false
createdAt: "2019-09-11T13:55:51.015Z"
updatedAt: "2019-09-11T13:58:07.201Z"
---

The C# SDK is distributed through NuGet.

For Windows, to install, run the command `Install-Package Optimizely.SDK` in the Package Manager Console:
[block:code]
{
"codes": [
{
"code": "PM> Install-Package Optimizely.SDK -Version 3.0.0\n\n",
"language": "shell",
"name": "Install the SDK"
}
]
}
[/block]
The package is on NuGet at https://www.nuget.org/packages/Optimizely.SDK. The full source code is at https://github.com/optimizely/csharp-sdk.

```shell
PM> Install-Package Optimizely.SDK -Version 3.0.0
```
The package is on NuGet at https://www.nuget.org/packages/Optimizely.SDK. The full source code is at https://github.com/optimizely/csharp-sdk.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,37 @@ This example demonstrates the basic usage of each of these concepts. This exampl
2. Run an A/B test called `app_redesign`. This experiment has two variations, `control` and `treatment`. It uses the `activate` method to assign the user to a variation, returning its key. As a side effect, the activate function also sends an impression event to Optimizely to record that the current user has been exposed to the experiment.

3. Use event tracking to track an event called `purchased`. This conversion event measures the impact of an experiment. Using the track method, the purchase is automatically attributed back to the running A/B and feature tests we've activated, and the SDK sends a network request to Optimizely via the customizable event dispatcher so we can count it in your results page.
[block:code]
```csharp
// Import Optimizely SDK
using OptimizelySDK;

// Instantiate an Optimizely client
var optimizelyClient = new Optimizely(datafile);

// Evaluate a feature flag and a variable
bool isFeatureEnabled = optimizelyClient.IsFeatureEnabled("price_filter", userId);
int? min_price = optimizelyClient.GetFeatureVariableInteger("price_filter", "min_price", userId);

// Activate an A/B test
var variation = optimizelyClient.Activate("app_redesign", userId);

if (variation != null && !string.IsNullOrEmpty(variation.Key))
{
"codes": [
if (variation.Key == "control")
{
// Execute code for variation A
}
else if (variation.Key == "treatment")
{
"code": "//Import Optimizely SDK\nusing OptimizelySDK;\n\n// Instantiate an Optimizely client\nvar optimizelyClient = new Optimizely(datafile);\n\n// Evaluate a feature flag and a variable\nbool isFeatureEnabled = optimizelyClient.IsFeatureEnabled(\"price_filter\", userId);\nint? min_price = optimizelyClient.GetFeatureVariableInteger(\"price_filter\", \"min_price\", userId);\n\n// Activate an A/B test\nvar variation = optimizelyClient.Activate(\"app_redesign\", userId);\n\tif (variation != null && !string.IsNullOrEmpty(variation.Key))\n\t{\n\t\tif (variation.Key == \"control\")\n\t\t{\n\t\t\t// Execute code for variation A\n\t\t}\n\t\telse if (variation.Key == \"treatment\")\n\t\t{\n\t\t\t// Execute code for variation B\n\t\t}\n\t}\n\telse\n\t{\n\t\t// Execute code for your users who don’t qualify for the experiment\n\t}\n\n// Track an event\noptimizelyClient.Track(\"purchased\", userId);\n\n",
"language": "csharp"
// Execute code for variation B
}
]
}
[/block]
else
{
// Execute code for your users who don’t qualify for the experiment
}

// Track an event
optimizelyClient.Track("purchased", userId);

```
Original file line number Diff line number Diff line change
Expand Up @@ -5,68 +5,109 @@ hidden: false
createdAt: "2020-01-17T11:52:50.547Z"
updatedAt: "2020-01-28T21:53:11.290Z"
---
[block:api-header]
{
"title": "Overview"
}
[/block]

## Overview

Optimizely Feature Experimentation SDKs open a well-defined set of public APIs, hiding all implementation details. However, some clients may need access to project configuration data within the "datafile".

In this document, we extend our public APIs to define data models and access methods, which clients can use to access project configuration data.

[block:api-header]
{
"title": "OptimizelyConfig API"
}
[/block]
## OptimizelyConfig API

A public configuration data model (OptimizelyConfig) is defined below as a structured format of static Optimizely Project data.

OptimizelyConfig can be accessed from OptimizelyClient (top-level) with this public API call:
[block:code]

```csharp
public OptimizelyConfig GetOptimizelyConfig()
```
`GetOptimizelyConfig` returns an `OptimizelyConfig` instance which includes a datafile revision number, all experiments, and feature flags mapped by their key values.

> **Note:** When the SDK datafile is updated (the client can add a notification listener for `OPTIMIZELY_CONFIG_UPDATE` to get notified), the client is expected to call the method to get the updated OptimizelyConfig data. See examples below.

```csharp
// OptimizelyConfig is a class describing the current project configuration data being used by this SDK instance.
public class OptimizelyConfig
{
"codes": [
{
"code": "public OptimizelyConfig GetOptimizelyConfig()",
"language": "csharp"
}
]
public string Revision { get; private set; }
public IDictionary<string, OptimizelyExperiment> ExperimentsMap { get; private set; }
public IDictionary<string, OptimizelyFeature> FeaturesMap { get; private set; }
}
[/block]
`GetOptimizelyConfig` returns an `OptimizelyConfig` instance which include a datafile revision number, all experiments, and feature flags mapped by their key values.
[block:callout]

// Entity.IdKeyEntity is an abstract class used for inheritance in OptimizelyExperiment, OptimizelyFeature, OptimizelyVariation, and OptimizelyVariable classes.
public abstract class IdKeyEntity : Entity, IEquatable<object>
{
"type": "info",
"title": "Note",
"body": "When the SDK datafile is updated (the client can add a notification listener for `OPTIMIZELY_CONFIG_UPDATE` to get notified), the client is expected to call the method to get the updated OptimizelyConfig data. See examples below."
public string Id { get; set; }
public string Key { get; set; }
}
[/block]

[block:code]
// OptimizelyFeature is a class describing a feature and inherited from Entity.IdKeyEntity.
public class OptimizelyFeature : Entity.IdKeyEntity
{
"codes": [
{
"code": "// OptimizelyConfig is class describing the current project configuration data being used by this SDK instance.\n public class OptimizelyConfig\n {\n public string Revision { get; private set; }\n public IDictionary<string, OptimizelyExperiment> ExperimentsMap { get; private set; }\n public IDictionary<string, OptimizelyFeature> FeaturesMap { get; private set; } \n }\n\n// Entity.IdKeyEntity is an abstract class used for inheritance in OptimizelyExperiment, OptimizelyFeature, OptimizelyVariation and OptimizelyVariable classes.\npublic abstract class IdKeyEntity : Entity, IEquatable<object>\n{\n public string Id { get; set; }\n public string Key { get; set; }\n}\n\n// OptimizelyFeature is a class describing a feature and inherited from Entity.IdKeyEntity.\npublic class OptimizelyFeature : Entity.IdKeyEntity\n{\n public IDictionary<string, OptimizelyExperiment> ExperimentsMap { get; private set; }\n public IDictionary<string, OptimizelyVariable> VariablesMap { get; private set; }\n}\n\n\n// OptimizelyExperiment is a class describing a feature test or an A/B test and inherited from Entity.IdKeyEntity.\npublic class OptimizelyExperiment : Entity.IdKeyEntity\n{\n public IDictionary<string, OptimizelyVariation> VariationsMap { get; private set; }\n}\n\n\n// OptimizelyVariation is a class describing a variation in a feature test or A/B test and inherited from Entity.IdKeyEntity.\npublic class OptimizelyVariation : Entity.IdKeyEntity\n{\n [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]\n public bool? FeatureEnabled { get; private set; }\n public IDictionary<string, OptimizelyVariable> VariablesMap { get; private set; }\n}\n\n\n// OptimizelyVariable is a class describing a feature variable and inherited from Entity.IdKeyEntity.\npublic class OptimizelyVariable : Entity.IdKeyEntity\n{\n public string Type { get; private set; }\n public string Value { get; private set; }\n}",
"language": "csharp"
}
]
public IDictionary<string, OptimizelyExperiment> ExperimentsMap { get; private set; }
public IDictionary<string, OptimizelyVariable> VariablesMap { get; private set; }
}
[/block]

[block:api-header]
// OptimizelyExperiment is a class describing a feature test or an A/B test and inherited from Entity.IdKeyEntity.
public class OptimizelyExperiment : Entity.IdKeyEntity
{
"title": "Examples"
public IDictionary<string, OptimizelyVariation> VariationsMap { get; private set; }
}
[/block]
OptimizelyConfig can be accessed from OptimizelyClient (top-level) like this:

[block:code]
// OptimizelyVariation is a class describing a variation in a feature test or A/B test and inherited from Entity.IdKeyEntity.
public class OptimizelyVariation : Entity.IdKeyEntity
{
"codes": [
{
"code": "var optimizelyConfig = optimizely.GetOptimizelyConfig();\n\n// all experiment keys\nvar experimentKeys = optimizelyConfig.ExperimentsMap.Keys;\nforeach(var experimentKey in experimentKeys) {\n // use experiment key data here.\n}\n\n// all experiments\nvar experiments = optimizelyConfig.ExperimentsMap.Values;\nforeach(var experiment in experiments) {\n // all variations\n var variations = experiment.VariationsMap.Values;\n foreach(var variation in variations) {\n var variables = variation.VariablesMap.Values;\n foreach(var variable in variables) {\n // use variable data here.\n }\n }\n}\n\n\n\n// all features\nvar features = optimizelyConfig.FeaturesMap.Values;\nforeach(var feature in features) {\n var experiments = feature.ExperimentsMap.Values;\n foreach(var experiment in experiments) {\n // use experiment data here.\n }\n}\n\n\n// listen to OPTIMIZELY_CONFIG_UPDATE to get updated data\nNotificationCenter.OptimizelyConfigUpdateCallback configUpdateListener = () => {\n var optimizelyConfig = optimizely.GetOptimizelyConfig();\n };\n optimizely.NotificationCenter.AddNotification(NotificationCenter.NotificationType.OptimizelyConfigUpdate, configUpdateListener);\n",
"language": "csharp"
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public bool? FeatureEnabled { get; private set; }
public IDictionary<string, OptimizelyVariable> VariablesMap { get; private set; }
}

// OptimizelyVariable is a class describing a feature variable and inherited from Entity.IdKeyEntity.
public class OptimizelyVariable : Entity.IdKeyEntity
{
public string Type { get; private set; }
public string Value { get; private set; }
}
```

## Examples

You can access the `OptimizelyConfig` from the `OptimizelyClient` (top-level) as shown below:

```csharp
var optimizelyConfig = optimizely.GetOptimizelyConfig();

// All experiment keys
var experimentKeys = optimizelyConfig.ExperimentsMap.Keys;
foreach(var experimentKey in experimentKeys) {
// Use experiment key data here.
}

// All experiments
var experiments = optimizelyConfig.ExperimentsMap.Values;
foreach(var experiment in experiments) {
// All variations
var variations = experiment.VariationsMap.Values;
foreach(var variation in variations) {
var variables = variation.VariablesMap.Values;
foreach(var variable in variables) {
// Use variable data here.
}
]
}
}
[/block]

// All features
var features = optimizelyConfig.FeaturesMap.Values;
foreach(var feature in features) {
var experiments = feature.ExperimentsMap.Values;
foreach(var experiment in experiments) {
// Use experiment data here.
}
}

// Listen to OPTIMIZELY_CONFIG_UPDATE to get updated data
NotificationCenter.OptimizelyConfigUpdateCallback configUpdateListener = () => {
var optimizelyConfig = optimizely.GetOptimizelyConfig();
};
optimizely.NotificationCenter.AddNotification(NotificationCenter.NotificationType.OptimizelyConfigUpdate, configUpdateListener);
```
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,75 @@ updatedAt: "2019-09-12T13:45:24.114Z"
Use a **User Profile Service** to persist information about your users and ensure variation assignments are sticky. For example, if you are working on a backend website, you can create an implementation that reads and saves user profiles from a Redis or memcached store.

In the C# SDK, there is no default implementation. Implementing a User Profile Service is optional and is only necessary if you want to keep variation assignments sticky even when experiment conditions are changed while it is running (for example, audiences, attributes, variation pausing, and traffic distribution). Otherwise, the C# SDK is stateless and rely on deterministic bucketing to return consistent assignments. See [How bucketing works](doc:how-bucketing-works) for more information.
[block:api-header]
{
"title": "Implement a service"
}
[/block]
Refer to the code samples below to provide your own User Profile Service. It should expose two functions with the following signatures:
## Implement a Service

To implement a User Profile Service, you can refer to the code samples provided below. Your User Profile Service should expose two functions with the following signatures:

- `lookup`: Takes a user ID string and returns a user profile matching the specified schema.
- `save`: Takes a user profile and persists it.

* `lookup`: Takes a user ID string and returns a user profile matching the schema below.
* `save`: Takes a user profile and persists it.
If you intend to use the User Profile Service purely for tracking purposes and not sticky bucketing, you can implement only the `save` method and always return `null` from the `lookup` method.

If you want to use the User Profile Service purely for tracking purposes and not sticky bucketing, you can implement only the `save` method (always return `nil` from `lookup`).
[block:code]
Here's an example implementation using C#:

```csharp
using System.Collections.Generic;
using OptimizelySDK;
using OptimizelySDK.Bucketing;

class InMemoryUserProfileService : UserProfileService
{
"codes": [
private Dictionary<string, Dictionary<string, object>> userProfiles = new Dictionary<string, Dictionary<string, object>>();

public override Dictionary<string, object> Lookup(string userId)
{
"code": "using System.Collections.Generic;\n\nusing OptimizelySDK;\nusing OptimizelySDK.Bucketing;\n\nclass InMemoryUserProfileService : UserProfileService\n{\n private Dictionary<String, Dictionary<string, object>> userProfiles = new Dictionary<String, Dictionary<string, object>>();\n Dictionary<string, object> UserProfileService.Lookup(string userId)\n {\n // Retrieve and return user profile\n // Replace with userprofile variable\n return null;\n }\n\n void UserProfileService.Save(Dictionary<string, object> userProfile)\n {\n // Save user profile\n }\n}\n\n\tvar optimizelyClient = new Optimizely(\n\t\tdatafile: datafile,\n userProfileService: userProfileService);\n\n",
"language": "csharp"
// Retrieve and return user profile
// Replace with the actual userprofile variable
return null;
}

public override void Save(Dictionary<string, object> userProfile)
{
// Save user profile
// Implement the logic to persist the user profile data
}
]
}
[/block]
The code example below shows the JSON schema for the user profile object.

Use `experiment_bucket_map` to override the default bucketing behavior and define an alternate experiment variation for a given user. For each experiment that you want to override, add an object to the map. Use the experiment ID as the key and include a `variation_id` property that specifies the desired variation. If there isn't an entry for an experiment, then the default bucketing behavior persists.
var optimizelyClient = new Optimizely(
datafile: datafile,
userProfileService: new InMemoryUserProfileService()
);
```
## User Profile JSON Schema

The following JSON schema represents the structure of a user profile object. This schema can be used to define user profiles within your User Profile Service.

In the example below, `^[a-zA-Z0-9]+$` is the experiment ID.
[block:code]
Use the `experiment_bucket_map` field to override the default bucketing behavior and specify an alternate experiment variation for a given user. For each experiment that you want to override, add an object to the `experiment_bucket_map`. Use the experiment ID as the key and include a `variation_id` property that specifies the desired variation. If there is no entry for an experiment, the default bucketing behavior persists.

In the example below, `^[a-zA-Z0-9]+$` represents the pattern for an experiment ID:

```json
{
"codes": [
{
"code": "{\n \"title\": \"UserProfile\",\n \"type\": \"object\",\n \"properties\": {\n \"user_id\": {\"type\": \"string\"},\n \"experiment_bucket_map\": {\"type\": \"object\",\n \"patternProperties\": {\n \"^[a-zA-Z0-9]+$\": {\"type\": \"object\",\n \"properties\": {\"variation_id\": {\"type\":\"string\"}},\n \"required\": [\"variation_id\"]}\n }\n }\n },\n \"required\": [\"user_id\", \"experiment_bucket_map\"]\n}\n\n",
"language": "json"
"title": "UserProfile",
"type": "object",
"properties": {
"user_id": {"type": "string"},
"experiment_bucket_map": {
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9]+$": {
"type": "object",
"properties": {
"variation_id": {"type": "string"}
},
"required": ["variation_id"]
}
}
}
]
},
"required": ["user_id", "experiment_bucket_map"]
}
[/block]
```
The C# SDK uses the User Profile Service you provide to override Optimizely's default bucketing behavior in cases when an experiment assignment has been saved.

When implementing your own User Profile Service, we recommend loading the user profiles into the User Profile Service on initialization and avoiding performing expensive, blocking lookups on the lookup function to minimize the performance impact of incorporating the service.
Expand Down
Loading
Loading