dotnet / dotnet/aspnetcore

OutputCache - SetVaryByRouteValue doesn't work

Open
#48,066 0 comments 0 reactions 0 assignees View on GitHub
area-middleware feature-output-caching
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

### Is there an existing issue for this?

- [X] I have searched the existing issues

### Describe the bug

I fetch the page by id and use slug like this:
[https://example.com/123/slug-a](https://example.com/123/slug-a)
[https://example.com/123/slug-b](https://example.com/123/slug-b )
[https://example.com/123/slug-c](https://example.com/123/slug-c )
All these routes should get the same page from output cache regardless of the slug value.
I use the `SetVaryByRouteValue("id")` but it doesn't work.

```
services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder.AddPolicy().Expire(TimeSpan.FromSeconds(10000)), true);
options.AddPolicy("VaryById", builder => builder.SetVaryByRouteValue("id"));
});
```

The method:
```
[Route("text/{id:int}/{textSlug?}")]
[OutputCache(PolicyName = "VaryById")]
public async Task TextPage(int id, string textSlug)
```

When I use `SetVaryByQuery("id")` I get the cached version of the page as long I don't change the id, but then I need to change my url to look like this:
[https://example.com/123?slug=slug-a](https://example.com/123?slug=slug-a)
[https://example.com/123?slug=slug-b](https://example.com/123?slug=slug-b)
[https://example.com/123?slug=slug-c](https://example.com/123?slug=slug-c)

### Expected Behavior

I expect to get the cached version as long that I don't change the id route value.
So for:
[https://example.com/123/slug-a](https://example.com/123/slug-a)
[https://example.com/123/slug-b](https://example.com/123/slug-b )
[https://example.com/123/slug-c](https://example.com/123/slug-c )
I should get the same page from Output Cache.

### Steps To Reproduce

These are my settings:

```
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.OutputCaching;
using Microsoft.Extensions.Primitives;
using System.Threading.Tasks;
using System.Threading;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Authorization;
using System.Reflection;
using System.Linq;

namespace Cache;

public sealed class MyCustomPolicy : IOutputCachePolicy
{
public static readonly MyCustomPolicy Instance = new();

public MyCustomPolicy()
{
}

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 = "*";

return ValueTask.CompletedTask;
}

ValueTask IOutputCachePolicy.ServeFromCacheAsync(OutputCacheContext context, CancellationToken cancellationToken)
{
return ValueTask.CompletedTask;
}

ValueTask IOutputCachePolicy.ServeResponseAsync(OutputCacheContext context, CancellationToken cancellationToken)
{
var response = context.HttpContext.Response;

// Verify existence of cookie headers
if (!StringValues.IsNullOrEmpty(response.Headers.SetCookie))
{
context.AllowCacheStorage = false;
}

// Check response code
if (response.StatusCode != StatusCodes.Status200OK)
{
context.AllowCacheStorage = false;
}

return ValueTask.CompletedTask;
}

private static bool AttemptOutputCaching(OutputCacheContext context)
{
// Check if the current request fulfills the requirements to be cached
var request = context.HttpContext.Request;

// Verify the method
if (!HttpMethods.IsGet(request.Method) && !HttpMethods.IsHead(request.Method))
{
return false;
}

// Verify existence of authorization attribute
var controllerActionDescriptor = context.HttpContext.GetEndpoint()?.Metadata.GetMetadata()?.MethodInfo;
var hasAuthorizeAttribute = controllerActionDescriptor?.GetCustomAttributes()?.OfType().Any() ?? false;
if (hasAuthorizeAttribute)
{
return false;
}

return true;
}
}
```

Add the service:

```
services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder.AddPolicy().Expire(TimeSpan.FromSeconds(10000)), true);
options.AddPolicy("VaryById", builder => builder.SetVaryByRouteValue("id"));
//options.AddPolicy("Query", builder => builder.SetVaryByQuery("id"));
});
```

The method:

```
[Route("text/{id:int}/{textSlug?}")]
[OutputCache(PolicyName = "VaryById")]
public async Task TextPage(int id, string textSlug)
```

### Exceptions (if any)

_No response_

### .NET Version

7.0.203

### Anything else?

I tried to add to the base policy the following and it didn't work:

`context.CacheVaryByRules.RouteValueNames = "id";`

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.