Skip to content

Commit 09a9091

Browse files
Merge pull request #10 from steuic/develop
Added support for custom cache key provider
2 parents a402465 + 1bddecd commit 09a9091

19 files changed

+770
-67
lines changed

.editorconfig

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
####################################################################
2+
# Editor Configuration (Updated 2022-01-07)
3+
#
4+
# (c)2021 superdev GmbH
5+
####################################################################
6+
7+
root = true
8+
9+
[*.cs]
10+
csharp_indent_block_contents = true
11+
csharp_indent_braces = false
12+
csharp_indent_case_contents = true
13+
csharp_indent_labels = one_less_than_current
14+
csharp_indent_switch_labels = true
15+
16+
csharp_new_line_before_catch = true
17+
csharp_new_line_before_else = true
18+
csharp_new_line_before_finally = true
19+
csharp_new_line_before_members_in_anonymous_types = true
20+
csharp_new_line_before_members_in_object_initializers = true
21+
csharp_new_line_before_open_brace = all
22+
csharp_new_line_between_query_expression_clauses = true
23+
24+
csharp_prefer_braces = true:error
25+
csharp_prefer_simple_default_expression = true:error
26+
csharp_prefer_simple_using_statement = false
27+
28+
csharp_preserve_single_line_blocks = true
29+
csharp_preserve_single_line_statements = false
30+
31+
csharp_space_after_cast = false
32+
csharp_space_after_colon_in_inheritance_clause = true
33+
csharp_space_after_comma = true
34+
csharp_space_after_dot = false
35+
36+
csharp_space_after_keywords_in_control_flow_statements = true
37+
csharp_space_after_semicolon_in_for_statement = true
38+
csharp_space_around_binary_operators = before_and_after
39+
csharp_space_around_declaration_statements = do_not_ignore
40+
csharp_space_before_colon_in_inheritance_clause = true
41+
csharp_space_before_comma = false
42+
csharp_space_before_dot = false
43+
csharp_space_before_open_square_brackets = false
44+
csharp_space_before_semicolon_in_for_statement = false
45+
csharp_space_between_empty_square_brackets = false
46+
csharp_space_between_method_call_empty_parameter_list_parentheses = false
47+
csharp_space_between_method_call_name_and_opening_parenthesis = false
48+
csharp_space_between_method_call_parameter_list_parentheses = false
49+
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
50+
csharp_space_between_method_declaration_name_and_open_parenthesis = false
51+
csharp_space_between_method_declaration_parameter_list_parentheses = false
52+
csharp_space_between_parentheses = none
53+
csharp_space_between_square_brackets = false
54+
55+
# Expression-bodied members
56+
csharp_style_expression_bodied_accessors = true:none
57+
csharp_style_expression_bodied_constructors = false:none
58+
csharp_style_expression_bodied_indexers = true:none
59+
csharp_style_expression_bodied_lambdas = true:none
60+
csharp_style_expression_bodied_local_functions = false
61+
csharp_style_expression_bodied_methods = false:none
62+
csharp_style_expression_bodied_operators = false:none
63+
csharp_style_expression_bodied_properties = true:silent
64+
65+
csharp_style_conditional_delegate_call = true:error
66+
csharp_style_inlined_variable_declaration = true:error
67+
csharp_style_pattern_matching_over_as_with_null_check = true:error
68+
csharp_style_pattern_matching_over_is_with_cast_check = true:error
69+
csharp_style_throw_expression = true:suggestion
70+
csharp_style_var_elsewhere = true:suggestion
71+
csharp_style_var_for_built_in_types = true:suggestion
72+
csharp_style_var_when_type_is_apparent = true:error
73+
csharp_style_implicit_object_creation_when_type_is_apparent = false
74+
csharp_style_prefer_switch_expression = false
75+
76+
dotnet_sort_system_directives_first = true
77+
dotnet_style_coalesce_expression = true:error
78+
dotnet_style_collection_initializer = true:error
79+
dotnet_style_explicit_tuple_names = true:error
80+
dotnet_style_null_propagation = true:error
81+
dotnet_style_object_initializer = true:none
82+
dotnet_style_predefined_type_for_locals_parameters_members = true:error
83+
dotnet_style_predefined_type_for_member_access = true:error
84+
85+
dotnet_style_qualification_for_event = true:error
86+
dotnet_style_qualification_for_field = true:error
87+
dotnet_style_qualification_for_method = true:error
88+
dotnet_style_qualification_for_property = true:error
89+
90+
end_of_line = crlf
91+
indent_size = 4
92+
indent_style = space
93+
insert_final_newline = false
94+
tab_width = 4
95+
96+
dotnet_naming_symbols.private_field_symbol.applicable_kinds = field
97+
dotnet_naming_symbols.private_field_symbol.applicable_accessibilities = private
98+
dotnet_naming_style.private_field_style.capitalization = camel_case
99+
dotnet_naming_rule.private_fields_are_camel_case.severity = error
100+
dotnet_naming_rule.private_fields_are_camel_case.symbols = private_field_symbol
101+
dotnet_naming_rule.private_fields_are_camel_case.style = private_field_style
102+
103+
dotnet_naming_symbols.non_private_field_symbol.applicable_kinds = field
104+
dotnet_naming_symbols.non_private_field_symbol.applicable_accessibilities = public,internal,friend,protected,protected_internal,protected_friend
105+
dotnet_naming_style.non_private_field_style.capitalization = pascal_case
106+
dotnet_naming_rule.non_private_fields_are_pascal_case.severity = error
107+
dotnet_naming_rule.non_private_fields_are_pascal_case.symbols = non_private_field_symbol
108+
dotnet_naming_rule.non_private_fields_are_pascal_case.style = non_private_field_style
109+
110+
dotnet_naming_symbols.parameter_symbol.applicable_kinds = parameter
111+
dotnet_naming_style.parameter_style.capitalization = camel_case
112+
dotnet_naming_rule.parameters_are_camel_case.severity = error
113+
dotnet_naming_rule.parameters_are_camel_case.symbols = parameter_symbol
114+
dotnet_naming_rule.parameters_are_camel_case.style = parameter_style
115+
116+
dotnet_naming_symbols.non_interface_type_symbol.applicable_kinds = class,struct,enum,delegate
117+
dotnet_naming_style.non_interface_type_style.capitalization = pascal_case
118+
dotnet_naming_rule.non_interface_types_are_pascal_case.severity = error
119+
dotnet_naming_rule.non_interface_types_are_pascal_case.symbols = non_interface_type_symbol
120+
dotnet_naming_rule.non_interface_types_are_pascal_case.style = non_interface_type_style
121+
122+
dotnet_naming_symbols.interface_type_symbol.applicable_kinds = interface
123+
dotnet_naming_style.interface_type_style.capitalization = pascal_case
124+
dotnet_naming_style.interface_type_style.required_prefix = I
125+
dotnet_naming_rule.interface_types_must_be_prefixed_with_I.severity = error
126+
dotnet_naming_rule.interface_types_must_be_prefixed_with_I.symbols = interface_type_symbol
127+
dotnet_naming_rule.interface_types_must_be_prefixed_with_I.style = interface_type_style
128+
129+
dotnet_naming_symbols.member_symbol.applicable_kinds = method,property,event
130+
dotnet_naming_style.member_style.capitalization = pascal_case
131+
dotnet_naming_rule.members_are_pascal_case.severity = error
132+
dotnet_naming_rule.members_are_pascal_case.symbols = member_symbol
133+
dotnet_naming_rule.members_are_pascal_case.style = member_style
134+
135+
dotnet_naming_rule.static_fields_should_be_pascal_case.severity = suggestion
136+
dotnet_naming_rule.static_fields_should_be_pascal_case.symbols = static_fields
137+
dotnet_naming_rule.static_fields_should_be_pascal_case.style = static_field_style
138+
dotnet_naming_symbols.static_fields.applicable_kinds = field
139+
dotnet_naming_symbols.static_fields.applicable_accessibilities = *
140+
dotnet_naming_symbols.static_fields.required_modifiers = static
141+
dotnet_naming_style.static_field_style.capitalization = pascal_case
142+
143+
# CS4014: Because this call is not awaited, execution of the current method continues before the call is completed
144+
dotnet_diagnostic.CS4014.severity = error
145+
146+
# IDE0051: Remove unused private members
147+
dotnet_diagnostic.IDE0051.severity = warning

HttpClient.Caching.sln

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
1313
ProjectSection(SolutionItems) = preProject
1414
build.yml = build.yml
1515
README.md = README.md
16+
.gitignore = .gitignore
17+
.editorconfig = .editorconfig
1618
EndProjectSection
1719
EndProject
1820
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleAppSample", "Samples\ConsoleAppSample\ConsoleAppSample.csproj", "{592B2324-79AA-4973-8CE5-F65BD503641F}"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System.Net.Http;
2+
3+
namespace Microsoft.Extensions.Caching.Abstractions
4+
{
5+
/// <summary>
6+
/// Provides keys to store or retrieve data in the cache
7+
/// </summary>
8+
public interface ICacheKeysProvider
9+
{
10+
/// <summary>
11+
/// Return the key for the request message <paramref name="request"/>
12+
/// </summary>
13+
/// <param name="request"></param>
14+
/// <returns></returns>
15+
string GetKey(HttpRequestMessage request);
16+
}
17+
}

HttpClient.Caching/HttpClient.Caching.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net45;netstandard1.2;netstandard2.0;netstandard2.1</TargetFrameworks>
4+
<TargetFrameworks>net452;netstandard1.2;netstandard2.0;netstandard2.1</TargetFrameworks>
55
<Authors>Thomas Galliker</Authors>
66
<Company>superdev GmbH</Company>
77
<Description>HttpClient.Caching adds http response caching to HttpClient.</Description>
8-
<Copyright>Copyright 2021</Copyright>
8+
<Copyright>Copyright 2022</Copyright>
99
<PackageProjectUrl>https://github.com/thomasgalliker/HttpClient.Caching</PackageProjectUrl>
1010
<PackageIconUrl>https://raw.githubusercontent.com/thomasgalliker/HttpClient.Caching/master/logo.png</PackageIconUrl>
1111
<RepositoryUrl>https://github.com/thomasgalliker/HttpClient.Caching</RepositoryUrl>
@@ -21,7 +21,7 @@
2121
<PackageReference Include="Newtonsoft.Json" Version="[11.0.2,)" />
2222
</ItemGroup>
2323

24-
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
24+
<ItemGroup Condition=" '$(TargetFramework)' == 'net452' ">
2525
<Reference Include="Microsoft.CSharp" />
2626
<Reference Include="System.Configuration" />
2727
<Reference Include="System.Core" />
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System;
2+
using System.Net.Http;
3+
using System.Text;
4+
using Microsoft.Extensions.Caching.Abstractions;
5+
6+
namespace Microsoft.Extensions.Caching.InMemory
7+
{
8+
/// <summary>
9+
/// Provides keys to store or retrieve data in the cache in the default way (http method + http request Uri)
10+
/// </summary>
11+
public class DefaultCacheKeysProvider : ICacheKeysProvider
12+
{
13+
/// <summary>
14+
/// Return the key for the request message <paramref name="request"/> by composing a string
15+
/// with <see cref="HttpRequestMessage.Method"/> and <see cref="HttpRequestMessage.RequestUri"/>
16+
/// </summary>
17+
/// <param name="request"></param>
18+
/// <returns>
19+
/// An example of return value: "MET_GET;URI_https://www.google.it"
20+
/// </returns>
21+
public string GetKey(HttpRequestMessage request)
22+
{
23+
if (request is null)
24+
{
25+
throw new ArgumentNullException(nameof(request));
26+
}
27+
28+
var sb = new StringBuilder();
29+
30+
sb.AppendFormat("MET_{0};", request.Method);
31+
sb.AppendFormat("URI_{0};", request.RequestUri);
32+
33+
return sb.ToString();
34+
}
35+
}
36+
}

HttpClient.Caching/InMemory/InMemoryCacheHandler.cs

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ public class InMemoryCacheHandler : DelegatingHandler
1919
private readonly IDictionary<HttpStatusCode, TimeSpan> cacheExpirationPerHttpResponseCode;
2020
private readonly IMemoryCache responseCache;
2121

22+
/// <summary>
23+
/// Cache key provider being used
24+
/// </summary>
25+
public ICacheKeysProvider CacheKeysProvider { get; }
26+
27+
2228
/// <summary>
2329
/// Create a new InMemoryCacheHandler.
2430
/// </summary>
@@ -31,8 +37,20 @@ public class InMemoryCacheHandler : DelegatingHandler
3137
/// An <see cref="IStatsProvider" /> that records statistic information about the caching
3238
/// behavior.
3339
/// </param>
34-
public InMemoryCacheHandler(HttpMessageHandler innerHandler = null, IDictionary<HttpStatusCode, TimeSpan> cacheExpirationPerHttpResponseCode = null, IStatsProvider statsProvider = null)
35-
: this(innerHandler, cacheExpirationPerHttpResponseCode, statsProvider, new MemoryCache(new MemoryCacheOptions()))
40+
/// <param name="cacheKeysProvider">
41+
/// An <see cref="ICacheKeysProvider"/> that provides keys to retrieve and store items in the cache
42+
/// </param>
43+
public InMemoryCacheHandler(HttpMessageHandler innerHandler = null,
44+
IDictionary<HttpStatusCode, TimeSpan> cacheExpirationPerHttpResponseCode = null,
45+
IStatsProvider statsProvider = null,
46+
ICacheKeysProvider cacheKeysProvider = null)
47+
: this(
48+
innerHandler,
49+
cacheExpirationPerHttpResponseCode,
50+
statsProvider,
51+
new MemoryCache(new MemoryCacheOptions()),
52+
cacheKeysProvider
53+
)
3654
{
3755
}
3856

@@ -49,12 +67,19 @@ public InMemoryCacheHandler(HttpMessageHandler innerHandler = null, IDictionary<
4967
/// behavior.
5068
/// </param>
5169
/// <param name="cache">The cache to be used.</param>
52-
internal InMemoryCacheHandler(HttpMessageHandler innerHandler, IDictionary<HttpStatusCode, TimeSpan> cacheExpirationPerHttpResponseCode, IStatsProvider statsProvider, IMemoryCache cache)
70+
/// <param name="cacheKeysProvider">The <see cref="ICacheKeysProvider"/> cache keys provider to use</param>
71+
internal InMemoryCacheHandler(
72+
HttpMessageHandler innerHandler,
73+
IDictionary<HttpStatusCode, TimeSpan> cacheExpirationPerHttpResponseCode,
74+
IStatsProvider statsProvider,
75+
IMemoryCache cache,
76+
ICacheKeysProvider cacheKeysProvider)
5377
: base(innerHandler ?? new HttpClientHandler())
5478
{
5579
this.StatsProvider = statsProvider ?? new StatsProvider(nameof(InMemoryCacheHandler));
5680
this.cacheExpirationPerHttpResponseCode = cacheExpirationPerHttpResponseCode ?? new Dictionary<HttpStatusCode, TimeSpan>();
5781
this.responseCache = cache ?? new MemoryCache(new MemoryCacheOptions());
82+
this.CacheKeysProvider = cacheKeysProvider ?? new DefaultCacheKeysProvider();
5883
}
5984

6085
/// <summary>
@@ -67,7 +92,8 @@ public void InvalidateCache(Uri uri, HttpMethod method = null)
6792
var methods = method != null ? new[] { method } : new[] { HttpMethod.Get, HttpMethod.Head };
6893
foreach (var m in methods)
6994
{
70-
var key = m + uri.ToString();
95+
var request = new HttpRequestMessage(m, uri);
96+
var key = CacheKeysProvider.GetKey(request);
7197
this.responseCache.Remove(key);
7298
}
7399
}
@@ -78,7 +104,7 @@ public void InvalidateCache(Uri uri, HttpMethod method = null)
78104
/// <returns>The HttpResponseMessage from cache, or a newly invoked one.</returns>
79105
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
80106
{
81-
var key = request.Method + request.RequestUri.ToString();
107+
var key = this.CacheKeysProvider.GetKey(request);
82108
// gets the data from cache, and returns the data if it's a cache hit
83109
if (request.Method == HttpMethod.Get || request.Method == HttpMethod.Head)
84110
{
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using System;
2+
using System.Linq;
3+
using System.Net.Http;
4+
using System.Text;
5+
using Microsoft.Extensions.Caching.Abstractions;
6+
7+
namespace Microsoft.Extensions.Caching.InMemory
8+
{
9+
/// <summary>
10+
/// Provides keys to store or retrieve data in the cache by using http method, specific headers and Uri
11+
/// </summary>
12+
public class MethodUriHeadersCacheKeysProvider : ICacheKeysProvider
13+
{
14+
private readonly string[] headersName;
15+
16+
/// <summary>
17+
/// Initialize the cache key provider passing the headers name that will be used to compose <paramref name="headersName"/>
18+
/// </summary>
19+
/// <param name="headersName"></param>
20+
public MethodUriHeadersCacheKeysProvider(string[] headersName)
21+
{
22+
if (headersName != null)
23+
{
24+
this.headersName = headersName.OrderBy(i => i).ToArray();
25+
}
26+
}
27+
28+
/// <summary>
29+
/// Return the key for the request message <paramref name="request"/> by composing a string
30+
/// with <see cref="HttpRequestMessage.Method"/>, <see cref="HttpRequestMessage.Headers"/> and <see cref="HttpRequestMessage.RequestUri"/>
31+
/// </summary>
32+
/// <param name="request"></param>
33+
/// <returns>
34+
/// An example of return value: "MET_GET;HEA_X-KID_389dfhuif;URI_https://www.google.it?par1=65&par2=20;"
35+
/// </returns>
36+
public string GetKey(HttpRequestMessage request)
37+
{
38+
if (request is null)
39+
{
40+
throw new ArgumentNullException(nameof(request));
41+
}
42+
43+
var sb = new StringBuilder();
44+
45+
sb.AppendFormat("MET_{0};", request.Method);
46+
if (this.headersName != null)
47+
{
48+
foreach (var headerName in this.headersName)
49+
{
50+
if (request.Headers.Contains(headerName))
51+
{
52+
sb.AppendFormat("HEA_{0}_{1};", headerName, this.GetHeaderValue(request, headerName));
53+
}
54+
}
55+
}
56+
57+
sb.AppendFormat("URI_{0};", request.RequestUri);
58+
59+
return sb.ToString();
60+
}
61+
62+
private string GetHeaderValue(HttpRequestMessage request, string headerName)
63+
{
64+
if (request is null)
65+
{
66+
throw new ArgumentNullException(nameof(request));
67+
}
68+
69+
if (string.IsNullOrEmpty(headerName))
70+
{
71+
throw new ArgumentException($"'{nameof(headerName)}' cannot be null or empty.", nameof(headerName));
72+
}
73+
74+
var orderedHeaderValues = string.Empty;
75+
76+
var headerValues = request.Headers.GetValues(headerName);
77+
if (headerValues != null)
78+
{
79+
orderedHeaderValues = string.Join(",", headerValues.OrderBy(i => i));
80+
}
81+
82+
return orderedHeaderValues;
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)