Skip to content

Razor Pages

Aghogho Bernard edited this page May 12, 2026 · 3 revisions

Razor Pages

CacheWeave supports Razor Pages via CacheWeavePageFilter, an IAsyncPageFilter.

Setup

Register CacheWeavePageFilter as a global page filter via AddMvcOptions:

builder.Services.AddCacheWeave(options => { ... });
builder.Services.AddRazorPages()
    .AddMvcOptions(options => options.Filters.AddService<CacheWeavePageFilter>());

Decorating Page Handlers

Apply [CacheWeave] to OnGet / OnGetAsync handlers:

using CacheWeave.Core;

public class ProductsModel : PageModel
{
    private readonly IMediator _mediator;

    public ProductsModel(IMediator mediator) => _mediator = mediator;

    public IReadOnlyList<ProductDto> Products { get; private set; } = [];

    [CacheWeave("razor:products:list", ExpirySeconds = 300, IncludeQueryParams = true)]
    public async Task<IActionResult> OnGetAsync(
        [FromQuery] int page = 1,
        [FromQuery] int pageSize = 20)
    {
        var result = await _mediator.Send(new GetProductsQuery { Page = page, PageSize = pageSize });
        Products = result.Items;
        return Page();
    }
}

Eviction from Page Handlers

Apply [CacheWeaveEvict] to mutation handlers:

public class CreateProductModel : PageModel
{
    [BindProperty]
    public CreateProductInputModel Input { get; set; } = new();

    [CacheWeaveEvict(Prefix = "razor:products:")]
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid) return Page();

        await _mediator.Send(new CreateProductCommand(Input));
        return RedirectToPage("./Index");
    }
}

Derived Keys in Razor Pages

Omit the key argument and CacheWeave derives it from the page name and handler:

[CacheWeave]  // key: "Products.OnGetAsync" (or similar derived name)
public async Task<IActionResult> OnGetAsync() { ... }

The derived key format for Razor Pages uses the ActionDescriptor.DisplayName since there is no ControllerActionDescriptor. The exact value depends on the page route — use an explicit key for predictability.

Route Parameters

Route parameters in Razor Pages (e.g. @page "{id}") are automatically included in the cache key by default. Framework route values (controller, action, page, area) are always excluded.

// Pages/Products/Detail.cshtml — @page "{id:int}"
public class DetailModel : PageModel
{
    public ProductDto Product { get; private set; } = default!;

    // GET /Products/Detail/42 → key: "razor:products:detail:id=42"
    [CacheWeave("razor:products:detail", ExpirySeconds = 600)]
    public async Task<IActionResult> OnGetAsync(int id)
    {
        Product = await _mediator.Send(new GetProductByIdQuery(id));
        return Page();
    }
}

Excluding Route Params

// GET /Products/Detail/42/draft → key: "razor:products:detail:id=42" (version excluded)
[CacheWeave("razor:products:detail", ExcludeRouteParams = ["version"])]
public async Task<IActionResult> OnGetAsync(int id, string version) { ... }

Disabling Route Params

[CacheWeave("razor:products:stats", IncludeRouteParams = false)]
public async Task<IActionResult> OnGetAsync(int id) { ... }

Caching Partial Pages

CacheWeavePageFilter operates at the handler level, not the view level. To cache partial views or view components, use ICacheWeaveService directly in the view component:

public class ProductStatsViewComponent(ICacheWeaveService cache) : ViewComponent
{
    public async Task<IViewComponentResult> InvokeAsync()
    {
        var stats = await cache.GetOrSetAsync<ProductStatsDto>(
            "products:stats",
            factory: async () => await _db.Products
                .GroupBy(_ => 1)
                .Select(g => new ProductStatsDto
                {
                    Total = g.Count(),
                    Active = g.Count(p => p.IsActive)
                })
                .FirstOrDefaultAsync(),
            expiry: TimeSpan.FromMinutes(10));

        return View(stats);
    }
}

Limitations

  • HashBody = true is not meaningful for GET handlers (no request body). Use IncludeQueryParams = true instead.
  • Sliding expiry works the same as in MVC controllers — the entry is re-written on every hit.
  • [CacheWeaveEvict] on a POST handler evicts after the handler executes and before the redirect response is sent.

Clone this wiki locally