Vary OutputCache by body parameters in.net 7

642 views Asked by At

I'm using the new .NET 7 OutputCache in my already developed application. and here is how I added the OutputCache.

builder.Services.AddOutputCache(x => x.AddPolicy("default", new CustomCachePolicy()));

app.UseOutputCache();

internal class CustomCachePolicy : IOutputCachePolicy {
    public ValueTask CacheRequestAsync(OutputCacheContext context, CancellationToken cancellation) {
        context.AllowCacheLookup = true;
        context.AllowCacheStorage = true;
        context.AllowLocking = true;
        context.EnableOutputCaching = true;
        context.ResponseExpirationTimeSpan = TimeSpan.FromSeconds(10);
        return ValueTask.CompletedTask;
    }

    public ValueTask ServeFromCacheAsync(OutputCacheContext context, CancellationToken cancellation) => ValueTask.CompletedTask;
    public ValueTask ServeResponseAsync(OutputCacheContext context, CancellationToken cancellation) => ValueTask.CompletedTask;
}

and here is the API I want to cache:

[HttpPost("Filter")]
[OutputCache( PolicyName = "default" )]
public ActionResult<GenericResponse<IQueryable<CategoryEntity>>> Filter(CategoryFilterDto dto) => Result(_repository.Filter(dto));

the problem here is that the Api is going to do some filter from the Body parameters that client is giving, and I want to vary by the params client is sending. I searched the internet and I saw the VaryByParams attribute but it seems does not exist in .NET 7.

how can I Vary the output based on the given body parameters in OutputCache of .NET 7

2

There are 2 answers

2
Dusan Madzarevic On BEST ANSWER

Just ran into this same issue, here's how I ended up doing it:

   builder.AddPolicy<CachingPolicy>().VaryByValue(
   (context) => {
       context.Request.EnableBuffering();

       using var reader = new StreamReader(context.Request.Body, leaveOpen: true);
       var body = reader.ReadToEndAsync();

       // Reset the stream position to enable subsequent reads
       context.Request.Body.Position = 0;

       var keyVal = new KeyValuePair<string, string>("requestBody", body.Result);

       return keyVal;
   }
)

Essentially this adds the string representation of the entire body as one of the values that constitutes the cache key. This is probably not ideal as changing anyting about the body, even just the whitespace, will result in a non-cached response, but it should be a good starting point to refine the implementation. If I end up improving it myself I'll edit this answer.

0
Hejo On

If you want to add it to your custom instance of IOutputCachePolicy:

ValueTask IOutputCachePolicy.CacheRequestAsync(
OutputCacheContext context,
CancellationToken cancellationToken)
{
    var attemptOutputCaching = AttemptOutputCaching(context);
    context.EnableOutputCaching = true;
    context.AllowCacheLookup = attemptOutputCaching;
    context.AllowCacheStorage = attemptOutputCaching;
    context.AllowLocking = true;
    // Vary by any query by default
    context.CacheVaryByRules.QueryKeys = "*";

    context.HttpContext.Request.EnableBuffering();

    using var reader = new StreamReader(context.HttpContext.Request.Body, leaveOpen: true);
    var body = reader.ReadToEndAsync();

    // Reset the stream position to enable subsequent reads
    context.HttpContext.Request.Body.Position = 0;

    var keyVal = new KeyValuePair<string, string>("requestBody", body.Result);
    context.CacheVaryByRules.VaryByValues.Add(keyVal);
    return ValueTask.CompletedTask;
}