Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Response & output caching for Blazor static server rendering #49130

Open
danroth27 opened this issue Jul 1, 2023 · 16 comments
Open

Response & output caching for Blazor static server rendering #49130

danroth27 opened this issue Jul 1, 2023 · 16 comments
Assignees
Labels
area-blazor Includes: Blazor, Razor Components enhancement This issue represents an ask for new feature or an enhancement to an existing one help candidate Indicates that the issues may be a good fit for community to help with. Requires work from eng. team Pillar: Complete Blazor Web
Milestone

Comments

@danroth27
Copy link
Member

In MVC & Razor Pages we have attributes, like ResponseCacheAttribute and OutputCacheAttribute, for configuring response & output caching on a page or action. We should consider having similar support for Blazor server-side rendering.

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-blazor Includes: Blazor, Razor Components label Jul 1, 2023
@mkArtakMSFT mkArtakMSFT added the enhancement This issue represents an ask for new feature or an enhancement to an existing one label Jul 3, 2023
@mkArtakMSFT mkArtakMSFT added this to the Backlog milestone Jul 3, 2023
@ghost
Copy link

ghost commented Jul 3, 2023

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.

@marinasundstrom
Copy link

marinasundstrom commented Jul 22, 2023

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

@danroth27 danroth27 changed the title Response & output caching for Blazor server-side rendering Response & output caching for Blazor static server rendering Oct 2, 2023
@mkArtakMSFT mkArtakMSFT modified the milestones: Backlog, BlazorPlanning Nov 5, 2023
@mkArtakMSFT mkArtakMSFT added the help candidate Indicates that the issues may be a good fit for community to help with. Requires work from eng. team label Dec 19, 2023
@mkArtakMSFT mkArtakMSFT modified the milestones: Planning: WebUI, Backlog Dec 19, 2023
@ghost
Copy link

ghost commented Dec 19, 2023

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.

@DamianEdwards
Copy link
Member

I think this already works, we use the OutputCache attribute on a Razor Component page rendered using SSR in the Aspire Starter App template.

@JeepNL
Copy link
Contributor

JeepNL commented Jan 17, 2024

@DamianEdwards Do you maybe have a link? I'm interested to try this!

@DamianEdwards
Copy link
Member

@JeepNL
Copy link
Contributor

JeepNL commented Jan 19, 2024

@DamianEdwards Thanks, I'm gonna check it out!

@dotnet-policy-service dotnet-policy-service bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@wtgodbe wtgodbe removed the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@dotnet-policy-service dotnet-policy-service bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@wtgodbe wtgodbe removed the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 13, 2024
@dotnet dotnet deleted a comment from dotnet-policy-service bot Feb 13, 2024
@dotnet dotnet deleted a comment from dotnet-policy-service bot Feb 13, 2024
@VahidN
Copy link

VahidN commented Mar 2, 2024

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"/>

@zubairkhakwani
Copy link

@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.

@zubairkhakwani
Copy link

@javiercn , I was wondering if there are any plans to add support for Blazor SSR pages anytime soon. I appreciate your time and efforts.

@danroth27
Copy link
Member Author

@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.

@danroth27
Copy link
Member Author

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.

@danroth27
Copy link
Member Author

I've opened #55520 to separately track adding a Blazor Cache component as an analog to the existing cache tag helper used in MVC & Razor Pages apps.

@zubairkhakwani
Copy link

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.

bandicam 2024-05-04 09-46-40-377

@VahidN
Copy link

VahidN commented May 4, 2024

@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
Copy link

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components enhancement This issue represents an ask for new feature or an enhancement to an existing one help candidate Indicates that the issues may be a good fit for community to help with. Requires work from eng. team Pillar: Complete Blazor Web
Projects
None yet
Development

No branches or pull requests

10 participants