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;
});
...
app.UseHttpsRedirection();
app.UseHttpCacheHeaders();
...
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"));
[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();
<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.
Steps to Reproduce
Expected Behavior
Actual Behavior
Workaround
Setting DisableGlobalHeaderGeneration to false.
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:
The StoreKey is found, even after invalidating it via the endpoint.