Skip to content

TempData design proposal #65039

@dariatiurina

Description

@dariatiurina

Summary

Enables developers to persist temporary data between HTTP requests in Blazor SSR using TempData with read-once semantics. Provides an API with pluggable storage backends (cookies by default, session as alternative) that automatically manages data lifecycle without requiring custom solutions or interactivity modes.

Motivation and goals

Right now in the Blazor SSR there is a gap in the storage in-between requests (see #49683). Currently to store data in-between requests, users are supposed to create their own solutions even for the simplest of the situations (e.g. success page after redirect) or use interactivity to escape the problem altogether. We already have multiple solutions for this problem in the MVC that we can use as a template. TempData is one of them that allows users to persist values in-between requests.

TempData provides short-lived, server-side storage that survives exactly one additional HTTP request (read-once semantics) or more, if the user wants to do so. By default it is write-read-delete, but there is an option to read it without deletion. It's specifically designed for redirect scenarios where data needs to pass from one request to another but should not persist longer.

In scope

  • Automatically delete data after being read, with opt-in retention via Peek() and Keep() methods.
  • TempData works out-of-the-box with cookie storage when calling AddRazorComponents().
  • Support both cookie-based (default) and session-based storage providers.
  • Support primitives, DateTime, Guid, enums, and collections (arrays, List, Dictionary<string, T>).
  • Automatic lifecycle management (no manual save/load needed).

Out of scope

  • Support for user-defined classes or custom object serialization is not a goal. Only JSON-serializable primitives and collections are supported.
  • Using both cookie and session storage simultaneously is not supported. Developers must choose one storage mechanism per application.
  • TempData is designed for SSR scenarios only. Support for Blazor WebAssembly and Blazor Server is excluded as they have different state management patterns.
  • Pluggable serialization providers beyond the built-in JSON serializer are not supported.

Risks / unknowns

  • Cookie Size Limits: browsers enforce 4KB cookie limits. Developers storing large amounts of data may exceed this limit, causing silent data loss or HTTP 400 errors. Mitigation: use ChunkingCookieManager to automatically split cookies across multiple cookie headers and recommendation to use session storage for larger data needs.

  • Session Affinity Requirements: Session storage requires sticky sessions in load-balanced environments. Without it, users may lose data mid-flow. Mitigation: clear documentation that session storage requires session affinity or distributed cache and recommendations for using cookies for stateless deployments.

Examples

Basic form with flash messages:

@page "/my-form"
@inject NavigationManager NavigationManager

<p id="message">@_message</p>

<form method="post" @formname="MyForm" @onsubmit="HandleSubmit">
    <AntiforgeryToken />
    <input type="text" name="Name" />
    <button type="submit">Submit</button>
</form>

@code {
    [CascadingParameter]
    public ITempData? TempData { get; set; }

    private string? _message;

    protected override void OnInitialized()
    {
        // Get removes the value after reading (one-time use)
        _message = TempData?.Get("Message") as string ?? "No message";
    }

    private void HandleSubmit()
    {
        // Set success message
        TempData!["Message"] = "Form submitted successfully!";
        
        // Redirect - message will be available on next request
        NavigationManager.NavigateTo("/my-form", forceLoad: true);
    }
}

Reading without consuming (Peek):

@code {
    protected override void OnInitialized()
    {
        // Peek reads without removing - value available on next request too
        var notification = TempData?.Peek("Notification") as string;
    }
}

Keeping values for another request:

@code {
    protected override void OnInitialized()
    {
        var message = TempData?.Get("Message") as string;
        
        // Keep this specific value for one more request
        TempData?.Keep("Message");
        
        // Or keep all values
        TempData?.Keep();
    }
}

Detailed design

Overview

TempData is automatically enabled when calling AddRazorComponents() and provided to components as a cascading value. It uses pluggable storage backends (cookies by default, with session storage as an alternative) and implements read-once semantics where values are automatically removed after being read unless explicitly retained.

Storage Providers

Developers can choose between two storage mechanisms:

Cookie-Based Storage (Default)

Cookie storage works out-of-the-box without additional configuration. The implementation:

  • Serializes data to JSON and encrypts it using ASP.NET Core Data Protection APIs
  • Stores encrypted data in a cookie named .AspNetCore.Components.TempData
  • Automatically handles cookie chunking for larger payloads

Configuration:

// Default - no configuration needed
builder.Services.AddRazorComponents();

// Optional: Customize cookie settings
builder.Services.AddCookieTempDataValueProvider(options =>
{
    options.Cookie.Name = ".MyApp.TempData";
    options.Cookie.SameSite = SameSiteMode.Strict;
});

Session Storage

Session storage is available for scenarios requiring larger data storage or when cookies are undesirable:

  • Requires explicit session state configuration
  • No practical size limits (within session constraints)
  • Requires sticky sessions in load-balanced environments

Configuration:

builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession();
builder.Services.AddSessionStorageTempDataValueProvider();

app.UseSession();

Component Access

TempData is provided as a cascading value, making it accessible throughout the component hierarchy without explicit injection:

@code {
    [CascadingParameter]
    public ITempData? TempData { get; set; }
}

API Design

The ITempData interface extends IDictionary<string, object?> for familiarity with MVC developers, while adding specialized methods for controlling retention:

public interface ITempData : IDictionary<string, object?>
{
    object? Get(string key);      // Read and mark for deletion
    object? Peek(string key);     // Read without marking for deletion
    void Keep();                  // Retain all values for next request
    void Keep(string key);        // Retain specific value for next request
}

Read-Once Semantics

TempData implements automatic cleanup to prevent stale data:

  1. On Load: All values from storage are marked as "retained" (available for this request)
  2. On Access: Get() removes the retention marker; Peek() preserves it
  3. On Save: Only retained values are persisted to storage
  4. Developer Control: Keep() re-adds retention markers to override automatic deletion

This ensures data persists exactly as long as needed without manual cleanup.

Supported Data Types

TempData supports JSON-serializable types:

  • Primitives: string, int, bool, double, decimal, etc.
  • Common Types: Guid, DateTime, DateTimeOffset, TimeSpan, Uri.
  • Collections: Arrays, List<T>, Dictionary<string, T> where T is a supported type that was mentioned before.
  • Enums: All enum types.

Complex user-defined types are not supported to keep the API predictable and prevent serialization issues. An exception is thrown at save time if unsupported types are detected.

Key Design Decisions

  • Cascading Value vs. Injection: provide TempData as a cascading value rather than requiring @inject in every component. This makes TempData accessible throughout the component tree without explicit injection, similar to how HttpContext is provided. Components that don't use TempData don't need to declare it.

  • Cookie as Default: use cookie storage by default, with session storage as opt-in. This matches MVC TempData behavior for easier migration, works without additional configuration (session requires middleware setup), suitable for most scenarios (flash messages, PRG pattern).

  • Lazy Loading: only load TempData from storage when first accessed by a component. This avoids unnecessary cookie parsing or session lookups for requests that don't use TempData, improving performance for components that don't need temporary data.

  • Case-Insensitive Keys: keys are case-insensitive (TempData["message"] == TempData["Message"]), preventing subtle bugs from casing differences.

Request Lifecycle Integration

TempData integrates with the request pipeline through:

  1. Automatic Registration: Registered as a cascading value when AddRazorComponents() is called
  2. Lazy Loading: Storage access deferred until first use
  3. Automatic Save: Values persisted via Response.OnStarting callback, ensuring data is saved even if components don't explicitly trigger persistence
  4. Single Instance: One TempData instance per request, stored in HttpContext.Items

This design ensures TempData "just works" without requiring explicit lifecycle management from developers.

Drawbacks

No Compile-Time Type Safety

TempData stores object? values, requiring runtime casting: var name = TempData["Name"] as string. There's no IntelliSense or type checking, increasing likelihood of runtime errors compared to strongly-typed alternatives.

Cookie Size Constraints

The default cookie storage has a ~4KB browser limit. While chunking helps, developers storing large datasets must switch to session storage, which introduces session affinity requirements.

Session Storage Complexity

Session storage requires additional middleware configuration (AddSession(), UseSession()), distributed cache setup for scaled deployments, and sticky sessions for load balancing. This adds deployment and operational complexity.

Considered Alternatives

The alternative that was considered was the [SupplyParameterFromTempData] attribute that would have worked similar to the [SupplyParameterFromQuery]. It was rejected due to not being robust and flexible enough. This approach did not allow for an easy-to-use Peek() and Keep() as well as a clear dictionary-like interface.

Metadata

Metadata

Assignees

Labels

area-blazorIncludes: Blazor, Razor Componentsdesign-proposalThis issue represents a design proposal for a different issue, linked in the description

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions