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 socketApi = new TestSocketSharedApi();
|
||||
var client = new TestDiSharedApiClient(
|
||||
SharedTransport.Socket,
|
||||
restApi,
|
||||
socketApi);
|
||||
|
||||
@@ -82,26 +81,48 @@ namespace CryptoExchange.Net.UnitTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RegisterSharedApiClientCapabilities_ShouldResolvePreferredTransport()
|
||||
public void RegisterSharedApiClient_ShouldResolvePreferredTransport()
|
||||
{
|
||||
var restApi = new TestRestSharedApi();
|
||||
var socketApi = new TestSocketSharedApi();
|
||||
var services = new ServiceCollection();
|
||||
|
||||
services.RegisterSharedApi<ITestRestSharedApi>(_ => restApi);
|
||||
services.RegisterSharedApi<ITestSocketSharedApi>(_ => socketApi);
|
||||
services.AddTransient<ITestSharedApiClient>(_ =>
|
||||
new TestDiSharedApiClient(SharedTransport.Socket, restApi, socketApi));
|
||||
services.RegisterSharedApiClientCapabilities<ITestSharedApiClient>();
|
||||
services.AddSingleton(restApi);
|
||||
services.AddSingleton(socketApi);
|
||||
|
||||
services.RegisterSharedApiClient<
|
||||
ITestSharedApiClient,
|
||||
TestDiSharedApiClient>(sharedApis => sharedApis
|
||||
.Add(client => client.Rest)
|
||||
.Add(client => client.Socket));
|
||||
|
||||
using var provider = services.BuildServiceProvider();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
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));
|
||||
Assert.That(
|
||||
provider.GetRequiredService<ITestSharedApiClient>(),
|
||||
Is.Not.Null);
|
||||
|
||||
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 TestDiSharedApiClient(
|
||||
SharedTransport preferredTransport,
|
||||
ITestRestSharedApi rest,
|
||||
ITestSocketSharedApi socket)
|
||||
: base(preferredTransport, rest, socket)
|
||||
TestRestSharedApi rest,
|
||||
TestSocketSharedApi socket)
|
||||
: base(SharedTransport.Socket, rest, socket)
|
||||
{
|
||||
Rest = rest;
|
||||
Socket = socket;
|
||||
|
||||
@@ -474,120 +474,6 @@ namespace CryptoExchange.Net
|
||||
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>
|
||||
/// Convert a hex encoded string to byte array
|
||||
/// </summary>
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
/// <summary>
|
||||
/// References for a capability available through REST.
|
||||
/// </summary>
|
||||
public sealed class SharedRestCapability<TCapability, TRest>
|
||||
public class SharedRestCapability<TCapability, TRest>
|
||||
: SharedCapabilityReference<TCapability>
|
||||
where TCapability : ISharedApiCapability
|
||||
where TRest : TCapability, ISharedRest
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
/// <summary>
|
||||
/// References for a capability available through REST and WebSocket.
|
||||
/// </summary>
|
||||
public sealed class SharedRestSocketCapability<TCapability, TRest, TSocket>
|
||||
public class SharedRestSocketCapability<TCapability, TRest, TSocket>
|
||||
: SharedCapabilityReference<TCapability>
|
||||
where TCapability : ISharedApiCapability
|
||||
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 Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace CryptoExchange.Net.SharedApis
|
||||
@@ -45,5 +48,33 @@ namespace CryptoExchange.Net.SharedApis
|
||||
resultData = resultData.Where(x => x.QuoteAssetSubType == request.QuoteAssetSubType);
|
||||
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