Skip to content

Commit 37e3028

Browse files
committed
Brennan's
1 parent 4013de5 commit 37e3028

37 files changed

+393
-342
lines changed

src/Middleware/OutputCaching/samples/OutputCachingSample/Startup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
app.MapGet("/nocache", Gravatar.WriteGravatar).CacheOutput(x => x.NoCache());
2727

28-
app.MapGet("/profile", Gravatar.WriteGravatar).CacheOutput(x => x.Policy("NoCache"));
28+
app.MapGet("/profile", Gravatar.WriteGravatar).CacheOutput("NoCache");
2929

3030
app.MapGet("/attribute", [OutputCache(PolicyName = "NoCache")] () => Gravatar.WriteGravatar);
3131

src/Middleware/OutputCaching/src/CachedVaryByRules.cs renamed to src/Middleware/OutputCaching/src/CacheVaryByRules.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.OutputCaching;
99
/// <summary>
1010
/// Represents vary-by rules.
1111
/// </summary>
12-
public sealed class CachedVaryByRules
12+
public sealed class CacheVaryByRules
1313
{
1414
private Dictionary<string, string>? _varyByCustom;
1515

src/Middleware/OutputCaching/src/CachedResponseBody.cs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ internal sealed class CachedResponseBody
1717
/// <param name="length">The length.</param>
1818
public CachedResponseBody(List<byte[]> segments, long length)
1919
{
20+
ArgumentNullException.ThrowIfNull(segments);
21+
2022
Segments = segments;
2123
Length = length;
2224
}
@@ -45,17 +47,7 @@ public async Task CopyToAsync(PipeWriter destination, CancellationToken cancella
4547
{
4648
cancellationToken.ThrowIfCancellationRequested();
4749

48-
Copy(segment, destination);
49-
50-
await destination.FlushAsync(cancellationToken);
50+
await destination.WriteAsync(segment, cancellationToken);
5151
}
5252
}
53-
54-
private static void Copy(byte[] segment, PipeWriter destination)
55-
{
56-
var span = destination.GetSpan(segment.Length);
57-
58-
segment.CopyTo(span);
59-
destination.Advance(segment.Length);
60-
}
6153
}

src/Middleware/OutputCaching/src/IOutputCachePolicy.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@ public interface IOutputCachePolicy
1313
/// At that point the cache middleware can still be enabled or disabled for the request.
1414
/// </summary>
1515
/// <param name="context">The current request's cache context.</param>
16-
ValueTask CacheRequestAsync(OutputCacheContext context);
16+
ValueTask CacheRequestAsync(OutputCacheContext context, CancellationToken cancellation);
1717

1818
/// <summary>
1919
/// Updates the <see cref="OutputCacheContext"/> before the cached response is used.
2020
/// At that point the freshness of the cached response can be updated.
2121
/// </summary>
2222
/// <param name="context">The current request's cache context.</param>
23-
ValueTask ServeFromCacheAsync(OutputCacheContext context);
23+
ValueTask ServeFromCacheAsync(OutputCacheContext context, CancellationToken cancellation);
2424

2525
/// <summary>
2626
/// Updates the <see cref="OutputCacheContext"/> before the response is served and can be cached.
2727
/// At that point cacheability of the response can be updated.
2828
/// </summary>
29-
ValueTask ServeResponseAsync(OutputCacheContext context);
29+
ValueTask ServeResponseAsync(OutputCacheContext context, CancellationToken cancellation);
3030
}

src/Middleware/OutputCaching/src/Memory/MemoryOutputCacheStore.cs

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Collections.Concurrent;
54
using Microsoft.Extensions.Caching.Memory;
65

76
namespace Microsoft.AspNetCore.OutputCaching.Memory;
87

98
internal sealed class MemoryOutputCacheStore : IOutputCacheStore
109
{
1110
private readonly IMemoryCache _cache;
12-
private readonly ConcurrentDictionary<string, HashSet<string>> _taggedEntries = new();
11+
private readonly Dictionary<string, HashSet<string>> _taggedEntries = new();
12+
private readonly object _tagsLock = new();
1313

1414
internal MemoryOutputCacheStore(IMemoryCache cache)
1515
{
@@ -22,11 +22,16 @@ public ValueTask EvictByTagAsync(string tag, CancellationToken cancellationToken
2222
{
2323
ArgumentNullException.ThrowIfNull(tag);
2424

25-
if (_taggedEntries.TryGetValue(tag, out var keys))
25+
lock (_tagsLock)
2626
{
27-
foreach (var key in keys)
27+
if (_taggedEntries.TryGetValue(tag, out var keys))
2828
{
29-
_cache.Remove(key);
29+
foreach (var key in keys)
30+
{
31+
_cache.Remove(key);
32+
}
33+
34+
_taggedEntries.Remove(tag);
3035
}
3136
}
3237

@@ -50,29 +55,41 @@ public ValueTask SetAsync(string key, byte[] value, string[]? tags, TimeSpan val
5055

5156
if (tags != null)
5257
{
53-
foreach (var tag in tags)
54-
{
55-
var keys = _taggedEntries.GetOrAdd(tag, _ => new HashSet<string>());
58+
// Lock with SetEntry() to prevent EvictByTagAsync() from trying to remove a tag whose entry hasn't been added yet.
59+
// It might be acceptable to not lock SetEntry() since in this case Remove(key) would just no-op and the user retry to evict.
5660

57-
// Copy the list of tags to prevent locking
58-
59-
var local = new HashSet<string>(keys)
61+
lock (_tagsLock)
62+
{
63+
foreach (var tag in tags)
6064
{
61-
key
62-
};
65+
if (!_taggedEntries.TryGetValue(tag, out var keys))
66+
{
67+
keys = new HashSet<string>();
68+
_taggedEntries[tag] = keys;
69+
}
6370

64-
_taggedEntries[tag] = local;
71+
keys.Add(key);
72+
}
73+
74+
SetEntry();
6575
}
6676
}
77+
else
78+
{
79+
SetEntry();
80+
}
6781

68-
_cache.Set(
82+
void SetEntry()
83+
{
84+
_cache.Set(
6985
key,
7086
value,
7187
new MemoryCacheEntryOptions
7288
{
7389
AbsoluteExpirationRelativeToNow = validFor,
7490
Size = value.Length
7591
});
92+
}
7693

7794
return ValueTask.CompletedTask;
7895
}

src/Middleware/OutputCaching/src/Microsoft.AspNetCore.OutputCaching.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
<Description>ASP.NET Core middleware for caching HTTP responses on the server.</Description>
55
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
66
<IsAspNetCoreApp>true</IsAspNetCoreApp>
7-
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
87
<IsPackable>false</IsPackable>
98
<IsTrimmable>true</IsTrimmable>
109
</PropertyGroup>

src/Middleware/OutputCaching/src/OutputCacheAttribute.cs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ namespace Microsoft.AspNetCore.OutputCaching;
66
/// <summary>
77
/// Specifies the parameters necessary for setting appropriate headers in output caching.
88
/// </summary>
9+
/// <remarks>
10+
/// This attribute requires the output cache middleware.
11+
/// </remarks>
912
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
1013
public sealed class OutputCacheAttribute : Attribute
1114
{
@@ -30,7 +33,7 @@ public int Duration
3033
/// Gets or sets the value which determines whether the reponse should be cached or not.
3134
/// When set to <see langword="true"/>, the response won't be cached.
3235
/// </summary>
33-
public bool NoCache
36+
public bool NoStore
3437
{
3538
get => _noCache ?? false;
3639
init => _noCache = value;
@@ -39,17 +42,11 @@ public bool NoCache
3942
/// <summary>
4043
/// Gets or sets the query keys to vary by.
4144
/// </summary>
42-
/// <remarks>
43-
/// <see cref="VaryByQueryKeys"/> requires the output cache middleware.
44-
/// </remarks>
4545
public string[]? VaryByQueryKeys { get; init; }
4646

4747
/// <summary>
4848
/// Gets or sets the headers to vary by.
4949
/// </summary>
50-
/// <remarks>
51-
/// <see cref="VaryByHeaders"/> requires the output cache middleware.
52-
/// </remarks>
5350
public string[]? VaryByHeaders { get; init; }
5451

5552
/// <summary>
@@ -66,14 +63,14 @@ internal IOutputCachePolicy BuildPolicy()
6663

6764
var builder = new OutputCachePolicyBuilder();
6865

69-
if (_noCache != null && _noCache.Value)
66+
if (PolicyName != null)
7067
{
71-
builder.NoCache();
68+
builder.AddPolicy(new NamedPolicy(PolicyName));
7269
}
7370

74-
if (PolicyName != null)
71+
if (_noCache != null && _noCache.Value)
7572
{
76-
builder.Policy(PolicyName);
73+
builder.NoCache();
7774
}
7875

7976
if (VaryByQueryKeys != null)

src/Middleware/OutputCaching/src/OutputCacheContext.cs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
#nullable disable
5-
64
using Microsoft.AspNetCore.Http;
75
using Microsoft.Extensions.Logging;
86

@@ -52,9 +50,9 @@ internal OutputCacheContext(HttpContext httpContext, IOutputCacheStore store, Ou
5250
public DateTimeOffset? ResponseTime { get; internal set; }
5351

5452
/// <summary>
55-
/// Gets the <see cref="CachedVaryByRules"/> instance.
53+
/// Gets the <see cref="CacheVaryByRules"/> instance.
5654
/// </summary>
57-
public CachedVaryByRules CachedVaryByRules { get; set; } = new();
55+
public CacheVaryByRules CacheVaryByRules { get; set; } = new();
5856

5957
/// <summary>
6058
/// Gets the tags of the cached response.
@@ -66,23 +64,22 @@ internal OutputCacheContext(HttpContext httpContext, IOutputCacheStore store, Ou
6664
/// </summary>
6765
public TimeSpan? ResponseExpirationTimeSpan { get; set; }
6866

69-
internal string CacheKey { get; set; }
67+
internal string CacheKey { get; set; } = default!;
7068

7169
internal TimeSpan CachedResponseValidFor { get; set; }
7270

7371
internal bool IsCacheEntryFresh { get; set; }
7472

7573
internal TimeSpan CachedEntryAge { get; set; }
7674

77-
internal OutputCacheEntry CachedResponse { get; set; }
75+
internal OutputCacheEntry CachedResponse { get; set; } = default!;
7876

7977
internal bool ResponseStarted { get; set; }
8078

81-
internal Stream OriginalResponseStream { get; set; }
79+
internal Stream OriginalResponseStream { get; set; } = default!;
8280

83-
internal OutputCacheStream OutputCacheStream { get; set; }
81+
internal OutputCacheStream OutputCacheStream { get; set; } = default!;
8482
internal ILogger Logger { get; }
8583
internal OutputCacheOptions Options { get; }
8684
internal IOutputCacheStore Store { get; }
87-
8885
}

src/Middleware/OutputCaching/src/OutputCacheEntry.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
namespace Microsoft.AspNetCore.OutputCaching;
77

8-
/// <inheritdoc />
98
internal sealed class OutputCacheEntry
109
{
1110
/// <summary>
@@ -31,5 +30,5 @@ internal sealed class OutputCacheEntry
3130
/// <summary>
3231
/// Gets the tags of the cache entry.
3332
/// </summary>
34-
public string[]? Tags { get; set; }
33+
public string[] Tags { get; set; } = Array.Empty<string>();
3534
}

src/Middleware/OutputCaching/src/OutputCacheEntryFormatter.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ internal static class OutputCacheEntryFormatter
2222
return null;
2323
}
2424

25-
using var br = new MemoryStream(content);
26-
27-
var formatter = await JsonSerializer.DeserializeAsync(br, FormatterEntrySerializerContext.Default.FormatterEntry, cancellationToken: cancellationToken);
25+
var formatter = JsonSerializer.Deserialize(content, FormatterEntrySerializerContext.Default.FormatterEntry);
2826

2927
if (formatter == null)
3028
{
@@ -35,26 +33,27 @@ internal static class OutputCacheEntryFormatter
3533
{
3634
StatusCode = formatter.StatusCode,
3735
Created = formatter.Created,
38-
Tags = formatter.Tags
36+
Tags = formatter.Tags,
37+
Headers = new(),
38+
Body = new CachedResponseBody(formatter.Body, formatter.Body.Sum(x => x.Length))
3939
};
4040

4141
if (formatter.Headers != null)
4242
{
43-
outputCacheEntry.Headers = new();
44-
4543
foreach (var header in formatter.Headers)
4644
{
4745
outputCacheEntry.Headers.TryAdd(header.Key, header.Value);
4846
}
4947
}
50-
var cachedResponseBody = new CachedResponseBody(formatter.Body, formatter.Body.Sum(x => x.Length));
51-
outputCacheEntry.Body = cachedResponseBody;
48+
5249
return outputCacheEntry;
5350
}
5451

5552
public static async ValueTask StoreAsync(string key, OutputCacheEntry value, TimeSpan duration, IOutputCacheStore store, CancellationToken cancellationToken)
5653
{
5754
ArgumentNullException.ThrowIfNull(value);
55+
ArgumentNullException.ThrowIfNull(value.Body);
56+
ArgumentNullException.ThrowIfNull(value.Headers);
5857

5958
var formatterEntry = new FormatterEntry
6059
{
@@ -73,9 +72,10 @@ public static async ValueTask StoreAsync(string key, OutputCacheEntry value, Tim
7372
}
7473
}
7574

76-
using var br = new MemoryStream();
75+
using var bufferStream = new MemoryStream();
76+
77+
JsonSerializer.Serialize(bufferStream, formatterEntry, FormatterEntrySerializerContext.Default.FormatterEntry);
7778

78-
await JsonSerializer.SerializeAsync(br, formatterEntry, FormatterEntrySerializerContext.Default.FormatterEntry, cancellationToken);
79-
await store.SetAsync(key, br.ToArray(), value.Tags ?? Array.Empty<string>(), duration, cancellationToken);
79+
await store.SetAsync(key, bufferStream.ToArray(), value.Tags ?? Array.Empty<string>(), duration, cancellationToken);
8080
}
8181
}

src/Middleware/OutputCaching/src/OutputCacheKeyProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ public string CreateStorageKey(OutputCacheContext context)
3333
{
3434
ArgumentNullException.ThrowIfNull(_builderPool);
3535

36-
var varyByRules = context.CachedVaryByRules;
36+
var varyByRules = context.CacheVaryByRules;
3737
if (varyByRules == null)
3838
{
39-
throw new InvalidOperationException($"{nameof(CachedVaryByRules)} must not be null on the {nameof(OutputCacheContext)}");
39+
throw new InvalidOperationException($"{nameof(CacheVaryByRules)} must not be null on the {nameof(OutputCacheContext)}");
4040
}
4141

4242
var request = context.HttpContext.Request;

0 commit comments

Comments
 (0)