From 7c557e20113e1efb82c739fc8311d24268763f05 Mon Sep 17 00:00:00 2001 From: Daria Tiurina Date: Wed, 10 Dec 2025 17:27:18 +0100 Subject: [PATCH 1/7] Basic E2E test --- .../test/E2ETest/Tests/TempDataTest.cs | 41 +++++++++++ .../Pages/TempData/TempDataComponent.razor | 68 +++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 src/Components/test/E2ETest/Tests/TempDataTest.cs create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/TempData/TempDataComponent.razor diff --git a/src/Components/test/E2ETest/Tests/TempDataTest.cs b/src/Components/test/E2ETest/Tests/TempDataTest.cs new file mode 100644 index 000000000000..ff3d0e205c86 --- /dev/null +++ b/src/Components/test/E2ETest/Tests/TempDataTest.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Components.TestServer.RazorComponents; +using Microsoft.AspNetCore.E2ETesting; +using Xunit.Abstractions; +using OpenQA.Selenium; +using TestServer; + +namespace Microsoft.AspNetCore.Components.E2ETest.Tests; + +public class TempDataTest : ServerTestBase>> +{ + public TempDataTest( + BrowserFixture browserFixture, + BasicTestAppServerSiteFixture> serverFixture, + ITestOutputHelper output) + : base(browserFixture, serverFixture, output) + { + } + + public override Task InitializeAsync() => InitializeAsync(BrowserFixture.StreamingContext); + + [Fact] + public void TempDataCanPersistThroughNavigation() + { + Navigate($"{ServerPathBase}/tempdata"); + Browser.Equal("No message", () => Browser.FindElement(By.Id("message")).Text); + + Browser.FindElement(By.Id("set-values-button")).Click(); + Browser.FindElement(By.Id("navigate-to-the-same-page")).Click(); + + Browser.Equal("Message", () => Browser.FindElement(By.Id("message")).Text); + } + + //TO-DO: Add test for Peek() + //TO-DO: Add test for Keep() + //TO-DO: Add test for Keep(string) +} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/TempData/TempDataComponent.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/TempData/TempDataComponent.razor new file mode 100644 index 000000000000..1fa878c07b47 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/TempData/TempDataComponent.razor @@ -0,0 +1,68 @@ +@page "/tempdata" +@using Microsoft.AspNetCore.Components.Forms +@inject NavigationManager NavigationManager + +

TempData Basic Test

+ +
Ready
+ +
+ + + + +
+ + + + +
+ +

@_message

+

@_peekedValue

+

@_keptValue

+ + +@code { + [CascadingParameter] + public ITempData? TempData { get; set; } + + [SupplyParameterFromQuery] + public bool ShouldKeep { get; set; } + + private string? _message; + private string? _peekedValue; + private string? _keptValue; + + protected override void OnInitialized() + { + if (TempData is null) + { + return; + } + _message = TempData.Get("Message") as string; + _message ??= "No message"; + _peekedValue = TempData.Peek("PeekedValue") as string; + _peekedValue ??= "No peeked value"; + _keptValue = TempData.Get("KeptValue") as string; + _keptValue ??= "No kept value"; + if (ShouldKeep && _keptValue is not null) + { + TempData.Keep("KeptValue"); + } + } + + private void SetValues() + { + if (TempData is null) + { + return; + } + + TempData["Message"] = "Message"; + TempData["PeekedValue"] = "Peeked value"; + TempData["KeptValue"] = "Kept valie"; + } + + private void NavigateToSamePage() => NavigationManager.NavigateTo("/subdir/tempdata", forceLoad: true); +} From 8f21417dc90cfa77869a5d4bc8fef1ead3c1bbb9 Mon Sep 17 00:00:00 2001 From: Daria Tiurina Date: Wed, 10 Dec 2025 17:27:48 +0100 Subject: [PATCH 2/7] First simple implementation that holds values no matter what --- src/Components/Components/src/ITempData.cs | 15 ++++++ .../Components/src/PublicAPI.Unshipped.txt | 15 ++++++ src/Components/Components/src/TempData.cs | 48 +++++++++++++++++++ ...orComponentsServiceCollectionExtensions.cs | 4 ++ 4 files changed, 82 insertions(+) create mode 100644 src/Components/Components/src/ITempData.cs create mode 100644 src/Components/Components/src/TempData.cs diff --git a/src/Components/Components/src/ITempData.cs b/src/Components/Components/src/ITempData.cs new file mode 100644 index 000000000000..994eaea7fc28 --- /dev/null +++ b/src/Components/Components/src/ITempData.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.Components; + +public interface ITempData +{ + object? this[string key] { get; set; } + object? Get(string key); + object? Peek(string key); + void Keep(); + void Keep(string key); + + //TO-DO: Add Save to save and clean-up after request +} diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index 7dc5c58110bf..d8638dab5502 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -1 +1,16 @@ #nullable enable +Microsoft.AspNetCore.Components.ITempData +Microsoft.AspNetCore.Components.ITempData.Get(string! key) -> object? +Microsoft.AspNetCore.Components.ITempData.Keep() -> void +Microsoft.AspNetCore.Components.ITempData.Keep(string! key) -> void +Microsoft.AspNetCore.Components.ITempData.this[string! key].get -> object? +Microsoft.AspNetCore.Components.ITempData.this[string! key].set -> void +Microsoft.AspNetCore.Components.ITempData.Peek(string! key) -> object? +Microsoft.AspNetCore.Components.TempData +Microsoft.AspNetCore.Components.TempData.Get(string! key) -> object? +Microsoft.AspNetCore.Components.TempData.Keep() -> void +Microsoft.AspNetCore.Components.TempData.Keep(string! key) -> void +Microsoft.AspNetCore.Components.TempData.Peek(string! key) -> object? +Microsoft.AspNetCore.Components.TempData.TempData() -> void +Microsoft.AspNetCore.Components.TempData.this[string! key].get -> object? +Microsoft.AspNetCore.Components.TempData.this[string! key].set -> void diff --git a/src/Components/Components/src/TempData.cs b/src/Components/Components/src/TempData.cs new file mode 100644 index 000000000000..9452529001d9 --- /dev/null +++ b/src/Components/Components/src/TempData.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.Components; + +/// +public class TempData : ITempData +{ + private readonly Dictionary _data = new(); + private readonly HashSet _retainedKeys = new(); + + /// + public object? this[string key] + { + get => Get(key); + set => _data[key] = value; + } + + /// + public object? Get(string key) + { + return _data.TryGetValue(key, out var value) ? value : null; + } + + /// + public object? Peek(string key) + { + return _data.TryGetValue(key, out var value) ? value : null; + } + + /// + public void Keep() + { + foreach (var key in _data.Keys) + { + _retainedKeys.Add(key); + } + } + + /// + public void Keep(string key) + { + if (_data.ContainsKey(key)) + { + _retainedKeys.Add(key); + } + } +} diff --git a/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs b/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs index dc365194fcbe..f6f6c191147e 100644 --- a/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs +++ b/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs @@ -75,6 +75,10 @@ public static IRazorComponentsBuilder AddRazorComponents(this IServiceCollection services.TryAddScoped(); services.TryAddScoped(); + // TO-DO: Change this for TempData to work correctly + services.TryAddSingleton(); + services.TryAddCascadingValue(sp => sp.GetRequiredService()); + services.TryAddScoped(); RegisterPersistentComponentStateServiceCollectionExtensions.AddPersistentServiceRegistration(services, RenderMode.InteractiveWebAssembly); From 43608b0d1fecc4de7f6e1077b7906cee103755f3 Mon Sep 17 00:00:00 2001 From: Daria Tiurina Date: Thu, 11 Dec 2025 14:28:48 +0100 Subject: [PATCH 3/7] Made TempData connected to the HttpContext and improved TempData --- src/Components/Components/src/ITempData.cs | 2 - .../Components/src/PublicAPI.Unshipped.txt | 2 + src/Components/Components/src/TempData.cs | 34 +++- ...orComponentsServiceCollectionExtensions.cs | 24 ++- .../DependencyInjection/TempDataService.cs | 154 ++++++++++++++++++ 5 files changed, 207 insertions(+), 9 deletions(-) create mode 100644 src/Components/Endpoints/src/DependencyInjection/TempDataService.cs diff --git a/src/Components/Components/src/ITempData.cs b/src/Components/Components/src/ITempData.cs index 994eaea7fc28..e8bf308629cf 100644 --- a/src/Components/Components/src/ITempData.cs +++ b/src/Components/Components/src/ITempData.cs @@ -10,6 +10,4 @@ public interface ITempData object? Peek(string key); void Keep(); void Keep(string key); - - //TO-DO: Add Save to save and clean-up after request } diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index d8638dab5502..2ceba88090dd 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -8,8 +8,10 @@ Microsoft.AspNetCore.Components.ITempData.this[string! key].set -> void Microsoft.AspNetCore.Components.ITempData.Peek(string! key) -> object? Microsoft.AspNetCore.Components.TempData Microsoft.AspNetCore.Components.TempData.Get(string! key) -> object? +Microsoft.AspNetCore.Components.TempData.GetDataToSave() -> System.Collections.Generic.IDictionary! Microsoft.AspNetCore.Components.TempData.Keep() -> void Microsoft.AspNetCore.Components.TempData.Keep(string! key) -> void +Microsoft.AspNetCore.Components.TempData.LoadDataFromCookie(System.Collections.Generic.IDictionary! data) -> void Microsoft.AspNetCore.Components.TempData.Peek(string! key) -> object? Microsoft.AspNetCore.Components.TempData.TempData() -> void Microsoft.AspNetCore.Components.TempData.this[string! key].get -> object? diff --git a/src/Components/Components/src/TempData.cs b/src/Components/Components/src/TempData.cs index 9452529001d9..93ebe73b7e24 100644 --- a/src/Components/Components/src/TempData.cs +++ b/src/Components/Components/src/TempData.cs @@ -13,19 +13,24 @@ public class TempData : ITempData public object? this[string key] { get => Get(key); - set => _data[key] = value; + set + { + _data[key] = value; + _retainedKeys.Add(key); + } } /// public object? Get(string key) { - return _data.TryGetValue(key, out var value) ? value : null; + _retainedKeys.Remove(key); + return _data.GetValueOrDefault(key); } /// public object? Peek(string key) { - return _data.TryGetValue(key, out var value) ? value : null; + return _data.GetValueOrDefault(key); } /// @@ -45,4 +50,27 @@ public void Keep(string key) _retainedKeys.Add(key); } } + + /// + public IDictionary GetDataToSave() + { + var dataToSave = new Dictionary(); + foreach (var key in _retainedKeys) + { + dataToSave[key] = _data[key]; + } + return dataToSave; + } + + /// + public void LoadDataFromCookie(IDictionary data) + { + _data.Clear(); + _retainedKeys.Clear(); + foreach (var kvp in data) + { + _data[kvp.Key] = kvp.Value; + _retainedKeys.Add(kvp.Key); + } + } } diff --git a/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs b/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs index f6f6c191147e..4e3a0bd2a49f 100644 --- a/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs +++ b/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs @@ -74,10 +74,26 @@ public static IRazorComponentsBuilder AddRazorComponents(this IServiceCollection services.TryAddCascadingValue(sp => sp.GetRequiredService().HttpContext); services.TryAddScoped(); services.TryAddScoped(); - - // TO-DO: Change this for TempData to work correctly - services.TryAddSingleton(); - services.TryAddCascadingValue(sp => sp.GetRequiredService()); + services.TryAddCascadingValue(sp => + { + var httpContext = sp.GetRequiredService().HttpContext; + if (httpContext is null) + { + return null!; + } + var key = typeof(ITempData); + if (!httpContext.Items.TryGetValue(key, out var tempData)) + { + var tempDataInstance = TempDataService.Load(httpContext); + httpContext.Items[key] = tempDataInstance; + httpContext.Response.OnStarting(() => + { + TempDataService.Save(httpContext, tempDataInstance); + return Task.CompletedTask; + }); + } + return (ITempData)httpContext.Items[key]!; + }); services.TryAddScoped(); RegisterPersistentComponentStateServiceCollectionExtensions.AddPersistentServiceRegistration(services, RenderMode.InteractiveWebAssembly); diff --git a/src/Components/Endpoints/src/DependencyInjection/TempDataService.cs b/src/Components/Endpoints/src/DependencyInjection/TempDataService.cs new file mode 100644 index 000000000000..1a3ffcd4ca89 --- /dev/null +++ b/src/Components/Endpoints/src/DependencyInjection/TempDataService.cs @@ -0,0 +1,154 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Components.Endpoints; + +internal sealed class TempDataService +{ + private const string CookieName = ".AspNetCore.Components.TempData"; + + public TempDataService() + { + // TO-DO: Add encoding later if needed + } + + public static TempData Load(HttpContext httpContext) + { + var returnTempData = new TempData(); + var serializedDataFromCookie = httpContext.Request.Cookies[CookieName]; + if (serializedDataFromCookie is null) + { + return returnTempData; + } + + var dataFromCookie = JsonSerializer.Deserialize>(serializedDataFromCookie); + if (dataFromCookie is null) + { + return returnTempData; + } + + var convertedData = new Dictionary(); + foreach (var kvp in dataFromCookie) + { + convertedData[kvp.Key] = ConvertJsonElement(kvp.Value); + } + + returnTempData.LoadDataFromCookie(convertedData); + return returnTempData; + } + + private static object? ConvertJsonElement(JsonElement element) + { + switch (element.ValueKind) + { + case JsonValueKind.String: + if (element.TryGetGuid(out var guid)) + { + return guid; + } + if (element.TryGetDateTime(out var dateTime)) + { + return dateTime; + } + return element.GetString(); + case JsonValueKind.Number: + return element.GetInt32(); + case JsonValueKind.True: + case JsonValueKind.False: + return element.GetBoolean(); + case JsonValueKind.Null: + return null; + case JsonValueKind.Array: + return DeserializeArray(element); + case JsonValueKind.Object: + return DeserializeDictionaryEntry(element); + default: + throw new InvalidOperationException($"TempData cannot deserialize value of type '{element.ValueKind}'."); + } + } + + private static object? DeserializeArray(JsonElement arrayElement) + { + var arrayLength = arrayElement.GetArrayLength(); + if (arrayLength == 0) + { + return null; + } + if (arrayElement[0].ValueKind == JsonValueKind.String) + { + var array = new List(arrayLength); + foreach (var item in arrayElement.EnumerateArray()) + { + array.Add(item.GetString()); + } + return array.ToArray(); + } + else if (arrayElement[0].ValueKind == JsonValueKind.Number) + { + var array = new List(arrayLength); + foreach (var item in arrayElement.EnumerateArray()) + { + array.Add(item.GetInt32()); + } + return array.ToArray(); + } + throw new InvalidOperationException($"TempData cannot deserialize array of type '{arrayElement[0].ValueKind}'."); + } + + private static Dictionary DeserializeDictionaryEntry(JsonElement objectElement) + { + var dictionary = new Dictionary(StringComparer.Ordinal); + foreach (var item in objectElement.EnumerateObject()) + { + dictionary[item.Name] = item.Value.GetString(); + } + return dictionary; + } + + public static void Save(HttpContext httpContext, TempData tempData) + { + var dataToSave = tempData.GetDataToSave(); + foreach (var kvp in dataToSave) + { + if (!CanSerializeType(kvp.Value?.GetType() ?? typeof(object))) + { + throw new InvalidOperationException($"TempData cannot store values of type '{kvp.Value?.GetType()}'."); + } + } + + if (dataToSave.Count == 0) + { + httpContext.Response.Cookies.Delete(CookieName, new CookieOptions + { + Path = httpContext.Request.PathBase.HasValue ? httpContext.Request.PathBase.Value : "/", + }); + return; + } + httpContext.Response.Cookies.Append(CookieName, JsonSerializer.Serialize(dataToSave), new CookieOptions + { + HttpOnly = true, + IsEssential = true, + SameSite = SameSiteMode.Lax, + Secure = httpContext.Request.IsHttps, + Path = httpContext.Request.PathBase.HasValue ? httpContext.Request.PathBase.Value : "/", + }); + } + + private static bool CanSerializeType(Type type) + { + type = Nullable.GetUnderlyingType(type) ?? type; + return + type.IsEnum || + type == typeof(int) || + type == typeof(string) || + type == typeof(bool) || + type == typeof(DateTime) || + type == typeof(Guid) || + typeof(ICollection).IsAssignableFrom(type) || + typeof(ICollection).IsAssignableFrom(type) || + typeof(IDictionary).IsAssignableFrom(type); + } +} From 5612308144266619133b22998fb4a757a3e85caa Mon Sep 17 00:00:00 2001 From: Daria Tiurina Date: Thu, 11 Dec 2025 14:29:03 +0100 Subject: [PATCH 4/7] Fixed E2E test to be SSR --- src/Components/test/E2ETest/Tests/TempDataTest.cs | 5 ++--- .../Pages/TempData/TempDataComponent.razor | 8 ++------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Components/test/E2ETest/Tests/TempDataTest.cs b/src/Components/test/E2ETest/Tests/TempDataTest.cs index ff3d0e205c86..db15f3e2abc7 100644 --- a/src/Components/test/E2ETest/Tests/TempDataTest.cs +++ b/src/Components/test/E2ETest/Tests/TempDataTest.cs @@ -11,11 +11,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests; -public class TempDataTest : ServerTestBase>> +public class TempDataTest : ServerTestBase>> { public TempDataTest( BrowserFixture browserFixture, - BasicTestAppServerSiteFixture> serverFixture, + BasicTestAppServerSiteFixture> serverFixture, ITestOutputHelper output) : base(browserFixture, serverFixture, output) { @@ -30,7 +30,6 @@ public void TempDataCanPersistThroughNavigation() Browser.Equal("No message", () => Browser.FindElement(By.Id("message")).Text); Browser.FindElement(By.Id("set-values-button")).Click(); - Browser.FindElement(By.Id("navigate-to-the-same-page")).Click(); Browser.Equal("Message", () => Browser.FindElement(By.Id("message")).Text); } diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/TempData/TempDataComponent.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/TempData/TempDataComponent.razor index 1fa878c07b47..457994644a38 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/TempData/TempDataComponent.razor +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/TempData/TempDataComponent.razor @@ -11,11 +11,6 @@ -
- - - -

@_message

@@ -61,7 +56,8 @@ TempData["Message"] = "Message"; TempData["PeekedValue"] = "Peeked value"; - TempData["KeptValue"] = "Kept valie"; + TempData["KeptValue"] = "Kept value"; + NavigateToSamePage(); } private void NavigateToSamePage() => NavigationManager.NavigateTo("/subdir/tempdata", forceLoad: true); From c73f3708c892144dc14a66f37576fa5c6a32d8f5 Mon Sep 17 00:00:00 2001 From: Daria Tiurina Date: Thu, 11 Dec 2025 18:05:06 +0100 Subject: [PATCH 5/7] Add for manual testing --- src/Components/test/testassets/Components.TestServer/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Components/test/testassets/Components.TestServer/Program.cs b/src/Components/test/testassets/Components.TestServer/Program.cs index 2f06b00b72ac..f42fbdc26f2e 100644 --- a/src/Components/test/testassets/Components.TestServer/Program.cs +++ b/src/Components/test/testassets/Components.TestServer/Program.cs @@ -38,6 +38,7 @@ public static async Task Main(string[] args) ["Hot Reload"] = (BuildWebHost(CreateAdditionalArgs(args)), "/subdir"), ["Dev server client-side blazor"] = CreateDevServerHost(CreateAdditionalArgs(args)), ["Global Interactivity"] = (BuildWebHost>(CreateAdditionalArgs(args)), "/subdir"), + ["SSR (No Interactivity)"] = (BuildWebHost>(CreateAdditionalArgs(args)), "/subdir"), }; var mainHost = BuildWebHost(args); From 9948bcdce0ff31d63ec82e6e69ea85283ba5d16b Mon Sep 17 00:00:00 2001 From: Daria Tiurina Date: Fri, 12 Dec 2025 15:20:39 +0100 Subject: [PATCH 6/7] Added new E2E tests --- .../test/E2ETest/Tests/TempDataTest.cs | 52 +++++++++++++++- .../Pages/TempData/TempDataComponent.razor | 59 ++++++++++++------- .../TempData/TempDataReadComponent.razor | 33 +++++++++++ 3 files changed, 120 insertions(+), 24 deletions(-) create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/TempData/TempDataReadComponent.razor diff --git a/src/Components/test/E2ETest/Tests/TempDataTest.cs b/src/Components/test/E2ETest/Tests/TempDataTest.cs index db15f3e2abc7..4b8d12377dcf 100644 --- a/src/Components/test/E2ETest/Tests/TempDataTest.cs +++ b/src/Components/test/E2ETest/Tests/TempDataTest.cs @@ -27,14 +27,60 @@ public TempDataTest( public void TempDataCanPersistThroughNavigation() { Navigate($"{ServerPathBase}/tempdata"); + Browser.Equal("No message", () => Browser.FindElement(By.Id("message")).Text); + Browser.FindElement(By.Id("set-values-button")).Click(); + Browser.Equal("Message", () => Browser.FindElement(By.Id("message")).Text); + } + [Fact] + public void TempDataCanPersistThroughDifferentPages() + { + Navigate($"{ServerPathBase}/tempdata"); + + Browser.Equal("No message", () => Browser.FindElement(By.Id("message")).Text); + Browser.FindElement(By.Id("set-values-button-diff-page")).Click(); + Browser.Equal("Message", () => Browser.FindElement(By.Id("message")).Text); + } + + [Fact] + public void TempDataPeekDoesntDelete() + { + Navigate($"{ServerPathBase}/tempdata"); + + Browser.Equal("No message", () => Browser.FindElement(By.Id("message")).Text); Browser.FindElement(By.Id("set-values-button")).Click(); + Browser.Equal("Message", () => Browser.FindElement(By.Id("message")).Text); + Browser.FindElement(By.Id("redirect-button")).Click(); + Browser.Equal("No message", () => Browser.FindElement(By.Id("message")).Text); + Browser.Equal("Peeked value", () => Browser.FindElement(By.Id("peeked-value")).Text); + } + [Fact] + public void TempDataKeepAllElements() + { + Navigate($"{ServerPathBase}/tempdata?ValueToKeep=all"); + + Browser.Equal("No message", () => Browser.FindElement(By.Id("message")).Text); + Browser.FindElement(By.Id("set-values-button")).Click(); + Browser.Equal("Kept value", () => Browser.FindElement(By.Id("kept-value")).Text); + Browser.Equal("Message", () => Browser.FindElement(By.Id("message")).Text); + Browser.FindElement(By.Id("redirect-button")).Click(); + Browser.Equal("Kept value", () => Browser.FindElement(By.Id("kept-value")).Text); Browser.Equal("Message", () => Browser.FindElement(By.Id("message")).Text); } - //TO-DO: Add test for Peek() - //TO-DO: Add test for Keep() - //TO-DO: Add test for Keep(string) + [Fact] + public void TempDataKeepOneElement() + { + Navigate($"{ServerPathBase}/tempdata?ValueToKeep=KeptValue"); + + Browser.Equal("No message", () => Browser.FindElement(By.Id("message")).Text); + Browser.FindElement(By.Id("set-values-button")).Click(); + Browser.Equal("Kept value", () => Browser.FindElement(By.Id("kept-value")).Text); + Browser.Equal("Message", () => Browser.FindElement(By.Id("message")).Text); + Browser.FindElement(By.Id("redirect-button")).Click(); + Browser.Equal("No message", () => Browser.FindElement(By.Id("message")).Text); + Browser.Equal("Kept value", () => Browser.FindElement(By.Id("kept-value")).Text); + } } diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/TempData/TempDataComponent.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/TempData/TempDataComponent.razor index 457994644a38..8a89af87b654 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/TempData/TempDataComponent.razor +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/TempData/TempDataComponent.razor @@ -4,13 +4,21 @@

TempData Basic Test

-
Ready
- -
+ +
+ + + + +
+ + + +

@_message

@@ -19,11 +27,14 @@ @code { + [SupplyParameterFromForm(Name = "_handler")] + public string? Handler { get; set; } + [CascadingParameter] public ITempData? TempData { get; set; } [SupplyParameterFromQuery] - public bool ShouldKeep { get; set; } + public string ValueToKeep { get; set; } = string.Empty; private string? _message; private string? _peekedValue; @@ -31,34 +42,40 @@ protected override void OnInitialized() { - if (TempData is null) + if (Handler is not null) { return; } - _message = TempData.Get("Message") as string; - _message ??= "No message"; - _peekedValue = TempData.Peek("PeekedValue") as string; - _peekedValue ??= "No peeked value"; - _keptValue = TempData.Get("KeptValue") as string; - _keptValue ??= "No kept value"; - if (ShouldKeep && _keptValue is not null) + _message = TempData!.Get("Message") as string ?? "No message"; + _peekedValue = TempData!.Peek("PeekedValue") as string ?? "No peeked value"; + _keptValue = TempData!.Get("KeptValue") as string ?? "No kept value"; + + Console.WriteLine("ValueToKeep = " + ValueToKeep); + + if (ValueToKeep == "all") { - TempData.Keep("KeptValue"); + TempData!.Keep(); } - } + else if (!string.IsNullOrEmpty(ValueToKeep)) + { + TempData!.Keep(ValueToKeep); + } + } - private void SetValues() + private void SetValues(bool differentPage = false) { - if (TempData is null) + TempData!["Message"] = "Message"; + TempData!["PeekedValue"] = "Peeked value"; + TempData!["KeptValue"] = "Kept value"; + if (differentPage) { + NavigateToDifferentPage(); return; } - - TempData["Message"] = "Message"; - TempData["PeekedValue"] = "Peeked value"; - TempData["KeptValue"] = "Kept value"; - NavigateToSamePage(); + NavigateToSamePageKeep(ValueToKeep); } private void NavigateToSamePage() => NavigationManager.NavigateTo("/subdir/tempdata", forceLoad: true); + private void NavigateToSamePageKeep(string valueToKeep) => NavigationManager.NavigateTo($"/subdir/tempdata?ValueToKeep={valueToKeep}", forceLoad: true); + private void NavigateToDifferentPage() => NavigationManager.NavigateTo("/subdir/tempdata/read", forceLoad: true); } diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/TempData/TempDataReadComponent.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/TempData/TempDataReadComponent.razor new file mode 100644 index 000000000000..950e58e1c24b --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/TempData/TempDataReadComponent.razor @@ -0,0 +1,33 @@ +@page "/tempdata/read" +@using Microsoft.AspNetCore.Components.Forms +@inject NavigationManager NavigationManager + +

TempData Read Test

+ +

@_message

+

@_peekedValue

+

@_keptValue

+ + +@code { + [CascadingParameter] + public ITempData? TempData { get; set; } + + private string? _message; + private string? _peekedValue; + private string? _keptValue; + + protected override void OnInitialized() + { + if (TempData is null) + { + return; + } + _message = TempData.Get("Message") as string; + _message ??= "No message"; + _peekedValue = TempData.Get("PeekedValue") as string; + _peekedValue ??= "No peeked value"; + _keptValue = TempData.Get("KeptValue") as string; + _keptValue ??= "No kept value"; + } +} From 9869aa7a3c94f15de4fb514fa756b66dbd3c8649 Mon Sep 17 00:00:00 2001 From: Daria Tiurina Date: Fri, 12 Dec 2025 16:09:39 +0100 Subject: [PATCH 7/7] Added XML comment --- src/Components/Components/src/ITempData.cs | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Components/Components/src/ITempData.cs b/src/Components/Components/src/ITempData.cs index e8bf308629cf..12ec4d4fc950 100644 --- a/src/Components/Components/src/ITempData.cs +++ b/src/Components/Components/src/ITempData.cs @@ -3,11 +3,35 @@ namespace Microsoft.AspNetCore.Components; +/// +/// Provides a dictionary for storing data that is needed for subsequent requests. +/// Data stored in TempData is automatically removed after it is read unless +/// or is called, or it is accessed via . +/// public interface ITempData { + /// + /// Gets or sets the value associated with the specified key. + /// object? this[string key] { get; set; } + + /// + /// Gets the value associated with the specified key and then schedules it for deletion. + /// object? Get(string key); + + /// + /// Gets the value associated with the specified key without scheduling it for deletion. + /// object? Peek(string key); + + /// + /// Makes all of the keys currently in TempData persist for another request. + /// void Keep(); + + /// + /// Makes the element with the persist for another request. + /// void Keep(string key); }