Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions NClient.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down
3 changes: 2 additions & 1 deletion Tests.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -22,4 +23,4 @@
"tests\\NClient\\NClient.Tests\\NClient.Tests.csproj"
]
}
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>Sets the Redis cache for the client.</summary>
public static INClientOptionalBuilder<TClient, TRequest, TResponse> WithRedisCaching<TClient, TRequest, TResponse>(
this INClientOptionalBuilder<TClient, TRequest, TResponse> optionalBuilder,
IDatabaseAsync dataBase)
where TClient : class
{
Ensure.IsNotNull(optionalBuilder, nameof(optionalBuilder));

return optionalBuilder.WithAdvancedResponseCaching(selector =>
{
selector.ForClient().Use(new RedisCacheWorkerProvider(dataBase));
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using NClient.Providers.Caching.Redis;
using NClient.Providers.Transport;
using StackExchange.Redis;

namespace NClient
{
public static class UseRedisCacheWorkerExtensions
{
/// <summary>Sets the cache worker that can store data to Redis.</summary>
public static INClientResponseCachingSelector<IRequest, IResponse> UseRedisCaching(
this INClientTransportResponseCachingSetter<IRequest, IResponse> optionalBuilder, IDatabaseAsync dataBase)
{
return optionalBuilder.Use(new RedisCacheWorkerProvider(dataBase));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<PackageId>$(AssemblyName)</PackageId>
<VersionPrefix>$(VersionPrefix)</VersionPrefix>
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
<PackageIcon>logo.png</PackageIcon>
<PackageProjectUrl>https://nclient.github.io</PackageProjectUrl>
<RepositoryUrl>https://github.com/nclient/nclient</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>NClient;NClient.Providers;NClient.Providers.Caching;Redis</PackageTags>
<Description>The provider that allows you to use the Redis for caching NClient requests.</Description>
<Authors>kingmidas74</Authors>
<Copyright>Copyright (c) $([System.DateTime]::Now.ToString('yyyy')) NClient</Copyright>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<IsPackable>true</IsPackable>
</PropertyGroup>

<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<LangVersion>9.0</LangVersion>
<Nullable>enable</Nullable>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<Configurations>Debug;Release</Configurations>
<Platforms>AnyCPU</Platforms>
<OutputPath>$(SolutionDir)/bin/src/$(Configuration)/$(AssemblyName)</OutputPath>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\NClient\NClient.Abstractions\NClient.Abstractions.csproj" />
</ItemGroup>

<ItemGroup>
<None Include="..\..\..\logo.png" Pack="true" PackagePath="" />
<None Include="..\..\..\LICENSE" Pack="true" PackagePath="" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\..\NClient\NClient.Common\Helpers\Ensure.cs">
<Link>Common\Ensure.cs</Link>
</Compile>
</ItemGroup>

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>NClient.Providers.Caching.Redis.Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>

<ItemGroup>
<PackageReference Include="StackExchange.Redis" Version="2.5.43" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -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<IResponse?> 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using NClient.Common.Helpers;
using NClient.Providers.Serialization;
using StackExchange.Redis;

namespace NClient.Providers.Caching.Redis
{
/// <summary>
/// The provider of the cache worker that store HTTP response data to Redis.
/// </summary>
public class RedisCacheWorkerProvider : IResponseCacheProvider
{
private readonly IDatabaseAsync _redisDb;

/// <summary>Initializes the Redis cache worker based serializer provider.</summary>
/// <param name="redisDb">The redis database.</param>
public RedisCacheWorkerProvider(IDatabaseAsync redisDb)
{
Ensure.IsNotNull(redisDb, nameof(redisDb));

_redisDb = redisDb;
}

/// <summary>Creates System.Text.Json <see cref="ISerializer"/> instance.</summary>
/// <param name="toolset">Tools that help implement providers.</param>
public IResponseCacheWorker Create(IToolset toolset)
{
return new RedisCacheWorker(_redisDb, toolset);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace NClient.Annotations
{
/// <summary>Identifies an action that should be cached.</summary>
public interface ICachingAttribute
{
/// <summary>The life time of cache.</summary>
double Milliseconds { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// ReSharper disable once CheckNamespace

namespace NClient
{
/// <summary>Selector for configuring mapping on the selected client layer.</summary>
/// <typeparam name="TRequest">The type of request that is used in the transport implementation.</typeparam>
/// <typeparam name="TResponse">The type of response that is used in the transport implementation.</typeparam>
public interface INClientResponseCachingSelector<TRequest, TResponse>
{
/// <summary>Select NClient layer.</summary>
INClientResponseCachingSetter<TRequest, TResponse> ForClient();

/// <summary>Select transport layer.</summary>
INClientTransportResponseCachingSetter<TRequest, TResponse> ForTransport();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using NClient.Providers.Caching;

// ReSharper disable once CheckNamespace
namespace NClient
{
/// <summary>Setter for custom functionality to mapping NClient requests.</summary>
/// <typeparam name="TRequest">The type of request that is used in the transport implementation.</typeparam>
/// <typeparam name="TResponse">The type of response that is used in the transport implementation.</typeparam>
public interface INClientResponseCachingSetter<TRequest, TResponse>
{
/// <summary>Sets a custom mappers that can convert NClient responses into custom ones.</summary>
/// <param name="cacheWorkers">The mappers that convert transport responses into custom results.</param>
INClientResponseCachingSelector<TRequest, TResponse> Use(IResponseCacheWorker cacheWorkers);

/// <summary>Sets a providers creating custom mappers that can convert NClient responses into custom ones.</summary>
/// <param name="cacheProvider">The providers of a mappers that convert transport responses into custom results.</param>
INClientResponseCachingSelector<TRequest, TResponse> Use(IResponseCacheProvider cacheProvider);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using NClient.Providers.Caching;

// ReSharper disable once CheckNamespace
namespace NClient
{
/// <summary>Setter for custom functionality to mapping transport requests.</summary>
/// <typeparam name="TRequest">The type of request that is used in the transport implementation.</typeparam>
/// <typeparam name="TResponse">The type of response that is used in the transport implementation.</typeparam>
public interface INClientTransportResponseCachingSetter<TRequest, TResponse>
{
/// <summary>Sets a custom mappers that can convert NClient responses into custom ones.</summary>
/// <param name="cacheWorker">The mapper that convert transport responses into custom results.</param>
INClientResponseCachingSelector<TRequest, TResponse> Use(IResponseCacheWorker cacheWorker);

/// <summary>Sets a providers creating custom mappers that can convert NClient responses into custom ones.</summary>
/// <param name="cacheProvider">The provider of a mappers that convert transport responses into custom results.</param>
INClientResponseCachingSelector<TRequest, TResponse> Use(IResponseCacheProvider cacheProvider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -108,6 +109,17 @@ public interface INClientOptionalBuilder<TClient, TRequest, TResponse> where TCl
/// <summary>Removes logging of client actions.</summary>
INClientOptionalBuilder<TClient, TRequest, TResponse> WithoutLogging();

#endregion

#region Caching

/// <summary>Sets cache workers that caching NClient responses into custom storage.</summary>
/// <param name="cacheProvider">The collection of cache workers that caching NClient responses into custom storage.</param>
INClientOptionalBuilder<TClient, TRequest, TResponse> WithResponseCaching(IResponseCacheProvider cacheProvider);

/// <summary>Sets advanced cache workers that caching NClient responses into custom storage.</summary>
INClientOptionalBuilder<TClient, TRequest, TResponse> WithAdvancedResponseCaching(Action<INClientResponseCachingSelector<TRequest, TResponse>> configure);

#endregion

/// <summary>Creates instance of <see cref="TClient"/>.</summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>Sets the mappers that convert NClient responses into custom results.</summary>
/// <param name="responseCachingSetter"></param>
/// <param name="responseCacheWorker">The responseCacheWorker that converts transport responses into custom results.</param>
public static INClientResponseCachingSelector<TRequest, TResponse> Use<TRequest, TResponse>(
this INClientResponseCachingSetter<TRequest, TResponse> responseCachingSetter,
IResponseCacheWorker responseCacheWorker)
{
return responseCachingSetter.Use(responseCacheWorker);
}

/// <summary>Sets the mappers that convert NClient responses into custom results.</summary>
/// <param name="transportResponseCachingSetter"></param>
/// <param name="responseCacheWorker">The responseCacheWorker that converts transport responses into custom results.</param>
public static INClientResponseCachingSelector<TRequest, TResponse> Use<TRequest, TResponse>(
this INClientTransportResponseCachingSetter<TRequest, TResponse> 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<IResponse?> TryGet(this IResponseCacheWorker? worker, IRequest request, CancellationToken cancellationToken = default)
{
if (worker is null)
return null;
return await worker.FindAsync(request, cancellationToken);
}
}
}
Loading