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/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
+}
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..526ebecf9
--- /dev/null
+++ b/src/NClient.Providers/NClient.Providers.Caching.Redis/Extensions/RedisCacheWorkerExtensions.cs
@@ -0,0 +1,24 @@
+using NClient.Common.Helpers;
+using NClient.Providers.Caching.Redis;
+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.WithAdvancedResponseCaching(selector =>
+ {
+ selector.ForClient().Use(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.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..ae9b8bc4a
--- /dev/null
+++ b/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorker.cs
@@ -0,0 +1,69 @@
+using System;
+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;
+ private readonly IToolset _toolset;
+
+ public RedisCacheWorker(IDatabaseAsync redisDb, IToolset toolset)
+ {
+ Ensure.IsNotNull(redisDb, nameof(redisDb));
+
+ _redisDb = redisDb;
+ _toolset = toolset;
+ }
+ public async Task FindAsync(IRequest request, CancellationToken cancellationToken = default)
+ {
+ Ensure.IsNotNull(request, nameof(request));
+
+ var serializedResponse = (await _redisDb.StringGetAsync(GenerateKey(request))).ToString();
+
+ if (string.IsNullOrEmpty(serializedResponse))
+ return default;
+
+ return (IResponse) _toolset.Serializer.Deserialize(serializedResponse, typeof(IResponse))!;
+ }
+ public async Task PutAsync(IRequest request, IResponse response, TimeSpan? lifeTime = null, CancellationToken cancellationToken = default)
+ {
+ Ensure.IsNotNull(request, nameof(request));
+ Ensure.IsNotNull(response, nameof(response));
+
+ if (lifeTime?.TotalMilliseconds <= 0)
+ return;
+
+ var serializedResponse = _toolset.Serializer.Serialize(response);
+
+ if (!await _redisDb.StringSetAsync(GenerateKey(request), serializedResponse, lifeTime))
+ throw new InvalidOperationException("Couldn't save data to Redis");
+ }
+
+ private string GenerateKey(IRequest request)
+ {
+ Ensure.IsNotNull(request, nameof(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..8ccef580e
--- /dev/null
+++ b/src/NClient.Providers/NClient.Providers.Caching.Redis/RedisCacheWorkerProvider.cs
@@ -0,0 +1,30 @@
+using NClient.Common.Helpers;
+using NClient.Providers.Serialization;
+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, toolset);
+ }
+ }
+}
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/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..37e1ec1d5
--- /dev/null
+++ b/src/NClient/NClient.Abstractions/Building/Configuration/Caching/INClientResponseCachingSetter.cs
@@ -0,0 +1,19 @@
+using NClient.Providers.Caching;
+
+// 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..4908b64d1
--- /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 fb42656d7..2aaec88c9 100644
--- a/src/NClient/NClient.Abstractions/Building/INClientOptionalBuilder.cs
+++ b/src/NClient/NClient.Abstractions/Building/INClientOptionalBuilder.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using NClient.Providers.Authorization;
+using NClient.Providers.Caching;
using NClient.Providers.Handling;
using NClient.Providers.Mapping;
using NClient.Providers.Serialization;
@@ -108,6 +109,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(IResponseCacheProvider cacheProvider);
+
+ /// 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..7e8442df4
--- /dev/null
+++ b/src/NClient/NClient.Abstractions/Extensions/Caching/ExtraCachingExtensions.cs
@@ -0,0 +1,46 @@
+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
+{
+ 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 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);
+ }
+
+ public static async Task Put(this IResponseCacheWorker? worker, IRequest request, IResponse response, ICachingAttribute? cachingAttribute, CancellationToken cancellationToken = default)
+ {
+ if (worker is not null)
+ await worker.PutAsync(request, response, TimeSpan.FromMilliseconds(cachingAttribute?.Milliseconds ?? 0), cancellationToken);
+ }
+
+ public static async Task 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.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.Abstractions/Providers/Caching/IResponseCacheProvider.cs b/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheProvider.cs
new file mode 100644
index 000000000..039732d5f
--- /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..47748bec7
--- /dev/null
+++ b/src/NClient/NClient.Abstractions/Providers/Caching/IResponseCacheWorker.cs
@@ -0,0 +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(IRequest request, CancellationToken cancellationToken = default);
+ public Task PutAsync(IRequest request, IResponse response, TimeSpan? lifeTime = null, CancellationToken cancellationToken = default);
+ }
+}
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/Client/Caching/ResponseCacheProvider.cs b/src/NClient/NClient.Standalone/Client/Caching/ResponseCacheProvider.cs
new file mode 100644
index 000000000..b5ddd45fb
--- /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 5595418c3..9c7bd2043 100644
--- a/src/NClient/NClient.Standalone/Client/TransportNClient.cs
+++ b/src/NClient/NClient.Standalone/Client/TransportNClient.cs
@@ -3,6 +3,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using NClient.Providers.Caching;
using NClient.Common.Helpers;
using NClient.Providers.Handling;
using NClient.Providers.Mapping;
@@ -36,6 +37,7 @@ internal class TransportNClient : ITransportNClient _transportResponseMapper;
private readonly IResponseMapper _responseMapper;
private readonly IResponseValidator _responseValidator;
+ private readonly IResponseCacheWorker? _transportResponseCacheWorker;
private readonly ILogger _logger;
public TimeSpan Timeout => _transport.Timeout;
@@ -49,7 +51,8 @@ public TransportNClient(
IResiliencePolicy resiliencePolicy,
IResponseMapper responseMapper,
IResponseMapper transportResponseMapper,
- IResponseValidator responseValidator,
+ IResponseValidator responseValidator,
+ IResponseCacheWorker? transportResponseCacheWorker,
ILogger logger)
{
_serializer = serializer;
@@ -61,6 +64,7 @@ public TransportNClient(
_responseMapper = responseMapper;
_transportResponseMapper = transportResponseMapper;
_responseValidator = responseValidator;
+ _transportResponseCacheWorker = transportResponseCacheWorker;
_logger = logger;
}
diff --git a/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs b/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs
index 8af845434..05d5597e5 100644
--- a/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs
+++ b/src/NClient/NClient.Standalone/Client/TransportNClientFactory.cs
@@ -1,4 +1,5 @@
-using NClient.Providers;
+using NClient.Providers;
+using NClient.Providers.Caching;
using NClient.Providers.Handling;
using NClient.Providers.Mapping;
using NClient.Providers.Resilience;
@@ -22,6 +23,7 @@ internal class TransportNClientFactory : ITransportNClientF
private readonly IResponseMapperProvider _responseMapperProvider;
private readonly IResponseMapperProvider _transportResponseMapperProvider;
private readonly IResponseValidatorProvider _responseValidatorProvider;
+ private readonly IResponseCacheProvider? _transportResponseCacheProvider;
private readonly IToolset _toolset;
public TransportNClientFactory(
@@ -33,6 +35,7 @@ public TransportNClientFactory(
IResponseMapperProvider responseMapperProvider,
IResponseMapperProvider transportResponseMapperProvider,
IResponseValidatorProvider responseValidatorProvider,
+ //IResponseCacheProvider? transportResponseCacheProvider,
IToolset toolset)
{
_transportProvider = transportProvider;
@@ -43,6 +46,7 @@ public TransportNClientFactory(
_responseMapperProvider = responseMapperProvider;
_transportResponseMapperProvider = transportResponseMapperProvider;
_responseValidatorProvider = responseValidatorProvider;
+ //_transportResponseCacheProvider = transportResponseCacheProvider;
_toolset = toolset;
}
@@ -58,6 +62,7 @@ public ITransportNClient Create()
_responseMapperProvider.Create(_toolset),
_transportResponseMapperProvider.Create(_toolset),
_responseValidatorProvider.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..6e1bc0703
--- /dev/null
+++ b/src/NClient/NClient.Standalone/ClientProxy/Building/Configuration/Caching/NClientResponseCachingSetter.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 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..292c93557
--- /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 88bb6b6db..14f6a0a8f 100644
--- a/src/NClient/NClient.Standalone/ClientProxy/Building/Context/BuilderContext.cs
+++ b/src/NClient/NClient.Standalone/ClientProxy/Building/Context/BuilderContext.cs
@@ -5,6 +5,7 @@
using NClient.Invocation;
using NClient.Providers.Api;
using NClient.Providers.Authorization;
+using NClient.Providers.Caching;
using NClient.Providers.Handling;
using NClient.Providers.Mapping;
using NClient.Providers.Resilience;
@@ -41,6 +42,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; }
@@ -84,6 +87,9 @@ public BuilderContext(BuilderContext builderContext)
ResponseMapperProviders = builderContext.ResponseMapperProviders.ToArray();
TransportResponseMapperProviders = builderContext.TransportResponseMapperProviders.ToArray();
+
+ CacheProvider = builderContext.CacheProvider;
+ TransportCacheProvider = builderContext.TransportCacheProvider;
Timeout = builderContext.Timeout;
@@ -270,6 +276,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 bf47a3777..b293bb5c5 100644
--- a/src/NClient/NClient.Standalone/ClientProxy/Building/NClientOptionalBuilder.cs
+++ b/src/NClient/NClient.Standalone/ClientProxy/Building/NClientOptionalBuilder.cs
@@ -5,11 +5,13 @@
using NClient.Common.Helpers;
using NClient.Core.Proxy;
using NClient.Providers.Authorization;
+using NClient.Providers.Caching;
using NClient.Providers.Handling;
using NClient.Providers.Mapping;
using NClient.Providers.Serialization;
using NClient.Providers.Validation;
using NClient.Standalone.Client.Authorization;
+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;
@@ -167,14 +169,29 @@ public INClientOptionalBuilder WithoutLogging()
return new NClientOptionalBuilder(_context
.WithoutLogging());
}
+
+ public INClientOptionalBuilder WithResponseCaching(IResponseCacheProvider cacheProvider)
+ {
+ return WithAdvancedResponseCaching(x => x
+ .ForTransport().Use(cacheProvider));
+ }
+
+ 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()
{
_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 58cf89650..74d4b9946 100644
--- a/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs
+++ b/src/NClient/NClient.Standalone/ClientProxy/Generation/Interceptors/ClientInterceptor.cs
@@ -5,10 +5,12 @@
using System.Threading.Tasks;
using Castle.DynamicProxy;
using Microsoft.Extensions.Logging;
+using NClient.Annotations;
using NClient.Core.Helpers;
using NClient.Exceptions;
using NClient.Providers;
using NClient.Providers.Api;
+using NClient.Providers.Caching;
using NClient.Providers.Authorization;
using NClient.Providers.Resilience;
using NClient.Providers.Transport;
@@ -34,6 +36,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;
@@ -48,7 +51,8 @@ public ClientInterceptor(
IRequestBuilderProvider requestBuilderProvider,
ITransportNClientFactory transportNClientFactory,
IMethodResiliencePolicyProvider methodResiliencePolicyProvider,
- IClientRequestExceptionFactory clientRequestExceptionFactory,
+ IClientRequestExceptionFactory clientRequestExceptionFactory,
+ IResponseCacheWorker? responseCacheWorker,
TimeSpan? timeout,
IToolset toolset)
{
@@ -63,6 +67,7 @@ public ClientInterceptor(
_transportNClientFactory = transportNClientFactory;
_methodResiliencePolicyProvider = methodResiliencePolicyProvider;
_clientRequestExceptionFactory = clientRequestExceptionFactory;
+ _responseCacheWorker = responseCacheWorker;
_timeout = timeout;
_toolset = toolset;
}
@@ -128,8 +133,8 @@ protected override async Task InterceptAsync(
var resiliencePolicy = methodInvocation.ResiliencePolicyProvider?.Create(_toolset)
?? _methodResiliencePolicyProvider.Create(methodInvocation.Method, request, _toolset);
-
- var result = await ExecuteHttpResponseAsync(transportNClient, request, resultType, resiliencePolicy, combinedCancellationToken).ConfigureAwait(false);
+
+ var result = await ExecuteHttpResponseAsync(transportNClient, request, resultType, resiliencePolicy, method.CachingAttribute, combinedCancellationToken).ConfigureAwait(false);
_toolset.Logger?.LogDebug("Processing request finished");
return result;
}
@@ -164,33 +169,59 @@ protected override async Task InterceptAsync(
}
}
- private static async Task