Skip to content

Commit

Permalink
Client Configuration (#14)
Browse files Browse the repository at this point in the history
Updated CryptoExchange.Net to version 8.3.0
Added support for loading client settings from IConfiguration
Updated client constructors to accept IOptions from DI
  • Loading branch information
JKorf authored Nov 19, 2024
1 parent 0d63829 commit 850fd6b
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 44 deletions.
19 changes: 8 additions & 11 deletions CoinGecko.Net/Clients/CoinGeckoRestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System;
using CoinGecko.Net.Objects.Options;
using CryptoExchange.Net.Clients;
using Microsoft.Extensions.Options;

namespace CoinGecko.Net.Clients
{
Expand All @@ -19,25 +20,23 @@ public class CoinGeckoRestClient: BaseRestClient, ICoinGeckoRestClient
/// Create a new instance of the CoinGeckoClient using provided options
/// </summary>
/// <param name="optionsDelegate">Option configuration delegate</param>
public CoinGeckoRestClient(Action<CoinGeckoRestOptions>? optionsDelegate = null) : this(null, null, optionsDelegate)
public CoinGeckoRestClient(Action<CoinGeckoRestOptions>? optionsDelegate = null)
: this(null, null, Options.Create(ApplyOptionsDelegate(optionsDelegate)))
{
}

/// <summary>
/// Create a new instance of the CoinGeckoClient
/// </summary>
/// <param name="optionsDelegate">Option configuration delegate</param>
/// <param name="options">Option configuration</param>
/// <param name="loggerFactory">The logger factory</param>
/// <param name="httpClient">Http client for this client</param>
public CoinGeckoRestClient(HttpClient? httpClient, ILoggerFactory? loggerFactory, Action<CoinGeckoRestOptions>? optionsDelegate = null)
public CoinGeckoRestClient(HttpClient? httpClient, ILoggerFactory? loggerFactory, IOptions<CoinGeckoRestOptions> options)
: base(loggerFactory, "CoinGecko")
{
var options = CoinGeckoRestOptions.Default.Copy();
if (optionsDelegate != null)
optionsDelegate(options);
Initialize(options);
Initialize(options.Value);

Api = AddApiClient(new CoinGeckoRestClientApi(_logger, httpClient, options));
Api = AddApiClient(new CoinGeckoRestClientApi(_logger, httpClient, options.Value));
}
#endregion

Expand All @@ -47,9 +46,7 @@ public CoinGeckoRestClient(HttpClient? httpClient, ILoggerFactory? loggerFactory
/// <param name="optionsDelegate">Option configuration delegate</param>
public static void SetDefaultOptions(Action<CoinGeckoRestOptions> optionsDelegate)
{
var options = CoinGeckoRestOptions.Default.Copy();
optionsDelegate(options);
CoinGeckoRestOptions.Default = options;
CoinGeckoRestOptions.Default = ApplyOptionsDelegate(optionsDelegate);
}
}
}
4 changes: 2 additions & 2 deletions CoinGecko.Net/CoinGecko.Net.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<Nullable>enable</Nullable>
Expand Down Expand Up @@ -48,7 +48,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="CryptoExchange.Net" Version="8.2.0" />
<PackageReference Include="CryptoExchange.Net" Version="8.3.0" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
33 changes: 28 additions & 5 deletions CoinGecko.Net/CoinGecko.Net.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions CoinGecko.Net/CoinGeckoEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,26 @@ internal CoinGeckoEnvironment(string name,
RestApiAddressPro = restBaseAddressPro;
}

/// <summary>
/// ctor for DI, use <see cref="CreateCustom"/> for creating a custom environment
/// </summary>
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
public CoinGeckoEnvironment() : base(TradeEnvironmentNames.Live)
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
{ }

/// <summary>
/// Get the CoinGecko environment by name
/// </summary>
public static CoinGeckoEnvironment? GetEnvironmentByName(string? name)
=> name switch
{
TradeEnvironmentNames.Live => Live,
"" => Live,
null => Live,
_ => default
};

/// <summary>
/// Live environment
/// </summary>
Expand Down
69 changes: 52 additions & 17 deletions CoinGecko.Net/ExtensionMethods/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
using CoinGecko.Net.Clients;
using CoinGecko.Net;
using CoinGecko.Net.Clients;
using CoinGecko.Net.Interfaces;
using CoinGecko.Net.Objects.Options;
using CryptoExchange.Net.Clients;
using CryptoExchange.Net.Interfaces;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Net;
using System.Net.Http;
Expand All @@ -15,35 +19,66 @@ namespace Microsoft.Extensions.DependencyInjection
public static class ServiceCollectionExtensions
{
/// <summary>
/// Add the ICoinGeckoClient to the sevice collection so they can be injected
/// Add services such as the ICoinGeckoRestClient. Configures the services based on the provided configuration.
/// </summary>
/// <param name="services">The service collection</param>
/// <param name="defaultRestOptionsDelegate">Set default options for the rest client</param>
/// <param name="configuration">The configuration(section) containing the options</param>
/// <returns></returns>
public static IServiceCollection AddCoinGecko(
this IServiceCollection services,
Action<CoinGeckoRestOptions>? defaultRestOptionsDelegate = null)
IConfiguration configuration)
{
var restOptions = CoinGeckoRestOptions.Default.Copy();
var options = new CoinGeckoRestOptions();
configuration.Bind(options);

if (defaultRestOptionsDelegate != null)
{
defaultRestOptionsDelegate(restOptions);
CoinGeckoRestClient.SetDefaultOptions(defaultRestOptionsDelegate);
}
var restEnvName = options.Environment?.Name ?? options.Environment?.Name ?? CoinGeckoEnvironment.Live.Name;
options.Environment = CoinGeckoEnvironment.GetEnvironmentByName(restEnvName) ?? options.Environment!;
options.ApiCredentials = options.ApiCredentials ?? options.ApiCredentials;

services.AddHttpClient<ICoinGeckoRestClient, CoinGeckoRestClient>(options =>
{
options.Timeout = restOptions.RequestTimeout;
}).ConfigurePrimaryHttpMessageHandler(() =>
services.AddSingleton(x => Options.Options.Create(options));

return AddCoinGeckoCore(services);
}

/// <summary>
/// Add services such as the ICoinGeckoRestClient. Services will be configured based on the provided options.
/// </summary>
/// <param name="services">The service collection</param>
/// <param name="optionsDelegate">Set options for the CoinGecko services</param>
/// <returns></returns>
public static IServiceCollection AddCoinGecko(
this IServiceCollection services,
Action<CoinGeckoRestOptions>? optionsDelegate = null)
{
services.Configure<CoinGeckoRestOptions>((x) => { optionsDelegate?.Invoke(x); });

return AddCoinGeckoCore(services);
}

private static IServiceCollection AddCoinGeckoCore(
this IServiceCollection services)
{
services.AddHttpClient<ICoinGeckoRestClient, CoinGeckoRestClient>((client, serviceProvider) =>
{
var options = serviceProvider.GetRequiredService<IOptions<CoinGeckoRestOptions>>().Value;
client.Timeout = options.RequestTimeout;
return new CoinGeckoRestClient(client, serviceProvider.GetRequiredService<ILoggerFactory>(), serviceProvider.GetRequiredService<IOptions<CoinGeckoRestOptions>>());
}).ConfigurePrimaryHttpMessageHandler((serviceProvider) => {
var handler = new HttpClientHandler();
if (restOptions.Proxy != null)
try
{
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
}
catch (PlatformNotSupportedException)
{ }
var options = serviceProvider.GetRequiredService<IOptions<CoinGeckoRestOptions>>().Value;
if (options.Proxy != null)
{
handler.Proxy = new WebProxy
{
Address = new Uri($"{restOptions.Proxy.Host}:{restOptions.Proxy.Port}"),
Credentials = restOptions.Proxy.Password == null ? null : new NetworkCredential(restOptions.Proxy.Login, restOptions.Proxy.Password)
Address = new Uri($"{options.Proxy.Host}:{options.Proxy.Port}"),
Credentials = options.Proxy.Password == null ? null : new NetworkCredential(options.Proxy.Login, options.Proxy.Password)
};
}
return handler;
Expand Down
4 changes: 2 additions & 2 deletions CoinGecko.Net/Objects/CoinGeckoApiCredentials.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ public class CoinGeckoApiCredentials : ApiCredentials
/// <summary>
/// ctor
/// </summary>
/// <param name="apiKey">The API key</param>
/// <param name="key">The API key</param>
/// <param name="demoKey">Whether or not this is a demo key</param>
public CoinGeckoApiCredentials(string apiKey, bool demoKey = false) : base(apiKey, "-")
public CoinGeckoApiCredentials(string key, bool demoKey = false) : base(key, "-")
{
DemoKey = demoKey;
}
Expand Down
18 changes: 13 additions & 5 deletions CoinGecko.Net/Objects/Options/CoinGeckoRestOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,29 @@ public class CoinGeckoRestOptions : RestExchangeOptions<CoinGeckoEnvironment, Co
/// <summary>
/// Default options for the CoinGecko client
/// </summary>
public static CoinGeckoRestOptions Default { get; set; } = new CoinGeckoRestOptions()
internal static CoinGeckoRestOptions Default { get; set; } = new CoinGeckoRestOptions()
{
Environment = CoinGeckoEnvironment.Live
};

/// <summary>
/// ctor
/// </summary>
public CoinGeckoRestOptions()
{
Default?.Set(this);
}

/// <summary>
/// Api options
/// </summary>
public RestApiOptions ApiOptions { get; private set; } = new RestApiOptions();

internal CoinGeckoRestOptions Copy()
internal CoinGeckoRestOptions Set(CoinGeckoRestOptions targetOptions)
{
var options = Copy<CoinGeckoRestOptions>();
options.ApiOptions = ApiOptions.Copy<RestApiOptions>();
return options;
targetOptions = base.Set<CoinGeckoRestOptions>(targetOptions);
targetOptions.ApiOptions = ApiOptions.Set(targetOptions.ApiOptions);
return targetOptions;
}
}
}
11 changes: 9 additions & 2 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,15 @@ <h2>API Access</h2>

<p>CoinGecko.Net can be configured using Dotnet dependency injection, after which the clients can be injected into your services. It also correctly configures logging and HttpClient usage.</p>

<pre><code>builder.Services.AddCoinGecko(options => {
// Options can be configured here
<pre><code>// Configure options from config file
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example
builder.Services.AddCoinGecko(builder.Configuration.GetSection("CoinGecko"));

// OR

builder.Services.AddCoinGecko(options => {
// Configure options in code
options.RequestTimeout = TimeSpan.FromSeconds(10);
});</code></pre>

<p>The <code>ICoinGeckoRestClient</code> can then be injected.</p>
Expand Down

0 comments on commit 850fd6b

Please sign in to comment.