Skip to content

TempData support for Blazor#64749

Merged
dariatiurina merged 50 commits intodotnet:mainfrom
dariatiurina:49683-tempdata
Feb 3, 2026
Merged

TempData support for Blazor#64749
dariatiurina merged 50 commits intodotnet:mainfrom
dariatiurina:49683-tempdata

Conversation

@dariatiurina
Copy link
Contributor

@dariatiurina dariatiurina commented Dec 12, 2025

TempData support for Blazor

Summary

This PR adds native TempData support for Blazor Server-Side Rendering (SSR), similar to the existing TempData in MVC. TempData provides a mechanism for storing data that persists between HTTP requests, making it ideal for scenarios like:

  • Flash messages after form submissions
  • Passing data during redirects (POST-Redirect-GET pattern)
  • One-time notifications

Changes

Core Components

Component Description
ITempData Interface defining the TempData contract, extends IDictionary<string, object?>
TempData Concrete implementation with lazy loading and automatic cleanup
ITempDataProvider Abstraction for storage backends
CookieTempDataProvider Default provider using encrypted cookies
SessionStorageTempDataProvider Alternative provider using session storage
JsonTempDataSerializer JSON-based serialization for TempData values

Integration

TempData is automatically registered when calling AddRazorComponents() and is provided as a cascading value. The default cookie-based provider is used unless explicitly configured otherwise.

Public API

ITempData Interface

namespace Microsoft.AspNetCore.Components;

/// <summary>
/// Provides a dictionary for storing data that is needed for subsequent requests.
/// Data stored in TempData is automatically removed after it is read unless
/// Keep() or Keep(string) is called, or it is accessed via Peek(string).
/// </summary>
public interface ITempData : IDictionary<string, object?>
{
    /// <summary>
    /// Gets the value associated with the specified key and then schedules it for deletion.
    /// </summary>
    object? Get(string key);

    /// <summary>
    /// Returns the value associated with the specified key without marking it for deletion.
    /// </summary>
    object? Peek(string key);

    /// <summary>
    /// Marks all keys in the dictionary for retention. Values will be available on the next request.
    /// </summary>
    void Keep();

    /// <summary>
    /// Marks the specified key for retention. The value will be available on the next request.
    /// </summary>
    void Keep(string key);
}

ITempDataProvider Interface

namespace Microsoft.AspNetCore.Components.Endpoints;

/// <summary>
/// Provides an abstraction for a provider that stores and retrieves temporary data.
/// </summary>
public interface ITempDataProvider
{
    /// <summary>
    /// Loads temporary data from the given <see cref="HttpContext"/>.
    /// </summary>
    IDictionary<string, object?> LoadTempData(HttpContext context);

    /// <summary>
    /// Saves temporary data to the given <see cref="HttpContext"/>.
    /// </summary>
    void SaveTempData(HttpContext context, IDictionary<string, object?> values);
}

Service Collection Extensions

namespace Microsoft.Extensions.DependencyInjection;

public static class TempDataProviderServiceCollectionExtensions
{
    /// <summary>
    /// Registers the cookie-based TempData provider (default).
    /// </summary>
    public static IServiceCollection AddCookieTempDataValueProvider(
        this IServiceCollection services,
        Action<CookieTempDataProviderOptions>? configure = null);

    /// <summary>
    /// Registers the session-based TempData provider.
    /// </summary>
    public static IServiceCollection AddSessionStorageTempDataValueProvider(
        this IServiceCollection services);
}

CookieTempDataProviderOptions

namespace Microsoft.AspNetCore.Components.Endpoints;

public class CookieTempDataProviderOptions
{
    /// <summary>
    /// The name of the cookie. Default: ".AspNetCore.Components.TempData"
    /// </summary>
    public string CookieName { get; set; }

    /// <summary>
    /// The cookie path. Default: "/"
    /// </summary>
    public string? Path { get; set; }

    /// <summary>
    /// The cookie domain.
    /// </summary>
    public string? Domain { get; set; }

    /// <summary>
    /// The SameSite attribute of the cookie. Default: SameSiteMode.Strict
    /// </summary>
    public SameSiteMode SameSite { get; set; }
}

Tests

TempData behavior (src/Components/Endpoints/test/TempData/TempDataTest.cs):

  • Indexer get/set operations
  • Get returns value and removes from retained keys
  • Peek returns value without removing from retained keys
  • Keep() retains all keys
  • Keep(string) retains specific key
  • ContainsKey returns correct results
  • Remove removes key and returns success
  • Save returns only retained keys
  • Load populates data from dictionary
  • Clear removes all data
  • WasLoaded tracks lazy loading state
  • Keys are case-insensitive

JSON Serialization (src/Components/Endpoints/test/TempData/JsonTempDataSerializerTest.cs):

  • Serialize/deserialize primitive types (string, int, bool, Guid, DateTimeOffset)
  • Serialize/deserialize arrays (string[], int[])
  • Serialize/deserialize dictionaries
  • Handle empty arrays and null values
  • Throw for unsupported types

Cookie Provider (src/Components/Endpoints/test/TempData/CookieTempDataProviderTest.cs):

  • Load returns empty dictionary when no cookie exists
  • Save deletes cookie when no data
  • Save sets encrypted cookie when data exists
  • Round-trip preserves all supported types
  • Handle invalid/corrupted cookies gracefully

Service Registration (src/Components/Endpoints/test/TempData/TempDataProviderServiceExtensionsTest.cs):

  • Cookie provider registers expected services
  • Session provider registers expected services
  • Custom options are applied correctly

E2E Tests

Integration tests (src/Components/test/E2ETest/Tests/TempDataTest.cs):

  • Values persist after same-page redirect
  • Values persist across different pages
  • Peek preserves values for subsequent requests
  • Keep() retains all values
  • Keep(key) retains specific value
  • Remove() deletes values
  • Works with both cookie and session providers

API usage

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();
    }
}

Design Decisions

  1. Cascading Parameter: TempData is provided as a cascading value rather than requiring explicit injection, making it easily accessible throughout the component tree.

  2. Lazy Loading: TempData is only loaded from storage when first accessed, avoiding unnecessary cookie parsing for requests that don't use TempData.

  3. Cookie as Default: Cookies are the default storage mechanism (matching MVC behavior) because they don't require additional middleware configuration.

  4. Data Protection: Cookie values are encrypted using ASP.NET Core Data Protection to prevent tampering and information disclosure.

  5. Response.OnStarting: TempData is saved using Response.OnStarting callback to ensure it's persisted even if the component doesn't explicitly save it.

Fixes #49683

@github-actions github-actions bot added the area-blazor Includes: Blazor, Razor Components label Dec 12, 2025
@dariatiurina dariatiurina self-assigned this Dec 12, 2025
@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 Jan 9, 2026
@dariatiurina dariatiurina removed the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Jan 12, 2026
Copy link
Member

@javiercn javiercn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good so far!

I haven't looked at the tests yet, but wanted to give the feedback so far.

Copy link
Member

@ilonatommy ilonatommy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed framework changes.

HttpOnly = true,
SameSite = SameSiteMode.Lax,
IsEssential = false,
SecurePolicy = CookieSecurePolicy.SameAsRequest,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MVC has

SecurePolicy = CookieSecurePolicy.None,

Why did we decide to follow a different pattern?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// <item><description><see cref="CookieBuilder.SecurePolicy"/> defaults to <see cref="CookieSecurePolicy.SameAsRequest" />.</description></item>

In XML doc comment it is mentioned that it defaults to the SameAsRequest. It was that way originally in MVC until #8043 changed it to None. I am pretty sure that currently this edge case, due to which it was changed, is extremely rare.

@dariatiurina dariatiurina requested a review from a team as a code owner January 20, 2026 10:22
Copy link
Member

@javiercn javiercn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dariatiurina looks great.

One thing is that I don't see support for [SupplyParameterFromTempData] in this change. Is this expected in a follow up PR?

JsonValueKind.Null => null,
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Number => element.GetInt32(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this what MVC does? This is incorrect, Number can be a floating-point value, so this can fail.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind this comment. I see MVC does exactly this.


private static object? DeserializeString(JsonElement element)
{
var type = GetStringType(element);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also questionable (doing this type of "probbing/testing")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same. I just saw MVC does the same thing.

We should reconsider this a bit. We have to give it some thought to see if we can do better. One option would be to store the type information internally for round tripping.

@dariatiurina dariatiurina merged commit 65468ce into dotnet:main Feb 3, 2026
25 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the 11.0-preview1 milestone Feb 3, 2026
@wtgodbe wtgodbe modified the milestones: 11.0-preview1, 11.0-preview2 Feb 4, 2026
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Blazor TempData

5 participants