Skip to content

Conversation

@dariatiurina
Copy link
Contributor

@dariatiurina dariatiurina commented Jan 22, 2026

SupplyParameterFromSession support for Blazor

Summary

Enable Blazor SSR developers to easily read and write session data using a declarative [SupplyParameterFromSession] attribute, providing a familiar pattern consistent with existing [SupplyParameterFromQuery] and [SupplyParameterFromForm] attributes.

Motivation

Currently, Blazor SSR lacks a simple, declarative way to access session data. Developers who want to persist user-specific data across HTTP requests (like shopping cart contents or multi-step form state) must:

  • Inject IHttpContextAccessor and manually interact with ISession
  • Handle serialization/deserialization manually
  • Write boilerplate code for each session value

This creates inconsistent patterns across applications and increases the likelihood of errors. MVC Razor Pages developers have easier access to session data, and Blazor SSR should offer a similar experience.

Goals

  • Enable developers to read session data declaratively using [SupplyParameterFromSession]
  • Enable developers to write session data by simply assigning values to decorated properties
  • Provide automatic JSON serialization/deserialization for supported types
  • Support custom session key names via the Name property
  • Make session keys case-insensitive for consistency
  • Integrate seamlessly with existing AddRazorComponents() setup

Non-Goals

  • Not replacing Session or databases for long-term storage
  • Not providing manipulation beyond read/write through the attribute
  • Not supporting all possible types - focus on JSON-serializable types
  • Not providing session management features (expiration, cleanup, etc.)
  • Not supporting WebAssembly or Server interactivity modes (SSR only in Phase 1)

Scenarios

Scenario 1: Multi-step form wizard

As a developer, I want to preserve form data across multiple pages so that users don't lose their input when navigating between steps.

  • Context: Building a checkout flow with shipping, payment, and confirmation pages
  • Flow: User fills Page 1 → Data persists to session → User navigates to Page 2 → Previous data available
  • Cleanup: Session data persists until session expires or is explicitly cleared

Scenario 2: User preferences

As a developer, I want to store user preferences (like theme or language) in the session so that they persist across page navigations without requiring database storage.

  • Context: Storing temporary user choices that don't need permanent storage
  • Flow: User selects preference → Stored in session → Available on all subsequent requests

Scenario 3: Shopping cart (before checkout)

As a developer, I want to maintain a shopping cart across pages so users can continue browsing and adding items.

  • Context: E-commerce application with anonymous shopping
  • Flow: User adds item → Cart stored in session → User navigates → Cart preserved

Detailed Design

Core Components

1. SupplyParameterFromSessionAttribute

A new attribute that inherits from CascadingParameterAttributeBase:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class SupplyParameterFromSessionAttribute : CascadingParameterAttributeBase
{
    /// <summary>
    /// Gets or sets the name of the session key. If not specified, the property name will be used.
    /// </summary>
    public string? Name { get; set; }
}

2. ISessionValueMapper Interface

public interface ISessionValueMapper
{
    object? GetValue(string sessionKey, Type targetType);
    void RegisterValueCallback(string sessionKey, Func<object?> valueGetter);
}

3. SessionValueMapper Implementation

The SessionValueMapper class handles:

  • Reading values from session and deserializing via System.Text.Json
  • Registering callbacks to persist values when the response starts
  • Converting session keys to lowercase for case-insensitivity

Lifecycle Flow

Component Rendered 
    ↓
Subscribe (RegisterValueCallback) 
    ↓
GetValue (read from session)
    ↓
Property available for use
    ↓
Response.OnStarting → PersistAllValues
    ↓
Values serialized to session

We do not use Unsubcribe due to complicated situations with the disposal of the components in SSR.

Usage Examples

Basic usage - reading and writing:

@page "/profile"

<input @bind="Email" />

@code {
    [SupplyParameterFromSession]
    public string? Email { get; set; }
    
    void UpdateEmail()
    {
        // Simply assign - value persists to session automatically
        Email = "new@email.com";
    }
}

Custom session key name:

[SupplyParameterFromSession(Name = "user_email")]
public string? UserEmail { get; set; }

Complex types:

[SupplyParameterFromSession]
public ShoppingCart? Cart { get; set; }

public class ShoppingCart
{
    public List<CartItem> Items { get; set; } = new();
    public decimal Total { get; set; }
}

Multi-step form:

// Page1.razor
[SupplyParameterFromSession]
public FormData? WizardData { get; set; }

void Next()
{
    WizardData = new FormData { Step1Value = inputValue };
    NavigationManager.NavigateTo("/page2", forceLoad: true);
}

// Page2.razor - WizardData is automatically available

Supported Types

  • Primitives: string, int, bool, double, decimal, etc.
  • Common types: Guid, DateTime, DateTimeOffset
  • Nullable versions: int?, DateTime?, etc.
  • Collections: List<T>, Dictionary<string, T>, arrays
  • Enums: All enum types
  • Custom classes: Any JSON-serializable class

Registration

The feature is automatically enabled when using AddRazorComponents():

// In RazorComponentsServiceCollectionExtensions
services.TryAddScoped<ISessionValueMapper, SessionValueMapper>();
services.AddSupplyValueFromSessionProvider();

Developers must still configure session middleware in their application:

// Program.cs
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(options =>
{
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
});

// ...

app.UseSession();

Risks and Unknowns

Risk 1: Session size limits

Developers may store excessive data in session, impacting performance.

Risk 2: Serialization failures

Complex types may fail to serialize/deserialize.

Risk 3: Session not configured

Developers may forget to configure session middleware.

Unknown: Performance impact

Need to measure the overhead of JSON serialization/deserialization per request.

Drawbacks

  • Requires session middleware to be configured separately
  • Only works with Blazor SSR (not WebAssembly or Server interactivity)
  • JSON serialization adds overhead compared to raw session access
  • Values persist only until session expires - not suitable for long-term storage
  • Developers must understand session lifecycle

Considered Alternatives

Alternative 1: TempData approach

Reuse MVC's TempData infrastructure.

Why rejected: TempData has single-read semantics (values are removed after reading), which doesn't fit session persistence use cases.

Alternative 2: ProtectedSessionStorage

Use the existing ProtectedSessionStorage from Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage.

Why rejected: Requires JavaScript interop, which is incompatible with Blazor SSR's static rendering model.

Alternative 3: Direct ISession injection

Developers inject IHttpContextAccessor and use ISession directly.

Why rejected: Works but is verbose and inconsistent with other SupplyParameterFrom* patterns. This proposal provides a better developer experience while still using ISession under the hood.

Examples

Complete multi-page form example

Step1.razor:

@page "/wizard/step1"

<form @onsubmit="GoToStep2" @formname="step1" method="post">
    <AntiforgeryToken />
    <input @bind="FormInput" name="FormInput" />
    <button type="submit">Next</button>
</form>

@code {
    [SupplyParameterFromSession]
    public WizardState? State { get; set; }
    
    [SupplyParameterFromForm]
    public string? FormInput { get; set; }
    
    void GoToStep2()
    {
        State = new WizardState { Name = FormInput };
        NavigationManager.NavigateTo("/wizard/step2", forceLoad: true);
    }
}

Step2.razor:

@page "/wizard/step2"

<p>Hello, @State?.Name!</p>
<input @bind="EmailInput" />
<button @onclick="Complete">Complete</button>

@code {
    [SupplyParameterFromSession]
    public WizardState? State { get; set; }
    
    string? EmailInput { get; set; }
    
    void Complete()
    {
        State!.Email = EmailInput;
        NavigationManager.NavigateTo("/wizard/complete", forceLoad: true);
    }
}

Open Questions

  1. Should we provide a way to clear session values explicitly through the attribute?
  2. What's the performance impact of JSON serialization on high-traffic pages?
  3. Should we add support for custom serializers?

Fixes #64422

@github-actions github-actions bot added the area-blazor Includes: Blazor, Razor Components label Jan 22, 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 SessionData

1 participant