1
1
// Licensed to the .NET Foundation under one or more agreements.
2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
+ using System ;
4
5
using System . Buffers ;
5
6
using Microsoft . AspNetCore . OutputCaching ;
6
7
using Microsoft . Extensions . Options ;
7
8
8
9
namespace Microsoft . Extensions . Caching . Distributed ;
9
- internal sealed class DistributedCache < T > : IDistributedCache < T >
10
+ internal sealed class DistributedCache : IAdvancedDistributedCache
10
11
{
11
- private readonly ICacheSerializer < T > _serializer ;
12
+ private readonly IServiceProvider _services ;
12
13
private readonly IDistributedCache _backend ;
13
14
private readonly IBufferDistributedCache ? _bufferBackend ;
14
15
15
- public DistributedCache ( IOptions < TypedDistributedCacheOptions > options , ICacheSerializer < T > serializer , IDistributedCache backend )
16
+ public DistributedCache ( IOptions < TypedDistributedCacheOptions > options , IServiceProvider services , IDistributedCache backend )
16
17
{
17
- _serializer = serializer ;
18
+ _services = services ;
18
19
_backend = backend ;
19
20
_bufferBackend = backend as IBufferDistributedCache ; // do the type test once only
20
21
_ = options ;
21
22
}
22
23
23
- // for the simple usage scenario (no TState), pack the original callback as the "state", and use a wrapper function that just unrolls and invokes from the state
24
- static readonly Func < Func < CancellationToken , ValueTask < T > > , CancellationToken , ValueTask < T > > _wrapped = static ( callback , ct ) => callback ( ct ) ;
25
- public ValueTask < T > GetAsync ( string key , Func < CancellationToken , ValueTask < T > > callback , DistributedCacheEntryOptions ? options = null , CancellationToken cancellationToken = default )
26
- => GetAsync ( key , callback , _wrapped , options , cancellationToken ) ;
24
+ static class WrappedCallbackCache < T >
25
+ {
26
+ // for the simple usage scenario (no TState), pack the original callback as the "state", and use a wrapper function that just unrolls and invokes from the state
27
+ public static readonly Func < Func < CancellationToken , ValueTask < T > > , CancellationToken , ValueTask < T > > Instance = static ( callback , ct ) => callback ( ct ) ;
28
+
29
+ }
30
+ public ValueTask < T > GetAsync < T > ( string key , Func < CancellationToken , ValueTask < T > > callback , DistributedCacheEntryOptions ? options = null , CancellationToken cancellationToken = default )
31
+ => GetAsync ( key , callback , WrappedCallbackCache < T > . Instance , options , cancellationToken ) ;
27
32
28
- public ValueTask < T > GetAsync < TState > ( string key , TState state , Func < TState , CancellationToken , ValueTask < T > > callback , DistributedCacheEntryOptions ? options = null , CancellationToken cancellationToken = default )
33
+ public ValueTask < T > GetAsync < TState , T > ( string key , TState state , Func < TState , CancellationToken , ValueTask < T > > callback , DistributedCacheEntryOptions ? options = null , CancellationToken cancellationToken = default )
29
34
{
30
35
ArgumentException . ThrowIfNullOrWhiteSpace ( key ) ;
31
36
ArgumentNullException . ThrowIfNull ( callback ) ;
32
37
33
38
return _bufferBackend is not null
34
39
? GetBufferedBackendAsync ( key , state , callback , options , cancellationToken )
35
40
: GetLegacyBackendAsync ( key , state , callback , options , cancellationToken ) ;
36
-
37
41
}
38
42
39
- ValueTask IDistributedCache < T > . RefreshAsync ( string key , CancellationToken cancellationToken ) => new ( _backend . RefreshAsync ( key , cancellationToken ) ) ;
43
+ ValueTask IAdvancedDistributedCache . RefreshAsync ( string key , CancellationToken cancellationToken ) => new ( _backend . RefreshAsync ( key , cancellationToken ) ) ;
40
44
41
- private ValueTask < T > GetBufferedBackendAsync < TState > ( string key , TState state , Func < TState , CancellationToken , ValueTask < T > > callback , DistributedCacheEntryOptions ? options , CancellationToken cancellationToken )
45
+ private ValueTask < T > GetBufferedBackendAsync < TState , T > ( string key , TState state , Func < TState , CancellationToken , ValueTask < T > > callback , DistributedCacheEntryOptions ? options , CancellationToken cancellationToken )
42
46
{
43
47
var buffer = new RecyclableArrayBufferWriter < byte > ( ) ;
44
48
var pendingGet = _bufferBackend ! . TryGetAsync ( key , buffer , cancellationToken ) ;
@@ -51,22 +55,22 @@ private ValueTask<T> GetBufferedBackendAsync<TState>(string key, TState state, F
51
55
// fast path; backend available immediately
52
56
if ( pendingGet . GetAwaiter ( ) . GetResult ( ) )
53
57
{
54
- var result = _serializer . Deserialize ( new ( buffer . GetCommittedMemory ( ) ) ) ;
58
+ var result = GetSerializer < T > ( ) . Deserialize ( new ( buffer . GetCommittedMemory ( ) ) ) ;
55
59
buffer . Dispose ( ) ;
56
60
return new ( result ) ;
57
61
}
58
62
59
63
// fall back to main code-path, but without the pending bytes (we've already checked those)
60
64
return AwaitedBackend ( this , key , state , callback , options , cancellationToken , buffer , default ) ;
61
65
62
- static async ValueTask < T > AwaitedBackend ( DistributedCache < T > @this , string key , TState state , Func < TState , CancellationToken , ValueTask < T > > callback , DistributedCacheEntryOptions ? options ,
66
+ static async ValueTask < T > AwaitedBackend ( DistributedCache @this , string key , TState state , Func < TState , CancellationToken , ValueTask < T > > callback , DistributedCacheEntryOptions ? options ,
63
67
CancellationToken cancellationToken , RecyclableArrayBufferWriter < byte > buffer , ValueTask < bool > pendingGet )
64
68
{
65
69
using ( buffer )
66
70
{
67
71
if ( await pendingGet )
68
72
{
69
- return @this . _serializer . Deserialize ( new ( buffer . GetCommittedMemory ( ) ) ) ;
73
+ return @this . GetSerializer < T > ( ) . Deserialize ( new ( buffer . GetCommittedMemory ( ) ) ) ;
70
74
}
71
75
72
76
var value = await callback ( state , cancellationToken ) ;
@@ -77,7 +81,7 @@ static async ValueTask<T> AwaitedBackend(DistributedCache<T> @this, string key,
77
81
else
78
82
{
79
83
buffer . Reset ( ) ;
80
- @this . _serializer . Serialize ( value , buffer ) ;
84
+ @this . GetSerializer < T > ( ) . Serialize ( value , buffer ) ;
81
85
await @this . _bufferBackend ! . SetAsync ( key , new ( buffer . GetCommittedMemory ( ) ) , options ?? _defaultOptions , cancellationToken ) ;
82
86
}
83
87
@@ -86,7 +90,20 @@ static async ValueTask<T> AwaitedBackend(DistributedCache<T> @this, string key,
86
90
}
87
91
}
88
92
89
- private ValueTask < T > GetLegacyBackendAsync < TState > ( string key , TState state , Func < TState , CancellationToken , ValueTask < T > > callback , DistributedCacheEntryOptions ? options , CancellationToken cancellationToken )
93
+ private ICacheSerializer < T > GetSerializer < T > ( )
94
+ {
95
+ var obj = ( ICacheSerializer < T > ? ) _services . GetService ( typeof ( ICacheSerializer < T > ) ) ;
96
+ if ( obj is null )
97
+ {
98
+ ThrowNoSerializer ( typeof ( T ) ) ;
99
+ }
100
+ return obj ! ;
101
+
102
+ }
103
+
104
+ static void ThrowNoSerializer ( Type type ) => throw new InvalidOperationException ( "No serializer registered for " + type . FullName ) ;
105
+
106
+ private ValueTask < T > GetLegacyBackendAsync < TState , T > ( string key , TState state , Func < TState , CancellationToken , ValueTask < T > > callback , DistributedCacheEntryOptions ? options , CancellationToken cancellationToken )
90
107
{
91
108
var pendingBytes = _backend . GetAsync ( key , cancellationToken ) ;
92
109
if ( ! pendingBytes . IsCompletedSuccessfully )
@@ -98,21 +115,21 @@ private ValueTask<T> GetLegacyBackendAsync<TState>(string key, TState state, Fun
98
115
var bytes = pendingBytes . Result ;
99
116
if ( bytes is not null )
100
117
{
101
- return new ( _serializer . Deserialize ( new ReadOnlySequence < byte > ( bytes ) ) ! ) ;
118
+ return new ( GetSerializer < T > ( ) . Deserialize ( new ReadOnlySequence < byte > ( bytes ) ) ! ) ;
102
119
}
103
120
104
121
// fall back to main code-path, but without the pending bytes (we've already checked those)
105
122
return AwaitedBackend ( this , key , state , callback , options , cancellationToken , null ) ;
106
123
107
- static async ValueTask < T > AwaitedBackend ( DistributedCache < T > @this , string key , TState state , Func < TState , CancellationToken , ValueTask < T > > callback , DistributedCacheEntryOptions ? options ,
124
+ static async ValueTask < T > AwaitedBackend ( DistributedCache @this , string key , TState state , Func < TState , CancellationToken , ValueTask < T > > callback , DistributedCacheEntryOptions ? options ,
108
125
CancellationToken cancellationToken , Task < byte [ ] ? > ? pendingBytes )
109
126
{
110
127
if ( pendingBytes is not null )
111
128
{
112
129
var bytes = await pendingBytes ;
113
130
if ( bytes is not null )
114
131
{
115
- return @this . _serializer . Deserialize ( new ReadOnlySequence < byte > ( bytes ) ) ;
132
+ return @this . GetSerializer < T > ( ) . Deserialize ( new ReadOnlySequence < byte > ( bytes ) ) ;
116
133
}
117
134
}
118
135
@@ -124,7 +141,7 @@ static async ValueTask<T> AwaitedBackend(DistributedCache<T> @this, string key,
124
141
else
125
142
{
126
143
using var writer = new RecyclableArrayBufferWriter < byte > ( ) ;
127
- @this . _serializer . Serialize ( value , writer ) ;
144
+ @this . GetSerializer < T > ( ) . Serialize ( value , writer ) ;
128
145
await @this . _backend . SetAsync ( key , writer . ToArray ( ) , options ?? _defaultOptions , cancellationToken ) ;
129
146
}
130
147
0 commit comments