Open danroth27 opened 1 year ago
We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.
This useful especially in the context of using server-side rendering (SSR) to gain performance.
Adding support for caching components would server those who are building apps relying on parts if a web page being cached due to high-load. E-commerce sites and sites selling tickets to concerts etc.
Also worth mentioning:
In MVC Razor and Razor Pages, you can cache certain portions of a view using a tag helper. So apart from attributes you can have a special Cache
component as well.
<Cache ExpiresAfter="@TimeSpan.FromSeconds(120)">
@DateTime.Now
</Cache>
Modeled after: https://learn.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/built-in/cache-tag-helper?view=aspnetcore-7.0
We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.
I think this already works, we use the OutputCache
attribute on a Razor Component page rendered using SSR in the Aspire Starter App template.
@DamianEdwards Do you maybe have a link? I'm interested to try this!
@DamianEdwards Thanks, I'm gonna check it out!
This is a simplified version of CacheTagHelper for Blazor SSR which can be used to cache the content of a given component:
CacheComponent.razor
@using Microsoft.Extensions.Caching.Memory
@typeparam TComponent where TComponent : IComponent
@if (_cachedContent != null)
{
@((MarkupString)_cachedContent)
}
@code{
private const string CacheKeyPrefix = $"__{nameof(CacheComponent<TComponent>)}__";
private readonly TimeSpan _defaultExpiration = TimeSpan.FromSeconds(30);
private string? _cachedContent;
[Inject] internal HtmlRenderer HtmlRenderer { set; get; } = null!;
[Inject] internal IMemoryCache MemoryCache { get; set; } = null!;
/// <summary>
/// Parameters for the component.
/// </summary>
[Parameter]
public IDictionary<string, object?>? Parameters { set; get; }
/// <summary>
/// Gets or sets the exact <see cref="DateTimeOffset" /> the cache entry should be evicted.
/// </summary>
[Parameter]
public DateTimeOffset? ExpiresOn { get; set; }
/// <summary>
/// Gets or sets the duration, from the time the cache entry was added, when it should be evicted.
/// </summary>
[Parameter]
public TimeSpan? ExpiresAfter { get; set; }
/// <summary>
/// Gets or sets the duration from last access that the cache entry should be evicted.
/// </summary>
[Parameter]
public TimeSpan? ExpiresSliding { get; set; }
/// <summary>
/// Gets or sets the <see cref="CacheItemPriority" /> policy for the cache entry.
/// </summary>
[Parameter]
public CacheItemPriority? Priority { get; set; }
/// <summary>
/// Gets or sets the key of the cache entry.
/// </summary>
[Parameter, EditorRequired]
public required string CacheKey { get; set; }
private string CacheEntryKey => $"{CacheKeyPrefix}{CacheKey}";
protected override Task OnInitializedAsync()
=> ProcessAsync();
public void InvalidateCache()
=> MemoryCache.Remove(CacheEntryKey);
private async Task ProcessAsync()
{
if (!MemoryCache.TryGetValue(CacheEntryKey, out _cachedContent))
{
_cachedContent = await HtmlRenderer.Dispatcher.InvokeAsync(async () =>
{
var output = await HtmlRenderer.RenderComponentAsync<TComponent>(Parameters is null ? ParameterView.Empty : ParameterView.FromDictionary(Parameters));
return output.ToHtmlString();
});
_ = MemoryCache.Set(CacheEntryKey, _cachedContent, GetMemoryCacheEntryOptions());
}
}
private MemoryCacheEntryOptions GetMemoryCacheEntryOptions()
{
var hasEvictionCriteria = false;
var options = new MemoryCacheEntryOptions();
options.SetSize(1);
if (ExpiresOn != null)
{
hasEvictionCriteria = true;
options.SetAbsoluteExpiration(ExpiresOn.Value);
}
if (ExpiresAfter != null)
{
hasEvictionCriteria = true;
options.SetAbsoluteExpiration(ExpiresAfter.Value);
}
if (ExpiresSliding != null)
{
hasEvictionCriteria = true;
options.SetSlidingExpiration(ExpiresSliding.Value);
}
if (Priority != null)
{
options.SetPriority(Priority.Value);
}
if (!hasEvictionCriteria)
{
options.SetSlidingExpiration(_defaultExpiration);
}
return options;
}
}
Requirements:
builder.Services.AddMemoryCache();
builder.Services.AddScoped<HtmlRenderer>();
Usage:
<CacheComponent TComponent="MySidebarComponent"
ExpiresAfter="TimeSpan.FromMinutes(1)"
CacheKey="side-bar-menu-1"/>
@DamianEdwards Thanks, I'm gonna check it out!
Did you manage to cache Blazor SSR page with or without .Net Aspire? Thank you in advance for your response.
@javiercn , I was wondering if there are any plans to add support for Blazor SSR pages anytime soon. I appreciate your time and efforts.
@javiercn , I was wondering if there are any plans to add support for Blazor SSR pages anytime soon. I appreciate your time and efforts.
@zubairkhakwani Unfortunately, this work isn't planned for Blazor in .NET 9 due to competing priorities. We are open to community contributions for designing and implementing this feature.
Did you manage to cache Blazor SSR page with or without .Net Aspire? Thank you in advance for your response.
@zubairkhakwani Output caching should already work with Blazor static SSR pages. You can set up that output caching middleware and add @attribute [OutputCache]
to static SSR pages that you want to cache. This functionality doesn't require .NET Aspire. Please let us know if you're seeing any issues with this functionality.
I've opened https://github.com/dotnet/aspnetcore/issues/55520 to separately track adding a Blazor Cache component as an analog to the existing cache tag helper used in MVC & Razor Pages apps.
Did you manage to cache Blazor SSR page with or without .Net Aspire? Thank you in advance for your response.
@zubairkhakwani Output caching should already work with Blazor static SSR pages. You can set up that output caching middleware and add
@attribute [OutputCache]
to static SSR pages that you want to cache. This functionality doesn't require .NET Aspire. Please let us know if you're seeing any issues with this functionality.
I have registered the middleware just like I do it in MVC app
builder.Services.AddOutputCache();
app.UseOutputCache();
and on my SSR component page I used it like
@attribute [OutputCache(Duration = 1000)]
to check if cache is working I am using @DateTime.Now.ToString("F") in the same component/Page but on every refresh the time is changed by one second.
I really appreciate your comment & please let me know if I am missing something or it is a bug. I am using dotnet 8.0.204
I am adding a GIF below to showcase the issue.
@zubairkhakwani This order of defining middlewares works for me:
app.UseStaticFiles();
app.UseSession();
app.UseRouting();
app.UseAntiforgery();
app.UseOutputCache();
app.MapRazorComponents<App>();
app.MapControllers();
app.Run();
@zubairkhakwani This order of defining middlewares works for me:
app.UseStaticFiles(); app.UseSession(); app.UseRouting(); app.UseAntiforgery(); app.UseOutputCache(); app.MapRazorComponents<App>(); app.MapControllers(); app.Run();
Thank you @VahidN putting app.UseOutputCache()
; right after
app.UseStaticFiles();
app.UseAntiforgery();
worked like a charm. I had tried putting it before app.MapRazorComponents<App>()
but it was not working because I was putting it after
app.UseAuthentication();
app.UseAuthorization();
Thank you again, and have a good day.
I renamed this because, as noted in the previous comments, [OutputCache]
works. [ResponseCache]
does not. Unlike [OutputCache]
, [ResponseCache]
was not designed to work outside of MVC and Razor Pages, but it does do things [OutputCache]
does not like make it easy to just set client caching rules as noted by #56769, so there's still a gap.
In MVC & Razor Pages we have attributes, like
ResponseCacheAttribute
andOutputCacheAttribute
, for configuring response & output caching on a page or action. We should consider having similar support for Blazor server-side rendering.