mirror of
https://github.com/JKorf/CryptoExchange.Net.git
synced 2026-10-14 15:11:44 +00:00
wip
This commit is contained in:
@@ -63,7 +63,6 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
var restApi = new TestRestSharedApi();
|
var restApi = new TestRestSharedApi();
|
||||||
var socketApi = new TestSocketSharedApi();
|
var socketApi = new TestSocketSharedApi();
|
||||||
var client = new TestDiSharedApiClient(
|
var client = new TestDiSharedApiClient(
|
||||||
SharedTransport.Socket,
|
|
||||||
restApi,
|
restApi,
|
||||||
socketApi);
|
socketApi);
|
||||||
|
|
||||||
@@ -82,26 +81,48 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void RegisterSharedApiClientCapabilities_ShouldResolvePreferredTransport()
|
public void RegisterSharedApiClient_ShouldResolvePreferredTransport()
|
||||||
{
|
{
|
||||||
var restApi = new TestRestSharedApi();
|
var restApi = new TestRestSharedApi();
|
||||||
var socketApi = new TestSocketSharedApi();
|
var socketApi = new TestSocketSharedApi();
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
|
|
||||||
services.RegisterSharedApi<ITestRestSharedApi>(_ => restApi);
|
services.AddSingleton(restApi);
|
||||||
services.RegisterSharedApi<ITestSocketSharedApi>(_ => socketApi);
|
services.AddSingleton(socketApi);
|
||||||
services.AddTransient<ITestSharedApiClient>(_ =>
|
|
||||||
new TestDiSharedApiClient(SharedTransport.Socket, restApi, socketApi));
|
services.RegisterSharedApiClient<
|
||||||
services.RegisterSharedApiClientCapabilities<ITestSharedApiClient>();
|
ITestSharedApiClient,
|
||||||
|
TestDiSharedApiClient>(sharedApis => sharedApis
|
||||||
|
.Add(client => client.Rest)
|
||||||
|
.Add(client => client.Socket));
|
||||||
|
|
||||||
using var provider = services.BuildServiceProvider();
|
using var provider = services.BuildServiceProvider();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
Assert.That(provider.GetRequiredService<ITestCapability>(), Is.SameAs(socketApi));
|
Assert.That(
|
||||||
Assert.That(provider.GetServices<ITestCapability>().ToArray(), Is.EqualTo(new[] { socketApi }));
|
provider.GetRequiredService<ITestSharedApiClient>(),
|
||||||
Assert.That(provider.GetRequiredService<ITestRestCapability>(), Is.SameAs(restApi));
|
Is.Not.Null);
|
||||||
Assert.That(provider.GetRequiredService<ITestSocketCapability>(), Is.SameAs(socketApi));
|
|
||||||
|
Assert.That(
|
||||||
|
provider.GetRequiredService<ISharedApiClientBase>(),
|
||||||
|
Is.InstanceOf<TestDiSharedApiClient>());
|
||||||
|
|
||||||
|
Assert.That(
|
||||||
|
provider.GetRequiredService<ITestCapability>(),
|
||||||
|
Is.SameAs(socketApi));
|
||||||
|
|
||||||
|
Assert.That(
|
||||||
|
provider.GetServices<ITestCapability>().ToArray(),
|
||||||
|
Is.EqualTo(new[] { socketApi }));
|
||||||
|
|
||||||
|
Assert.That(
|
||||||
|
provider.GetRequiredService<ITestRestCapability>(),
|
||||||
|
Is.SameAs(restApi));
|
||||||
|
|
||||||
|
Assert.That(
|
||||||
|
provider.GetRequiredService<ITestSocketCapability>(),
|
||||||
|
Is.SameAs(socketApi));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,10 +313,9 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
public ITestSocketSharedApi Socket { get; }
|
public ITestSocketSharedApi Socket { get; }
|
||||||
|
|
||||||
public TestDiSharedApiClient(
|
public TestDiSharedApiClient(
|
||||||
SharedTransport preferredTransport,
|
TestRestSharedApi rest,
|
||||||
ITestRestSharedApi rest,
|
TestSocketSharedApi socket)
|
||||||
ITestSocketSharedApi socket)
|
: base(SharedTransport.Socket, rest, socket)
|
||||||
: base(preferredTransport, rest, socket)
|
|
||||||
{
|
{
|
||||||
Rest = rest;
|
Rest = rest;
|
||||||
Socket = socket;
|
Socket = socket;
|
||||||
|
|||||||
@@ -474,120 +474,6 @@ namespace CryptoExchange.Net
|
|||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers a strict Shared API aggregate and all capability interfaces implemented by it.
|
|
||||||
/// </summary>
|
|
||||||
public static IServiceCollection RegisterSharedApi<
|
|
||||||
#if NET5_0_OR_GREATER
|
|
||||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
|
|
||||||
#endif
|
|
||||||
TSharedApi>(
|
|
||||||
this IServiceCollection services,
|
|
||||||
Func<IServiceProvider, TSharedApi> factory)
|
|
||||||
where TSharedApi : class, ISharedApi
|
|
||||||
{
|
|
||||||
services.AddTransient<TSharedApi>(factory);
|
|
||||||
|
|
||||||
var allInterfaces = typeof(TSharedApi).GetInterfaces();
|
|
||||||
|
|
||||||
var transportCapabilityInterfaces = allInterfaces
|
|
||||||
.Where(x =>
|
|
||||||
typeof(ISharedApiCapability).IsAssignableFrom(x)
|
|
||||||
&& (typeof(ISharedRest).IsAssignableFrom(x)
|
|
||||||
|| typeof(ISharedSocket).IsAssignableFrom(x))
|
|
||||||
&& x != typeof(ISharedRest)
|
|
||||||
&& x != typeof(ISharedSocket)
|
|
||||||
&& x != typeof(ISharedSubscription))
|
|
||||||
.Distinct()
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
var baseCapabilityInterfaces = allInterfaces
|
|
||||||
.Where(x =>
|
|
||||||
x != typeof(ISharedApiCapability)
|
|
||||||
&& typeof(ISharedApiCapability).IsAssignableFrom(x)
|
|
||||||
&& !typeof(ISharedRest).IsAssignableFrom(x)
|
|
||||||
&& !typeof(ISharedSocket).IsAssignableFrom(x))
|
|
||||||
.Distinct()
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
services.AddSingleton(new SharedApiRegistrationInfo(
|
|
||||||
typeof(TSharedApi),
|
|
||||||
baseCapabilityInterfaces));
|
|
||||||
|
|
||||||
foreach (var capabilityInterface in transportCapabilityInterfaces)
|
|
||||||
{
|
|
||||||
services.AddTransient(
|
|
||||||
capabilityInterface,
|
|
||||||
serviceProvider =>
|
|
||||||
serviceProvider.GetRequiredService<TSharedApi>());
|
|
||||||
}
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers transport-agnostic Shared API capabilities through the
|
|
||||||
/// exchange-specific Shared API aggregate client.
|
|
||||||
/// </summary>
|
|
||||||
public static IServiceCollection RegisterSharedApiClientCapabilities<
|
|
||||||
#if NET5_0_OR_GREATER
|
|
||||||
[DynamicallyAccessedMembers(
|
|
||||||
DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicConstructors)]
|
|
||||||
#endif
|
|
||||||
TSharedApiClient>(
|
|
||||||
this IServiceCollection services)
|
|
||||||
where TSharedApiClient : class, ISharedApiClientBase
|
|
||||||
{
|
|
||||||
services.AddTransient<ISharedApiClientBase>(serviceProvider =>
|
|
||||||
serviceProvider.GetRequiredService<TSharedApiClient>());
|
|
||||||
|
|
||||||
var sharedApiTypes = new HashSet<Type>(
|
|
||||||
typeof(TSharedApiClient)
|
|
||||||
.GetProperties()
|
|
||||||
.Where(property =>
|
|
||||||
typeof(ISharedApi).IsAssignableFrom(
|
|
||||||
property.PropertyType))
|
|
||||||
.Select(property => property.PropertyType));
|
|
||||||
|
|
||||||
var capabilityInterfaces = services
|
|
||||||
.Where(descriptor =>
|
|
||||||
descriptor.ImplementationInstance
|
|
||||||
is SharedApiRegistrationInfo)
|
|
||||||
.Select(descriptor =>
|
|
||||||
(SharedApiRegistrationInfo)descriptor.ImplementationInstance!)
|
|
||||||
.Where(registration =>
|
|
||||||
sharedApiTypes.Contains(registration.SharedApiType))
|
|
||||||
.SelectMany(registration =>
|
|
||||||
registration.BaseCapabilityTypes)
|
|
||||||
.Distinct()
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
foreach (var capabilityInterface in capabilityInterfaces)
|
|
||||||
{
|
|
||||||
services.AddTransient(
|
|
||||||
capabilityInterface,
|
|
||||||
serviceProvider =>
|
|
||||||
{
|
|
||||||
var sharedApiClient = serviceProvider
|
|
||||||
.GetRequiredService<TSharedApiClient>();
|
|
||||||
|
|
||||||
if (sharedApiClient is not ISharedApiClientResolver resolver)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"{typeof(TSharedApiClient).Name} should derive from " +
|
|
||||||
$"{nameof(SharedApiClientBase)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolver.GetCapability(capabilityInterface)
|
|
||||||
?? throw new InvalidOperationException(
|
|
||||||
$"No implementation of " +
|
|
||||||
$"{capabilityInterface.Name} is available");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convert a hex encoded string to byte array
|
/// Convert a hex encoded string to byte array
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// References for a capability available through REST.
|
/// References for a capability available through REST.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class SharedRestCapability<TCapability, TRest>
|
public class SharedRestCapability<TCapability, TRest>
|
||||||
: SharedCapabilityReference<TCapability>
|
: SharedCapabilityReference<TCapability>
|
||||||
where TCapability : ISharedApiCapability
|
where TCapability : ISharedApiCapability
|
||||||
where TRest : TCapability, ISharedRest
|
where TRest : TCapability, ISharedRest
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// References for a capability available through REST and WebSocket.
|
/// References for a capability available through REST and WebSocket.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class SharedRestSocketCapability<TCapability, TRest, TSocket>
|
public class SharedRestSocketCapability<TCapability, TRest, TSocket>
|
||||||
: SharedCapabilityReference<TCapability>
|
: SharedCapabilityReference<TCapability>
|
||||||
where TCapability : ISharedApiCapability
|
where TCapability : ISharedApiCapability
|
||||||
where TRest : TCapability, ISharedRest
|
where TRest : TCapability, ISharedRest
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace CryptoExchange.Net.SharedApis
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Shared API dependency injection registration builder
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TSharedApiClient"></typeparam>
|
||||||
|
public class SharedApiClientRegistrationBuilder<TSharedApiClient>
|
||||||
|
where TSharedApiClient : class, ISharedApiClientBase
|
||||||
|
{
|
||||||
|
private readonly IServiceCollection _services;
|
||||||
|
private readonly HashSet<Type> _baseCapabilityTypes = new();
|
||||||
|
|
||||||
|
internal SharedApiClientRegistrationBuilder(
|
||||||
|
IServiceCollection services)
|
||||||
|
{
|
||||||
|
_services = services;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a shared API
|
||||||
|
/// </summary>
|
||||||
|
public SharedApiClientRegistrationBuilder<TSharedApiClient> Add<
|
||||||
|
#if NET5_0_OR_GREATER
|
||||||
|
[DynamicallyAccessedMembers(
|
||||||
|
DynamicallyAccessedMemberTypes.Interfaces)]
|
||||||
|
#endif
|
||||||
|
TSharedApi>(
|
||||||
|
Func<TSharedApiClient, TSharedApi> selector)
|
||||||
|
where TSharedApi : class, ISharedApi
|
||||||
|
{
|
||||||
|
if (selector is null)
|
||||||
|
throw new ArgumentNullException(nameof(selector));
|
||||||
|
|
||||||
|
_services.AddTransient<TSharedApi>(
|
||||||
|
serviceProvider =>
|
||||||
|
{
|
||||||
|
var client = serviceProvider
|
||||||
|
.GetRequiredService<TSharedApiClient>();
|
||||||
|
|
||||||
|
return selector(client);
|
||||||
|
});
|
||||||
|
|
||||||
|
var interfaces = typeof(TSharedApi).GetInterfaces();
|
||||||
|
|
||||||
|
var transportCapabilities = interfaces
|
||||||
|
.Where(type =>
|
||||||
|
typeof(ISharedApiCapability).IsAssignableFrom(type)
|
||||||
|
&& (typeof(ISharedRest).IsAssignableFrom(type)
|
||||||
|
|| typeof(ISharedSocket).IsAssignableFrom(type))
|
||||||
|
&& type != typeof(ISharedRest)
|
||||||
|
&& type != typeof(ISharedSocket)
|
||||||
|
&& type != typeof(ISharedSubscription))
|
||||||
|
.Distinct();
|
||||||
|
|
||||||
|
foreach (var capabilityType in transportCapabilities)
|
||||||
|
{
|
||||||
|
_services.AddTransient(
|
||||||
|
capabilityType,
|
||||||
|
serviceProvider =>
|
||||||
|
{
|
||||||
|
var client = serviceProvider
|
||||||
|
.GetRequiredService<TSharedApiClient>();
|
||||||
|
|
||||||
|
return selector(client);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseCapabilities = interfaces
|
||||||
|
.Where(type =>
|
||||||
|
type != typeof(ISharedApiCapability)
|
||||||
|
&& typeof(ISharedApiCapability).IsAssignableFrom(type)
|
||||||
|
&& !typeof(ISharedRest).IsAssignableFrom(type)
|
||||||
|
&& !typeof(ISharedSocket).IsAssignableFrom(type));
|
||||||
|
|
||||||
|
foreach (var capabilityType in baseCapabilities)
|
||||||
|
_baseCapabilityTypes.Add(capabilityType);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RegisterTransportAgnosticCapabilities()
|
||||||
|
{
|
||||||
|
foreach (var capabilityType in _baseCapabilityTypes)
|
||||||
|
{
|
||||||
|
_services.AddTransient(
|
||||||
|
capabilityType,
|
||||||
|
serviceProvider =>
|
||||||
|
{
|
||||||
|
var client = serviceProvider
|
||||||
|
.GetRequiredService<TSharedApiClient>();
|
||||||
|
|
||||||
|
var resolver = (ISharedApiClientResolver)client;
|
||||||
|
|
||||||
|
return resolver.GetCapability(capabilityType)
|
||||||
|
?? throw new InvalidOperationException(
|
||||||
|
$"No implementation of " +
|
||||||
|
$"{capabilityType.Name} is available on " +
|
||||||
|
$"{typeof(TSharedApiClient).Name}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace CryptoExchange.Net.SharedApis
|
|
||||||
{
|
|
||||||
internal sealed class SharedApiRegistrationInfo
|
|
||||||
{
|
|
||||||
public Type SharedApiType { get; }
|
|
||||||
public Type[] BaseCapabilityTypes { get; }
|
|
||||||
|
|
||||||
public SharedApiRegistrationInfo(
|
|
||||||
Type sharedApiType,
|
|
||||||
Type[] baseCapabilityTypes)
|
|
||||||
{
|
|
||||||
SharedApiType = sharedApiType;
|
|
||||||
BaseCapabilityTypes = baseCapabilityTypes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
using CryptoExchange.Net.Objects;
|
using CryptoExchange.Net.Objects;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace CryptoExchange.Net.SharedApis
|
namespace CryptoExchange.Net.SharedApis
|
||||||
@@ -45,5 +48,33 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
resultData = resultData.Where(x => x.QuoteAssetSubType == request.QuoteAssetSubType);
|
resultData = resultData.Where(x => x.QuoteAssetSubType == request.QuoteAssetSubType);
|
||||||
return resultData.ToArray();
|
return resultData.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register Shared API client in DI container
|
||||||
|
/// </summary>
|
||||||
|
public static IServiceCollection RegisterSharedApiClient<
|
||||||
|
TSharedApiClient,
|
||||||
|
#if NET5_0_OR_GREATER
|
||||||
|
[DynamicallyAccessedMembers(
|
||||||
|
DynamicallyAccessedMemberTypes.PublicConstructors)]
|
||||||
|
#endif
|
||||||
|
TImplementation
|
||||||
|
>(this IServiceCollection services, Action<SharedApiClientRegistrationBuilder<TSharedApiClient>> configure)
|
||||||
|
where TImplementation : class, TSharedApiClient
|
||||||
|
where TSharedApiClient : class, ISharedApiClientBase
|
||||||
|
{
|
||||||
|
services.AddTransient<TSharedApiClient, TImplementation>();
|
||||||
|
services.AddTransient<ISharedApiClientBase>(
|
||||||
|
serviceProvider =>
|
||||||
|
serviceProvider.GetRequiredService<TSharedApiClient>());
|
||||||
|
|
||||||
|
var builder =
|
||||||
|
new SharedApiClientRegistrationBuilder<TSharedApiClient>(services);
|
||||||
|
|
||||||
|
configure(builder);
|
||||||
|
builder.RegisterTransportAgnosticCapabilities();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user