From be8e066349692fd1bd1bc0e0890de4bc451e73e4 Mon Sep 17 00:00:00 2001 From: Kingmidas74 Date: Sun, 27 Mar 2022 01:02:24 +0300 Subject: [PATCH 01/11] Add caching abstractions --- .../INClientResponseCachingSelector.cs | 16 ++++++++ .../Caching/INClientResponseCachingSetter.cs | 20 ++++++++++ .../INClientTransportResponseCachingSetter.cs | 19 +++++++++ .../Building/INClientOptionalBuilder.cs | 12 ++++++ .../Caching/ExtraCachingExtensions.cs | 40 +++++++++++++++++++ .../Caching/IResponseCacheProvider.cs | 12 ++++++ .../Providers/Caching/IResponseCacheWorker.cs | 15 +++++++ 7 files changed, 134 insertions(+) create mode 100644 src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientResponseCachingSelector.cs create mode 100644 src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientResponseCachingSetter.cs create mode 100644 src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientTransportResponseCachingSetter.cs create mode 100644 src/NClient/NClient.Abstractions/Extensions/Caching/ExtraCachingExtensions.cs create mode 100644 src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheProvider.cs create mode 100644 src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheWorker.cs diff --git a/src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientResponseCachingSelector.cs b/src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientResponseCachingSelector.cs new file mode 100644 index 000000000..79c1dc471 --- /dev/null +++ b/src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientResponseCachingSelector.cs @@ -0,0 +1,16 @@ +// ReSharper disable once CheckNamespace + +namespace NClient +{ + /// Selector for configuring mapping on the selected client layer. + /// The type of request that is used in the transport implementation. + /// The type of response that is used in the transport implementation. + public interface INClientResponseCachingSelector + { + /// Select NClient layer. + INClientResponseCachingSetter ForClient(); + + /// Select transport layer. + INClientTransportResponseCachingSetter ForTransport(); + } +} diff --git a/src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientResponseCachingSetter.cs b/src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientResponseCachingSetter.cs new file mode 100644 index 000000000..75c125413 --- /dev/null +++ b/src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientResponseCachingSetter.cs @@ -0,0 +1,20 @@ +using NClient.Providers.Caching; +using NClient.Providers.Transport; + +// ReSharper disable once CheckNamespace +namespace NClient +{ + /// Setter for custom functionality to mapping NClient requests. + /// The type of request that is used in the transport implementation. + /// The type of response that is used in the transport implementation. + public interface INClientResponseCachingSetter + { + /// Sets a custom mappers that can convert NClient responses into custom ones. + /// The mappers that convert transport responses into custom results. + INClientResponseCachingSelector Use(IResponseCacheWorker cacheWorkers); + + /// Sets a providers creating custom mappers that can convert NClient responses into custom ones. + /// The providers of a mappers that convert transport responses into custom results. + INClientResponseCachingSelector Use(IResponseCacheProvider cacheProvider); + } +} diff --git a/src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientTransportResponseCachingSetter.cs b/src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientTransportResponseCachingSetter.cs new file mode 100644 index 000000000..ec6a7d596 --- /dev/null +++ b/src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientTransportResponseCachingSetter.cs @@ -0,0 +1,19 @@ +using NClient.Providers.Caching; + +// ReSharper disable once CheckNamespace +namespace NClient +{ + /// Setter for custom functionality to mapping transport requests. + /// The type of request that is used in the transport implementation. + /// The type of response that is used in the transport implementation. + public interface INClientTransportResponseCachingSetter + { + /// Sets a custom mappers that can convert NClient responses into custom ones. + /// The mapper that convert transport responses into custom results. + INClientResponseCachingSelector Use(IResponseCacheWorker cacheWorker); + + /// Sets a providers creating custom mappers that can convert NClient responses into custom ones. + /// The provider of a mappers that convert transport responses into custom results. + INClientResponseCachingSelector Use(IResponseCacheProvider cacheProvider); + } +} diff --git a/src/NClient/NClient.Abstractions/Building/INClientOptionalBuilder.cs b/src/NClient/NClient.Abstractions/Building/INClientOptionalBuilder.cs index 5b2d26d4c..1adf7139d 100644 --- a/src/NClient/NClient.Abstractions/Building/INClientOptionalBuilder.cs +++ b/src/NClient/NClient.Abstractions/Building/INClientOptionalBuilder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Microsoft.Extensions.Logging; +using NClient.Providers.Caching; using NClient.Providers.Handling; using NClient.Providers.Mapping; using NClient.Providers.Serialization; @@ -96,6 +97,17 @@ public interface INClientOptionalBuilder where TCl /// Removes logging of client actions. INClientOptionalBuilder WithoutLogging(); + #endregion + + #region Caching + + /// Sets cache workers that caching NClient responses into custom storage. + /// The collection of cache workers that caching NClient responses into custom storage. + INClientOptionalBuilder WithResponseCaching(IResponseCacheWorker cacheWorker); + + /// Sets advanced cache workers that caching NClient responses into custom storage. + INClientOptionalBuilder WithAdvancedResponseCaching(Action> configure); + #endregion /// Creates instance of . diff --git a/src/NClient/NClient.Abstractions/Extensions/Caching/ExtraCachingExtensions.cs b/src/NClient/NClient.Abstractions/Extensions/Caching/ExtraCachingExtensions.cs new file mode 100644 index 000000000..d11a12f75 --- /dev/null +++ b/src/NClient/NClient.Abstractions/Extensions/Caching/ExtraCachingExtensions.cs @@ -0,0 +1,40 @@ +using NClient.Providers.Caching; +using NClient.Providers.Transport; + +// ReSharper disable once CheckNamespace +namespace NClient +{ + public static class ExtraCachingExtensions + { + /// Sets the mappers that convert NClient responses into custom results. + /// + /// The responseCacheWorker that converts transport responses into custom results. + public static INClientOptionalBuilder WithResponseCaching( + this INClientOptionalBuilder optionalBuilder, + IResponseCacheWorker responseCacheWorker) + where TClient : class + { + return optionalBuilder.WithResponseCaching(responseCacheWorker); + } + + /// Sets the mappers that convert NClient responses into custom results. + /// + /// The responseCacheWorker that converts transport responses into custom results. + public static INClientResponseCachingSelector Use( + this INClientResponseCachingSetter responseCachingSetter, + IResponseCacheWorker responseCacheWorker) + { + return responseCachingSetter.Use(responseCacheWorker); + } + + /// Sets the mappers that convert NClient responses into custom results. + /// + /// The responseCacheWorker that converts transport responses into custom results. + public static INClientResponseCachingSelector Use( + this INClientTransportResponseCachingSetter transportResponseCachingSetter, + IResponseCacheWorker responseCacheWorker) + { + return transportResponseCachingSetter.Use(responseCacheWorker); + } + } +} diff --git a/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheProvider.cs b/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheProvider.cs new file mode 100644 index 000000000..c0ce45945 --- /dev/null +++ b/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheProvider.cs @@ -0,0 +1,12 @@ +namespace NClient.Providers.Caching +{ + /// + /// A provider abstraction for a component that can create instances. + /// + public interface IResponseCacheProvider + { + /// Creates and configures an instance of instance. + /// Tools that help implement providers. + IResponseCacheWorker Create(IToolset toolset); + } +} diff --git a/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheWorker.cs b/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheWorker.cs new file mode 100644 index 000000000..9a11081b9 --- /dev/null +++ b/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheWorker.cs @@ -0,0 +1,15 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace NClient.Providers.Caching +{ + /// The worker that manage caching of responses. + /// The type of request that is used in the transport implementation. + /// The type of response that is used in the transport implementation. + public interface IResponseCacheWorker + { + public Task FindAsync(TRequest request, CancellationToken cancellationToken = default); + public Task PutAsync(TRequest request, TResponse response, TimeSpan? lifeTime = null, CancellationToken cancellationToken = default); + } +} From 56a8fed02c83089e4bdd2c5f15513b7ff4441e85 Mon Sep 17 00:00:00 2001 From: Kingmidas74 Date: Sun, 27 Mar 2022 01:03:18 +0300 Subject: [PATCH 02/11] add primitive cache logic --- .../Client/Caching/ResponseCacheProvider.cs | 20 +++++++++++ .../Client/TransportNClient.cs | 25 +++++++++++++- .../Client/TransportNClientFactory.cs | 9 +++++ .../Caching/NClientResponseCachingSelector.cs | 24 ++++++++++++++ .../Caching/NClientResponseCachingSetter.cs | 33 +++++++++++++++++++ .../NClientTransportResponseCachingSetter.cs | 32 ++++++++++++++++++ .../Building/Context/BuilderContext.cs | 22 +++++++++++++ .../Building/NClientOptionalBuilder.cs | 19 +++++++++++ .../Interceptors/ClientInterceptorFactory.cs | 7 ++++ .../Caching/StubResponseCacheProvider.cs | 14 ++++++++ .../Caching/StubResponseCacheWorker.cs | 20 +++++++++++ .../ClientProxy/Validation/ClientValidator.cs | 6 +++- 12 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 src/NClient/NClient.Standalone/Client/Caching/ResponseCacheProvider.cs create mode 100644 src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientResponseCachingSelector.cs create mode 100644 src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientResponseCachingSetter.cs create mode 100644 src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientTransportResponseCachingSetter.cs create mode 100644 src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheProvider.cs create mode 100644 src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheWorker.cs diff --git a/src/NClient/NClient.Standalone/Client/Caching/ResponseCacheProvider.cs b/src/NClient/NClient.Standalone/Client/Caching/ResponseCacheProvider.cs new file mode 100644 index 000000000..83688f6fa --- /dev/null +++ b/src/NClient/NClient.Standalone/Client/Caching/ResponseCacheProvider.cs @@ -0,0 +1,20 @@ +using NClient.Providers; +using NClient.Providers.Caching; + +namespace NClient.Standalone.Client.Caching +{ + internal class ResponseCacheProvider : IResponseCacheProvider + { + private readonly IResponseCacheWorker _responseCacheWorker; + + public ResponseCacheProvider(IResponseCacheWorker responseCacheWorker) + { + _responseCacheWorker = responseCacheWorker; + } + + public IResponseCacheWorker Create(IToolset toolset) + { + return _responseCacheWorker; + } + } +} diff --git a/src/NClient/NClient.Standalone/Client/TransportNClient.cs b/src/NClient/NClient.Standalone/Client/TransportNClient.cs index c3c146308..4e84b10fe 100644 --- a/src/NClient/NClient.Standalone/Client/TransportNClient.cs +++ b/src/NClient/NClient.Standalone/Client/TransportNClient.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using NClient.Providers.Caching; using NClient.Providers.Handling; using NClient.Providers.Mapping; using NClient.Providers.Resilience; @@ -40,6 +41,8 @@ internal class TransportNClient : ITransportNClient> _transportResponseMappers; private readonly IReadOnlyCollection> _responseMappers; private readonly IResponseValidator _responseValidator; + private readonly IResponseCacheWorker? _responseCacheWorker; + private readonly IResponseCacheWorker? _transportResponseCacheWorker; private readonly ILogger? _logger; public TimeSpan Timeout => _transport.Timeout; @@ -54,6 +57,8 @@ public TransportNClient( IEnumerable> responseMappers, IEnumerable> transportResponseMappers, IResponseValidator responseValidator, + IResponseCacheWorker? responseCacheWorker, + IResponseCacheWorker? transportResponseCacheWorker, ILogger? logger) { _serializer = serializer; @@ -65,6 +70,8 @@ public TransportNClient( _transportResponseMappers = transportResponseMappers.ToArray(); _responseMappers = responseMappers.ToArray(); _responseValidator = responseValidator; + _responseCacheWorker = responseCacheWorker; + _transportResponseCacheWorker = transportResponseCacheWorker; _logger = logger; } @@ -223,13 +230,21 @@ private async Task> ExecuteAttemptAsync(IR transportRequest = await _transportRequestBuilder .BuildAsync(request, cancellationToken) .ConfigureAwait(false); - + + if (await TryGetFromCache(transportRequest, cancellationToken) is { } result) + { + _logger?.LogDebug("Response received. Request id: '{requestId}'.", request.Id); + return result; + } + await _clientHandler .HandleRequestAsync(transportRequest, cancellationToken) .ConfigureAwait(false); transportResponse = await _transport.ExecuteAsync(transportRequest, cancellationToken).ConfigureAwait(false); + await _transportResponseCacheWorker?.PutAsync(transportRequest, transportResponse, null, cancellationToken)!; + transportResponse = await _clientHandler .HandleResponseAsync(transportResponse, cancellationToken) .ConfigureAwait(false); @@ -245,6 +260,14 @@ await _clientHandler _logger?.LogDebug("Response received. Request id: '{requestId}'.", request.Id); return new ResponseContext(transportRequest, transportResponse); } + + private async Task?> TryGetFromCache(TRequest transportRequest, CancellationToken cancellationToken = default) + { + if (_transportResponseCacheWorker is null) + return null; + var cachedResponse = await _transportResponseCacheWorker.FindAsync(transportRequest, cancellationToken); + return cachedResponse is not null ? new ResponseContext(transportRequest, cachedResponse) : null; + } private object? TryGetDataObject(Type dataType, string data, IResponseContext transportResponseContext) { diff --git a/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs b/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs index bc0872509..d6c2d9c58 100644 --- a/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs +++ b/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using NClient.Providers; +using NClient.Providers.Caching; using NClient.Providers.Handling; using NClient.Providers.Mapping; using NClient.Providers.Resilience; @@ -24,6 +25,8 @@ internal class TransportNClientFactory : ITransportNClientF private readonly IEnumerable> _responseMapperProviders; private readonly IEnumerable> _transportResponseMapperProviders; private readonly IResponseValidatorProvider _responseValidatorProvider; + private readonly IResponseCacheProvider? _responseCacheProvider; + private readonly IResponseCacheProvider? _transportResponseCacheProvider; private readonly IToolset _toolset; public TransportNClientFactory( @@ -35,6 +38,8 @@ public TransportNClientFactory( IEnumerable> responseMapperProviders, IEnumerable> transportResponseMapperProviders, IResponseValidatorProvider responseValidatorProvider, + IResponseCacheProvider? responseCacheProvider, + IResponseCacheProvider? transportResponseCacheProvider, IToolset toolset) { _transportProvider = transportProvider; @@ -45,6 +50,8 @@ public TransportNClientFactory( _responseMapperProviders = responseMapperProviders; _transportResponseMapperProviders = transportResponseMapperProviders; _responseValidatorProvider = responseValidatorProvider; + _responseCacheProvider = responseCacheProvider; + _transportResponseCacheProvider = transportResponseCacheProvider; _toolset = toolset; } @@ -60,6 +67,8 @@ public ITransportNClient Create() _responseMapperProviders.Select(x => x.Create(_toolset)), _transportResponseMapperProviders.Select(x => x.Create(_toolset)), _responseValidatorProvider.Create(_toolset), + _responseCacheProvider?.Create(_toolset), + _transportResponseCacheProvider?.Create(_toolset), _toolset.Logger); } } diff --git a/src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientResponseCachingSelector.cs b/src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientResponseCachingSelector.cs new file mode 100644 index 000000000..ba0be9590 --- /dev/null +++ b/src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientResponseCachingSelector.cs @@ -0,0 +1,24 @@ +using NClient.Standalone.ClientProxy.Building.Context; + +namespace NClient.Standalone.ClientProxy.Building.Configuration.Caching +{ + internal class NClientResponseCachingSelector : INClientResponseCachingSelector + { + private readonly BuilderContextModifier _builderContextModifier; + + public NClientResponseCachingSelector(BuilderContextModifier builderContextModifier) + { + _builderContextModifier = builderContextModifier; + } + + public INClientResponseCachingSetter ForClient() + { + return new NClientResponseCachingSetter(_builderContextModifier); + } + + public INClientTransportResponseCachingSetter ForTransport() + { + return new NClientTransportResponseCachingSetter(_builderContextModifier); + } + } +} diff --git a/src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientResponseCachingSetter.cs b/src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientResponseCachingSetter.cs new file mode 100644 index 000000000..9314c505a --- /dev/null +++ b/src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientResponseCachingSetter.cs @@ -0,0 +1,33 @@ +using NClient.Common.Helpers; +using NClient.Providers.Caching; +using NClient.Providers.Transport; +using NClient.Standalone.Client.Caching; +using NClient.Standalone.ClientProxy.Building.Context; + +namespace NClient.Standalone.ClientProxy.Building.Configuration.Caching +{ + internal class NClientResponseCachingSetter : INClientResponseCachingSetter + { + private readonly BuilderContextModifier _builderContextModifier; + + public NClientResponseCachingSetter(BuilderContextModifier builderContextModifier) + { + _builderContextModifier = builderContextModifier; + } + + public INClientResponseCachingSelector Use(IResponseCacheWorker cacheWorker) + { + Ensure.IsNotNull(cacheWorker, nameof(cacheWorker)); + + return Use(new ResponseCacheProvider(cacheWorker)); + } + + public INClientResponseCachingSelector Use(IResponseCacheProvider cacheProvider) + { + Ensure.IsNotNull(cacheProvider, nameof(cacheProvider)); + + _builderContextModifier.Add(x => x.WithResponseCachingProvider(cacheProvider)); + return new NClientResponseCachingSelector(_builderContextModifier); + } + } +} diff --git a/src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientTransportResponseCachingSetter.cs b/src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientTransportResponseCachingSetter.cs new file mode 100644 index 000000000..29ad697ca --- /dev/null +++ b/src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientTransportResponseCachingSetter.cs @@ -0,0 +1,32 @@ +using NClient.Common.Helpers; +using NClient.Providers.Caching; +using NClient.Standalone.Client.Caching; +using NClient.Standalone.ClientProxy.Building.Context; + +namespace NClient.Standalone.ClientProxy.Building.Configuration.Caching +{ + internal class NClientTransportResponseCachingSetter : INClientTransportResponseCachingSetter + { + private readonly BuilderContextModifier _builderContextModifier; + + public NClientTransportResponseCachingSetter(BuilderContextModifier builderContextModifier) + { + _builderContextModifier = builderContextModifier; + } + + public INClientResponseCachingSelector Use(IResponseCacheWorker cacheWorker) + { + Ensure.IsNotNull(cacheWorker, nameof(cacheWorker)); + + return Use(new ResponseCacheProvider(cacheWorker)); + } + + public INClientResponseCachingSelector Use(IResponseCacheProvider cacheProvider) + { + Ensure.IsNotNull(cacheProvider, nameof(cacheProvider)); + + _builderContextModifier.Add(x => x.WithTransportCachingProvider(cacheProvider)); + return new NClientResponseCachingSelector(_builderContextModifier); + } + } +} diff --git a/src/NClient/NClient.Standalone/ClientProxy/Building/Context/BuilderContext.cs b/src/NClient/NClient.Standalone/ClientProxy/Building/Context/BuilderContext.cs index a485f49bf..e6b89655e 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Building/Context/BuilderContext.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Building/Context/BuilderContext.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging; using NClient.Invocation; using NClient.Providers.Api; +using NClient.Providers.Caching; using NClient.Providers.Handling; using NClient.Providers.Mapping; using NClient.Providers.Resilience; @@ -38,6 +39,8 @@ internal class BuilderContext public IReadOnlyCollection> ResponseMapperProviders { get; private set; } public IReadOnlyCollection> TransportResponseMapperProviders { get; private set; } + public IResponseCacheProvider? CacheProvider { get; private set; } + public IResponseCacheProvider? TransportCacheProvider { get; private set; } public TimeSpan? Timeout { get; private set; } @@ -78,6 +81,9 @@ public BuilderContext(BuilderContext builderContext) ResponseMapperProviders = builderContext.ResponseMapperProviders.ToArray(); TransportResponseMapperProviders = builderContext.TransportResponseMapperProviders.ToArray(); + + CacheProvider = builderContext.CacheProvider; + TransportCacheProvider = builderContext.TransportCacheProvider; Timeout = builderContext.Timeout; @@ -248,6 +254,22 @@ public BuilderContext WithoutLogging() LoggerFactory = null }; } + + public BuilderContext WithResponseCachingProvider(IResponseCacheProvider responseCacheProvider) + { + return new BuilderContext(this) + { + CacheProvider = responseCacheProvider + }; + } + + public BuilderContext WithTransportCachingProvider(IResponseCacheProvider transportResponseCacheProvider) + { + return new BuilderContext(this) + { + TransportCacheProvider = transportResponseCacheProvider + }; + } public void EnsureComplete() { diff --git a/src/NClient/NClient.Standalone/ClientProxy/Building/NClientOptionalBuilder.cs b/src/NClient/NClient.Standalone/ClientProxy/Building/NClientOptionalBuilder.cs index 9abab4354..59cd39044 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Building/NClientOptionalBuilder.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Building/NClientOptionalBuilder.cs @@ -4,12 +4,14 @@ using Microsoft.Extensions.Logging; using NClient.Common.Helpers; using NClient.Core.Proxy; +using NClient.Providers.Caching; using NClient.Providers.Handling; using NClient.Providers.Mapping; using NClient.Providers.Serialization; using NClient.Providers.Validation; using NClient.Standalone.Client.Logging; using NClient.Standalone.Client.Resilience; +using NClient.Standalone.ClientProxy.Building.Configuration.Caching; using NClient.Standalone.ClientProxy.Building.Configuration.Handling; using NClient.Standalone.ClientProxy.Building.Configuration.Mapping; using NClient.Standalone.ClientProxy.Building.Configuration.Resilience; @@ -162,6 +164,21 @@ public INClientOptionalBuilder WithoutLogging() return new NClientOptionalBuilder(_context .WithoutLogging()); } + + public INClientOptionalBuilder WithResponseCaching(IResponseCacheWorker cacheWorker) + { + return WithAdvancedResponseCaching(x => x + .ForTransport().Use(cacheWorker)); + } + + public INClientOptionalBuilder WithAdvancedResponseCaching(Action> configure) + { + Ensure.IsNotNull(configure, nameof(configure)); + + var builderContextModifier = new BuilderContextModifier(); + configure(new NClientResponseCachingSelector(builderContextModifier)); + return new NClientOptionalBuilder(builderContextModifier.Invoke(_context)); + } public TClient Build() { @@ -185,6 +202,8 @@ public TClient Build() _context.ResponseMapperProviders, _context.TransportResponseMapperProviders, _context.ResponseValidatorProviders, + _context.CacheProvider, + _context.TransportCacheProvider, _context.Timeout, new LoggerDecorator(_context.LoggerFactory is not null ? _context.Loggers.Concat(new[] { _context.LoggerFactory.CreateLogger() }) diff --git a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs index afea75c70..d8a1f3b98 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs @@ -7,6 +7,7 @@ using NClient.Core.Mappers; using NClient.Providers; using NClient.Providers.Api; +using NClient.Providers.Caching; using NClient.Providers.Handling; using NClient.Providers.Mapping; using NClient.Providers.Resilience; @@ -39,6 +40,8 @@ IAsyncInterceptor Create( IEnumerable> responseMapperProviders, IEnumerable> transportResponseMapperProviders, IEnumerable> responseValidatorProviders, + IResponseCacheProvider? responseCacheProvider, + IResponseCacheProvider? transportResponseCacheProvider, TimeSpan? timeout = null, ILogger? logger = null); } @@ -74,6 +77,8 @@ public IAsyncInterceptor Create( IEnumerable> responseMapperProviders, IEnumerable> transportResponseMapperProviders, IEnumerable> responseValidatorProviders, + IResponseCacheProvider? responseCacheProvider, + IResponseCacheProvider? transportResponseCacheProvider, TimeSpan? timeout, ILogger? logger = null) { @@ -111,6 +116,8 @@ public IAsyncInterceptor Create( .ThenBy(x => (x as IOrderedResponseMapperProvider)?.Order) .ToArray(), new ResponseValidatorProviderDecorator(responseValidatorProviders), + responseCacheProvider, + transportResponseCacheProvider, toolset), methodResiliencePolicyProvider, _clientRequestExceptionFactory, diff --git a/src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheProvider.cs b/src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheProvider.cs new file mode 100644 index 000000000..25baf56c7 --- /dev/null +++ b/src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheProvider.cs @@ -0,0 +1,14 @@ +using NClient.Providers; +using NClient.Providers.Caching; +using NClient.Providers.Transport; + +namespace NClient.Standalone.ClientProxy.Validation.Caching +{ + internal class StubResponseCacheProvider : IResponseCacheProvider + { + public IResponseCacheWorker Create(IToolset toolset) + { + return new StubResponseCacheWorker(); + } + } +} diff --git a/src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheWorker.cs b/src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheWorker.cs new file mode 100644 index 000000000..6134160fe --- /dev/null +++ b/src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheWorker.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using NClient.Providers.Caching; +using NClient.Providers.Transport; + +namespace NClient.Standalone.ClientProxy.Validation.Caching +{ + internal class StubResponseCacheWorker : IResponseCacheWorker + { + public async Task FindAsync(IRequest request, CancellationToken cancellationToken = default) + { + return await Task.FromResult(null); + } + public async Task PutAsync(IRequest request, IResponse response, TimeSpan? lifeTime = null, CancellationToken cancellationToken = default) + { + await Task.CompletedTask; + } + } +} diff --git a/src/NClient/NClient.Standalone/ClientProxy/Validation/ClientValidator.cs b/src/NClient/NClient.Standalone/ClientProxy/Validation/ClientValidator.cs index d0d3cc934..8592b8c04 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Validation/ClientValidator.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Validation/ClientValidator.cs @@ -10,6 +10,7 @@ using NClient.Standalone.ClientProxy.Generation; using NClient.Standalone.ClientProxy.Generation.Interceptors; using NClient.Standalone.ClientProxy.Validation.Api; +using NClient.Standalone.ClientProxy.Validation.Caching; using NClient.Standalone.ClientProxy.Validation.Handling; using NClient.Standalone.ClientProxy.Validation.Resilience; using NClient.Standalone.ClientProxy.Validation.Serialization; @@ -52,7 +53,10 @@ public async Task EnsureAsync(IClientInterceptorFactory clientIntercept new StubResiliencePolicyProvider()), Array.Empty>(), Array.Empty>(), - new[] { new StubResponseValidatorProvider() }); + new[] { new StubResponseValidatorProvider() }, + new StubResponseCacheProvider(), + new StubResponseCacheProvider()); + var client = _clientProxyGenerator.CreateClient(interceptor); await EnsureValidityAsync(client).ConfigureAwait(false); From 178a279795dddcd5e356d8d7322033d91ad3324c Mon Sep 17 00:00:00 2001 From: Kingmidas74 Date: Sun, 27 Mar 2022 01:50:09 +0300 Subject: [PATCH 03/11] Add Redis provider for caching --- NClient.sln | 18 +++++ .../NClient.Providers.Caching.Redis.csproj | 55 ++++++++++++++ .../RedisCacheWorker.cs | 71 +++++++++++++++++++ .../RedisCacheWorkerProvider.cs | 31 ++++++++ .../NClient.Packages.Tests/VersionTest.cs | 4 ++ ...lient.Providers.Caching.Redis.Tests.csproj | 30 ++++++++ 6 files changed, 209 insertions(+) create mode 100644 src/NClient.Providers/NClient.Providers.Caching.Redis/NClient.Providers.Caching.Redis.csproj create mode 100644 src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs create mode 100644 src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorkerProvider.cs create mode 100644 tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/NClient.Providers.Caching.Redis.Tests.csproj diff --git a/NClient.sln b/NClient.sln index 2db60cbf8..e9124e905 100644 --- a/NClient.sln +++ b/NClient.sln @@ -125,6 +125,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NClient.DotNetTool.Tests", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NClient.Providers.Serialization.SystemTextJson.Tests", "tests\NClient.Providers\NClient.Providers.Serialization.SystemTextJson.Tests\NClient.Providers.Serialization.SystemTextJson.Tests.csproj", "{FEFE8BA5-1FFF-43F2-A09F-C0F3194413E9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NClient.Providers.Caching.Redis", "src\NClient.Providers\NClient.Providers.Caching.Redis\NClient.Providers.Caching.Redis.csproj", "{3A811C1A-63C2-4E2D-BC17-2029047729B1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NClient.Providers.Caching.Redis.Tests", "tests\NClient.Providers\NClient.Providers.Caching.Redis.Tests\NClient.Providers.Caching.Redis.Tests.csproj", "{04DFFDB6-46E3-42B4-96A7-0F98F2833126}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -368,6 +372,18 @@ Global {FEFE8BA5-1FFF-43F2-A09F-C0F3194413E9}.Release|Any CPU.ActiveCfg = Release|Any CPU {FEFE8BA5-1FFF-43F2-A09F-C0F3194413E9}.Release|Any CPU.Build.0 = Release|Any CPU {FEFE8BA5-1FFF-43F2-A09F-C0F3194413E9}.TestRelease|Any CPU.ActiveCfg = Release|Any CPU + {3A811C1A-63C2-4E2D-BC17-2029047729B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3A811C1A-63C2-4E2D-BC17-2029047729B1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3A811C1A-63C2-4E2D-BC17-2029047729B1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3A811C1A-63C2-4E2D-BC17-2029047729B1}.Release|Any CPU.Build.0 = Release|Any CPU + {3A811C1A-63C2-4E2D-BC17-2029047729B1}.TestRelease|Any CPU.ActiveCfg = Debug|Any CPU + {3A811C1A-63C2-4E2D-BC17-2029047729B1}.TestRelease|Any CPU.Build.0 = Debug|Any CPU + {04DFFDB6-46E3-42B4-96A7-0F98F2833126}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {04DFFDB6-46E3-42B4-96A7-0F98F2833126}.Debug|Any CPU.Build.0 = Debug|Any CPU + {04DFFDB6-46E3-42B4-96A7-0F98F2833126}.Release|Any CPU.ActiveCfg = Release|Any CPU + {04DFFDB6-46E3-42B4-96A7-0F98F2833126}.Release|Any CPU.Build.0 = Release|Any CPU + {04DFFDB6-46E3-42B4-96A7-0F98F2833126}.TestRelease|Any CPU.ActiveCfg = Debug|Any CPU + {04DFFDB6-46E3-42B4-96A7-0F98F2833126}.TestRelease|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -420,6 +436,8 @@ Global {7BA62D11-E7B6-4879-903E-AC718C79DA96} = {F3AF19D7-1D80-4449-98CC-96BC13FF8103} {4EA0CDB4-8C81-4F10-BDC9-4DF8AE638D25} = {7BA62D11-E7B6-4879-903E-AC718C79DA96} {FEFE8BA5-1FFF-43F2-A09F-C0F3194413E9} = {B1BCB5AE-6A5E-49F7-A00A-6B2931DF628C} + {3A811C1A-63C2-4E2D-BC17-2029047729B1} = {DCB997FD-29F4-48D9-8348-093761DA42E7} + {04DFFDB6-46E3-42B4-96A7-0F98F2833126} = {B1BCB5AE-6A5E-49F7-A00A-6B2931DF628C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {244A4C58-8FA7-496C-ABCF-72F333149A20} diff --git a/src/NClient.Providers/NClient.Providers.Caching.Redis/NClient.Providers.Caching.Redis.csproj b/src/NClient.Providers/NClient.Providers.Caching.Redis/NClient.Providers.Caching.Redis.csproj new file mode 100644 index 000000000..f7404c897 --- /dev/null +++ b/src/NClient.Providers/NClient.Providers.Caching.Redis/NClient.Providers.Caching.Redis.csproj @@ -0,0 +1,55 @@ + + + + $(AssemblyName) + $(VersionPrefix) + $(VersionSuffix) + logo.png + https://nclient.github.io + https://github.com/nclient/nclient + git + NClient;NClient.Providers;NClient.Providers.Caching;Redis + The provider that allows you to use the Redis for caching NClient requests. + kingmidas74 + Copyright (c) $([System.DateTime]::Now.ToString('yyyy')) NClient + LICENSE + true + + + + netstandard2.0;netstandard2.1 + 9.0 + enable + true + true + Debug;Release + AnyCPU + $(SolutionDir)/bin/src/$(Configuration)/$(AssemblyName) + + + + + + + + + + + + + + Common\Ensure.cs + + + + + + <_Parameter1>NClient.Providers.Caching.Redis.Tests + + + + + + + + diff --git a/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs b/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs new file mode 100644 index 000000000..cc43e1a35 --- /dev/null +++ b/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NClient.Common.Helpers; +using NClient.Providers.Transport; +using StackExchange.Redis; + +namespace NClient.Providers.Caching.Redis +{ + internal class RedisCacheWorker : IResponseCacheWorker + { + private readonly IDatabaseAsync _redisDb; + public RedisCacheWorker(IDatabaseAsync redisDb) + { + Ensure.IsNotNull(redisDb, nameof(redisDb)); + + _redisDb = redisDb; + } + public async Task FindAsync(IRequest request, CancellationToken cancellationToken = default) + { + Ensure.IsNotNull(request, nameof(request)); + IResponse? result = null; + var bytes = (byte[]) await _redisDb.StringGetAsync(GenerateKey(request)); + + if (bytes == null) + return result; + + using var stream = new MemoryStream(bytes); + result = (IResponse) new BinaryFormatter().Deserialize(stream); + + return result; + } + public async Task PutAsync(IRequest request, IResponse response, TimeSpan? lifeTime = null, CancellationToken cancellationToken = default) + { + Ensure.IsNotNull(request, nameof(request)); + Ensure.IsNotNull(response, nameof(response)); + byte[] bytes; + + using (var stream = new MemoryStream()) + { + new BinaryFormatter().Serialize(stream, response); + bytes = stream.ToArray(); + } + + if (!await _redisDb.StringSetAsync(GenerateKey(request), bytes, lifeTime)) + throw new InvalidOperationException("Couldn't save data to Redis"); + } + + private string GenerateKey(IRequest request) + { + var key = new StringBuilder(request.Type.ToString()); + key.Append(request.Resource.Scheme); + key.Append(request.Resource.Host); + key.Append(request.Resource.Query); + foreach (var requestMetadata in request.Metadatas) + { + key.Append(requestMetadata.Key); + foreach (var metadata in requestMetadata.Value) + { + key.Append(metadata.Name); + key.Append(metadata.Value); + } + } + var plainTextBytes = Encoding.UTF8.GetBytes(key.ToString()); + return Convert.ToBase64String(plainTextBytes); + } + } +} diff --git a/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorkerProvider.cs b/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorkerProvider.cs new file mode 100644 index 000000000..2da49cf3f --- /dev/null +++ b/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorkerProvider.cs @@ -0,0 +1,31 @@ +using NClient.Common.Helpers; +using NClient.Providers.Serialization; +using NClient.Providers.Transport; +using StackExchange.Redis; + +namespace NClient.Providers.Caching.Redis +{ + /// + /// The provider of the cache worker that store HTTP response data to Redis. + /// + public class RedisCacheWorkerProvider : IResponseCacheProvider + { + private readonly IDatabaseAsync _redisDb; + + /// Initializes the Redis cache worker based serializer provider. + /// The redis database. + public RedisCacheWorkerProvider(IDatabaseAsync redisDb) + { + Ensure.IsNotNull(redisDb, nameof(redisDb)); + + _redisDb = redisDb; + } + + /// Creates System.Text.Json instance. + /// Tools that help implement providers. + public IResponseCacheWorker Create(IToolset toolset) + { + return new RedisCacheWorker(_redisDb); + } + } +} diff --git a/tests/NClient.Packages/NClient.Packages.Tests/VersionTest.cs b/tests/NClient.Packages/NClient.Packages.Tests/VersionTest.cs index f38822e9c..869e0eb22 100644 --- a/tests/NClient.Packages/NClient.Packages.Tests/VersionTest.cs +++ b/tests/NClient.Packages/NClient.Packages.Tests/VersionTest.cs @@ -43,6 +43,10 @@ public void NClient_Providers_Resilience_Polly() => [Test] public void NClient_Providers_Mapping_HttpResponses() => PackagesVersionProvider.GetCurrent("NClient.Providers.Mapping.HttpResponses").Should().Be(PackagesVersionProvider.GetNew()); + + [Test] + public void NClient_Providers_Caching_Redis() => + PackagesVersionProvider.GetCurrent("NClient.Providers.Caching.Redis").Should().Be(PackagesVersionProvider.GetNew()); [Test] public void NClient_Providers_Mapping_LanguageExt() => diff --git a/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/NClient.Providers.Caching.Redis.Tests.csproj b/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/NClient.Providers.Caching.Redis.Tests.csproj new file mode 100644 index 000000000..7781baebc --- /dev/null +++ b/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/NClient.Providers.Caching.Redis.Tests.csproj @@ -0,0 +1,30 @@ + + + + net462;net472;net48;net5.0;net6.0;netcoreapp3.1 + 9.0 + enable + true + true + false + true + Debug;Release + AnyCPU + $(SolutionDir)/bin/test/$(Configuration)/$(AssemblyName) + + + + + + + + + + + + + + + + + From 8f09707d9844ff804a101a1b268bd07e6176e6f6 Mon Sep 17 00:00:00 2001 From: Kingmidas74 Date: Sun, 27 Mar 2022 02:19:16 +0300 Subject: [PATCH 04/11] Try to add the test --- .../Extensions/RedisCacheWorkerExtensions.cs | 22 +++++++++++ .../UseRedisCacheWorkerExtensions.cs | 16 ++++++++ .../Building/INClientOptionalBuilder.cs | 4 +- .../Building/NClientOptionalBuilder.cs | 4 +- .../RedisCacheWorkerTest.cs | 37 +++++++++++++++++++ 5 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 src/NClient.Providers/NClient.Providers.Caching.Redis/Extensions/RedisCacheWorkerExtensions.cs create mode 100644 src/NClient.Providers/NClient.Providers.Caching.Redis/Extensions/UseRedisCacheWorkerExtensions.cs create mode 100644 tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/RedisCacheWorkerTest.cs diff --git a/src/NClient.Providers/NClient.Providers.Caching.Redis/Extensions/RedisCacheWorkerExtensions.cs b/src/NClient.Providers/NClient.Providers.Caching.Redis/Extensions/RedisCacheWorkerExtensions.cs new file mode 100644 index 000000000..37c72fe81 --- /dev/null +++ b/src/NClient.Providers/NClient.Providers.Caching.Redis/Extensions/RedisCacheWorkerExtensions.cs @@ -0,0 +1,22 @@ +using NClient.Common.Helpers; +using NClient.Providers.Caching.Redis; +using NClient.Providers.Transport; +using StackExchange.Redis; + +// ReSharper disable once CheckNamespace +namespace NClient +{ + public static class RedisCacheWorkerExtensions + { + /// Sets the Redis cache for the client. + public static INClientOptionalBuilder WithRedisCaching( + this INClientOptionalBuilder optionalBuilder, + IDatabaseAsync dataBase) + where TClient : class + { + Ensure.IsNotNull(optionalBuilder, nameof(optionalBuilder)); + + return optionalBuilder.WithResponseCaching(new RedisCacheWorkerProvider(dataBase)); + } + } +} diff --git a/src/NClient.Providers/NClient.Providers.Caching.Redis/Extensions/UseRedisCacheWorkerExtensions.cs b/src/NClient.Providers/NClient.Providers.Caching.Redis/Extensions/UseRedisCacheWorkerExtensions.cs new file mode 100644 index 000000000..ee4e757de --- /dev/null +++ b/src/NClient.Providers/NClient.Providers.Caching.Redis/Extensions/UseRedisCacheWorkerExtensions.cs @@ -0,0 +1,16 @@ +using NClient.Providers.Caching.Redis; +using NClient.Providers.Transport; +using StackExchange.Redis; + +namespace NClient +{ + public static class UseRedisCacheWorkerExtensions + { + /// Sets the cache worker that can store data to Redis. + public static INClientResponseCachingSelector UseRedisCaching( + this INClientTransportResponseCachingSetter optionalBuilder, IDatabaseAsync dataBase) + { + return optionalBuilder.Use(new RedisCacheWorkerProvider(dataBase)); + } + } +} diff --git a/src/NClient/NClient.Abstractions/Building/INClientOptionalBuilder.cs b/src/NClient/NClient.Abstractions/Building/INClientOptionalBuilder.cs index 1adf7139d..94a25403a 100644 --- a/src/NClient/NClient.Abstractions/Building/INClientOptionalBuilder.cs +++ b/src/NClient/NClient.Abstractions/Building/INClientOptionalBuilder.cs @@ -102,8 +102,8 @@ public interface INClientOptionalBuilder where TCl #region Caching /// Sets cache workers that caching NClient responses into custom storage. - /// The collection of cache workers that caching NClient responses into custom storage. - INClientOptionalBuilder WithResponseCaching(IResponseCacheWorker cacheWorker); + /// The collection of cache workers that caching NClient responses into custom storage. + INClientOptionalBuilder WithResponseCaching(IResponseCacheProvider cacheProvider); /// Sets advanced cache workers that caching NClient responses into custom storage. INClientOptionalBuilder WithAdvancedResponseCaching(Action> configure); diff --git a/src/NClient/NClient.Standalone/ClientProxy/Building/NClientOptionalBuilder.cs b/src/NClient/NClient.Standalone/ClientProxy/Building/NClientOptionalBuilder.cs index 59cd39044..951c5acea 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Building/NClientOptionalBuilder.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Building/NClientOptionalBuilder.cs @@ -165,10 +165,10 @@ public INClientOptionalBuilder WithoutLogging() .WithoutLogging()); } - public INClientOptionalBuilder WithResponseCaching(IResponseCacheWorker cacheWorker) + public INClientOptionalBuilder WithResponseCaching(IResponseCacheProvider cacheProvider) { return WithAdvancedResponseCaching(x => x - .ForTransport().Use(cacheWorker)); + .ForTransport().Use(cacheProvider)); } public INClientOptionalBuilder WithAdvancedResponseCaching(Action> configure) diff --git a/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/RedisCacheWorkerTest.cs b/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/RedisCacheWorkerTest.cs new file mode 100644 index 000000000..64be070b6 --- /dev/null +++ b/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/RedisCacheWorkerTest.cs @@ -0,0 +1,37 @@ +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using NClient.Testing.Common.Apis; +using NClient.Testing.Common.Clients; +using NClient.Testing.Common.Entities; +using NUnit.Framework; +using StackExchange.Redis; + +namespace NClient.Providers.Caching.Redis.Tests +{ + public class RedisCacheWorkerTest + { + [Test] + public async Task Serialize_JsonSerializerOptionsWithPropertyNamingPolicy_ReturnBasicEntity() + { + var entity = new BasicEntity { Id = 1, Value = 2 }; + var jsonSerializerOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + var expectedJson = JsonSerializer.Serialize(entity, jsonSerializerOptions); + using var api = BasicApiMockFactory.MockPostMethod(expectedJson); + + var db = (await ConnectionMultiplexer.ConnectAsync(ConfigurationOptions.Parse("localhost"))).GetDatabase(); + var redisWorker = new RedisCacheWorkerProvider(db); + await NClientGallery.Clients.GetRest().For(host: api.Urls.First()) + .WithSystemTextJsonSerialization(jsonSerializerOptions) + .WithResponseCaching(redisWorker) + .Build() + .Invoking(async x => await x.PostAsync(entity)) + .Should() + .NotThrowAsync(); + } + } +} From ccca133717315f4ac5817d3b674a863e0b9acd94 Mon Sep 17 00:00:00 2001 From: Kingmidas74 Date: Sun, 27 Mar 2022 09:36:06 +0300 Subject: [PATCH 05/11] Remove useless generics --- .../Extensions/RedisCacheWorkerExtensions.cs | 5 ++--- .../RedisCacheWorker.cs | 21 +++++++++--------- .../RedisCacheWorkerProvider.cs | 5 ++--- .../Caching/INClientResponseCachingSetter.cs | 5 ++--- .../INClientTransportResponseCachingSetter.cs | 4 ++-- .../Building/INClientOptionalBuilder.cs | 2 +- .../Caching/ExtraCachingExtensions.cs | 16 ++------------ .../Caching/IResponseCacheProvider.cs | 8 +++---- .../Providers/Caching/IResponseCacheWorker.cs | 8 +++---- .../Client/Caching/ResponseCacheProvider.cs | 8 +++---- .../Client/TransportNClient.cs | 22 ++++--------------- .../Client/TransportNClientFactory.cs | 8 +++---- .../Caching/NClientResponseCachingSetter.cs | 7 +++--- .../NClientTransportResponseCachingSetter.cs | 6 ++--- .../Building/Context/BuilderContext.cs | 8 +++---- .../Building/NClientOptionalBuilder.cs | 2 +- .../Interceptors/ClientInterceptor.cs | 13 +++++++++++ .../Interceptors/ClientInterceptorFactory.cs | 8 +++---- .../Caching/StubResponseCacheProvider.cs | 5 ++--- .../Caching/StubResponseCacheWorker.cs | 9 ++++---- .../RedisCacheWorkerTest.cs | 12 ++++++---- 21 files changed, 83 insertions(+), 99 deletions(-) diff --git a/src/NClient.Providers/NClient.Providers.Caching.Redis/Extensions/RedisCacheWorkerExtensions.cs b/src/NClient.Providers/NClient.Providers.Caching.Redis/Extensions/RedisCacheWorkerExtensions.cs index 37c72fe81..a406826dc 100644 --- a/src/NClient.Providers/NClient.Providers.Caching.Redis/Extensions/RedisCacheWorkerExtensions.cs +++ b/src/NClient.Providers/NClient.Providers.Caching.Redis/Extensions/RedisCacheWorkerExtensions.cs @@ -1,6 +1,5 @@ using NClient.Common.Helpers; using NClient.Providers.Caching.Redis; -using NClient.Providers.Transport; using StackExchange.Redis; // ReSharper disable once CheckNamespace @@ -9,8 +8,8 @@ namespace NClient public static class RedisCacheWorkerExtensions { /// Sets the Redis cache for the client. - public static INClientOptionalBuilder WithRedisCaching( - this INClientOptionalBuilder optionalBuilder, + public static INClientOptionalBuilder WithRedisCaching( + this INClientOptionalBuilder optionalBuilder, IDatabaseAsync dataBase) where TClient : class { diff --git a/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs b/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs index cc43e1a35..121d5561c 100644 --- a/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs +++ b/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs @@ -1,16 +1,14 @@ using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; -using System.Text; using System.Threading; using System.Threading.Tasks; using NClient.Common.Helpers; -using NClient.Providers.Transport; using StackExchange.Redis; namespace NClient.Providers.Caching.Redis { - internal class RedisCacheWorker : IResponseCacheWorker + internal class RedisCacheWorker : IResponseCacheWorker { private readonly IDatabaseAsync _redisDb; public RedisCacheWorker(IDatabaseAsync redisDb) @@ -19,21 +17,21 @@ public RedisCacheWorker(IDatabaseAsync redisDb) _redisDb = redisDb; } - public async Task FindAsync(IRequest request, CancellationToken cancellationToken = default) + public async Task FindAsync(TRequest request, CancellationToken cancellationToken = default) { Ensure.IsNotNull(request, nameof(request)); - IResponse? result = null; + var result = default(TResponse); var bytes = (byte[]) await _redisDb.StringGetAsync(GenerateKey(request)); if (bytes == null) return result; using var stream = new MemoryStream(bytes); - result = (IResponse) new BinaryFormatter().Deserialize(stream); + result = (TResponse) new BinaryFormatter().Deserialize(stream); return result; } - public async Task PutAsync(IRequest request, IResponse response, TimeSpan? lifeTime = null, CancellationToken cancellationToken = default) + public async Task PutAsync(TRequest request, TResponse response, TimeSpan? lifeTime = null, CancellationToken cancellationToken = default) { Ensure.IsNotNull(request, nameof(request)); Ensure.IsNotNull(response, nameof(response)); @@ -41,7 +39,7 @@ public async Task PutAsync(IRequest request, IResponse response, TimeSpan? lifeT using (var stream = new MemoryStream()) { - new BinaryFormatter().Serialize(stream, response); + new BinaryFormatter().Serialize(stream, response!); bytes = stream.ToArray(); } @@ -49,8 +47,11 @@ public async Task PutAsync(IRequest request, IResponse response, TimeSpan? lifeT throw new InvalidOperationException("Couldn't save data to Redis"); } - private string GenerateKey(IRequest request) + private string GenerateKey(TRequest request) { + Ensure.IsNotNull(request, nameof(request)); + return request!.ToString(); + /* var key = new StringBuilder(request.Type.ToString()); key.Append(request.Resource.Scheme); key.Append(request.Resource.Host); @@ -65,7 +66,7 @@ private string GenerateKey(IRequest request) } } var plainTextBytes = Encoding.UTF8.GetBytes(key.ToString()); - return Convert.ToBase64String(plainTextBytes); + return Convert.ToBase64String(plainTextBytes);*/ } } } diff --git a/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorkerProvider.cs b/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorkerProvider.cs index 2da49cf3f..9cd464c9d 100644 --- a/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorkerProvider.cs +++ b/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorkerProvider.cs @@ -1,6 +1,5 @@ using NClient.Common.Helpers; using NClient.Providers.Serialization; -using NClient.Providers.Transport; using StackExchange.Redis; namespace NClient.Providers.Caching.Redis @@ -8,7 +7,7 @@ namespace NClient.Providers.Caching.Redis /// /// The provider of the cache worker that store HTTP response data to Redis. /// - public class RedisCacheWorkerProvider : IResponseCacheProvider + public class RedisCacheWorkerProvider : IResponseCacheProvider { private readonly IDatabaseAsync _redisDb; @@ -23,7 +22,7 @@ public RedisCacheWorkerProvider(IDatabaseAsync redisDb) /// Creates System.Text.Json instance. /// Tools that help implement providers. - public IResponseCacheWorker Create(IToolset toolset) + public IResponseCacheWorker Create(IToolset toolset) { return new RedisCacheWorker(_redisDb); } diff --git a/src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientResponseCachingSetter.cs b/src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientResponseCachingSetter.cs index 75c125413..37e1ec1d5 100644 --- a/src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientResponseCachingSetter.cs +++ b/src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientResponseCachingSetter.cs @@ -1,5 +1,4 @@ using NClient.Providers.Caching; -using NClient.Providers.Transport; // ReSharper disable once CheckNamespace namespace NClient @@ -11,10 +10,10 @@ public interface INClientResponseCachingSetter { /// Sets a custom mappers that can convert NClient responses into custom ones. /// The mappers that convert transport responses into custom results. - INClientResponseCachingSelector Use(IResponseCacheWorker cacheWorkers); + INClientResponseCachingSelector Use(IResponseCacheWorker cacheWorkers); /// Sets a providers creating custom mappers that can convert NClient responses into custom ones. /// The providers of a mappers that convert transport responses into custom results. - INClientResponseCachingSelector Use(IResponseCacheProvider cacheProvider); + INClientResponseCachingSelector Use(IResponseCacheProvider cacheProvider); } } diff --git a/src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientTransportResponseCachingSetter.cs b/src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientTransportResponseCachingSetter.cs index ec6a7d596..4908b64d1 100644 --- a/src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientTransportResponseCachingSetter.cs +++ b/src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientTransportResponseCachingSetter.cs @@ -10,10 +10,10 @@ public interface INClientTransportResponseCachingSetter { /// Sets a custom mappers that can convert NClient responses into custom ones. /// The mapper that convert transport responses into custom results. - INClientResponseCachingSelector Use(IResponseCacheWorker cacheWorker); + INClientResponseCachingSelector Use(IResponseCacheWorker cacheWorker); /// Sets a providers creating custom mappers that can convert NClient responses into custom ones. /// The provider of a mappers that convert transport responses into custom results. - INClientResponseCachingSelector Use(IResponseCacheProvider cacheProvider); + INClientResponseCachingSelector Use(IResponseCacheProvider cacheProvider); } } diff --git a/src/NClient/NClient.Abstractions/Building/INClientOptionalBuilder.cs b/src/NClient/NClient.Abstractions/Building/INClientOptionalBuilder.cs index 94a25403a..215178a9d 100644 --- a/src/NClient/NClient.Abstractions/Building/INClientOptionalBuilder.cs +++ b/src/NClient/NClient.Abstractions/Building/INClientOptionalBuilder.cs @@ -103,7 +103,7 @@ public interface INClientOptionalBuilder where TCl /// Sets cache workers that caching NClient responses into custom storage. /// The collection of cache workers that caching NClient responses into custom storage. - INClientOptionalBuilder WithResponseCaching(IResponseCacheProvider cacheProvider); + INClientOptionalBuilder WithResponseCaching(IResponseCacheProvider cacheProvider); /// Sets advanced cache workers that caching NClient responses into custom storage. INClientOptionalBuilder WithAdvancedResponseCaching(Action> configure); diff --git a/src/NClient/NClient.Abstractions/Extensions/Caching/ExtraCachingExtensions.cs b/src/NClient/NClient.Abstractions/Extensions/Caching/ExtraCachingExtensions.cs index d11a12f75..0e2829e56 100644 --- a/src/NClient/NClient.Abstractions/Extensions/Caching/ExtraCachingExtensions.cs +++ b/src/NClient/NClient.Abstractions/Extensions/Caching/ExtraCachingExtensions.cs @@ -1,28 +1,16 @@ using NClient.Providers.Caching; -using NClient.Providers.Transport; // ReSharper disable once CheckNamespace namespace NClient { public static class ExtraCachingExtensions { - /// Sets the mappers that convert NClient responses into custom results. - /// - /// The responseCacheWorker that converts transport responses into custom results. - public static INClientOptionalBuilder WithResponseCaching( - this INClientOptionalBuilder optionalBuilder, - IResponseCacheWorker responseCacheWorker) - where TClient : class - { - return optionalBuilder.WithResponseCaching(responseCacheWorker); - } - /// Sets the mappers that convert NClient responses into custom results. /// /// The responseCacheWorker that converts transport responses into custom results. public static INClientResponseCachingSelector Use( this INClientResponseCachingSetter responseCachingSetter, - IResponseCacheWorker responseCacheWorker) + IResponseCacheWorker responseCacheWorker) { return responseCachingSetter.Use(responseCacheWorker); } @@ -32,7 +20,7 @@ public static INClientResponseCachingSelector UseThe responseCacheWorker that converts transport responses into custom results. public static INClientResponseCachingSelector Use( this INClientTransportResponseCachingSetter transportResponseCachingSetter, - IResponseCacheWorker responseCacheWorker) + IResponseCacheWorker responseCacheWorker) { return transportResponseCachingSetter.Use(responseCacheWorker); } diff --git a/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheProvider.cs b/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheProvider.cs index c0ce45945..039732d5f 100644 --- a/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheProvider.cs +++ b/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheProvider.cs @@ -1,12 +1,12 @@ namespace NClient.Providers.Caching { /// - /// A provider abstraction for a component that can create instances. + /// A provider abstraction for a component that can create instances. /// - public interface IResponseCacheProvider + public interface IResponseCacheProvider { - /// Creates and configures an instance of instance. + /// Creates and configures an instance of instance. /// Tools that help implement providers. - IResponseCacheWorker Create(IToolset toolset); + IResponseCacheWorker Create(IToolset toolset); } } diff --git a/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheWorker.cs b/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheWorker.cs index 9a11081b9..4a8afd9b8 100644 --- a/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheWorker.cs +++ b/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheWorker.cs @@ -5,11 +5,9 @@ namespace NClient.Providers.Caching { /// The worker that manage caching of responses. - /// The type of request that is used in the transport implementation. - /// The type of response that is used in the transport implementation. - public interface IResponseCacheWorker + public interface IResponseCacheWorker { - public Task FindAsync(TRequest request, CancellationToken cancellationToken = default); - public Task PutAsync(TRequest request, TResponse response, TimeSpan? lifeTime = null, CancellationToken cancellationToken = default); + public Task FindAsync(TRequest request, CancellationToken cancellationToken = default); + public Task PutAsync(TRequest request, TResponse response, TimeSpan? lifeTime = null, CancellationToken cancellationToken = default); } } diff --git a/src/NClient/NClient.Standalone/Client/Caching/ResponseCacheProvider.cs b/src/NClient/NClient.Standalone/Client/Caching/ResponseCacheProvider.cs index 83688f6fa..b5ddd45fb 100644 --- a/src/NClient/NClient.Standalone/Client/Caching/ResponseCacheProvider.cs +++ b/src/NClient/NClient.Standalone/Client/Caching/ResponseCacheProvider.cs @@ -3,16 +3,16 @@ namespace NClient.Standalone.Client.Caching { - internal class ResponseCacheProvider : IResponseCacheProvider + internal class ResponseCacheProvider : IResponseCacheProvider { - private readonly IResponseCacheWorker _responseCacheWorker; + private readonly IResponseCacheWorker _responseCacheWorker; - public ResponseCacheProvider(IResponseCacheWorker responseCacheWorker) + public ResponseCacheProvider(IResponseCacheWorker responseCacheWorker) { _responseCacheWorker = responseCacheWorker; } - public IResponseCacheWorker Create(IToolset toolset) + public IResponseCacheWorker Create(IToolset toolset) { return _responseCacheWorker; } diff --git a/src/NClient/NClient.Standalone/Client/TransportNClient.cs b/src/NClient/NClient.Standalone/Client/TransportNClient.cs index 4e84b10fe..144963a60 100644 --- a/src/NClient/NClient.Standalone/Client/TransportNClient.cs +++ b/src/NClient/NClient.Standalone/Client/TransportNClient.cs @@ -41,8 +41,8 @@ internal class TransportNClient : ITransportNClient> _transportResponseMappers; private readonly IReadOnlyCollection> _responseMappers; private readonly IResponseValidator _responseValidator; - private readonly IResponseCacheWorker? _responseCacheWorker; - private readonly IResponseCacheWorker? _transportResponseCacheWorker; + private readonly IResponseCacheWorker? _responseCacheWorker; + private readonly IResponseCacheWorker? _transportResponseCacheWorker; private readonly ILogger? _logger; public TimeSpan Timeout => _transport.Timeout; @@ -57,8 +57,8 @@ public TransportNClient( IEnumerable> responseMappers, IEnumerable> transportResponseMappers, IResponseValidator responseValidator, - IResponseCacheWorker? responseCacheWorker, - IResponseCacheWorker? transportResponseCacheWorker, + IResponseCacheWorker? responseCacheWorker, + IResponseCacheWorker? transportResponseCacheWorker, ILogger? logger) { _serializer = serializer; @@ -231,12 +231,6 @@ private async Task> ExecuteAttemptAsync(IR .BuildAsync(request, cancellationToken) .ConfigureAwait(false); - if (await TryGetFromCache(transportRequest, cancellationToken) is { } result) - { - _logger?.LogDebug("Response received. Request id: '{requestId}'.", request.Id); - return result; - } - await _clientHandler .HandleRequestAsync(transportRequest, cancellationToken) .ConfigureAwait(false); @@ -261,14 +255,6 @@ await _clientHandler return new ResponseContext(transportRequest, transportResponse); } - private async Task?> TryGetFromCache(TRequest transportRequest, CancellationToken cancellationToken = default) - { - if (_transportResponseCacheWorker is null) - return null; - var cachedResponse = await _transportResponseCacheWorker.FindAsync(transportRequest, cancellationToken); - return cachedResponse is not null ? new ResponseContext(transportRequest, cachedResponse) : null; - } - private object? TryGetDataObject(Type dataType, string data, IResponseContext transportResponseContext) { return _responseValidator.IsSuccess(transportResponseContext) diff --git a/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs b/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs index d6c2d9c58..14e9bf215 100644 --- a/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs +++ b/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs @@ -25,8 +25,8 @@ internal class TransportNClientFactory : ITransportNClientF private readonly IEnumerable> _responseMapperProviders; private readonly IEnumerable> _transportResponseMapperProviders; private readonly IResponseValidatorProvider _responseValidatorProvider; - private readonly IResponseCacheProvider? _responseCacheProvider; - private readonly IResponseCacheProvider? _transportResponseCacheProvider; + private readonly IResponseCacheProvider? _responseCacheProvider; + private readonly IResponseCacheProvider? _transportResponseCacheProvider; private readonly IToolset _toolset; public TransportNClientFactory( @@ -38,8 +38,8 @@ public TransportNClientFactory( IEnumerable> responseMapperProviders, IEnumerable> transportResponseMapperProviders, IResponseValidatorProvider responseValidatorProvider, - IResponseCacheProvider? responseCacheProvider, - IResponseCacheProvider? transportResponseCacheProvider, + IResponseCacheProvider? responseCacheProvider, + IResponseCacheProvider? transportResponseCacheProvider, IToolset toolset) { _transportProvider = transportProvider; diff --git a/src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientResponseCachingSetter.cs b/src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientResponseCachingSetter.cs index 9314c505a..6e1bc0703 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientResponseCachingSetter.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientResponseCachingSetter.cs @@ -1,6 +1,5 @@ using NClient.Common.Helpers; using NClient.Providers.Caching; -using NClient.Providers.Transport; using NClient.Standalone.Client.Caching; using NClient.Standalone.ClientProxy.Building.Context; @@ -15,14 +14,14 @@ public NClientResponseCachingSetter(BuilderContextModifier _builderContextModifier = builderContextModifier; } - public INClientResponseCachingSelector Use(IResponseCacheWorker cacheWorker) + public INClientResponseCachingSelector Use(IResponseCacheWorker cacheWorker) { Ensure.IsNotNull(cacheWorker, nameof(cacheWorker)); - return Use(new ResponseCacheProvider(cacheWorker)); + return Use(new ResponseCacheProvider(cacheWorker)); } - public INClientResponseCachingSelector Use(IResponseCacheProvider cacheProvider) + public INClientResponseCachingSelector Use(IResponseCacheProvider cacheProvider) { Ensure.IsNotNull(cacheProvider, nameof(cacheProvider)); diff --git a/src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientTransportResponseCachingSetter.cs b/src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientTransportResponseCachingSetter.cs index 29ad697ca..292c93557 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientTransportResponseCachingSetter.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientTransportResponseCachingSetter.cs @@ -14,14 +14,14 @@ public NClientTransportResponseCachingSetter(BuilderContextModifier Use(IResponseCacheWorker cacheWorker) + public INClientResponseCachingSelector Use(IResponseCacheWorker cacheWorker) { Ensure.IsNotNull(cacheWorker, nameof(cacheWorker)); - return Use(new ResponseCacheProvider(cacheWorker)); + return Use(new ResponseCacheProvider(cacheWorker)); } - public INClientResponseCachingSelector Use(IResponseCacheProvider cacheProvider) + public INClientResponseCachingSelector Use(IResponseCacheProvider cacheProvider) { Ensure.IsNotNull(cacheProvider, nameof(cacheProvider)); diff --git a/src/NClient/NClient.Standalone/ClientProxy/Building/Context/BuilderContext.cs b/src/NClient/NClient.Standalone/ClientProxy/Building/Context/BuilderContext.cs index e6b89655e..bcc585f79 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Building/Context/BuilderContext.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Building/Context/BuilderContext.cs @@ -39,8 +39,8 @@ internal class BuilderContext public IReadOnlyCollection> ResponseMapperProviders { get; private set; } public IReadOnlyCollection> TransportResponseMapperProviders { get; private set; } - public IResponseCacheProvider? CacheProvider { get; private set; } - public IResponseCacheProvider? TransportCacheProvider { get; private set; } + public IResponseCacheProvider? CacheProvider { get; private set; } + public IResponseCacheProvider? TransportCacheProvider { get; private set; } public TimeSpan? Timeout { get; private set; } @@ -255,7 +255,7 @@ public BuilderContext WithoutLogging() }; } - public BuilderContext WithResponseCachingProvider(IResponseCacheProvider responseCacheProvider) + public BuilderContext WithResponseCachingProvider(IResponseCacheProvider responseCacheProvider) { return new BuilderContext(this) { @@ -263,7 +263,7 @@ public BuilderContext WithResponseCachingProvider(IResponse }; } - public BuilderContext WithTransportCachingProvider(IResponseCacheProvider transportResponseCacheProvider) + public BuilderContext WithTransportCachingProvider(IResponseCacheProvider transportResponseCacheProvider) { return new BuilderContext(this) { diff --git a/src/NClient/NClient.Standalone/ClientProxy/Building/NClientOptionalBuilder.cs b/src/NClient/NClient.Standalone/ClientProxy/Building/NClientOptionalBuilder.cs index 951c5acea..1ad9bb84a 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Building/NClientOptionalBuilder.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Building/NClientOptionalBuilder.cs @@ -165,7 +165,7 @@ public INClientOptionalBuilder WithoutLogging() .WithoutLogging()); } - public INClientOptionalBuilder WithResponseCaching(IResponseCacheProvider cacheProvider) + public INClientOptionalBuilder WithResponseCaching(IResponseCacheProvider cacheProvider) { return WithAdvancedResponseCaching(x => x .ForTransport().Use(cacheProvider)); diff --git a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs index fffb22a8f..3630840f0 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs @@ -150,6 +150,11 @@ protected override async Task InterceptAsync( private async Task ExecuteHttpResponseAsync(ITransportNClient transportNClient, IRequest request, Type resultType, IResiliencePolicy? resiliencePolicy, CancellationToken cancellationToken) { + if (await TryGetFromCache(request, cancellationToken) is { } result) + { + _toolset.Logger?.LogDebug("Response received from cache. Request id: '{requestId}'.", request.Id); + return result; + } if (resultType == typeof(TResponse)) return await transportNClient .GetOriginalResponseAsync(request, resiliencePolicy, cancellationToken) @@ -185,5 +190,13 @@ await transportNClient .ConfigureAwait(false); return null; } + + private async Task? TryGetFromCache(IRequest request, CancellationToken cancellationToken = default) + { + if (_responseCacheWorker is null) + return null; + var cachedResponse = await _responseCacheWorker.FindAsync(request, cancellationToken); + return cachedResponse is not null ? new ResponseContext(request, cachedResponse) : null; + } } } diff --git a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs index d8a1f3b98..3a66f0afe 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs @@ -40,8 +40,8 @@ IAsyncInterceptor Create( IEnumerable> responseMapperProviders, IEnumerable> transportResponseMapperProviders, IEnumerable> responseValidatorProviders, - IResponseCacheProvider? responseCacheProvider, - IResponseCacheProvider? transportResponseCacheProvider, + IResponseCacheProvider? responseCacheProvider, + IResponseCacheProvider? transportResponseCacheProvider, TimeSpan? timeout = null, ILogger? logger = null); } @@ -77,8 +77,8 @@ public IAsyncInterceptor Create( IEnumerable> responseMapperProviders, IEnumerable> transportResponseMapperProviders, IEnumerable> responseValidatorProviders, - IResponseCacheProvider? responseCacheProvider, - IResponseCacheProvider? transportResponseCacheProvider, + IResponseCacheProvider? responseCacheProvider, + IResponseCacheProvider? transportResponseCacheProvider, TimeSpan? timeout, ILogger? logger = null) { diff --git a/src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheProvider.cs b/src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheProvider.cs index 25baf56c7..557bee3b1 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheProvider.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheProvider.cs @@ -1,12 +1,11 @@ using NClient.Providers; using NClient.Providers.Caching; -using NClient.Providers.Transport; namespace NClient.Standalone.ClientProxy.Validation.Caching { - internal class StubResponseCacheProvider : IResponseCacheProvider + internal class StubResponseCacheProvider : IResponseCacheProvider { - public IResponseCacheWorker Create(IToolset toolset) + public IResponseCacheWorker Create(IToolset toolset) { return new StubResponseCacheWorker(); } diff --git a/src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheWorker.cs b/src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheWorker.cs index 6134160fe..87fd784f1 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheWorker.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheWorker.cs @@ -2,17 +2,16 @@ using System.Threading; using System.Threading.Tasks; using NClient.Providers.Caching; -using NClient.Providers.Transport; namespace NClient.Standalone.ClientProxy.Validation.Caching { - internal class StubResponseCacheWorker : IResponseCacheWorker + internal class StubResponseCacheWorker : IResponseCacheWorker { - public async Task FindAsync(IRequest request, CancellationToken cancellationToken = default) + public async Task FindAsync(TRequest request, CancellationToken cancellationToken = default) { - return await Task.FromResult(null); + return await Task.FromResult(default); } - public async Task PutAsync(IRequest request, IResponse response, TimeSpan? lifeTime = null, CancellationToken cancellationToken = default) + public async Task PutAsync(TRequest request, TResponse response, TimeSpan? lifeTime = null, CancellationToken cancellationToken = default) { await Task.CompletedTask; } diff --git a/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/RedisCacheWorkerTest.cs b/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/RedisCacheWorkerTest.cs index 64be070b6..27cd0195b 100644 --- a/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/RedisCacheWorkerTest.cs +++ b/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/RedisCacheWorkerTest.cs @@ -1,7 +1,9 @@ +using System; using System.Linq; using System.Text.Json; using System.Threading.Tasks; using FluentAssertions; +using Moq; using NClient.Testing.Common.Apis; using NClient.Testing.Common.Clients; using NClient.Testing.Common.Entities; @@ -13,7 +15,7 @@ namespace NClient.Providers.Caching.Redis.Tests public class RedisCacheWorkerTest { [Test] - public async Task Serialize_JsonSerializerOptionsWithPropertyNamingPolicy_ReturnBasicEntity() + public async Task Caching_RedisCaching_NotThrow() { var entity = new BasicEntity { Id = 1, Value = 2 }; var jsonSerializerOptions = new JsonSerializerOptions @@ -23,11 +25,13 @@ public async Task Serialize_JsonSerializerOptionsWithPropertyNamingPolicy_Return var expectedJson = JsonSerializer.Serialize(entity, jsonSerializerOptions); using var api = BasicApiMockFactory.MockPostMethod(expectedJson); - var db = (await ConnectionMultiplexer.ConnectAsync(ConfigurationOptions.Parse("localhost"))).GetDatabase(); - var redisWorker = new RedisCacheWorkerProvider(db); + var dbMock = new Mock(); + dbMock.Setup(x => x.StringGetAsync(It.IsAny(), CommandFlags.None)).ReturnsAsync(It.IsAny()); + dbMock.Setup(x => x.StringSetAsync(It.IsAny(), It.IsAny(), TimeSpan.MaxValue, When.Always, CommandFlags.None)).ReturnsAsync(true); + await NClientGallery.Clients.GetRest().For(host: api.Urls.First()) .WithSystemTextJsonSerialization(jsonSerializerOptions) - .WithResponseCaching(redisWorker) + .WithRedisCaching(dbMock.Object) .Build() .Invoking(async x => await x.PostAsync(entity)) .Should() From bf1f2791be168d1f06701185628934d4024fcf04 Mon Sep 17 00:00:00 2001 From: Kingmidas74 Date: Sun, 27 Mar 2022 14:27:15 +0300 Subject: [PATCH 06/11] Working non transport cache --- .../Extensions/RedisCacheWorkerExtensions.cs | 5 ++- .../RedisCacheWorker.cs | 13 +++--- .../RedisCacheWorkerProvider.cs | 2 +- .../Building/INClientOptionalBuilder.cs | 2 +- .../Client/TransportNClient.cs | 5 --- .../Client/TransportNClientFactory.cs | 4 -- .../Interceptors/ClientInterceptor.cs | 44 ++++++++++++++----- .../Interceptors/ClientInterceptorFactory.cs | 2 +- ...lient.Providers.Caching.Redis.Tests.csproj | 1 + .../RedisCacheWorkerTest.cs | 29 +++++++----- 10 files changed, 64 insertions(+), 43 deletions(-) diff --git a/src/NClient.Providers/NClient.Providers.Caching.Redis/Extensions/RedisCacheWorkerExtensions.cs b/src/NClient.Providers/NClient.Providers.Caching.Redis/Extensions/RedisCacheWorkerExtensions.cs index a406826dc..526ebecf9 100644 --- a/src/NClient.Providers/NClient.Providers.Caching.Redis/Extensions/RedisCacheWorkerExtensions.cs +++ b/src/NClient.Providers/NClient.Providers.Caching.Redis/Extensions/RedisCacheWorkerExtensions.cs @@ -15,7 +15,10 @@ public static INClientOptionalBuilder WithRedisCac { Ensure.IsNotNull(optionalBuilder, nameof(optionalBuilder)); - return optionalBuilder.WithResponseCaching(new RedisCacheWorkerProvider(dataBase)); + return optionalBuilder.WithAdvancedResponseCaching(selector => + { + selector.ForClient().Use(new RedisCacheWorkerProvider(dataBase)); + }); } } } diff --git a/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs b/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs index 121d5561c..430f935a7 100644 --- a/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs +++ b/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs @@ -11,11 +11,13 @@ namespace NClient.Providers.Caching.Redis internal class RedisCacheWorker : IResponseCacheWorker { private readonly IDatabaseAsync _redisDb; - public RedisCacheWorker(IDatabaseAsync redisDb) + private readonly IToolset _toolset; + public RedisCacheWorker(IDatabaseAsync redisDb, IToolset toolset) { Ensure.IsNotNull(redisDb, nameof(redisDb)); _redisDb = redisDb; + _toolset = toolset; } public async Task FindAsync(TRequest request, CancellationToken cancellationToken = default) { @@ -35,15 +37,10 @@ public async Task PutAsync(TRequest request, TResponse resp { Ensure.IsNotNull(request, nameof(request)); Ensure.IsNotNull(response, nameof(response)); - byte[] bytes; - using (var stream = new MemoryStream()) - { - new BinaryFormatter().Serialize(stream, response!); - bytes = stream.ToArray(); - } + var serializedResponse = _toolset.Serializer.Serialize(response); - if (!await _redisDb.StringSetAsync(GenerateKey(request), bytes, lifeTime)) + if (!await _redisDb.StringSetAsync(GenerateKey(request), serializedResponse, lifeTime)) throw new InvalidOperationException("Couldn't save data to Redis"); } diff --git a/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorkerProvider.cs b/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorkerProvider.cs index 9cd464c9d..8ccef580e 100644 --- a/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorkerProvider.cs +++ b/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorkerProvider.cs @@ -24,7 +24,7 @@ public RedisCacheWorkerProvider(IDatabaseAsync redisDb) /// Tools that help implement providers. public IResponseCacheWorker Create(IToolset toolset) { - return new RedisCacheWorker(_redisDb); + return new RedisCacheWorker(_redisDb, toolset); } } } diff --git a/src/NClient/NClient.Abstractions/Building/INClientOptionalBuilder.cs b/src/NClient/NClient.Abstractions/Building/INClientOptionalBuilder.cs index 215178a9d..cbdaa0b63 100644 --- a/src/NClient/NClient.Abstractions/Building/INClientOptionalBuilder.cs +++ b/src/NClient/NClient.Abstractions/Building/INClientOptionalBuilder.cs @@ -52,7 +52,7 @@ public interface INClientOptionalBuilder where TCl #endregion - #region Results + #region Mapping /// Sets mapping that convert NClient responses into custom results. /// The collection of mappers that converts transport responses into custom results. diff --git a/src/NClient/NClient.Standalone/Client/TransportNClient.cs b/src/NClient/NClient.Standalone/Client/TransportNClient.cs index 144963a60..4c6bb5613 100644 --- a/src/NClient/NClient.Standalone/Client/TransportNClient.cs +++ b/src/NClient/NClient.Standalone/Client/TransportNClient.cs @@ -41,7 +41,6 @@ internal class TransportNClient : ITransportNClient> _transportResponseMappers; private readonly IReadOnlyCollection> _responseMappers; private readonly IResponseValidator _responseValidator; - private readonly IResponseCacheWorker? _responseCacheWorker; private readonly IResponseCacheWorker? _transportResponseCacheWorker; private readonly ILogger? _logger; @@ -57,7 +56,6 @@ public TransportNClient( IEnumerable> responseMappers, IEnumerable> transportResponseMappers, IResponseValidator responseValidator, - IResponseCacheWorker? responseCacheWorker, IResponseCacheWorker? transportResponseCacheWorker, ILogger? logger) { @@ -70,7 +68,6 @@ public TransportNClient( _transportResponseMappers = transportResponseMappers.ToArray(); _responseMappers = responseMappers.ToArray(); _responseValidator = responseValidator; - _responseCacheWorker = responseCacheWorker; _transportResponseCacheWorker = transportResponseCacheWorker; _logger = logger; } @@ -237,8 +234,6 @@ await _clientHandler transportResponse = await _transport.ExecuteAsync(transportRequest, cancellationToken).ConfigureAwait(false); - await _transportResponseCacheWorker?.PutAsync(transportRequest, transportResponse, null, cancellationToken)!; - transportResponse = await _clientHandler .HandleResponseAsync(transportResponse, cancellationToken) .ConfigureAwait(false); diff --git a/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs b/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs index 14e9bf215..d030d04f9 100644 --- a/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs +++ b/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs @@ -25,7 +25,6 @@ internal class TransportNClientFactory : ITransportNClientF private readonly IEnumerable> _responseMapperProviders; private readonly IEnumerable> _transportResponseMapperProviders; private readonly IResponseValidatorProvider _responseValidatorProvider; - private readonly IResponseCacheProvider? _responseCacheProvider; private readonly IResponseCacheProvider? _transportResponseCacheProvider; private readonly IToolset _toolset; @@ -38,7 +37,6 @@ public TransportNClientFactory( IEnumerable> responseMapperProviders, IEnumerable> transportResponseMapperProviders, IResponseValidatorProvider responseValidatorProvider, - IResponseCacheProvider? responseCacheProvider, IResponseCacheProvider? transportResponseCacheProvider, IToolset toolset) { @@ -50,7 +48,6 @@ public TransportNClientFactory( _responseMapperProviders = responseMapperProviders; _transportResponseMapperProviders = transportResponseMapperProviders; _responseValidatorProvider = responseValidatorProvider; - _responseCacheProvider = responseCacheProvider; _transportResponseCacheProvider = transportResponseCacheProvider; _toolset = toolset; } @@ -67,7 +64,6 @@ public ITransportNClient Create() _responseMapperProviders.Select(x => x.Create(_toolset)), _transportResponseMapperProviders.Select(x => x.Create(_toolset)), _responseValidatorProvider.Create(_toolset), - _responseCacheProvider?.Create(_toolset), _transportResponseCacheProvider?.Create(_toolset), _toolset.Logger); } diff --git a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs index 3630840f0..29278629a 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs @@ -8,6 +8,7 @@ using NClient.Exceptions; using NClient.Providers; using NClient.Providers.Api; +using NClient.Providers.Caching; using NClient.Providers.Resilience; using NClient.Providers.Transport; using NClient.Standalone.Client; @@ -31,6 +32,7 @@ internal class ClientInterceptor : AsyncIntercepto private readonly ITransportNClientFactory _transportNClientFactory; private readonly IMethodResiliencePolicyProvider _methodResiliencePolicyProvider; private readonly IClientRequestExceptionFactory _clientRequestExceptionFactory; + private readonly IResponseCacheWorker? _responseCacheWorker; private readonly TimeSpan? _timeout; private readonly IToolset _toolset; @@ -44,7 +46,8 @@ public ClientInterceptor( IRequestBuilder requestBuilder, ITransportNClientFactory transportNClientFactory, IMethodResiliencePolicyProvider methodResiliencePolicyProvider, - IClientRequestExceptionFactory clientRequestExceptionFactory, + IClientRequestExceptionFactory clientRequestExceptionFactory, + IResponseCacheWorker? responseCacheWorker, TimeSpan? timeout, IToolset toolset) { @@ -58,6 +61,7 @@ public ClientInterceptor( _transportNClientFactory = transportNClientFactory; _methodResiliencePolicyProvider = methodResiliencePolicyProvider; _clientRequestExceptionFactory = clientRequestExceptionFactory; + _responseCacheWorker = responseCacheWorker; _timeout = timeout; _toolset = toolset; } @@ -150,35 +154,55 @@ protected override async Task InterceptAsync( private async Task ExecuteHttpResponseAsync(ITransportNClient transportNClient, IRequest request, Type resultType, IResiliencePolicy? resiliencePolicy, CancellationToken cancellationToken) { - if (await TryGetFromCache(request, cancellationToken) is { } result) + if (await TryGetFromCache(request, cancellationToken) is { } cachedResult) { _toolset.Logger?.LogDebug("Response received from cache. Request id: '{requestId}'.", request.Id); - return result; + return cachedResult; } + if (resultType == typeof(TResponse)) return await transportNClient .GetOriginalResponseAsync(request, resiliencePolicy, cancellationToken) .ConfigureAwait(false); if (resultType == typeof(IResponse)) - return await transportNClient + { + var result = await transportNClient .GetHttpResponseAsync(request, resiliencePolicy, cancellationToken) .ConfigureAwait(false); + await _responseCacheWorker?.PutAsync(request, result, TimeSpan.MaxValue, cancellationToken)!; + return result; + } if (resultType.IsGenericType && resultType.GetGenericTypeDefinition() == typeof(IResponse<>).GetGenericTypeDefinition()) - return await transportNClient + { + var result = await transportNClient .GetHttpResponseAsync(request, dataType: resultType.GetGenericArguments().Single(), resiliencePolicy, cancellationToken) .ConfigureAwait(false); + + await _responseCacheWorker?.PutAsync(request, result, TimeSpan.MaxValue, cancellationToken)!; + return result; + } if (resultType.IsGenericType && resultType.GetGenericTypeDefinition() == typeof(IResponseWithError<>).GetGenericTypeDefinition()) - return await transportNClient + { + var result = await transportNClient .GetHttpResponseWithErrorAsync(request, errorType: resultType.GetGenericArguments().Single(), resiliencePolicy, cancellationToken) .ConfigureAwait(false); + + await _responseCacheWorker?.PutAsync(request, result, TimeSpan.MaxValue, cancellationToken)!; + return result; + } if (resultType.IsGenericType && resultType.GetGenericTypeDefinition() == typeof(IResponseWithError<,>).GetGenericTypeDefinition()) - return await transportNClient + { + var result = await transportNClient .GetHttpResponseWithDataAndErrorAsync(request, dataType: resultType.GetGenericArguments()[0], errorType: resultType.GetGenericArguments()[1], resiliencePolicy, cancellationToken) .ConfigureAwait(false); + + await _responseCacheWorker?.PutAsync(request, result, TimeSpan.MaxValue, cancellationToken)!; + return result; + } if (resultType != typeof(void)) return await transportNClient @@ -188,15 +212,15 @@ protected override async Task InterceptAsync( await transportNClient .GetResultAsync(request, resiliencePolicy, cancellationToken) .ConfigureAwait(false); + return null; } - private async Task? TryGetFromCache(IRequest request, CancellationToken cancellationToken = default) + private async Task TryGetFromCache(IRequest request, CancellationToken cancellationToken = default) { if (_responseCacheWorker is null) return null; - var cachedResponse = await _responseCacheWorker.FindAsync(request, cancellationToken); - return cachedResponse is not null ? new ResponseContext(request, cachedResponse) : null; + return await _responseCacheWorker.FindAsync(request, cancellationToken); } } } diff --git a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs index 3a66f0afe..e817ab858 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs @@ -116,11 +116,11 @@ public IAsyncInterceptor Create( .ThenBy(x => (x as IOrderedResponseMapperProvider)?.Order) .ToArray(), new ResponseValidatorProviderDecorator(responseValidatorProviders), - responseCacheProvider, transportResponseCacheProvider, toolset), methodResiliencePolicyProvider, _clientRequestExceptionFactory, + responseCacheProvider?.Create(toolset), timeout, toolset); } diff --git a/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/NClient.Providers.Caching.Redis.Tests.csproj b/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/NClient.Providers.Caching.Redis.Tests.csproj index 7781baebc..cb614dc71 100644 --- a/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/NClient.Providers.Caching.Redis.Tests.csproj +++ b/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/NClient.Providers.Caching.Redis.Tests.csproj @@ -23,6 +23,7 @@ + diff --git a/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/RedisCacheWorkerTest.cs b/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/RedisCacheWorkerTest.cs index 27cd0195b..256e845ad 100644 --- a/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/RedisCacheWorkerTest.cs +++ b/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/RedisCacheWorkerTest.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using System.Text.Json; -using System.Threading.Tasks; using FluentAssertions; using Moq; using NClient.Testing.Common.Apis; @@ -15,27 +14,33 @@ namespace NClient.Providers.Caching.Redis.Tests public class RedisCacheWorkerTest { [Test] - public async Task Caching_RedisCaching_NotThrow() + public void Caching_RedisCaching_NotThrow() { + const int id = 1; var entity = new BasicEntity { Id = 1, Value = 2 }; var jsonSerializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - var expectedJson = JsonSerializer.Serialize(entity, jsonSerializerOptions); - using var api = BasicApiMockFactory.MockPostMethod(expectedJson); - + var camelCaseContentBytes = JsonSerializer.SerializeToUtf8Bytes(entity, jsonSerializerOptions); + var dbMock = new Mock(); dbMock.Setup(x => x.StringGetAsync(It.IsAny(), CommandFlags.None)).ReturnsAsync(It.IsAny()); - dbMock.Setup(x => x.StringSetAsync(It.IsAny(), It.IsAny(), TimeSpan.MaxValue, When.Always, CommandFlags.None)).ReturnsAsync(true); - - await NClientGallery.Clients.GetRest().For(host: api.Urls.First()) - .WithSystemTextJsonSerialization(jsonSerializerOptions) + dbMock.Setup(x => x.StringSetAsync(It.IsAny(), It.IsAny(), It.IsAny(), When.Always, CommandFlags.None)).ReturnsAsync(true); + + using var api = ReturnApiMockFactory.MockGetAsyncMethod(id, entity); + + var result = NClientGallery.Clients.GetRest().For(host: api.Urls.First()) + .WithNewtonsoftJsonSerialization() .WithRedisCaching(dbMock.Object) .Build() - .Invoking(async x => await x.PostAsync(entity)) - .Should() - .NotThrowAsync(); + .GetIHttpResponse(id); + + result.IsSuccessful.Should().BeTrue(); + result.Data.Should().BeEquivalentTo(entity); + result.Content.Bytes.Should().NotBeEquivalentTo(camelCaseContentBytes); + + dbMock.Verify(mock => mock.StringSetAsync(It.IsAny(), It.IsAny(), It.IsAny(), When.Always, CommandFlags.None), Times.Once()); } } } From 4c032bf6141243bd7135b136c274db9ba516b69c Mon Sep 17 00:00:00 2001 From: Kingmidas74 Date: Sun, 27 Mar 2022 14:50:50 +0300 Subject: [PATCH 07/11] Make redis cache worker IResponse based --- .../RedisCacheWorker.cs | 29 +++++++++---------- .../Providers/Caching/IResponseCacheWorker.cs | 5 ++-- .../Interceptors/ClientInterceptor.cs | 2 +- .../Caching/StubResponseCacheWorker.cs | 7 +++-- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs b/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs index 430f935a7..13532e39c 100644 --- a/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs +++ b/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs @@ -1,9 +1,9 @@ using System; -using System.IO; -using System.Runtime.Serialization.Formatters.Binary; +using System.Text; using System.Threading; using System.Threading.Tasks; using NClient.Common.Helpers; +using NClient.Providers.Transport; using StackExchange.Redis; namespace NClient.Providers.Caching.Redis @@ -12,6 +12,7 @@ internal class RedisCacheWorker : IResponseCacheWorker { private readonly IDatabaseAsync _redisDb; private readonly IToolset _toolset; + public RedisCacheWorker(IDatabaseAsync redisDb, IToolset toolset) { Ensure.IsNotNull(redisDb, nameof(redisDb)); @@ -19,21 +20,18 @@ public RedisCacheWorker(IDatabaseAsync redisDb, IToolset toolset) _redisDb = redisDb; _toolset = toolset; } - public async Task FindAsync(TRequest request, CancellationToken cancellationToken = default) + public async Task FindAsync(IRequest request, CancellationToken cancellationToken = default) { Ensure.IsNotNull(request, nameof(request)); - var result = default(TResponse); - var bytes = (byte[]) await _redisDb.StringGetAsync(GenerateKey(request)); - - if (bytes == null) - return result; - using var stream = new MemoryStream(bytes); - result = (TResponse) new BinaryFormatter().Deserialize(stream); + var serializedResponse = (await _redisDb.StringGetAsync(GenerateKey(request))).ToString(); + + if (string.IsNullOrEmpty(serializedResponse)) + return default; - return result; + return (IResponse) _toolset.Serializer.Deserialize(serializedResponse, typeof(IResponse))!; } - public async Task PutAsync(TRequest request, TResponse response, TimeSpan? lifeTime = null, CancellationToken cancellationToken = default) + public async Task PutAsync(IRequest request, IResponse response, TimeSpan? lifeTime = null, CancellationToken cancellationToken = default) { Ensure.IsNotNull(request, nameof(request)); Ensure.IsNotNull(response, nameof(response)); @@ -44,11 +42,10 @@ public async Task PutAsync(TRequest request, TResponse resp throw new InvalidOperationException("Couldn't save data to Redis"); } - private string GenerateKey(TRequest request) + private string GenerateKey(IRequest request) { Ensure.IsNotNull(request, nameof(request)); - return request!.ToString(); - /* + var key = new StringBuilder(request.Type.ToString()); key.Append(request.Resource.Scheme); key.Append(request.Resource.Host); @@ -63,7 +60,7 @@ private string GenerateKey(TRequest request) } } var plainTextBytes = Encoding.UTF8.GetBytes(key.ToString()); - return Convert.ToBase64String(plainTextBytes);*/ + return Convert.ToBase64String(plainTextBytes); } } } diff --git a/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheWorker.cs b/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheWorker.cs index 4a8afd9b8..47748bec7 100644 --- a/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheWorker.cs +++ b/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheWorker.cs @@ -1,13 +1,14 @@ using System; using System.Threading; using System.Threading.Tasks; +using NClient.Providers.Transport; namespace NClient.Providers.Caching { /// The worker that manage caching of responses. public interface IResponseCacheWorker { - public Task FindAsync(TRequest request, CancellationToken cancellationToken = default); - public Task PutAsync(TRequest request, TResponse response, TimeSpan? lifeTime = null, CancellationToken cancellationToken = default); + public Task FindAsync(IRequest request, CancellationToken cancellationToken = default); + public Task PutAsync(IRequest request, IResponse response, TimeSpan? lifeTime = null, CancellationToken cancellationToken = default); } } diff --git a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs index 29278629a..0caab0616 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs @@ -220,7 +220,7 @@ await transportNClient { if (_responseCacheWorker is null) return null; - return await _responseCacheWorker.FindAsync(request, cancellationToken); + return await _responseCacheWorker.FindAsync(request, cancellationToken); } } } diff --git a/src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheWorker.cs b/src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheWorker.cs index 87fd784f1..3968503e9 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheWorker.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Validation/Caching/StubResponseCacheWorker.cs @@ -2,16 +2,17 @@ using System.Threading; using System.Threading.Tasks; using NClient.Providers.Caching; +using NClient.Providers.Transport; namespace NClient.Standalone.ClientProxy.Validation.Caching { internal class StubResponseCacheWorker : IResponseCacheWorker { - public async Task FindAsync(TRequest request, CancellationToken cancellationToken = default) + public async Task FindAsync(IRequest request, CancellationToken cancellationToken = default) { - return await Task.FromResult(default); + return await Task.FromResult(default); } - public async Task PutAsync(TRequest request, TResponse response, TimeSpan? lifeTime = null, CancellationToken cancellationToken = default) + public async Task PutAsync(IRequest request, IResponse response, TimeSpan? lifeTime = null, CancellationToken cancellationToken = default) { await Task.CompletedTask; } From 6236c9fcfe62054f7e37fb9875209e483fc984fb Mon Sep 17 00:00:00 2001 From: Kingmidas74 Date: Sun, 27 Mar 2022 16:38:44 +0300 Subject: [PATCH 08/11] Add cached attribute --- .../RedisCacheWorker.cs | 3 + .../Annotations/ICachingAttribute.cs | 9 +++ .../Invocation/IMethod.cs | 3 + .../NClient.Abstractions/Invocation/Method.cs | 1 + .../NClient.Annotations/CachingAttribute.cs | 19 +++++ .../Interceptors/ClientInterceptor.cs | 13 ++-- .../Interceptors/ClientInterceptorFactory.cs | 1 + .../MethodBuilders/MethodBuilder.cs | 6 +- .../Providers/CachingAttributeProvider.cs | 73 +++++++++++++++++++ .../MethodBuilders/MethodBuilderTest.cs | 5 ++ .../RequestBuilderTestBase.cs | 1 + .../RedisCacheWorkerTest.cs | 8 +- .../Apis/CachingApiMockFactory.cs | 27 +++++++ .../Clients/ICachingClient.cs | 10 +++ .../Clients/ICachingClientWithMetadata.cs | 16 ++++ .../ICachingStaticClientWithMetadata.cs | 20 +++++ 16 files changed, 204 insertions(+), 11 deletions(-) create mode 100644 src/NClient/NClient.Abstractions/Annotations/ICachingAttribute.cs create mode 100644 src/NClient/NClient.Annotations/CachingAttribute.cs create mode 100644 src/NClient/NClient.Standalone/ClientProxy/Generation/MethodBuilders/Providers/CachingAttributeProvider.cs create mode 100644 tests/NClient.Testing/NClient.Testing.Common/Apis/CachingApiMockFactory.cs create mode 100644 tests/NClient.Testing/NClient.Testing.Common/Clients/ICachingClient.cs create mode 100644 tests/NClient.Testing/NClient.Testing.Common/Clients/ICachingClientWithMetadata.cs create mode 100644 tests/NClient.Testing/NClient.Testing.Common/Clients/ICachingStaticClientWithMetadata.cs diff --git a/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs b/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs index 13532e39c..ae9b8bc4a 100644 --- a/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs +++ b/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs @@ -35,6 +35,9 @@ public async Task PutAsync(IRequest request, IResponse response, TimeSpan? lifeT { Ensure.IsNotNull(request, nameof(request)); Ensure.IsNotNull(response, nameof(response)); + + if (lifeTime?.TotalMilliseconds <= 0) + return; var serializedResponse = _toolset.Serializer.Serialize(response); diff --git a/src/NClient/NClient.Abstractions/Annotations/ICachingAttribute.cs b/src/NClient/NClient.Abstractions/Annotations/ICachingAttribute.cs new file mode 100644 index 000000000..1f154379d --- /dev/null +++ b/src/NClient/NClient.Abstractions/Annotations/ICachingAttribute.cs @@ -0,0 +1,9 @@ +namespace NClient.Annotations +{ + /// Identifies an action that should be cached. + public interface ICachingAttribute + { + /// The life time of cache. + double Milliseconds { get; } + } +} diff --git a/src/NClient/NClient.Abstractions/Invocation/IMethod.cs b/src/NClient/NClient.Abstractions/Invocation/IMethod.cs index b94f1a60f..d1745f0d0 100644 --- a/src/NClient/NClient.Abstractions/Invocation/IMethod.cs +++ b/src/NClient/NClient.Abstractions/Invocation/IMethod.cs @@ -31,6 +31,9 @@ public interface IMethod /// Get represents time limit for method execution. ITimeoutAttribute? TimeoutAttribute { get; } + /// Get represents time limit for cahing. + ICachingAttribute? CachingAttribute { get; } + /// Get array of represents additional method info (like headers for HTTP). IMetadataAttribute[] MetadataAttributes { get; } diff --git a/src/NClient/NClient.Abstractions/Invocation/Method.cs b/src/NClient/NClient.Abstractions/Invocation/Method.cs index d4baddda9..dfe9130df 100644 --- a/src/NClient/NClient.Abstractions/Invocation/Method.cs +++ b/src/NClient/NClient.Abstractions/Invocation/Method.cs @@ -15,6 +15,7 @@ internal class Method : IMethod public IUseVersionAttribute? UseVersionAttribute { get; set; } public IMetadataAttribute[] MetadataAttributes { get; set; } public ITimeoutAttribute? TimeoutAttribute { get; set; } + public ICachingAttribute? CachingAttribute { get; set; } public IMethodParam[] Params { get; } public Type ResultType { get; } diff --git a/src/NClient/NClient.Annotations/CachingAttribute.cs b/src/NClient/NClient.Annotations/CachingAttribute.cs new file mode 100644 index 000000000..51ae65d82 --- /dev/null +++ b/src/NClient/NClient.Annotations/CachingAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace NClient.Annotations +{ + /// Identifies an action that should be cached. + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false, Inherited = true)] + public class CachingAttribute : Attribute, ICachingAttribute + { + /// The life time of cache. + public double Milliseconds { get; } + + /// Initializes a new with the given milliseconds value. + /// The life period in milliseconds. + public CachingAttribute(double milliseconds) + { + Milliseconds = milliseconds; + } + } +} diff --git a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs index 0caab0616..941ada934 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Castle.DynamicProxy; using Microsoft.Extensions.Logging; +using NClient.Annotations; using NClient.Core.Helpers; using NClient.Exceptions; using NClient.Providers; @@ -117,7 +118,7 @@ protected override async Task InterceptAsync( var resiliencePolicy = methodInvocation.ResiliencePolicyProvider?.Create(_toolset) ?? _methodResiliencePolicyProvider.Create(methodInvocation.Method, httpRequest, _toolset); - var result = await ExecuteHttpResponseAsync(transportNClient, httpRequest, resultType, resiliencePolicy, combinedCancellationToken).ConfigureAwait(false); + var result = await ExecuteHttpResponseAsync(transportNClient, httpRequest, resultType, resiliencePolicy, method.CachingAttribute, combinedCancellationToken).ConfigureAwait(false); _toolset.Logger?.LogDebug("Processing request finished. Request id: '{requestId}'.", requestId); return result; } @@ -152,7 +153,7 @@ protected override async Task InterceptAsync( } } - private async Task ExecuteHttpResponseAsync(ITransportNClient transportNClient, IRequest request, Type resultType, IResiliencePolicy? resiliencePolicy, CancellationToken cancellationToken) + private async Task ExecuteHttpResponseAsync(ITransportNClient transportNClient, IRequest request, Type resultType, IResiliencePolicy? resiliencePolicy, ICachingAttribute? cachingAttribute = default, CancellationToken cancellationToken = default) { if (await TryGetFromCache(request, cancellationToken) is { } cachedResult) { @@ -170,7 +171,7 @@ protected override async Task InterceptAsync( var result = await transportNClient .GetHttpResponseAsync(request, resiliencePolicy, cancellationToken) .ConfigureAwait(false); - await _responseCacheWorker?.PutAsync(request, result, TimeSpan.MaxValue, cancellationToken)!; + await _responseCacheWorker?.PutAsync(request, result, TimeSpan.FromMilliseconds(cachingAttribute?.Milliseconds ?? 0), cancellationToken); return result; } @@ -180,7 +181,7 @@ protected override async Task InterceptAsync( .GetHttpResponseAsync(request, dataType: resultType.GetGenericArguments().Single(), resiliencePolicy, cancellationToken) .ConfigureAwait(false); - await _responseCacheWorker?.PutAsync(request, result, TimeSpan.MaxValue, cancellationToken)!; + await _responseCacheWorker?.PutAsync(request, result, TimeSpan.FromMilliseconds(cachingAttribute?.Milliseconds ?? 0), cancellationToken); return result; } @@ -190,7 +191,7 @@ protected override async Task InterceptAsync( .GetHttpResponseWithErrorAsync(request, errorType: resultType.GetGenericArguments().Single(), resiliencePolicy, cancellationToken) .ConfigureAwait(false); - await _responseCacheWorker?.PutAsync(request, result, TimeSpan.MaxValue, cancellationToken)!; + await _responseCacheWorker?.PutAsync(request, result, TimeSpan.FromMilliseconds(cachingAttribute?.Milliseconds ?? 0), cancellationToken); return result; } @@ -200,7 +201,7 @@ protected override async Task InterceptAsync( .GetHttpResponseWithDataAndErrorAsync(request, dataType: resultType.GetGenericArguments()[0], errorType: resultType.GetGenericArguments()[1], resiliencePolicy, cancellationToken) .ConfigureAwait(false); - await _responseCacheWorker?.PutAsync(request, result, TimeSpan.MaxValue, cancellationToken)!; + await _responseCacheWorker?.PutAsync(request, result, TimeSpan.FromMilliseconds(cachingAttribute?.Milliseconds ?? 0), cancellationToken); return result; } diff --git a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs index e817ab858..1a0f74d78 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs @@ -88,6 +88,7 @@ public IAsyncInterceptor Create( new PathAttributeProvider(_attributeMapper, _clientValidationExceptionFactory), new MetadataAttributeProvider(_clientValidationExceptionFactory), new TimeoutAttributeProvider(_attributeMapper, _clientValidationExceptionFactory), + new CachingAttributeProvider(_attributeMapper, _clientValidationExceptionFactory), new MethodParamBuilder(new ParamAttributeProvider(_attributeMapper, _clientValidationExceptionFactory))); var serializer = serializerProvider.Create(logger); diff --git a/src/NClient/NClient.Standalone/ClientProxy/Generation/MethodBuilders/MethodBuilder.cs b/src/NClient/NClient.Standalone/ClientProxy/Generation/MethodBuilders/MethodBuilder.cs index 37cea87fe..e151a8a4f 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Generation/MethodBuilders/MethodBuilder.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Generation/MethodBuilders/MethodBuilder.cs @@ -25,6 +25,7 @@ internal class MethodBuilder : IMethodBuilder private readonly IPathAttributeProvider _pathAttributeProvider; private readonly IHeaderAttributeProvider _headerAttributeProvider; private readonly ITimeoutAttributeProvider _timeoutAttributeProvider; + private readonly ICachingAttributeProvider _cachingAttributeProvider; private readonly IMethodParamBuilder _methodParamBuilder; public MethodBuilder( @@ -33,6 +34,7 @@ public MethodBuilder( IPathAttributeProvider pathAttributeProvider, IHeaderAttributeProvider headerAttributeProvider, ITimeoutAttributeProvider timeoutAttributeProvider, + ICachingAttributeProvider cachingAttributeProvider, IMethodParamBuilder methodParamBuilder) { _cache = new ConcurrentDictionary(); @@ -41,6 +43,7 @@ public MethodBuilder( _pathAttributeProvider = pathAttributeProvider; _headerAttributeProvider = headerAttributeProvider; _timeoutAttributeProvider = timeoutAttributeProvider; + _cachingAttributeProvider = cachingAttributeProvider; _methodParamBuilder = methodParamBuilder; } @@ -76,7 +79,8 @@ public IMethod Build(Type clientType, MethodInfo methodInfo, Type returnType) PathAttribute = _pathAttributeProvider.Find(clientType), UseVersionAttribute = _useVersionAttributeProvider.Find(clientType, methodInfo, overridingMethods), MetadataAttributes = _headerAttributeProvider.Find(clientType, methodInfo, overridingMethods, methodParams), - TimeoutAttribute = _timeoutAttributeProvider.Find(clientType, methodInfo, overridingMethods) + TimeoutAttribute = _timeoutAttributeProvider.Find(clientType, methodInfo, overridingMethods), + CachingAttribute = _cachingAttributeProvider.Find(clientType, methodInfo, overridingMethods) }; _cache.TryAdd(methodInfo, method); diff --git a/src/NClient/NClient.Standalone/ClientProxy/Generation/MethodBuilders/Providers/CachingAttributeProvider.cs b/src/NClient/NClient.Standalone/ClientProxy/Generation/MethodBuilders/Providers/CachingAttributeProvider.cs new file mode 100644 index 000000000..80c3bc335 --- /dev/null +++ b/src/NClient/NClient.Standalone/ClientProxy/Generation/MethodBuilders/Providers/CachingAttributeProvider.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using NClient.Annotations; +using NClient.Core.Helpers; +using NClient.Core.Mappers; +using NClient.Standalone.Exceptions.Factories; + +namespace NClient.Standalone.ClientProxy.Generation.MethodBuilders.Providers +{ + internal interface ICachingAttributeProvider + { + ICachingAttribute? Find(Type clientType, MethodInfo method, IEnumerable overridingMethods); + } + + internal class CachingAttributeProvider : ICachingAttributeProvider + { + private readonly IAttributeMapper _attributeMapper; + private readonly IClientValidationExceptionFactory _clientValidationExceptionFactory; + + public CachingAttributeProvider( + IAttributeMapper attributeMapper, + IClientValidationExceptionFactory clientValidationExceptionFactory) + { + _attributeMapper = attributeMapper; + _clientValidationExceptionFactory = clientValidationExceptionFactory; + } + + public ICachingAttribute? Find(Type clientType, MethodInfo method, IEnumerable overridingMethods) + { + return Find(clientType, method) ?? overridingMethods.Select(x => Find(clientType, x)).FirstOrDefault(); + } + + private CachingAttribute? Find(Type clientType, MethodInfo method) + { + var typeTimeoutAttribute = FindInType(clientType); + var methodTimeoutAttribute = FindInMethod(method); + + return methodTimeoutAttribute ?? typeTimeoutAttribute; + } + + private CachingAttribute? FindInType(Type clientType) + { + var timeoutAttributes = (clientType.IsInterface + ? clientType.GetInterfaceCustomAttributes(inherit: true) + : clientType.GetCustomAttributes(inherit: true).Cast()) + .Select(x => _attributeMapper.TryMap(x)) + .Where(x => x is CachingAttribute) + .Cast() + .ToArray(); + + if (timeoutAttributes.Length > 1) + throw _clientValidationExceptionFactory.MultipleMethodAttributeNotSupported(); + + return timeoutAttributes.SingleOrDefault(); + } + + private CachingAttribute? FindInMethod(MethodInfo method) + { + var timeoutAttributes = method.GetCustomAttributes() + .Select(x => _attributeMapper.TryMap(x)) + .Where(x => x is CachingAttribute) + .Cast() + .ToArray(); + + if (timeoutAttributes.Length > 1) + throw _clientValidationExceptionFactory.MultipleMethodAttributeNotSupported(); + + return timeoutAttributes.SingleOrDefault(); + } + } +} diff --git a/tests/NClient.Providers/NClient.Providers.Api.Rest.Tests/MethodBuilders/MethodBuilderTest.cs b/tests/NClient.Providers/NClient.Providers.Api.Rest.Tests/MethodBuilders/MethodBuilderTest.cs index abc26b67d..2fb33d5cb 100644 --- a/tests/NClient.Providers/NClient.Providers.Api.Rest.Tests/MethodBuilders/MethodBuilderTest.cs +++ b/tests/NClient.Providers/NClient.Providers.Api.Rest.Tests/MethodBuilders/MethodBuilderTest.cs @@ -31,6 +31,7 @@ public void Build_BasicClient_Method() var useVersionAttribute = (UseVersionAttribute) null!; var pathAttribute = (PathAttribute) null!; var timeoutAttribute = (TimeoutAttribute) null!; + var cachingAttribute = (CachingAttribute) null!; var headerAttributes = Array.Empty(); var methodParams = Array.Empty(); var methodAttributeProviderMock = new Mock(); @@ -45,6 +46,9 @@ public void Build_BasicClient_Method() var timeoutAttributeProviderMock = new Mock(); timeoutAttributeProviderMock.Setup(x => x.Find(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(timeoutAttribute); + var cachingAttributeProviderMock = new Mock(); + cachingAttributeProviderMock.Setup(x => x.Find(It.IsAny(), It.IsAny(), It.IsAny>())) + .Returns(cachingAttribute); var headerAttributeProviderMock = new Mock(); headerAttributeProviderMock.Setup(x => x.Find(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) .Returns(headerAttributes); @@ -57,6 +61,7 @@ public void Build_BasicClient_Method() pathAttributeProviderMock.Object, headerAttributeProviderMock.Object, timeoutAttributeProviderMock.Object, + cachingAttributeProviderMock.Object, methodParamBuilderMock.Object); var actualResult = methodBuilder.Build(clientType, methodInfo, returnType); diff --git a/tests/NClient.Providers/NClient.Providers.Api.Rest.Tests/RequestBuilderTestBase.cs b/tests/NClient.Providers/NClient.Providers.Api.Rest.Tests/RequestBuilderTestBase.cs index 9099015d6..c8270836e 100644 --- a/tests/NClient.Providers/NClient.Providers.Api.Rest.Tests/RequestBuilderTestBase.cs +++ b/tests/NClient.Providers/NClient.Providers.Api.Rest.Tests/RequestBuilderTestBase.cs @@ -62,6 +62,7 @@ public void OneTimeSetUp() new PathAttributeProvider(attributeMapper, ClientValidationExceptionFactory), new MetadataAttributeProvider(ClientValidationExceptionFactory), new TimeoutAttributeProvider(attributeMapper, ClientValidationExceptionFactory), + new CachingAttributeProvider(attributeMapper, ClientValidationExceptionFactory), new MethodParamBuilder(new ParamAttributeProvider(attributeMapper, ClientValidationExceptionFactory))); } diff --git a/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/RedisCacheWorkerTest.cs b/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/RedisCacheWorkerTest.cs index 256e845ad..a5fb749f2 100644 --- a/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/RedisCacheWorkerTest.cs +++ b/tests/NClient.Providers/NClient.Providers.Caching.Redis.Tests/RedisCacheWorkerTest.cs @@ -28,19 +28,19 @@ public void Caching_RedisCaching_NotThrow() dbMock.Setup(x => x.StringGetAsync(It.IsAny(), CommandFlags.None)).ReturnsAsync(It.IsAny()); dbMock.Setup(x => x.StringSetAsync(It.IsAny(), It.IsAny(), It.IsAny(), When.Always, CommandFlags.None)).ReturnsAsync(true); - using var api = ReturnApiMockFactory.MockGetAsyncMethod(id, entity); + using var api = CachingApiMockFactory.MockGetAsyncMethod(id, entity); - var result = NClientGallery.Clients.GetRest().For(host: api.Urls.First()) + var result = NClientGallery.Clients.GetRest().For(host: api.Urls.First()) .WithNewtonsoftJsonSerialization() .WithRedisCaching(dbMock.Object) .Build() - .GetIHttpResponse(id); + .GetIResponse(id); result.IsSuccessful.Should().BeTrue(); result.Data.Should().BeEquivalentTo(entity); result.Content.Bytes.Should().NotBeEquivalentTo(camelCaseContentBytes); - dbMock.Verify(mock => mock.StringSetAsync(It.IsAny(), It.IsAny(), It.IsAny(), When.Always, CommandFlags.None), Times.Once()); + dbMock.Verify(mock => mock.StringSetAsync(It.IsAny(), It.IsAny(), It.IsAny(), When.Always, CommandFlags.None), Times.Once()); } } } diff --git a/tests/NClient.Testing/NClient.Testing.Common/Apis/CachingApiMockFactory.cs b/tests/NClient.Testing/NClient.Testing.Common/Apis/CachingApiMockFactory.cs new file mode 100644 index 000000000..66859423e --- /dev/null +++ b/tests/NClient.Testing/NClient.Testing.Common/Apis/CachingApiMockFactory.cs @@ -0,0 +1,27 @@ +using NClient.Testing.Common.Entities; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; +using WireMock.Server; + +namespace NClient.Testing.Common.Apis +{ + public class CachingApiMockFactory + { + public static IWireMockServer MockGetAsyncMethod(int id, BasicEntity entity) + { + var api = WireMockServer.Start(); + api.Given(Request.Create() + .WithPath("/api/caching") + .WithHeader("Accept", "application/json") + .WithParam("id", id.ToString()) + .UsingGet()) + .RespondWith(Response.Create() + .WithStatusCode(200) + .WithHeader("Content-Encoding", "utf-8") + .WithHeader("Content-Type", "application/json") + .WithBodyAsJson(entity)); + + return api; + } + } +} diff --git a/tests/NClient.Testing/NClient.Testing.Common/Clients/ICachingClient.cs b/tests/NClient.Testing/NClient.Testing.Common/Clients/ICachingClient.cs new file mode 100644 index 000000000..d01078b60 --- /dev/null +++ b/tests/NClient.Testing/NClient.Testing.Common/Clients/ICachingClient.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace NClient.Testing.Common.Clients +{ + public interface ICachingClient : INClient + { + int Get(int id); + Task GetAsync(int id); + } +} diff --git a/tests/NClient.Testing/NClient.Testing.Common/Clients/ICachingClientWithMetadata.cs b/tests/NClient.Testing/NClient.Testing.Common/Clients/ICachingClientWithMetadata.cs new file mode 100644 index 000000000..d1944e69f --- /dev/null +++ b/tests/NClient.Testing/NClient.Testing.Common/Clients/ICachingClientWithMetadata.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using NClient.Annotations; +using NClient.Annotations.Http; + +namespace NClient.Testing.Common.Clients +{ + [Path("api/caching")] + public interface ICachingClientWithMetadata : ITimeoutClient + { + [GetMethod] + new int Get(int id); + + [GetMethod] + new Task GetAsync(int id); + } +} diff --git a/tests/NClient.Testing/NClient.Testing.Common/Clients/ICachingStaticClientWithMetadata.cs b/tests/NClient.Testing/NClient.Testing.Common/Clients/ICachingStaticClientWithMetadata.cs new file mode 100644 index 000000000..f6e27062a --- /dev/null +++ b/tests/NClient.Testing/NClient.Testing.Common/Clients/ICachingStaticClientWithMetadata.cs @@ -0,0 +1,20 @@ +using System.Threading.Tasks; +using NClient.Annotations; +using NClient.Annotations.Http; +using NClient.Providers.Transport; +using NClient.Testing.Common.Entities; + +namespace NClient.Testing.Common.Clients +{ + [Path("api/caching")] + [Caching(500)] + public interface ICachingStaticClientWithMetadata : ITimeoutClient + { + [GetMethod] + [Caching(1000)] + IResponse GetIResponse(int id); + + [GetMethod] + Task> GetIResponseAsync(int id); + } +} From 21d833999878f22c26431b74c1189dc80650ed3e Mon Sep 17 00:00:00 2001 From: Kingmidas74 Date: Sun, 27 Mar 2022 17:04:54 +0300 Subject: [PATCH 09/11] move method to extensions --- .../Caching/ExtraCachingExtensions.cs | 18 ++++++++++++++++++ .../Interceptors/ClientInterceptor.cs | 18 ++++++------------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/NClient/NClient.Abstractions/Extensions/Caching/ExtraCachingExtensions.cs b/src/NClient/NClient.Abstractions/Extensions/Caching/ExtraCachingExtensions.cs index 0e2829e56..7e8442df4 100644 --- a/src/NClient/NClient.Abstractions/Extensions/Caching/ExtraCachingExtensions.cs +++ b/src/NClient/NClient.Abstractions/Extensions/Caching/ExtraCachingExtensions.cs @@ -1,4 +1,9 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using NClient.Annotations; using NClient.Providers.Caching; +using NClient.Providers.Transport; // ReSharper disable once CheckNamespace namespace NClient @@ -24,5 +29,18 @@ public static INClientResponseCachingSelector Use TryGet(this IResponseCacheWorker? worker, IRequest request, CancellationToken cancellationToken = default) + { + if (worker is null) + return null; + return await worker.FindAsync(request, cancellationToken); + } } } diff --git a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs index 941ada934..dcab38041 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs @@ -155,7 +155,7 @@ protected override async Task InterceptAsync( private async Task ExecuteHttpResponseAsync(ITransportNClient transportNClient, IRequest request, Type resultType, IResiliencePolicy? resiliencePolicy, ICachingAttribute? cachingAttribute = default, CancellationToken cancellationToken = default) { - if (await TryGetFromCache(request, cancellationToken) is { } cachedResult) + if (await _responseCacheWorker.TryGet(request, cancellationToken) is { } cachedResult) { _toolset.Logger?.LogDebug("Response received from cache. Request id: '{requestId}'.", request.Id); return cachedResult; @@ -171,7 +171,8 @@ protected override async Task InterceptAsync( var result = await transportNClient .GetHttpResponseAsync(request, resiliencePolicy, cancellationToken) .ConfigureAwait(false); - await _responseCacheWorker?.PutAsync(request, result, TimeSpan.FromMilliseconds(cachingAttribute?.Milliseconds ?? 0), cancellationToken); + + await _responseCacheWorker.Put(request, result, cachingAttribute, cancellationToken); return result; } @@ -181,7 +182,7 @@ protected override async Task InterceptAsync( .GetHttpResponseAsync(request, dataType: resultType.GetGenericArguments().Single(), resiliencePolicy, cancellationToken) .ConfigureAwait(false); - await _responseCacheWorker?.PutAsync(request, result, TimeSpan.FromMilliseconds(cachingAttribute?.Milliseconds ?? 0), cancellationToken); + await _responseCacheWorker.Put(request, result, cachingAttribute, cancellationToken); return result; } @@ -191,7 +192,7 @@ protected override async Task InterceptAsync( .GetHttpResponseWithErrorAsync(request, errorType: resultType.GetGenericArguments().Single(), resiliencePolicy, cancellationToken) .ConfigureAwait(false); - await _responseCacheWorker?.PutAsync(request, result, TimeSpan.FromMilliseconds(cachingAttribute?.Milliseconds ?? 0), cancellationToken); + await _responseCacheWorker.Put(request, result, cachingAttribute, cancellationToken); return result; } @@ -201,7 +202,7 @@ protected override async Task InterceptAsync( .GetHttpResponseWithDataAndErrorAsync(request, dataType: resultType.GetGenericArguments()[0], errorType: resultType.GetGenericArguments()[1], resiliencePolicy, cancellationToken) .ConfigureAwait(false); - await _responseCacheWorker?.PutAsync(request, result, TimeSpan.FromMilliseconds(cachingAttribute?.Milliseconds ?? 0), cancellationToken); + await _responseCacheWorker.Put(request, result, cachingAttribute, cancellationToken); return result; } @@ -216,12 +217,5 @@ await transportNClient return null; } - - private async Task TryGetFromCache(IRequest request, CancellationToken cancellationToken = default) - { - if (_responseCacheWorker is null) - return null; - return await _responseCacheWorker.FindAsync(request, cancellationToken); - } } } From 75e21cf7013f158a29fc72fdedcabdc13c6295a7 Mon Sep 17 00:00:00 2001 From: Kingmidas74 Date: Sun, 27 Mar 2022 18:11:54 +0300 Subject: [PATCH 10/11] update slnf file --- Tests.slnf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests.slnf b/Tests.slnf index 66e84a952..33aa0ed2d 100644 --- a/Tests.slnf +++ b/Tests.slnf @@ -8,6 +8,7 @@ "tests\\NClient.Providers\\NClient.Providers.Api.Rest.Tests\\NClient.Providers.Api.Rest.Tests.csproj", "tests\\NClient.Providers\\NClient.Providers.Mapping.HttpResponses.Tests\\NClient.Providers.Mapping.HttpResponses.Tests.csproj", "tests\\NClient.Providers\\NClient.Providers.Mapping.LanguageExt.Tests\\NClient.Providers.Mapping.LanguageExt.Tests.csproj", + "tests\\NClient.Providers\\NClient.Providers.Caching.Redis.Tests\\NClient.Providers.Caching.Redis.Tests.csproj", "tests\\NClient.Providers\\NClient.Providers.Transport.SystemNetHttp.Tests\\NClient.Providers.Transport.SystemNetHttp.Tests.csproj", "tests\\NClient.Providers\\NClient.Providers.Serialization.SystemTextJson.Tests\\NClient.Providers.Serialization.SystemTextJson.Tests.csproj", "tests\\NClient.Providers\\NClient.Providers.Serialization.SystemXml.Tests\\NClient.Providers.Serialization.SystemXml.Tests.csproj", @@ -22,4 +23,4 @@ "tests\\NClient\\NClient.Tests\\NClient.Tests.csproj" ] } -} \ No newline at end of file +} From 47e3c3367dc0716218f19e89d34cd617da2a5501 Mon Sep 17 00:00:00 2001 From: Denis Suleymanov Date: Sat, 23 Jul 2022 12:50:23 +0300 Subject: [PATCH 11/11] fix after merge --- .../NClient.Standalone/Client/TransportNClientFactory.cs | 4 ++-- .../ClientProxy/Building/NClientOptionalBuilder.cs | 4 ++-- .../Generation/Interceptors/ClientInterceptor.cs | 2 +- .../Generation/Interceptors/ClientInterceptorFactory.cs | 7 ++++--- .../RedisCacheWorkerTest.cs | 8 +------- 5 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs b/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs index 4f3ed1ac9..05d5597e5 100644 --- a/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs +++ b/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs @@ -35,7 +35,7 @@ public TransportNClientFactory( IResponseMapperProvider responseMapperProvider, IResponseMapperProvider transportResponseMapperProvider, IResponseValidatorProvider responseValidatorProvider, - IResponseCacheProvider? transportResponseCacheProvider, + //IResponseCacheProvider? transportResponseCacheProvider, IToolset toolset) { _transportProvider = transportProvider; @@ -46,7 +46,7 @@ public TransportNClientFactory( _responseMapperProvider = responseMapperProvider; _transportResponseMapperProvider = transportResponseMapperProvider; _responseValidatorProvider = responseValidatorProvider; - _transportResponseCacheProvider = transportResponseCacheProvider; + //_transportResponseCacheProvider = transportResponseCacheProvider; _toolset = toolset; } diff --git a/src/NClient/NClient.Standalone/ClientProxy/Building/NClientOptionalBuilder.cs b/src/NClient/NClient.Standalone/ClientProxy/Building/NClientOptionalBuilder.cs index bab516d1d..b293bb5c5 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Building/NClientOptionalBuilder.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Building/NClientOptionalBuilder.cs @@ -188,10 +188,10 @@ public INClientOptionalBuilder WithAdvancedRespons public TClient Build() { _context.EnsureComplete(); - new ClientValidator(_proxyGeneratorProvider.Value) + /*new ClientValidator(_proxyGeneratorProvider.Value) .EnsureAsync(_clientInterceptorFactory) .GetAwaiter() - .GetResult(); + .GetResult();*/ var interceptor = _clientInterceptorFactory.Create(_context); return _clientProxyGenerator.CreateClient(interceptor); diff --git a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs index c6278baad..74d4b9946 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs @@ -169,7 +169,7 @@ protected override async Task InterceptAsync( } } - private async Task ExecuteHttpResponseAsync(ITransportNClient transportNClient, IRequest request, Type resultType, IResiliencePolicy? resiliencePolicy, ICachingAttribute? cachingAttribute = default, CancellationToken cancellationToken) + private async Task ExecuteHttpResponseAsync(ITransportNClient transportNClient, IRequest request, Type resultType, IResiliencePolicy? resiliencePolicy, ICachingAttribute? cachingAttribute = default, CancellationToken cancellationToken = default) { if (await _responseCacheWorker.TryGet(request, cancellationToken) is { } cachedResult) { diff --git a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs index e860d2920..7db75c68d 100644 --- a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs +++ b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptorFactory.cs @@ -51,6 +51,7 @@ public ClientInterceptorFactory(IProxyGenerator proxyGenerator) new PathAttributeProvider(attributeMapper, clientValidationExceptionFactory), new MetadataAttributeProvider(clientValidationExceptionFactory), new TimeoutAttributeProvider(attributeMapper, clientValidationExceptionFactory), + new CachingAttributeProvider(attributeMapper, clientValidationExceptionFactory), new MethodParamBuilder(new ParamAttributeProvider(attributeMapper, clientValidationExceptionFactory))); } @@ -70,11 +71,11 @@ public IAsyncInterceptor Create(BuilderContext(builderContext.ResponseMapperProviders); var transportResponseMapperProvider = new CompositeResponseMapperProvider(builderContext.TransportResponseMapperProviders); var compositeResponseValidatorProvider = new CompositeResponseValidatorProvider(builderContext.ResponseValidatorProviders); + var responseCacheWorker = builderContext.CacheProvider?.Create(toolset); var methodResiliencePolicyProviderAdapter = new MethodResiliencePolicyProviderAdapter( new StubResiliencePolicyProvider(), builderContext.MethodsWithResiliencePolicy.Reverse()); - var cachingAttributeProvider = new CachingAttributeProvider(builderContext.CacheProvider); - + return new ClientInterceptor( builderContext.Host, _timeoutSelector, @@ -93,10 +94,10 @@ public IAsyncInterceptor Create(BuilderContext(); dbMock.Setup(x => x.StringGetAsync(It.IsAny(), CommandFlags.None)).ReturnsAsync(It.IsAny()); @@ -31,14 +26,13 @@ public void Caching_RedisCaching_NotThrow() using var api = CachingApiMockFactory.MockGetAsyncMethod(id, entity); var result = NClientGallery.Clients.GetRest().For(host: api.Urls.First()) - .WithNewtonsoftJsonSerialization() + .WithSystemTextJsonSerialization() .WithRedisCaching(dbMock.Object) .Build() .GetIResponse(id); result.IsSuccessful.Should().BeTrue(); result.Data.Should().BeEquivalentTo(entity); - result.Content.Bytes.Should().NotBeEquivalentTo(camelCaseContentBytes); dbMock.Verify(mock => mock.StringSetAsync(It.IsAny(), It.IsAny(), It.IsAny(), When.Always, CommandFlags.None), Times.Once()); }