-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Description
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()andKeep()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
ChunkingCookieManagerto 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:
- On Load: All values from storage are marked as "retained" (available for this request)
- On Access:
Get()removes the retention marker;Peek()preserves it - On Save: Only retained values are persisted to storage
- 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
@injectin every component. This makes TempData accessible throughout the component tree without explicit injection, similar to howHttpContextis 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:
- Automatic Registration: Registered as a cascading value when
AddRazorComponents()is called - Lazy Loading: Storage access deferred until first use
- Automatic Save: Values persisted via
Response.OnStartingcallback, ensuring data is saved even if components don't explicitly trigger persistence - 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.