diff --git a/SW.Bitween.Api/Resources/Adapters/GetProperties.cs b/SW.Bitween.Api/Resources/Adapters/GetProperties.cs index 67bb6312..ad74656b 100644 --- a/SW.Bitween.Api/Resources/Adapters/GetProperties.cs +++ b/SW.Bitween.Api/Resources/Adapters/GetProperties.cs @@ -25,9 +25,9 @@ async public Task Handle(string key) var decodedKey = Uri.UnescapeDataString(key); // Check if it's a native adapter - if (decodedKey.StartsWith("native.", StringComparison.OrdinalIgnoreCase)) + if (decodedKey.StartsWith(NativeAdapterDiscoveryService.NativePrefix, StringComparison.OrdinalIgnoreCase)) { - return _nativeAdapterDiscovery.GetNativeAdapterProperties(decodedKey); + return _nativeAdapterDiscovery.GetExpectedStartupValues(decodedKey); } // Handle serverless adapters diff --git a/SW.Bitween.Api/Resources/Subscriptions/Update.cs b/SW.Bitween.Api/Resources/Subscriptions/Update.cs index 21bc5906..ff73bc2d 100644 --- a/SW.Bitween.Api/Resources/Subscriptions/Update.cs +++ b/SW.Bitween.Api/Resources/Subscriptions/Update.cs @@ -119,9 +119,9 @@ public Validate(BitweenDbContext dbContext,IHttpContextAccessor httpContextAcces var mustProps = Enumerable.Empty(); // Check if it's a native adapter - if (mapperId.StartsWith("native.", StringComparison.OrdinalIgnoreCase)) + if (mapperId.StartsWith(NativeAdapterDiscoveryService.NativePrefix, StringComparison.OrdinalIgnoreCase)) { - var properties = nativeAdapterDiscovery.GetNativeAdapterProperties(mapperId); + var properties = nativeAdapterDiscovery.GetExpectedStartupValues(mapperId); mustProps = properties.Where(p => p.Value.EndsWith(" *")).Select(p => p.Key); } else @@ -146,9 +146,9 @@ public Validate(BitweenDbContext dbContext,IHttpContextAccessor httpContextAcces var mustProps = Enumerable.Empty(); // Check if it's a native adapter - if (handlerId.StartsWith("native.", StringComparison.OrdinalIgnoreCase)) + if (handlerId.StartsWith(NativeAdapterDiscoveryService.NativePrefix, StringComparison.OrdinalIgnoreCase)) { - var properties = nativeAdapterDiscovery.GetNativeAdapterProperties(handlerId); + var properties = nativeAdapterDiscovery.GetExpectedStartupValues(handlerId); mustProps = properties.Where(p => p.Value.EndsWith(" *")).Select(p => p.Key); } else @@ -183,9 +183,9 @@ public Validate(BitweenDbContext dbContext,IHttpContextAccessor httpContextAcces var mustProps = Enumerable.Empty(); // Check if it's a native adapter - if (model.ReceiverId.StartsWith("native.", StringComparison.OrdinalIgnoreCase)) + if (model.ReceiverId.StartsWith(NativeAdapterDiscoveryService.NativePrefix, StringComparison.OrdinalIgnoreCase)) { - var properties = nativeAdapterDiscovery.GetNativeAdapterProperties(model.ReceiverId); + var properties = nativeAdapterDiscovery.GetExpectedStartupValues(model.ReceiverId); mustProps = properties.Where(p => p.Value.EndsWith(" *")).Select(p => p.Key); } else diff --git a/SW.Bitween.Api/Services/NativeAdapterDiscoveryService.cs b/SW.Bitween.Api/Services/NativeAdapterDiscoveryService.cs index 6bf15119..a737a011 100644 --- a/SW.Bitween.Api/Services/NativeAdapterDiscoveryService.cs +++ b/SW.Bitween.Api/Services/NativeAdapterDiscoveryService.cs @@ -7,139 +7,107 @@ namespace SW.Bitween { - public class NativeAdapterDiscoveryService + public class NativeAdapterDiscoveryService( + IEnumerable nativeHandlers, + IEnumerable nativeReceivers, + IEnumerable nativeValidators, + IEnumerable nativeAdapters) { - private readonly Dictionary> _adaptersCache; - - public NativeAdapterDiscoveryService() - { - _adaptersCache = new Dictionary>(); - DiscoverNativeAdapters(); - } - - private void DiscoverNativeAdapters() + public const string NativePrefix = "native"; + public Dictionary GetExpectedStartupValues(string adapterId) { - var assemblies = new List() { typeof(DictionaryConverter).Assembly }; - - + var result = new Dictionary(); + + var adapter = nativeAdapters.FirstOrDefault(a => a.GetType().Name.Equals(adapterId, StringComparison.OrdinalIgnoreCase)); + if (adapter == null) + return result; + + var properties = adapter.StartupValuesType.GetProperties(BindingFlags.Public | BindingFlags.Instance); - foreach (var assembly in assemblies) + foreach (var prop in properties) { - try - { - var types = assembly.GetTypes() - .Where(t => t.IsClass && !t.IsAbstract); + var defaultValue = GetDefaultValue(prop); + var hasRequiredAttribute = + prop.GetCustomAttribute() != null; + var isRequired = hasRequiredAttribute || (!IsNullableType(prop.PropertyType) && defaultValue == null); - foreach (var type in types) - { - if (typeof(IInfolinkHandler).IsAssignableFrom(type)) - { - AddAdapter("handlers", type); - } - else if (typeof(IInfolinkValidator).IsAssignableFrom(type)) - { - AddAdapter("validators", type); - } - else if (typeof(IInfolinkReceiver).IsAssignableFrom(type)) - { - AddAdapter("receivers", type); - } - } + if (isRequired) + { + result[prop.Name] = $"{prop.Name} *"; } - catch + else { - // Skip assemblies that can't be loaded or scanned + result[prop.Name] = $"{prop.Name} ({defaultValue ?? "null"})"; } } + + return result; } + - private void AddAdapter(string category, Type type) + public INativeInfolinkHandler GetNativeHandler(string adapterId, Dictionary settings) { - if (!_adaptersCache.ContainsKey(category)) + var result = + nativeHandlers.FirstOrDefault(a => a.Name.Equals(adapterId, StringComparison.OrdinalIgnoreCase)); + if (result != null) { - _adaptersCache[category] = new List(); + result.InitializeStartupValues(settings); } - var adapterName = type.Name;//.Replace("Handler", "").Replace("Mapper", "") - //.Replace("Validator", "").Replace("Receiver", "").ToLower(); - - _adaptersCache[category].Add(new NativeAdapterInfo - { - Key = $"native.{adapterName}", - Name = type.Name, - Type = type, - Category = category - }); + return result; } - public IEnumerable GetNativeAdapters(string prefix) + public INativeInfolinkReceiver GetNativeReceiver(string adapterId, IDictionary settings) { - if (string.IsNullOrEmpty(prefix)) + var result = + nativeReceivers.FirstOrDefault(a => a.GetType().Name.Equals(adapterId, StringComparison.OrdinalIgnoreCase)); + if (result != null) { - return _adaptersCache.Values.SelectMany(v => v).Select(a => a.Key); + result.InitializeStartupValues(settings); } - var category = prefix.ToLower().TrimStart('.'); - - if (_adaptersCache.TryGetValue(category, out var adapters)) - { - return adapters.Select(a => a.Key); - } - - return Enumerable.Empty(); - } - - public NativeAdapterInfo GetNativeAdapterInfo(string adapterId) - { - return _adaptersCache.Values - .SelectMany(v => v) - .FirstOrDefault(a => a.Key.Equals(adapterId, StringComparison.OrdinalIgnoreCase)); + return result; } - public Dictionary GetNativeAdapterProperties(string adapterId) + public INativeInfolinkValidator GetNativeValidator(string adapterId, IDictionary settings) { - var adapterInfo = GetNativeAdapterInfo(adapterId); - if (adapterInfo == null) - return new Dictionary(); - - var result = new Dictionary(); - - // Get constructor parameters - var constructor = adapterInfo.Type.GetConstructors() - .FirstOrDefault(c => c.GetParameters().Length > 0); - - if (constructor == null) - return result; - - // Get the first parameter type (input model) - var inputParameter = constructor.GetParameters().FirstOrDefault(); - if (inputParameter == null) - return result; - - var inputType = inputParameter.ParameterType; - - // Get all properties from the input model - var properties = inputType.GetProperties(BindingFlags.Public | BindingFlags.Instance); - - foreach (var prop in properties) + var result = + nativeValidators.FirstOrDefault(a => a.GetType().Name.Equals(adapterId, StringComparison.OrdinalIgnoreCase)); + if (result != null) { - var defaultValue = GetDefaultValue(prop); - var hasRequiredAttribute = prop.GetCustomAttribute() != null; - var isRequired = hasRequiredAttribute || (!IsNullableType(prop.PropertyType) && defaultValue == null); - - if (isRequired) - { - result[prop.Name] = $"{prop.Name} *"; - } - else - { - result[prop.Name] = $"{prop.Name} ({defaultValue ?? "null"})"; - } + result.InitializeStartupValues(settings); } return result; } - + + public List GetNativeAdapters(string? type) + { + List adapters; + + switch (type?.ToLower()) + { + case "handlers": + adapters = nativeHandlers.Cast().ToList(); + break; + case "receivers": + adapters = nativeReceivers.Cast().ToList(); + break; + case "validators": + adapters = nativeValidators.Cast().ToList(); + break; + case "mappers": + adapters = nativeHandlers.Cast().ToList(); + break; + case null: + adapters = nativeAdapters.ToList(); + break; + default: + return new List(); + } + + return adapters.Select(a => a.GetType().Name).ToList(); + } private string? GetDefaultValue(PropertyInfo property) { // Try to get default value from DefaultValueAttribute if it exists @@ -156,7 +124,7 @@ public Dictionary GetNativeAdapterProperties(string adapterId) private bool IsNullableType(Type type) { - return !type.IsValueType || + return !type.IsValueType || Nullable.GetUnderlyingType(type) != null || (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)); } @@ -169,4 +137,4 @@ public class NativeAdapterInfo public Type Type { get; set; } = null!; public string Category { get; set; } = string.Empty; } -} +} \ No newline at end of file diff --git a/SW.Bitween.Api/Services/ReceivingService.cs b/SW.Bitween.Api/Services/ReceivingService.cs index 684001aa..55008231 100644 --- a/SW.Bitween.Api/Services/ReceivingService.cs +++ b/SW.Bitween.Api/Services/ReceivingService.cs @@ -79,10 +79,10 @@ async Task RunReceiver(IServiceProvider serviceProvider, string serverlessId, IDictionary startupParameters, int subId) { // Check if it's a native adapter - if (serverlessId.StartsWith("native.", StringComparison.OrdinalIgnoreCase)) + if (serverlessId.StartsWith(NativeAdapterDiscoveryService.NativePrefix, StringComparison.OrdinalIgnoreCase)) { var nativeAdapterDiscovery = serviceProvider.GetRequiredService(); - var receiver = InstantiateNativeReceiver(nativeAdapterDiscovery, serverlessId, startupParameters); + var receiver = nativeAdapterDiscovery.GetNativeReceiver(serverlessId, startupParameters); await receiver.Initialize(); var fileList = (await receiver.ListFiles()).ToList(); @@ -128,58 +128,6 @@ async Task RunReceiver(IServiceProvider serviceProvider, string serverlessId, } } - private IInfolinkReceiver InstantiateNativeReceiver(NativeAdapterDiscoveryService nativeAdapterDiscovery, - string adapterId, IDictionary properties) - { - var adapterInfo = nativeAdapterDiscovery.GetNativeAdapterInfo(adapterId); - if (adapterInfo == null) - throw new BitweenException($"Native adapter not found: {adapterId}"); - - // Get the constructor that takes a parameter - var constructor = adapterInfo.Type.GetConstructors() - .FirstOrDefault(c => c.GetParameters().Length > 0); - - if (constructor == null) - throw new BitweenException($"Native adapter {adapterId} must have a constructor that accepts an input model"); - - // Get the input parameter type - var inputParameter = constructor.GetParameters().First(); - var inputType = inputParameter.ParameterType; - - // Create an instance of the input model by mapping properties - var inputInstance = Activator.CreateInstance(inputType); - - // Map dictionary properties to the input model - foreach (var prop in inputType.GetProperties()) - { - // Case-insensitive property lookup - var propEntry = properties.FirstOrDefault(p => - string.Equals(p.Key, prop.Name, StringComparison.OrdinalIgnoreCase)); - - if (!string.IsNullOrEmpty(propEntry.Key)) - { - var value = propEntry.Value; - try - { - var convertedValue = Convert.ChangeType(value, - Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType); - prop.SetValue(inputInstance, convertedValue); - } - catch - { - // If conversion fails, set string value directly - if (prop.PropertyType == typeof(string)) - prop.SetValue(inputInstance, value); - } - } - } - - // Instantiate the adapter with the input model - var adapter = Activator.CreateInstance(adapterInfo.Type, inputInstance); - - return (IInfolinkReceiver)adapter; - } - //public void Dispose() //{ diff --git a/SW.Bitween.Api/Services/XchangeService.cs b/SW.Bitween.Api/Services/XchangeService.cs index 2035002d..31c4d2bc 100644 --- a/SW.Bitween.Api/Services/XchangeService.cs +++ b/SW.Bitween.Api/Services/XchangeService.cs @@ -136,9 +136,9 @@ private async Task RunMapper(Xchange xchange, XchangeFile xchangeFi mapperProperties["xchangeid"] = xchange.Id; // Check if it's a native adapter - if (xchange.MapperId.StartsWith("native.", StringComparison.OrdinalIgnoreCase)) + if (xchange.MapperId.StartsWith(NativeAdapterDiscoveryService.NativePrefix, StringComparison.OrdinalIgnoreCase)) { - var handler = InstantiateNativeAdapter(xchange.MapperId, mapperProperties); + var handler = _nativeAdapterDiscovery.GetNativeHandler(xchange.MapperId, mapperProperties); xchangeFile = await handler.Handle(xchangeFile); } else @@ -165,9 +165,10 @@ public async Task RunValidator(string validatorId, IDictionary p InfolinkValidatorResult result; // Check if it's a native adapter - if (validatorId.StartsWith("native.", StringComparison.OrdinalIgnoreCase)) + if (validatorId.StartsWith(NativeAdapterDiscoveryService.NativePrefix, StringComparison.OrdinalIgnoreCase)) { - var validator = InstantiateNativeAdapter(validatorId, properties); + var validator = _nativeAdapterDiscovery.GetNativeValidator(validatorId, properties); + result = await validator.Validate(xchangeFile); } else @@ -191,9 +192,9 @@ private async Task RunHandler(Xchange xchange, XchangeFile xchangeF handlerProperties["xchangeid"] = xchange.Id; // Check if it's a native adapter - if (xchange.HandlerId.StartsWith("native.", StringComparison.OrdinalIgnoreCase)) + if (xchange.HandlerId.StartsWith(NativeAdapterDiscoveryService.NativePrefix, StringComparison.OrdinalIgnoreCase)) { - var handler = InstantiateNativeAdapter(xchange.HandlerId, handlerProperties); + var handler = _nativeAdapterDiscovery.GetNativeHandler(xchange.HandlerId, handlerProperties); xchangeFile = await handler.Handle(xchangeFile); } else @@ -209,57 +210,57 @@ private async Task RunHandler(Xchange xchange, XchangeFile xchangeF return xchangeFile; } - private T InstantiateNativeAdapter(string adapterId, IDictionary properties) - { - var adapterInfo = _nativeAdapterDiscovery.GetNativeAdapterInfo(adapterId); - if (adapterInfo == null) - throw new BitweenException($"Native adapter not found: {adapterId}"); - - // Get the constructor that takes a parameter - var constructor = adapterInfo.Type.GetConstructors() - .FirstOrDefault(c => c.GetParameters().Length > 0); - - if (constructor == null) - throw new BitweenException( - $"Native adapter {adapterId} must have a constructor that accepts an input model"); - - // Get the input parameter type - var inputParameter = constructor.GetParameters().First(); - var inputType = inputParameter.ParameterType; - - // Create an instance of the input model by mapping properties - var inputInstance = Activator.CreateInstance(inputType); - - // Map dictionary properties to the input model - foreach (var prop in inputType.GetProperties()) - { - // Case-insensitive property lookup - var propEntry = properties.FirstOrDefault(p => - string.Equals(p.Key, prop.Name, StringComparison.OrdinalIgnoreCase)); - - if (!string.IsNullOrEmpty(propEntry.Key)) - { - var value = propEntry.Value; - try - { - var convertedValue = Convert.ChangeType(value, - Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType); - prop.SetValue(inputInstance, convertedValue); - } - catch - { - // If conversion fails, set string value directly - if (prop.PropertyType == typeof(string)) - prop.SetValue(inputInstance, value); - } - } - } - - // Instantiate the adapter with the input model - var adapter = Activator.CreateInstance(adapterInfo.Type, inputInstance); - - return (T)adapter; - } + // private T InstantiateNativeAdapter(string adapterId, IDictionary properties) + // { + // var adapterInfo = _nativeAdapterDiscovery.GetNativeAdapterInfo(adapterId); + // if (adapterInfo == null) + // throw new BitweenException($"Native adapter not found: {adapterId}"); + // + // // Get the constructor that takes a parameter + // var constructor = adapterInfo.Type.GetConstructors() + // .FirstOrDefault(c => c.GetParameters().Length > 0); + // + // if (constructor == null) + // throw new BitweenException( + // $"Native adapter {adapterId} must have a constructor that accepts an input model"); + // + // // Get the input parameter type + // var inputParameter = constructor.GetParameters().First(); + // var inputType = inputParameter.ParameterType; + // + // // Create an instance of the input model by mapping properties + // var inputInstance = Activator.CreateInstance(inputType); + // + // // Map dictionary properties to the input model + // foreach (var prop in inputType.GetProperties()) + // { + // // Case-insensitive property lookup + // var propEntry = properties.FirstOrDefault(p => + // string.Equals(p.Key, prop.Name, StringComparison.OrdinalIgnoreCase)); + // + // if (!string.IsNullOrEmpty(propEntry.Key)) + // { + // var value = propEntry.Value; + // try + // { + // var convertedValue = Convert.ChangeType(value, + // Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType); + // prop.SetValue(inputInstance, convertedValue); + // } + // catch + // { + // // If conversion fails, set string value directly + // if (prop.PropertyType == typeof(string)) + // prop.SetValue(inputInstance, value); + // } + // } + // } + // + // // Instantiate the adapter with the input model + // var adapter = Activator.CreateInstance(adapterInfo.Type, inputInstance); + // + // return (T)adapter; + // } private async Task AddFile(string xchangeId, XchangeFileType type, XchangeFile file) { @@ -453,9 +454,9 @@ private async Task NotifyResult(Notifier notifier, XchangeResult xchangeResult, try { // Check if it's a native adapter - if (notifier.HandlerId.StartsWith("native.", StringComparison.OrdinalIgnoreCase)) + if (notifier.HandlerId.StartsWith(NativeAdapterDiscoveryService.NativePrefix, StringComparison.OrdinalIgnoreCase)) { - var handler = InstantiateNativeAdapter(notifier.HandlerId, handlerProperties); + var handler = _nativeAdapterDiscovery.GetNativeHandler(notifier.HandlerId, handlerProperties); await handler.Handle(new XchangeFile(JsonConvert.SerializeObject(notificationData), xchangeResult.Id)); } else diff --git a/SW.Bitween.NativeAdapters/HttpHandler/DynamicHttpProxy.cs b/SW.Bitween.NativeAdapters/HttpHandler/DynamicHttpProxy.cs new file mode 100644 index 00000000..2fa9d5bc --- /dev/null +++ b/SW.Bitween.NativeAdapters/HttpHandler/DynamicHttpProxy.cs @@ -0,0 +1,62 @@ +using System.Collections.Concurrent; +using System.Threading.Channels; +using Microsoft.Extensions.Hosting; + +namespace SW.Bitween.NativeAdapters; + +public interface IDynamicHttpProxy +{ + HttpClient GetClient(string fullUrl); +} +public class DynamicHttpProxy(IHttpClientFactory httpClientFactory) : BackgroundService, IDynamicHttpProxy +{ + private readonly ConcurrentDictionary _cache = new(); + private readonly Channel _usageChannel = Channel.CreateUnbounded(); + + // Internal LRU state (only accessed by the background thread) + private readonly LinkedList _lruList = new(); + private const int MaxCapacity = 200; + + public HttpClient GetClient(string fullUrl) + { + var uri = new Uri(fullUrl); + string origin = $"{uri.Scheme}://{uri.Authority}"; + + // Fast path: No lock, thread-safe read + var client = _cache.GetOrAdd(origin, key => { + var newClient = httpClientFactory.CreateClient(key); + newClient.BaseAddress = new Uri(key); + return newClient; + }); + + // Notify background worker of usage (non-blocking) + _usageChannel.Writer.TryWrite(origin); + + return client; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + // The Scavenger Loop + await foreach (var origin in _usageChannel.Reader.ReadAllAsync(stoppingToken)) + { + // Maintenance logic happens here, off the hot path + UpdateLru(origin); + } + } + + private void UpdateLru(string origin) + { + // Reorder list + _lruList.Remove(origin); + _lruList.AddFirst(origin); + + // Prune if we went over capacity + while (_cache.Count > MaxCapacity) + { + var oldest = _lruList.Last.Value; + _lruList.RemoveLast(); + _cache.TryRemove(oldest, out _); + } + } +} \ No newline at end of file diff --git a/SW.Bitween.NativeAdapters/HttpHandler/HttpHandler.cs b/SW.Bitween.NativeAdapters/HttpHandler/NativeHttpHandler.cs similarity index 93% rename from SW.Bitween.NativeAdapters/HttpHandler/HttpHandler.cs rename to SW.Bitween.NativeAdapters/HttpHandler/NativeHttpHandler.cs index 4f66dd04..64bada7f 100644 --- a/SW.Bitween.NativeAdapters/HttpHandler/HttpHandler.cs +++ b/SW.Bitween.NativeAdapters/HttpHandler/NativeHttpHandler.cs @@ -7,7 +7,7 @@ namespace SW.Bitween.NativeAdapters; -public class HttpHandler : IInfolinkHandler +public class NativeHttpHandler(IDynamicHttpProxy httpProxy) : INativeInfolinkHandler { private HttpMethod HttpMethodFromString(string method) { @@ -24,16 +24,13 @@ private HttpMethod HttpMethodFromString(string method) } } - private readonly HttpHandlerInput _options; + private HttpHandlerInput _options = new(); + - public HttpHandler(HttpHandlerInput options) - { - _options = options ?? throw new ArgumentNullException(nameof(options)); - } public async Task Handle(XchangeFile xchangeFile) { - HttpClient client = new HttpClient(); + HttpClient client = httpProxy.GetClient(_options.Url); if (_options.AuthType == "ApiKey") client.DefaultRequestHeaders.Add("ApiKey", _options.ApiKey); else if (_options.AuthType == "Bearer") @@ -156,5 +153,12 @@ public async Task Handle(XchangeFile xchangeFile) return xchangeFile1; } + + public string Name => "native.httpHandler"; + public void InitializeStartupValues(IDictionary settings) + { + _options = settings.ConvertTo(); + } + public Type StartupValuesType => typeof(HttpHandlerInput); } \ No newline at end of file diff --git a/SW.Bitween.NativeAdapters/Interfaces.cs b/SW.Bitween.NativeAdapters/Interfaces.cs new file mode 100644 index 00000000..d3a8b87d --- /dev/null +++ b/SW.Bitween.NativeAdapters/Interfaces.cs @@ -0,0 +1,14 @@ +using SW.PrimitiveTypes; + +namespace SW.Bitween.NativeAdapters; + +public interface INativeAdapter +{ + public string Name { get; } + public void InitializeStartupValues(IDictionary settings); + public Type StartupValuesType { get; } +} + +public interface INativeInfolinkHandler : INativeAdapter,IInfolinkHandler { } +public interface INativeInfolinkValidator: IInfolinkValidator,INativeAdapter{} +public interface INativeInfolinkReceiver: IInfolinkReceiver,INativeAdapter{} diff --git a/SW.Bitween.NativeAdapters/ReflectionExtensions.cs b/SW.Bitween.NativeAdapters/ReflectionExtensions.cs new file mode 100644 index 00000000..9d705e33 --- /dev/null +++ b/SW.Bitween.NativeAdapters/ReflectionExtensions.cs @@ -0,0 +1,36 @@ +namespace SW.Bitween.NativeAdapters; + +public static class ReflectionExtensions +{ + public static T ConvertTo(this IDictionary settings) + { + var inputInstance = Activator.CreateInstance(typeof(T)); + + // Map dictionary properties to the input model + foreach (var prop in typeof(T).GetProperties()) + { + // Case-insensitive property lookup + var propEntry = settings.FirstOrDefault(p => + string.Equals(p.Key, prop.Name, StringComparison.OrdinalIgnoreCase)); + + if (!string.IsNullOrEmpty(propEntry.Key)) + { + var value = propEntry.Value; + try + { + var convertedValue = Convert.ChangeType(value, + Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType); + prop.SetValue(inputInstance, convertedValue); + } + catch + { + // If conversion fails, set string value directly + if (prop.PropertyType == typeof(string)) + prop.SetValue(inputInstance, value); + } + } + } + + return (T)inputInstance; + } +} \ No newline at end of file diff --git a/SW.Bitween.NativeAdapters/SW.Bitween.NativeAdapters.csproj b/SW.Bitween.NativeAdapters/SW.Bitween.NativeAdapters.csproj index f0d60c4b..f3186c66 100644 --- a/SW.Bitween.NativeAdapters/SW.Bitween.NativeAdapters.csproj +++ b/SW.Bitween.NativeAdapters/SW.Bitween.NativeAdapters.csproj @@ -5,6 +5,9 @@ enable enable + + + diff --git a/SW.Bitween.NativeAdapters/ServiceCollectionExtensions.cs b/SW.Bitween.NativeAdapters/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..3d224c54 --- /dev/null +++ b/SW.Bitween.NativeAdapters/ServiceCollectionExtensions.cs @@ -0,0 +1,27 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace SW.Bitween.NativeAdapters; + +public static class ServiceCollectionExtensions +{ + public static void AddNativeAdapters(this IServiceCollection serviceCollection) + { + serviceCollection.ConfigureHttpClientDefaults(builder => + { + builder.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler + { + PooledConnectionLifetime = TimeSpan.FromMinutes(2), + MaxConnectionsPerServer = 100 + }); + }); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(sp => + sp.GetRequiredService()); + + serviceCollection.AddHostedService(sp => + sp.GetRequiredService()); + + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); + } +} \ No newline at end of file diff --git a/SW.Bitween.Web/Startup.cs b/SW.Bitween.Web/Startup.cs index 946195b2..3273102d 100644 --- a/SW.Bitween.Web/Startup.cs +++ b/SW.Bitween.Web/Startup.cs @@ -1,6 +1,5 @@ using System; using System.Text; -using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; @@ -30,6 +29,7 @@ using SW.Logger.ElasticSerach; using Azure.Identity; using Microsoft.Data.SqlClient; +using SW.Bitween.NativeAdapters; using SqlAuthenticationProvider = Microsoft.Data.SqlClient.SqlAuthenticationProvider; using SqlAuthenticationMethod = Microsoft.Data.SqlClient.SqlAuthenticationMethod; @@ -57,7 +57,7 @@ public void ConfigureServices(IServiceCollection services) services.AddMemoryCache(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddScoped(); services.AddScoped(); services.AddHttpContextAccessor(); @@ -274,6 +274,9 @@ public void ConfigureServices(IServiceCollection services) builder.AllowAnyMethod(); }); }); + + services.AddNativeAdapters(); + }