HttpCacheHeaders
HttpCacheHeaders copied to clipboard
Middleware returns 304 when DisableGlobalHeaderGeneration = true and StoreKey is MarkForInvalidation
Steps to Reproduce
- New Blazor 8 Web App Project
- Hosted by Asp.NET Core
- Install NuGet Marvin.Cache.Headers
- Configure
builder.Services.AddHttpCacheHeaders(
o =>
{
o.CacheLocation = CacheLocation.Private;
o.MaxAge = 20;
},
v =>
{
v.MustRevalidate = false;
v.VaryByAll = false;
},
m =>
{
m.DisableGlobalHeaderGeneration = true;
m.IgnoredStatusCodes = HttpStatusCodes.AllErrors;
});
- Add Middleware
...
app.UseHttpsRedirection();
app.UseHttpCacheHeaders();
...
- Add Client Service
public class HttpWeatherForecastService
{
private readonly HttpClient client;
public HttpWeatherForecastService(HttpClient client)
{
this.client = client;
}
public async Task<WeatherForecast[]> GetListAsync()
{
var forcast = await this.client.GetFromJsonAsync<WeatherForecast[]>("/api/Weather/Get");
return forcast;
}
public async Task Invalidate()
{
await this.client.PostAsync("api/Weather/InvalidateCache", null);
}
}
...
builder.Services.AddTransient<HttpWeatherForecastService>();
builder.Services.AddHttpClient("API", c => c.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
builder.Services.AddTransient(
sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("API"));
- Add Endpoints
[Route("api/[controller]/[action]")]
[ApiController]
public class WeatherController : ControllerBase
{
private readonly IWeatherForecastService service;
private readonly IValidatorValueInvalidator invalidator;
private readonly IStoreKeyAccessor storeKeyAccessor;
private readonly IValidatorValueStore valueStore;
public WeatherController(IWeatherForecastService service,
IValidatorValueInvalidator invalidator,
IStoreKeyAccessor storeKeyAccessor,
IValidatorValueStore valueStore)
{
this.service = service;
this.invalidator = invalidator;
this.storeKeyAccessor = storeKeyAccessor;
this.valueStore = valueStore;
}
[HttpPost]
public async Task<IActionResult> InvalidateCache()
{
var validatorValues = this.storeKeyAccessor.FindByKeyPart("Weather");
await foreach (var value in validatorValues)
{
await this.invalidator.MarkForInvalidation(value);
}
return this.Ok();
}
[HttpGet]
[HttpCacheExpiration(CacheLocation = CacheLocation.Private, MaxAge = 15)]
[HttpCacheValidation(MustRevalidate = false, ProxyRevalidate = false, VaryByAll = false)]
public async Task<ActionResult<WeatherForecast[]>> Get()
{
var startDate = DateOnly.FromDateTime(DateTime.Now);
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot",
"Sweltering", "Scorching"
};
var forecasts = Enumerable.Range(1, 5)
.Select(
index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[
Random.Shared.Next(summaries.Length)]
})
.ToArray();
return this.Ok(forecasts);
}
}
...
app.MapRazorComponents<App>()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(Cache_Invalidation.Client._Imports).Assembly);
app.MapControllers();
- Add Helpter Buttons
<p>This component demonstrates showing data.</p>
<button @onclick="Invalidate">
Invalidate
</button>
<button @onclick="OnInitializedAsync">
Get Data
</button>
...
public partial class Weather
{
private WeatherForecast[]? forecasts;
[Inject]
protected HttpWeatherForecastService Service { get; set; }
protected override async Task OnInitializedAsync()
{
this.forecasts = await this.Service.GetListAsync();
}
private void Invalidate()
{
this.Service.Invalidate();
}
}
Expected Behavior
- When clicking the Invalidate Button and waiting 15 seconds for the cache to stale.
- Then clicking Get Data
- I Expected the Middleware to not find the StoreKey, since it has been invalidated.
Actual Behavior
- Doing the first two steps of Expected Behavior.
- The Middleware still returns a 304 Not Modified result.
Workaround Setting DisableGlobalHeaderGeneration to false.
m.DisableGlobalHeaderGeneration = true;
This works, but now every Endpoint generates Cache Headers. This is not my desired behavior because I don't want ETags on Blazor Framework files. Also I have to set [HttpCacheIgnore] on every other endpoint that I don't want cache on.
Debugging I have debugged the HttpCacheHeadersMiddleware: ConditionalGetOrHeadIsValid returns true because of this Code:
ValidatorValue async = await this._store.GetAsync(await this._storeKeyGenerator.GenerateStoreKey(this.ConstructStoreKeyContext(httpContext.Request, this._validationModelOptions)));
The StoreKey is found, even after invalidating it via the endpoint.