You submit a form in your Blazor static SSR app, redirect to a confirmation page, and need to show a success banner. In MVC or Razor Pages, you'd reach for TempData without thinking. In Blazor, you've been stashing messages in query strings, injecting scoped services, or wiring up custom state containers just to survive a single redirect. That friction has quietly shaped how developers build Blazor SSR forms — and not for the better.
.NET 11 Preview 2 changes this. Blazor now supports ITempData as a first-class cascading parameter during static server-side rendering, bringing the same ephemeral state mechanism that MVC developers have relied on for over a decade. No extra packages, no custom plumbing — just a [CascadingParameter] and you're done.
What TempData actually is
If you've spent your career in Blazor and never touched MVC, TempData is a dictionary-like store that persists values across exactly one HTTP request. You write a value before a redirect, read it on the next page load, and it vanishes automatically. It sits in the gap between "too small for a database" and "too cross-request for component state".
The underlying storage is cookie-based by default, using ASP.NET Core Data Protection for encryption. Values are serialised into the response cookie, sent to the browser, and returned on the next request. The framework reads them, makes them available, and marks them for deletion — unless you explicitly tell it not to.
This pattern is the backbone of the POST-Redirect-GET (PRG) flow: accept a form submission, redirect to avoid duplicate posts on refresh, and display a one-time message on the destination page.
How it works in Blazor
TempData is automatically registered when you call AddRazorComponents() in your Program.cs. There is nothing else to configure. The framework provides it as a cascading value, so any static SSR component can consume it through the familiar [CascadingParameter] attribute.
public partial class Contact
{
[CascadingParameter]
public ITempData? TempData { get; set; }
[SupplyParameterFromForm]
public ContactForm? Input { get; set; }
[Inject]
public NavigationManager Navigation { get; set; } = default!;
private void HandleSubmit()
{
// Process the form...
TempData!["SuccessMessage"] = $"Thanks {Input!.Name}, your enquiry has been sent.";
Navigation.NavigateTo("/contact/confirmation");
}
}
On the confirmation page, you read the value back:
public partial class ContactConfirmation
{
[CascadingParameter]
public ITempData? TempData { get; set; }
private string? confirmationMessage;
protected override void OnInitialized()
{
confirmationMessage = TempData?.Get("SuccessMessage") as string;
}
}
The corresponding Razor markup is straightforward:
@page "/contact/confirmation"
@if (!string.IsNullOrEmpty(confirmationMessage))
{
<div class="alert alert-success">@confirmationMessage</div>
}
<p>We'll be in touch shortly.</p>
If the user refreshes the confirmation page, the message is gone — which is exactly the behaviour you want.
The ITempData lifecycle: Get, Peek, and Keep
The ITempData interface exposes three key operations that control how long values survive. Understanding the distinction is critical to avoiding the "where did my message go?" debugging sessions.
Get(key) reads the value and marks it for deletion. On the next request, it's gone. This is the default behaviour when you access values through the indexer as well.
Peek(key) reads the value without marking it for deletion. The value survives into the next request. Use this when you need to read a value but aren't sure the current request is the one that should consume it.
Keep(key) prevents a previously-marked value from being deleted. If you called Get but then realise you need the value to survive one more hop, Keep saves it.
public partial class OrderReview
{
[CascadingParameter]
public ITempData? TempData { get; set; }
private string? pendingMessage;
protected override void OnInitialized()
{
// Read the message without consuming it
pendingMessage = TempData?.Peek("OrderMessage") as string;
}
private void ConfirmOrder()
{
// Now consume it — it won't survive the next request
var message = TempData?.Get("OrderMessage") as string;
// Process order...
}
private void GoBack()
{
// Keep the message alive for the previous page
TempData?.Keep("OrderMessage");
Navigation.NavigateTo("/orders");
}
}
This three-method design gives you precise control over the value's lifespan without managing timers or expiration logic.
Building a reusable flash message component
Most applications need flash messages in more than one place. Rather than scattering TempData reads across every page, build a component that handles the pattern once.
@if (!string.IsNullOrEmpty(message))
{
<div class="alert alert-@alertType" role="alert">
@message
</div>
}
@code {
[CascadingParameter]
public ITempData? TempData { get; set; }
[Parameter]
public string Key { get; set; } = "FlashMessage";
[Parameter]
public string AlertType { get; set; } = "info";
private string? message;
private string alertType = "info";
protected override void OnInitialized()
{
if (TempData?.Get(Key) is string value)
{
message = value;
alertType = AlertType;
}
// Check for a type override stored alongside the message
if (TempData?.Get($"{Key}:Type") is string storedType)
{
alertType = storedType;
}
}
}
Now any page can display flash messages by dropping in the component:
@page "/dashboard"
<FlashMessage Key="Notification" />
<h1>Dashboard</h1>
<!-- rest of the page -->
And any form handler or action can set a flash message before redirecting:
TempData!["Notification"] = "Your settings have been saved.";
TempData["Notification:Type"] = "success";
Navigation.NavigateTo("/dashboard");
The POST-Redirect-GET pattern done properly
The PRG pattern prevents duplicate form submissions when users refresh the browser after a POST. Without TempData, implementing this in Blazor static SSR required either query string parameters (visible to the user and bookmarkable) or a scoped service that broke down under concurrent requests.
Here's a complete example — an item creation form with proper PRG:
public partial class CreateItem
{
[CascadingParameter]
public ITempData? TempData { get; set; }
[SupplyParameterFromForm]
public CreateItemRequest? Input { get; set; }
[Inject]
public NavigationManager Navigation { get; set; } = default!;
[Inject]
public IItemService ItemService { get; set; } = default!;
private async Task HandleSubmit()
{
var item = await ItemService.CreateAsync(Input!);
TempData!["FlashMessage"] = $"Item '{item.Name}' created successfully.";
TempData["FlashMessage:Type"] = "success";
Navigation.NavigateTo($"/items/{item.Id}");
}
}
The destination page reads the flash message, shows it once, and a refresh produces a clean page with no form resubmission prompt — exactly as users expect.
Storage providers: cookies vs session
By default, Blazor's TempData uses CookieTempDataProvider, which serialises values into an encrypted cookie. This works well for small amounts of data and requires no server-side session state.
If you need to store larger payloads, you can switch to SessionStateTempDataProvider, which stores values in the server's session and only sends a session identifier in the cookie:
builder.Services.AddSession();
builder.Services.AddSingleton<ITempDataProvider, SessionStateTempDataProvider>();
var app = builder.Build();
app.UseSession();
// TIP
Stick with the cookie provider for simple flash messages and small strings. Switch to session storage only if you're passing complex objects or hitting cookie size limits (typically around 4 KB per cookie).
The cookie provider encrypts data using ASP.NET Core Data Protection, so values are not readable by the client. However, the cookie payload does increase response size, so avoid storing large object graphs.
Serialisation constraints
TempData values must be serialisable to JSON. The default cookie provider uses System.Text.Json under the hood, which means your values need to be primitives, strings, or types that serialise cleanly.
// Good — primitives and simple types
TempData["Count"] = 42;
TempData["Message"] = "Item saved";
TempData["Tags"] = new[] { "urgent", "review" };
// Bad — complex objects with circular references or non-serialisable members
TempData["Order"] = complexOrderObject; // Will throw at serialisation time
If you need to pass structured data, serialise it yourself:
// Writing
TempData["OrderSummary"] = JsonSerializer.Serialize(new OrderSummary
{
Id = order.Id,
Total = order.Total,
ItemCount = order.Items.Count
});
// Reading
if (TempData?.Get("OrderSummary") is string json)
{
var summary = JsonSerializer.Deserialize<OrderSummary>(json);
}
This keeps you in control of the serialisation format and avoids surprises from unsupported types.
Common pitfalls
Using TempData with interactive render modes. TempData is designed for static SSR only. It relies on HTTP request/response cycles to shuttle cookies back and forth. If your component runs in Interactive Server or WebAssembly mode, TempData won't be populated because there's no traditional HTTP request triggering the component render. Stick to static SSR pages for TempData consumption.
Forgetting the Get/Peek distinction. If you read a value with Get in OnInitialized but the component re-renders for any reason during the same request, the value is already marked for deletion. If you need to read it multiple times within the same request lifecycle, use Peek first and Get only when you're ready to consume it.
Storing too much data in cookies. The cookie-based provider has practical size limits. Browsers typically enforce a 4 KB limit per cookie, and some reverse proxies have header size limits. If your TempData payload approaches these limits, you'll get silent truncation or request failures. Keep TempData lean — a message string and perhaps an identifier, not a full object graph.
Not checking for null. TempData is nullable on the cascading parameter and values may not exist. Always null-check both the TempData instance and the returned value. A missing key returns null, not an exception — but a missing TempData cascading parameter (for example, in a non-SSR context) will give you a null reference if you're not careful.
Assuming values persist across multiple redirects. A value written with TempData["Key"] = value survives exactly one request after the redirect. If you redirect twice (A -> B -> C), the value is available on B but gone by C — unless you call Keep on B.
Summary
- TempData arrives in Blazor static SSR with .NET 11 Preview 2, available as a
[CascadingParameter]with no extra registration beyondAddRazorComponents() - The
Get,Peek, andKeepmethods give precise control over value lifetimes across HTTP requests - The POST-Redirect-GET pattern finally works naturally in Blazor without query string hacks or scoped service workarounds
- Cookie-based storage is the default, with session storage available for larger payloads
- Keep TempData values small, serialisable, and limited to static SSR components — it does not work with interactive render modes
- Build a reusable
FlashMessagecomponent to avoid scattering TempData reads across your application