Skip to content

Commit

Permalink
Merge pull request #65 from nventive/dev/jpl/cache
Browse files Browse the repository at this point in the history
feat: Add support for simple application caching.
  • Loading branch information
jeanplevesque authored Oct 20, 2022
2 parents c827f33 + 26bdb76 commit 1716f5c
Show file tree
Hide file tree
Showing 15 changed files with 1,044 additions and 19 deletions.
14 changes: 12 additions & 2 deletions MallardMessageHandlers.sln
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29905.134
# Visual Studio Version 17
VisualStudioVersion = 17.3.32901.215
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MallardMessageHandlers", "src\MallardMessageHandlers\MallardMessageHandlers.csproj", "{8EEF328D-CBC8-45DB-9526-904CC58D2F57}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MallardMessageHandlers.Tests", "src\MallardMessageHandlers.Tests\MallardMessageHandlers.Tests.csproj", "{47E50E30-646E-441B-B2EF-77A33CA4D4B1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MallardMessageHandlers.Refit", "src\MallardMessageHandlers.Refit\MallardMessageHandlers.Refit.csproj", "{3032964A-167E-4DF5-B279-B88E504A282F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -31,6 +33,14 @@ Global
{47E50E30-646E-441B-B2EF-77A33CA4D4B1}.Release|Any CPU.Build.0 = Release|Any CPU
{47E50E30-646E-441B-B2EF-77A33CA4D4B1}.Release|NuGet.ActiveCfg = Release|Any CPU
{47E50E30-646E-441B-B2EF-77A33CA4D4B1}.Release|NuGet.Build.0 = Release|Any CPU
{3032964A-167E-4DF5-B279-B88E504A282F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3032964A-167E-4DF5-B279-B88E504A282F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3032964A-167E-4DF5-B279-B88E504A282F}.Debug|NuGet.ActiveCfg = Debug|Any CPU
{3032964A-167E-4DF5-B279-B88E504A282F}.Debug|NuGet.Build.0 = Debug|Any CPU
{3032964A-167E-4DF5-B279-B88E504A282F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3032964A-167E-4DF5-B279-B88E504A282F}.Release|Any CPU.Build.0 = Release|Any CPU
{3032964A-167E-4DF5-B279-B88E504A282F}.Release|NuGet.ActiveCfg = Release|Any CPU
{3032964A-167E-4DF5-B279-B88E504A282F}.Release|NuGet.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
79 changes: 71 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ This repository contains multiple implementations of `DelegatingHandlers` for di
Here is a list of the `DelegatingHandlers` provided.

- [NetworkExceptionHandler](#NetworkExceptionHandler) : Throws an exception if the HttpRequest fails and there is no network.
- [SimpleCacheHandler](#SimpleCacheHandler) : Implement simple application caching using instructions from custom HTTP headers.
- [ExceptionHubHandler](#ExceptionHubHandler) : Reports all exceptions that occur on the pipeline.
- [ExceptionInterpreterHandler](#ExceptionInterpreterHandler) : Interprets error responses and converts them to exceptions.
- [AuthenticationTokenHandler](#AuthenticationTokenHandler) : Adds the authentication token to the authorization header.
Expand Down Expand Up @@ -90,6 +91,74 @@ private void ConfigureNetworkExceptionHandler(IServiceCollection services)
}
```

### SimpleCacheHandler

The `SimpleCacheHandler` is a `DelegatingHandler` that executes custom caching instructions.

When you use Refit for your endpoints declaration, you can neatly specify caching instructions with attributes.
Just install the the `MallardMessageHandler.Refit` package to get those Refit-compatible attributes.

- You can specify time-to-live at different levels.
- On a per-call level (using attributes)
- Globally (using default headers)
- You can support force-refresh scenarios (i.e. don't read the cache, but update it).
This can be useful for things like pull-to-refresh.
- You can disable the cache on a per-call level.
This only makes sense when you define default caching globally.

```csharp
using MallardMessageHandlers.SimpleCaching;

public interface ISampleEndpoint
{
[Get("/sample")]
Task<string> GetSampleDefault(CancellationToken ct, [ForceRefresh] bool forceRefresh = false);

[Get("/sample")]
[TimeToLive(totalMinutes: 5)] // You can customize the TTL on a per-call basis.
Task<string> GetSampleCustomTTL(CancellationToken ct, [ForceRefresh] bool forceRefresh = false);

[Get("/sample")]
[NoCache] // When you have a default TTL, you can bypass it on a per-call basis.
Task<string> GetSampleNoCache(CancellationToken ct);
}
```

Here's how you can configure a default time-to-live for all calls.
```csharp
serviceCollection
.AddRefitClient<ISampleEndpoint>()
.ConfigureHttpClient((client) =>
{
// You can configure a default time-to-live for all calls.
// "600" represents 10 minutes (600 seconds).
client.DefaultRequestHeaders.Add(SimpleCacheHandler.CacheTimeToLiveHeaderName, "600");
});
```

The `SimpleCacheHandler` has a few dependencies.
- `ISimpleCacheService` which implements the actual caching of data.
- The interface is pretty simple, so you can easily create implementations.
- You can also use our `MemorySimpleCacheService` implementation.
- `ISimpleCacheKeyProvider` which generates the cache keys from the `HttpMessageRequest` objects.
- You can use the `SimpleCacheKeyProvider` to create your keys using a custom `Func<HttpRequestMessage,string>`.
- You can also use one of our built-in implementations:
- `SimpleCacheKeyProvider.FromUriOnly`
- `SimpleCacheKeyProvider.FromUriAndAuthorizationHash`

```csharp
private void ConfigureCacheHandler(IServiceCollection services)
{
// The ISimpleCacheService and ISimpleCacheKeyProvider are shared for all HttpRequests so we add them as singleton.
services
.AddSingleton<ISimpleCacheService, MemorySimpleCacheService>();
.AddSingleton<ISimpleCacheKeyProvider>(CacheKeyProvider.FromUriOnly);

// The SimpleCacheHandler must be recreated for all HttpRequests so we add it as transient.
services.AddTransient<SimpleCacheHandler>();
}
```

### ExceptionHubHandler

The `ExceptionHubHandler` is a `DelegatingHandler` that will report all exceptions thrown during the execution of the `HttpRequest` to an `IExceptionHub`.
Expand Down Expand Up @@ -232,10 +301,9 @@ private void ConfigureAuthenticationTokenHandler(IServiceCollection services)
}
```

## Changelog
## Breaking Changes

Please consult the [CHANGELOG](CHANGELOG.md) for more information about version
history.
Please consult the [BREAKING_CHANGES.md](BREAKING_CHANGES.md) for more information about breaking changes and version history.

## License

Expand All @@ -248,8 +316,3 @@ Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on the process for
contributing to this project.

Be mindful of our [Code of Conduct](CODE_OF_CONDUCT.md).

## Contributors

<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<RootNamespace>MallardMessageHandlers</RootNamespace>
<Authors>nventive</Authors>
<Company>nventive</Company>
<AssemblyName>MallardMessageHandlers.Refit</AssemblyName>
<PackageId>MallardMessageHandlers.Refit</PackageId>
<Description>MallardMessageHandlers.Refit</Description>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>10</LangVersion>
<PackageTags>delegatinghandler;oauth;network-status;network;networkexception;authenticationtoken;exceptioninterpreter;exceptionhub;refit</PackageTags>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/nventive/MallardMessageHandlers</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>

<!--Needed for Source Link support -->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>

<ItemGroup>
<None Include="..\..\README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Refit" Version="6.0.1" />
</ItemGroup>

<ItemGroup>
<!--Needed for Source Link support -->
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\MallardMessageHandlers\MallardMessageHandlers.csproj" />
</ItemGroup>
</Project>
62 changes: 62 additions & 0 deletions src/MallardMessageHandlers.Refit/SimpleCaching/RefitAttributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Text;
using Refit;

namespace MallardMessageHandlers.SimpleCaching
{
/// <summary>
/// Sets the cache time-to-live for the current call.
/// </summary>
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Method)]
public class TimeToLiveAttribute : HeadersAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="TimeToLiveAttribute"/> class.
/// </summary>
/// <param name="totalSeconds">The time-to-live in seconds.</param>
public TimeToLiveAttribute(int totalSeconds)
: base(SimpleCacheHandler.CacheTimeToLiveHeaderName + ":" + totalSeconds)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="TimeToLiveAttribute"/> class.
/// </summary>
/// <param name="totalMinutes">The time-to-live in minutes.</param>
public TimeToLiveAttribute(double totalMinutes)
: base(SimpleCacheHandler.CacheTimeToLiveHeaderName + ":" + (int)(totalMinutes * 60))
{
}
}

/// <summary>
/// Bypasses the other simple caching instructions.
/// </summary>
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Method)]
public class NoCacheAttribute : HeadersAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="NoCacheAttribute"/> class.
/// </summary>
public NoCacheAttribute()
: base(SimpleCacheHandler.CacheDisableHeaderName + ":true")
{
}
}

/// <summary>
/// Associates the force-refresh simple caching instruction with a boolean parameter.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public class ForceRefresh : HeaderAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="ForceRefresh"/> class.
/// </summary>
public ForceRefresh()
: base(SimpleCacheHandler.CacheForceRefreshHeaderName)
{
}
}
}
Loading

0 comments on commit 1716f5c

Please sign in to comment.