dotnet / dotnet/aspnetcore

OutputCache: expose `OutputCacheStream ` for reading in `ServeResponseAsync(..)` so response tags can be built

Open
#57,517 2 comments 0 reactions 0 assignees View on GitHub
api-suggestion area-middleware feature-output-caching
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 6h
Merged PRs (30d)
290

Description

## Background and Motivation

For outout caching its often useful to be able to create tags based on the response. This occurs where different requests all touch the same underlying data and the tag representing this underlying data cannot be constructed based on the request data. For example, say, requests for a same user details based on UserId, Identity and EmailAddress.

```
https://api.com/userdetailsbyid?userid=1234
https://api.com/userdetailsbyemail?emailaddress=ua@oa.com
https://api.com/userdetailsbyidentity?identityprovider=https://idp.com&subject=abc
```

At the moment the only way to build the tags for these queries is to use the request data. The problem is that any command handlers would need to look up these details, and evict all these tags separately

```c#
await _outputCacheStore.EvictByTagAsync("Tag_User_by_userid=...);
await _outputCacheStore.EvictByTagAsync("Tag_User_by_emailaddress=...);
await _outputCacheStore.EvictByTagAsync("Tag_User_by_identity=...);
etc
```

What I'd like to do is to add a tag for each request in a custom policy by using the response which in my example contains the userid

```c#
await _outputCacheStore.EvictByTagAsync(
"Tag_UserId_1234",
CancellationToken.None
);
```

The workaround is to use reflection in the `ServeResponseAsync(..)` to get the value of the `outputCacheContext.OutputCacheStream` then invoke the `GetCachedResponseBody()` in the internal `OutputCacheStream` before reading the response (my policy is a generic typed policy so it's easy to deserialise and add the tags to the tags collection:

```c#
if (_responseTags.Count != 0)
{
var responseBodyJson = await ExtractResponseBodyAsync(
context: context,
cancellationToken: cancellationToken
);

var view = JsonConvert.DeserializeObject(responseBodyJson);

if (view is not null)
{
foreach (var tag in _responseTags)
{
context.Tags.Add(tag.Compile().Invoke(view));
}
}
}
```

Then somehow write it back. Who knows what this breaks...

## Proposed API

Make the `OutputCacheStream` property public or provide a public method to `GetCachedResponseBody()` from the `OutputCacheContext` in the `ServeResponseAsync(...)` method.

## Usage Examples

```c#
public async ValueTask ServeResponseAsync(
OutputCacheContext context,
CancellationToken cancellationToken)
{
var responseBody= await context.GetCachedResponseBody();
//etc
}
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.