Skip to content

SupplyParameterFromTempData support for Blazor#65306

Open
dariatiurina wants to merge 68 commits intodotnet:mainfrom
dariatiurina:49683-supply-parameter-from-tempdata
Open

SupplyParameterFromTempData support for Blazor#65306
dariatiurina wants to merge 68 commits intodotnet:mainfrom
dariatiurina:49683-supply-parameter-from-tempdata

Conversation

@dariatiurina
Copy link
Contributor

@dariatiurina dariatiurina commented Feb 3, 2026

SupplyParameterFromTempData

Summary

Provides [SupplyParameterFromTempData] attribute for Blazor SSR components to read and write TempData values, consistent with [SupplyParameterFromQuery] and [SupplyParameterFromForm] patterns.

Motivation

While TempData is accessible via the [CascadingParameter] ITempData approach, many scenarios only need simple read/write of a single value. The attribute-based approach:

  • Reduces boilerplate for common use cases
  • Provides consistency with existing SupplyParameterFrom* attributes
  • Enables automatic two-way binding without manual TempData["key"] access

Design

Attribute

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

    /// <inheritdoc />
    internal override bool SingleDelivery => false;
}

ITempDataValueMapper Interface

The mapper interface bridges the value provider and the underlying TempData store:

public interface ITempDataValueMapper
{
    object? GetValue(string tempDataKey, Type targetType);
    void RegisterValueCallback(string tempDataKey, Func<object?> valueGetter);
    void DeleteValueCallback(string tempDataKey);
}
  • GetValue reads from TempData using Get() semantics (marks for deletion).
  • RegisterValueCallback stores a getter that will be invoked at persist time to capture the current property value.
  • DeleteValueCallback removes a registered callback (called on component unsubscribe to prevent memory leaks).

Lifecycle

  1. Initialize: TempDataValueMapper.SetRequestContext(HttpContext) is called during EndpointHtmlRenderer.InitializeStandardComponentServicesAsync, wiring the mapper to the current request.
  2. Subscribe: When a component renders, SupplyParameterFromTempDataValueProvider.Subscribe() uses reflection to create a PropertyGetter for the decorated property and registers a callback via ITempDataValueMapper.RegisterValueCallback().
  3. GetValue: SupplyParameterFromTempDataValueProvider.GetCurrentValue() reads the value from TempData using ITempDataValueMapper.GetValue() (which calls TempData.Get() — marks for deletion). Deserialization errors are caught and logged, returning null.
  4. Persist: When TempDataService.Save() is called, it invokes TempDataValueMapper.PersistValues(tempData), which iterates all registered callbacks, reads the current property values from the components, and writes them back into TempData. Callback exceptions are caught, logged, and do not prevent other keys from being persisted.
  5. Unsubscribe: When a component is disposed, SupplyParameterFromTempDataValueProvider.Unsubscribe() calls ITempDataValueMapper.DeleteValueCallback() to remove the registered callback.

Implementation Details

  • Case-insensitive callbacks: The internal callback dictionary uses StringComparer.OrdinalIgnoreCase.
  • Duplicate key guard: RegisterValueCallback throws InvalidOperationException if a callback is already registered for the same key — multiple components cannot bind to the same TempData key.
  • TempData resolution: TempDataValueMapper reads TempData from HttpContext.Items[typeof(ITempData)], the same single instance used by the cascading parameter approach.
  • Error logging: Two log events are defined — TempDataPersistFail (callback exception during persist) and TempDataDeserializeFail (JsonException during read).

Registration

Automatically enabled when calling AddRazorComponents():

services.TryAddScoped<ITempDataValueMapper, TempDataValueMapper>();
services.AddSupplyValueFromTempDataProvider();

AddSupplyValueFromTempDataProvider registers the SupplyParameterFromTempDataValueProvider as a scoped ICascadingValueSupplier:

public static IServiceCollection AddSupplyValueFromTempDataProvider(this IServiceCollection services)
{
    services.TryAddEnumerable(ServiceDescriptor.Scoped<ICascadingValueSupplier, SupplyParameterFromTempDataValueProvider>());
    return services;
}

Usage

Basic:

@code {
    [SupplyParameterFromTempData]
    public string? Message { get; set; }
}

Custom key:

@code {
    [SupplyParameterFromTempData(Name = "flash_message")]
    public string? Message { get; set; }
}

Form with redirect:

@page "/form"

<p>@Message</p>

<form method="post" @formname="MyForm" @onsubmit="Submit">
    <AntiforgeryToken />
    <button type="submit">Submit</button>
</form>

@code {
    [SupplyParameterFromTempData]
    public string? Message { get; set; }

    void Submit()
    {
        Message = "Success!";
        NavigationManager.NavigateTo("/form", forceLoad: true);
    }
}

Out of Scope

  • Peek() and Keep() semantics - use [CascadingParameter] ITempData for advanced control
  • Custom serialization

Risks

  • Callback ordering: When a component uses both TempData["key"] directly and [SupplyParameterFromTempData] for the same key, the final persisted value depends on execution order. Mitigation: Document that mixing approaches for the same key is unsupported.
  • Duplicate key across components: If two components on the same page both use [SupplyParameterFromTempData] with the same key, the second RegisterValueCallback call throws InvalidOperationException. Mitigation: Error message clearly identifies the conflicting key.

Fixes #49683

Copy link
Member

Choose a reason for hiding this comment

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

Does setting the property causes it to be saved to temp data for the next navigation?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. But reading through this property always goes through Get() so it only persists once.

Copilot AI review requested due to automatic review settings February 6, 2026 16:11
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds a new [SupplyParameterFromTempData] attribute for Blazor server-side rendering components, enabling automatic read/write of TempData values. This follows the established pattern of [SupplyParameterFromQuery] and [SupplyParameterFromForm] attributes, providing a more convenient alternative to manually accessing TempData via cascading parameters. The implementation includes a value mapper service that reads TempData values on component initialization and persists property values back to TempData before the response is sent.

Changes:

  • New SupplyParameterFromTempDataAttribute in Components assembly for marking properties
  • ITempDataValueMapper interface and TempDataValueMapper implementation for managing read/write operations
  • SupplyParameterFromTempDataValueProvider as the cascading value supplier
  • Automatic registration in AddRazorComponents() with service collection extensions
  • Integration hooks in EndpointHtmlRenderer and TempDataService for initialization and persistence
  • Unit and E2E tests covering basic scenarios

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/Components/Components/src/SupplyParameterFromTempDataAttribute.cs New attribute definition inheriting from CascadingParameterAttributeBase
src/Components/Endpoints/src/TempData/ITempDataValueMapper.cs Public interface for TempData value mapping operations
src/Components/Endpoints/src/TempData/TempDataValueMapper.cs Core implementation handling read/write with callback registration
src/Components/Endpoints/src/TempData/SupplyParameterFromTempDataValueProvider.cs ICascadingValueSupplier implementation providing TempData values to components
src/Components/Endpoints/src/TempData/SupplyParameterFromTempDataServiceCollectionExtensions.cs Service registration extension method
src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs Adds initialization hook for TempDataValueMapper
src/Components/Endpoints/src/DependencyInjection/TempDataService.cs Adds persistence hook to invoke registered callbacks
src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs Registers TempDataValueMapper and provider services automatically
src/Components/Endpoints/test/TempData/TempDataValueMapperTest.cs Comprehensive unit tests for the mapper implementation
src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/TempData/TempDataComponent.razor Test component demonstrating usage
src/Components/test/E2ETest/Tests/TempDataCookieTest.cs E2E test for cookie-based TempData provider
src/Components/test/E2ETest/Tests/TempDataSessionStorageTest.cs E2E test for session storage TempData provider
src/Components/Endpoints/src/PublicAPI.Unshipped.txt API surface additions
src/Components/Components/src/PublicAPI.Unshipped.txt API surface additions for attribute
src/Components/Components/src/Microsoft.AspNetCore.Components.csproj Adds InternalsVisibleTo for Endpoints assembly
src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Prerendering.cs Visibility modifier updates
src/Components/Endpoints/src/Rendering/EndpointComponentState.cs Visibility modifier updates
src/Framework/App.Runtime/src/CompatibilitySuppressions.xml BOM character removal

dariatiurina and others added 9 commits February 9, 2026 15:10
…bute.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…onents/Pages/TempData/TempDataComponent.razor

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

namespace Microsoft.AspNetCore.Components.Endpoints;

internal partial class TempDataValueMapper : ITempDataValueMapper
Copy link
Member

Choose a reason for hiding this comment

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

Can you name a scenario where someone would want to implement a custom ITempDataValueMapper or
use ITempDataValueMapper directly without SupplyParameterFromTempDataValueProvider? I cannot think of any. I think we can simplify the implementation.

TempDataValueMapper and ITempDataValueMapper have 3 unrelated responsibilities: getting value, managing callbacks and being DI injection point. We could merge it into SupplyParameterFromTempDataValueProvider that would have the responsibility of supplying the values to compoennts that declare [SupplyParameterFromTempData], reading, subscribing etc would be just implementation details of that responsibility.

That would shrink the volume of new public APIs and would make it easier to read the code. It would require removing TempDataValueMapper and ITempDataValueMapper, moving PersistValues and GetTempData to SupplyParameterFromTempDataValueProvider together with subscription bits. + updating the registration to services.TryAddScoped<SupplyParameterFromTempDataValueProvider>();

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

3 participants