Git Product home page Git Product logo

optimizely / csharp-sdk Goto Github PK

View Code? Open in Web Editor NEW
19.0 92.0 20.0 6.08 MB

.NET based C# SDK for Optimizely Feature Experimentation and Optimizely Full Stack (legacy)

Home Page: https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/csharp-sdk

License: Apache License 2.0

C# 86.95% CSS 0.02% JavaScript 12.31% Shell 0.02% HTML 0.69% ASP.NET 0.01%
optimizely-environment-prod optimizely-environment-public optimizely-owner-px

csharp-sdk's Introduction

Optimizely C# SDK

Semantic Build Status NuGet Apache 2.0

This repository houses the .Net based C# SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy).

Optimizely Feature Experimentation is an A/B testing and feature management tool for product development teams, letting you experiment at every step. Using Optimizely Feature Experimentation allows for every feature on your roadmap to be an opportunity to discover hidden insights. Learn more at Optimizely.com, or see the developer documentation.

Optimizely Rollouts is free feature flags for development teams. You can easily roll out and roll back features in any application without code deploys, mitigating risk for every feature on your roadmap.

Get Started

Refer to the C# SDK's developer documentation for detailed instructions on getting started with using the SDK.

Install the C# SDK

The SDK can be installed through NuGet:

PM> Install-Package Optimizely.SDK

An ASP.Net MVC sample project demonstrating how to use the SDK is available as well:

PM> Install-Package Optimizely.SDK.Sample

Simply compile and run the Sample application to see it in use. Note that the way the Demo App stores data in memory is not recommended for production use and is merely illustrates how to use the SDK.

Feature Management Access

To access the Feature Management configuration in the Optimizely dashboard, please contact your Optimizely customer success manager.

Use the C# SDK

See the Optimizely Feature Experimentation developer documentation to learn how to set up your first C# project and use the SDK.

Initialization

Create the Optimizely Client, for example:

private static Optimizely Optimizely =
    new Optimizely(
        datafile: myProjectConfig,
        eventDispatcher: myEventDispatcher,
        logger: myLogger,
        errorHandler: myErrorHandler,
        skipJsonValidation: false);

Since this class parses the Project Config file, you should not create this per request.

APIs

This class exposes three main calls:

  1. Activate
  2. Track
  3. GetVariation

Activate and Track are used in the demonstration app. See the Optimizely documentation regarding how to use these.

Plug-in Interfaces

The Optimizely client object accepts the following plug-ins:

  1. IEventDispatcher handles the HTTP requests to Optimizely. The default implementation is an asynchronous "fire and forget".
  2. ILogger exposes a single method, Log, to record activity in the SDK. An example of a class to bridge the SDK's Log to Log4Net is provided in the Demo Application.
  3. IErrorHandler allows you to implement custom logic when Exceptions are thrown. Note that Exception information is already included in the Log.
  4. ProjectConfigManager exposes method for retrieving ProjectConfig instance. Examples include FallbackProjectConfigManager and HttpProjectConfigManager.
  5. EventProcessor provides an intermediary processing stage within event production. It's assumed that the EventProcessor dispatches events via a provided EventDispatcher. Examples include ForwardingEventProcessor and BatchEventProcessor. These are optional plug-ins and default behavior is implement if none are provided.

OptimizelyFactory

OptimizelyFactory provides basic utility to instantiate the Optimizely SDK with a minimal number of configuration options.

OptimizelyFactory does not capture all configuration and initialization options. For more use cases, build the resources via their respective builder classes.

Use OptimizelyFactory

You must provide the SDK key at runtime, either directly via the factory method:

Optimizely optimizely = OptimizelyFactory.newDefaultInstance(<<SDK_KEY>>);

You can also provide default datafile with the SDK key.

Optimizely optimizely = OptimizelyFactory.newDefaultInstance(<<SDK_KEY>>, <<Fallback>>);
Using App.config in OptimizelyFactory

OptimizelyFactory provides support of setting configuration variables in App.config: User can provide variables using following procedure:

  1. In App.config file of your project in add following:
<configSections>
    <section name="optlySDKConfigSection"
             type="OptimizelySDK.OptimizelySDKConfigSection, OptimizelySDK, Version=4.0.0.0, Culture=neutral, PublicKeyToken=null" />
  </configSections>
  1. Now add optlySDKConfigSection below . In this section you can add and set following HttpProjectConfigManager and BatchEventProcessor variables:
<optlySDKConfigSection>
  
    <HttpProjectConfig sdkKey="43214321" 
                       url="www.testurl.com" 
                       format="https://cdn.optimizely.com/data/{0}.json" 
                       pollingInterval="2000" 
                       blockingTimeOutPeriod="10000" 
                       datafileAccessToken="testingtoken123"
                       autoUpdate="true" 
                       defaultStart="true">
    </HttpProjectConfig>

    <BatchEventProcessor batchSize="10"
                         flushInterval="2000"
                         timeoutInterval="10000"
                         defaultStart="true">
    </BatchEventProcessor>
  
  </optlySDKConfigSection>
  1. After setting these variables you can instantiate the Optimizely SDK using function:
Optimizely optimizely = OptimizelyFactory.newDefaultInstance();

BatchEventProcessor

BatchEventProcessor is a batched implementation of the EventProcessor * Events passed to the BatchEventProcessor are immediately added to a BlockingQueue. * The BatchEventProcessor maintains a single consumer thread that pulls events off of the BlockingQueue and buffers them for either a configured batch size or for a maximum duration before the resulting LogEvent is sent to the NotificationManager.

Use BatchEventProcessor
EventProcessor eventProcessor = new BatchEventProcessor.Builder()
                .WithMaxBatchSize(MaxEventBatchSize)
                .WithFlushInterval(MaxEventFlushInterval)
                .WithEventDispatcher(eventDispatcher)
                .WithNotificationCenter(notificationCenter)
                .Build();
Max Event Batch Size

The Max event batch size is used to limit eventQueue batch size and events will be dispatched when limit reaches.

Flush Interval

The FlushInterval is used to specify a delay between consecutive flush events call. Event batch will be dispatched after meeting this specified timeSpan.

Event Dispatcher

Custom EventDispatcher can be passed.

Notification Center

Custom NotificationCenter can be passed.

HttpProjectConfigManager

HttpProjectConfigManager is an implementation of the abstract PollingProjectConfigManager. The Poll method is extended and makes an HTTP GET request to the configured URL to asynchronously download the project datafile and initialize an instance of the ProjectConfig.

By default, HttpProjectConfigManager will block until the first successful datafile retrieval, up to a configurable timeout. Set the frequency of the polling method and the blocking timeout with HttpProjectConfigManager.Builder.

Use HttpProjectConfigManager
HttpProjectConfigManager httpManager = new HttpProjectConfigManager.Builder()
	.WithSdkKey(sdkKey)
	.WithPollingInterval(TimeSpan.FromMinutes(1))
	.Build();
SDK key

The SDK key is used to compose the outbound HTTP request to the default datafile location on the Optimizely CDN.

Polling interval

The polling interval is used to specify a fixed delay between consecutive HTTP requests for the datafile. Between 1 to 4294967294 miliseconds is valid duration. Otherwise default 5 minutes will be used.

Blocking Timeout Period

The blocking timeout period is used to specify a maximum time to wait for initial bootstrapping. Between 1 to 4294967294 miliseconds is valid blocking timeout period. Otherwise default value 15 seconds will be used.

Initial datafile

You can provide an initial datafile via the builder to bootstrap the ProjectConfigManager so that it can be used immediately without blocking execution.

URL

The URL is used to specify the location of datafile.

Format

This option enables user to provide a custom URL format to fetch the datafile.

Start by default

This option is used to specify whether to start the config manager on initialization or not. If no value is provided, by default it is true and will start polling datafile from remote immediately.

Datafile access token

This option is used to provide token for datafile belonging to a secure environment.

SDK Development

Contributing

Please see CONTRIBUTING.

Third Party Licenses

Optimizely SDK uses third party software: murmurhash-signed, Newtonsoft.Json, and NJsonSchema.

Other Optimzely SDKs

csharp-sdk's People

Contributors

andrewleap-optimizely avatar daniel-pons avatar dustin-sier avatar fayyazarshad avatar fscelliott avatar jaeopt avatar juancarlostong avatar kellyroach-optimizely avatar mfahadahmed avatar mikechu-optimizely avatar mikeproeng37 avatar mnoman09 avatar msohailhussain avatar nchilada avatar nevett avatar oakbani avatar optibot avatar ozayr-zaviar avatar shaharyar123 avatar trishahanlon avatar yasirfolio3 avatar yuanc avatar zashraf1985 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

csharp-sdk's Issues

Timing issue with Activate() & Track() being called sequentially while not able to await on the async method calls

I hit a bug with Optimizely C# SDK when toying in "short lived" console app. The issue is mainly that the SDK does not employ C# async/await best practice "Async All the Way". Reference can be here.

The problematic code is here and the usages are here. And the two scenarios that I came up with are as below:

//Scenario 1
var client = new Optimizely(dataFile);
client.Activate("experimentId", "userId", attrs);
//Activate() will call SendImpressionEvent() which calls EventDispatcher.DispatchEvent(impressionEvent); But Activate() does not have an async method signature, hence not possible to await on the response.

client.Track("Clicks", "userId");
//By the time Track() is called, if above DispatchEvent is still in-flight (not complete), I'm assuming Track() will fail due to "userId" has not been activated for "experimentId".
//Scenario 2
var client = new Optimizely(dataFile);
client.Activate("experimentId", "userId", attrs);
client.Track("Clicks", "userId");
//At this point in time, my console app exits, because Track() does not have an async method signature, hence I'm not able to tell if the DispatchEvent inside Track() has completed successfully ot not.

I'll also submit a PR for fix soon.

Update:

Having had a bit more time to think about this issue, I believe it is best for DispatchEvent() to be sequential and not fire & forget. For fire & forget, it is best to be more explicit in terms of method signature.

Proposed change is here:

https://github.com/HistoricallyCorrect/csharp-sdk/commit/44ffbbfd2598bcd9a60d678b88d1e667d1865eab

2.2 Release has a broken signature

I get this at runtime with the latest 2.2 release

Could not load file or assembly 'OptimizelySDK' or one of its dependencies. Strong name signature could not be verified. The assembly may have been tampered with, or it was delay signed but not fully signed with the correct private key. (Exception from HRESULT: 0x80131045)

OptimizelyUserContext should implement an interface for mockability

Similar to how Optimizely implements IOptimizely, OptimizelyUserContext should also implement an interface so that consumers of your SDK can properly mock it for unit testing.

I think it would be a fairly small change.

  1. Add an IOptimizelyUserContext interface.
  2. Implement that interface in the OptimizelyUserContext class.
  3. Update OptimizelyUserContext constructor to accept an IOptimizely instance instead of an Optimizely instance.
  4. Update the CreateUserContext method signature to return this new interface rather than the actual type.

All actual implementations remain the same.

csharp-sdk does not build for UWP apps

Create a new "Blank App (Universal Windows)" C# UWP app using VS-2019 using default parameters.
add Optimizely.SDK 3.9.1, NuGet package from https://www.nuget.org/packages/Optimizely.SDK

Add some simple calls to initialize SDK. Try to build x86 or x64 give same errors:
Severity Code Description Project File Line Suppression State
Error Could not copy the file "obj\x86\Debug\App.xbf" because it was not found. OptimizelyUWPTest
Error Could not copy the file "obj\x86\Debug\MainPage.xbf" because it was not found. OptimizelyUWPTest
Error Could not copy the file "obj\x86\Debug\OptimizelyUWPTest.xr.xml" because it was not found. OptimizelyUWPTest
Error Cannot resolve Assembly or Windows Metadata file 'Type universe cannot resolve assembly: System.Configuration.ConfigurationManager, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51.' OptimizelyUWPTest C:\Users\angel\ms-src\Scratch\Optimizely\OptimizelyUWPTest\OptimizelyUWPTest\OptimizelyUWPTest.csproj

Note that a C# .NET Core console app does work.

See attached test project.
OptimizelyUWPTest.zip

HttpClientEventDispatcher45 doesn't log full exception

The following exception handling code in HttpClientEventDispatcher45 only logs the message:

catch (Exception ex)
{
    Logger.Log(LogLevel.ERROR, string.Format("Error Dispatching Event: {0}", ex.Message));
}

Which results in logs like this:

Error Dispatching Event: An error occurred while sending the request.

There is probably a more helpful inner exception that is missing because only ex.Message is being logged.

I think this code should log ex.ToString() instead so that we get the inner exception details.

Last-Modified format is invalid

After updating to version 3.11.3 from 3.11.2.1 (which added reading the Last-Modified header) we get error The format of value '05/18/2023 13:35:06 +00:00' is invalid and cannot get the latest configuration.

Is this supported with Xamarin?

I was trying to compile with this packages and I saw that there is a sdependency that Xamarin does not use which is the System.Confugration.ConfigurationManager. Should this be added in order to use this package for Xamarin? Or do we have to create bindings for the native packages?

Adding another variant of sorting by reviews in the Demo App

I'm looking at the .NET demo, and understand how the two sort_by_price sort_by_name variants work. To add another variant sort_by_review, I have to modify the current code. Is there any pattern that you have seen being used by .NET server side developers where variant tests can be created without impacting the existing variant tests code?

HttpClientEventDispatcher45 creates a new HttpClient for every request

HttpClientEventDispatcher45 creates a new HttpClient for every request, which goes against the usage guidance for this class. Doing this will cause socket exhaustion under heavy load.

From the .NET framework docs:

HttpClient is intended to be instantiated once and re-used throughout the life of an application. Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors. Below is an example using HttpClient correctly.

EventTagUtils.GetNumericValue() incorrectly logs an error when no value key is present

My organization is in the process of upgrading clients and have started seeing this error:

The numeric metric key is not in event tags.

After some investigation, we have traced it down to this function call

var eventVallue = EventTagUtils.GetNumericValue(eventTags, Logger);

Contrast EventTagUtils.GetNumericValue() to EventTagUtils.GetRevenueValue(). Notice that when the revenue key is not present in the event tags, it logs as a debug. I assume the same pattern should be applied to GetNumericValue()

My organization is working around this by simply adding "value":0, to our event tags for now since error level logs will cause issues in our production system.

HttpClientEventDispatcher45 should allow HttpClient injection

As a consequence of #33, the HttpClientEventDispatcher45 class has a singleton HttpClient instance that is used for all requests.

For ASP.NET Core 2.2+ applications, this works against the benefits added by new abstractions such as IHttpClientFactory which handles things like connection pooling and honouring DNS TTL values.

If the .NET Standard versions of the SDK added a constructor overload that allowed an HttpClient instance to be provided by the consuming application, such applications could customise the event dispatcher to leverage their own built-in IoC for HttpClient, which would then also leverage any custom HttpMessageHandler instances, such as for logging and monitoring etc.

At present the only way for the integrator to achieve such benefits is to implement a custom IEventDispatcher which is 99% copy-pasted code from the SDK itself.

Give the Optimizely client an interface to facilitate testing

It is very difficult to write unit tests around our code that makes use of Optimizely, since it provides no interface from which to create a suitable mock. We can make do by creating our own wrapping class, but this gets messy quickly.

Can an IOptimizely interface be added, so that we can use NSubstitute or a similar library to mock the Optimizely client?

IOptimizely doesnt have GetFeatureVariableJSON

GetFeatureVariableJSON is available in Optimizely it would be nice to have this as part of IOptimizely interface. So that we can mock implementation of Optimizely in our unit test cases.

SDK should provide async methods for async operations

As noted in #51, the SDK does not follow accepted best practices in its use of async/await.

Specifically, the use of Task.Run() to make async calls "fire-and-forget" here:

Task.Run(() => DispatchEventAsync(logEvent));

At the expense of "returning to the caller immediately", this spawns new tasks to perform the HTTP calls.

A better approach would be to add asynchronous methods to the API, say by introducing a new async version of IOptimizely that the Optimizely class could also implement for integrators to opt into using. This would allow integrators to decide whether to make async tasks fire-and-forget or not, as well as allow CancellationTokens to be flowed to the underlying HttpClient calls where appropriate.

From looking at the code, the calls this flowing of async would apply to are those below:

SendImpressionEvent(experiment, variation, userId, userAttributes);

EventDispatcher.DispatchEvent(conversionEvent);

SendImpressionEvent(decision.Experiment, decision.Variation, userId, userAttributes);

EventDispatcher.DispatchEvent(impressionEvent);

Such changes would allow the SDK to be "async all the way down".

OptimizelySDK.nuspec dependency question

Hi guys, are these dependencies for use of the lib or can they be marked as dev only ?

any help would be appreciated, cheers

<group targetFramework=".NETFramework4.5">
                <dependency id="JsonNet.PrivateSettersContractResolvers.Source" version="0.1.0" />
                <dependency id="murmurhash" version="1.0.0" />
                <dependency id="Newtonsoft.Json" version="9.0.1" />
                <dependency id="NJsonSchema" version="8.30.6304.31883" />
</group>

Make optimizely compatible with newer version of NJsonSchema

Hi,

I am using NJsonSchema v10.4.6 on my app (due to some new dependency added) and optimizely started throwing the following exception.

System.TypeLoadException: Could not load type 'NJsonSchema.JsonSchema4' from assembly 'NJsonSchema, Version=10.4.6.0, Culture=neutral, PublicKeyToken=c2f9c3bdfae56102'.
  ?, in bool Validator.ValidateJSONSchema(string configJson, string schemaJson)
  ?, in new Optimizely(string datafile, IEventDispatcher eventDispatcher, ILogger logger, IErrorHandler errorHandler, UserProfileService userProfileService, bool skipJsonValidation, EventProcessor eventProcessor)

Is it possible to make optimizely sdk compatible with newer version of NJsonSchema ?

High Severity Vulnerability - Newtonsoft.Json < 13.0.1

There is a high severity vulnerability in Newtonsoft.Json prior to version 13.0.1. Because of the continued use of NJsonSchema 8.33.6323.36213 which was brought up as an issue previously in issue #284 and closed with a "we might fix this someday" response using this package forces the use of Newtonsoft.Json 12.0.3.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.