diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index da9f8cb8a0dd..850194aa7784 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -102,7 +102,7 @@ static Microsoft.Extensions.DependencyInjection.CascadingValueServiceCollectionE virtual Microsoft.AspNetCore.Components.Rendering.ComponentState.DisposeAsync() -> System.Threading.Tasks.ValueTask virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.AddPendingTask(Microsoft.AspNetCore.Components.Rendering.ComponentState? componentState, System.Threading.Tasks.Task! task) -> void virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.CreateComponentState(int componentId, Microsoft.AspNetCore.Components.IComponent! component, Microsoft.AspNetCore.Components.Rendering.ComponentState? parentComponentState) -> Microsoft.AspNetCore.Components.Rendering.ComponentState! -virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.RenderTree.EventFieldInfo? fieldInfo, System.EventArgs! eventArgs, bool quiesce) -> System.Threading.Tasks.Task! +virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.RenderTree.EventFieldInfo? fieldInfo, System.EventArgs! eventArgs, bool waitForQuiescence) -> System.Threading.Tasks.Task! virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.ResolveComponentForRenderMode(System.Type! componentType, int? parentComponentId, Microsoft.AspNetCore.Components.IComponentActivator! componentActivator, Microsoft.AspNetCore.Components.IComponentRenderMode! renderMode) -> Microsoft.AspNetCore.Components.IComponent! ~Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame.ComponentRenderMode.get -> Microsoft.AspNetCore.Components.IComponentRenderMode ~Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame.NamedEventAssignedName.get -> string diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index 53c6c33393e4..f1ba4a9e2b93 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -380,7 +380,7 @@ protected virtual ComponentState CreateComponentState(int componentId, IComponen /// public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fieldInfo, EventArgs eventArgs) { - return DispatchEventAsync(eventHandlerId, fieldInfo, eventArgs, quiesce: false); + return DispatchEventAsync(eventHandlerId, fieldInfo, eventArgs, waitForQuiescence: false); } /// @@ -389,15 +389,20 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie /// The value from the original event attribute. /// Arguments to be passed to the event handler. /// Information that the renderer can use to update the state of the existing render tree to match the UI. - /// Whether to wait for quiescence or not. + /// A flag indicating whether to wait for quiescence. /// /// A which will complete once all asynchronous processing related to the event /// has completed. /// - public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fieldInfo, EventArgs eventArgs, bool quiesce) + public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fieldInfo, EventArgs eventArgs, bool waitForQuiescence) { Dispatcher.AssertAccess(); + if (waitForQuiescence) + { + _pendingTasks ??= new(); + } + var callback = GetRequiredEventCallback(eventHandlerId); Log.HandlingEvent(_logger, eventHandlerId, eventArgs); @@ -425,22 +430,9 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie _isBatchInProgress = true; task = callback.InvokeAsync(eventArgs); - if (quiesce) - { - // If we are waiting for quiescence, the quiescence task will capture any async exception. - // If the exception is thrown synchronously, we just want it to flow to the callers, and - // not go through the ErrorBoundary. - _pendingTasks ??= new(); - AddPendingTask(receiverComponentState, task); - } } catch (Exception e) { - if (quiesce) - { - // Exception filters are not AoT friendly. - throw; - } HandleExceptionViaErrorBoundary(e, receiverComponentState); return Task.CompletedTask; } @@ -453,15 +445,19 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie ProcessPendingRender(); } - if (quiesce) + // Task completed synchronously or is still running. We already processed all of the rendering + // work that was queued so let our error handler deal with it. + var errorHandledTask = GetErrorHandledTask(task, receiverComponentState); + + if (waitForQuiescence) { + AddPendingTask(receiverComponentState, errorHandledTask); return WaitForQuiescence(); } - - // Task completed synchronously or is still running. We already processed all of the rendering - // work that was queued so let our error handler deal with it. - var result = GetErrorHandledTask(task, receiverComponentState); - return result; + else + { + return errorHandledTask; + } } /// diff --git a/src/Components/Endpoints/src/FormMapping/HttpContextFormValueMapper.cs b/src/Components/Endpoints/src/FormMapping/HttpContextFormValueMapper.cs index d5348a5a10ea..76503423de45 100644 --- a/src/Components/Endpoints/src/FormMapping/HttpContextFormValueMapper.cs +++ b/src/Components/Endpoints/src/FormMapping/HttpContextFormValueMapper.cs @@ -73,7 +73,7 @@ private static bool MatchesScope(string incomingScopeQualifiedFormName, string c public void Map(FormValueMappingContext context) { // This will func to a proper binder - if (!CanMap(context.ValueType, context.MappingScopeName, context.RestrictToFormName)) + if (!CanMap(context.ValueType, context.AcceptMappingScopeName, context.AcceptFormName)) { context.SetResult(null); } diff --git a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs index aa88719b10e5..592ccd8049b5 100644 --- a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs +++ b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs @@ -77,16 +77,29 @@ await EndpointHtmlRenderer.InitializeStandardComponentServicesAsync( ParameterView.Empty, waitForQuiescence: isPost); - var isBadRequest = false; - var quiesceTask = isPost ? _renderer.DispatchSubmitEventAsync(handler, out isBadRequest) : htmlContent.QuiescenceTask; - if (isBadRequest) + Task quiesceTask; + if (!isPost) { - return; + quiesceTask = htmlContent.QuiescenceTask; } - - if (isPost) + else { - await Task.WhenAll(_renderer.NonStreamingPendingTasks); + try + { + var isBadRequest = false; + quiesceTask = _renderer.DispatchSubmitEventAsync(handler, out isBadRequest); + if (isBadRequest) + { + return; + } + + await Task.WhenAll(_renderer.NonStreamingPendingTasks); + } + catch (NavigationException ex) + { + await EndpointHtmlRenderer.HandleNavigationException(_context, ex); + quiesceTask = Task.CompletedTask; + } } // Importantly, we must not yield this thread (which holds exclusive access to the renderer sync context) @@ -95,7 +108,7 @@ await EndpointHtmlRenderer.InitializeStandardComponentServicesAsync( // renderer sync context and cause a batch that would get missed. htmlContent.WriteTo(writer, HtmlEncoder.Default); // Don't use WriteToAsync, as per the comment above - if (!quiesceTask.IsCompleted) + if (!quiesceTask.IsCompletedSuccessfully) { await _renderer.SendStreamingUpdatesAsync(_context, quiesceTask, writer); } diff --git a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.EventDispatch.cs b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.EventDispatch.cs index 7b9cd0c35c4f..085f0adf4eaf 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.EventDispatch.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.EventDispatch.cs @@ -48,7 +48,7 @@ internal Task DispatchSubmitEventAsync(string? handlerName, out bool isBadReques var frameLocation = locationsForName.Single(); var eventHandlerId = FindEventHandlerIdForNamedEvent("onsubmit", frameLocation.ComponentId, frameLocation.FrameIndex); return eventHandlerId.HasValue - ? DispatchEventAsync(eventHandlerId.Value, null, EventArgs.Empty, quiesce: true) + ? DispatchEventAsync(eventHandlerId.Value, null, EventArgs.Empty, waitForQuiescence: true) : Task.CompletedTask; } diff --git a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Prerendering.cs b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Prerendering.cs index ff7b84402fae..7018501ca171 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Prerendering.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Prerendering.cs @@ -133,7 +133,7 @@ private async Task WaitForResultReady(bool waitForQuiescence, PrerenderedCompone } } - private static ValueTask HandleNavigationException(HttpContext httpContext, NavigationException navigationException) + public static ValueTask HandleNavigationException(HttpContext httpContext, NavigationException navigationException) { if (httpContext.Response.HasStarted) { diff --git a/src/Components/Web/src/Forms/Mapping/FormValueMappingContext.cs b/src/Components/Web/src/Forms/Mapping/FormValueMappingContext.cs index f2b18b9ae1d8..660944b4b622 100644 --- a/src/Components/Web/src/Forms/Mapping/FormValueMappingContext.cs +++ b/src/Components/Web/src/Forms/Mapping/FormValueMappingContext.cs @@ -13,31 +13,31 @@ public class FormValueMappingContext /// /// Initializes a new instance of . /// - /// The name of the current . Values will only be mapped if the incoming data corresponds to this scope name. - /// If set, indicates that the mapping should only receive values if the incoming form matches this name. If null, the mapping should receive data from any form in the mapping scope. + /// The name of a . Values will only be mapped if the incoming data corresponds to this scope name. + /// If set, indicates that the mapping should only receive values if the incoming form matches this name. If null, the mapping should receive data from any form in the mapping scope. /// The of the value to map. /// The name of the parameter to map data to. - public FormValueMappingContext(string mappingScopeName, string? restrictToFormName, Type valueType, string parameterName) + public FormValueMappingContext(string acceptMappingScopeName, string? acceptFormName, Type valueType, string parameterName) { - ArgumentNullException.ThrowIfNull(mappingScopeName, nameof(mappingScopeName)); + ArgumentNullException.ThrowIfNull(acceptMappingScopeName, nameof(acceptMappingScopeName)); ArgumentNullException.ThrowIfNull(valueType, nameof(valueType)); ArgumentNullException.ThrowIfNull(parameterName, nameof(parameterName)); - MappingScopeName = mappingScopeName; - RestrictToFormName = restrictToFormName; + AcceptMappingScopeName = acceptMappingScopeName; + AcceptFormName = acceptFormName; ParameterName = parameterName; ValueType = valueType; } /// - /// Gets the name of the current . + /// Gets the name of that is allowed to supply data in this context. /// - public string MappingScopeName { get; } + public string AcceptMappingScopeName { get; } /// /// If set, indicates that the mapping should only receive values if the incoming form matches this name. If null, the mapping should receive data from any form in the mapping scope. /// - public string? RestrictToFormName { get; } + public string? AcceptFormName { get; } /// /// Gets the name of the parameter to map data to. diff --git a/src/Components/Web/src/PublicAPI.Unshipped.txt b/src/Components/Web/src/PublicAPI.Unshipped.txt index b187e9cc2847..3215ee46ed2b 100644 --- a/src/Components/Web/src/PublicAPI.Unshipped.txt +++ b/src/Components/Web/src/PublicAPI.Unshipped.txt @@ -42,14 +42,14 @@ Microsoft.AspNetCore.Components.Forms.Mapping.FormMappingError.ErrorMessages.get Microsoft.AspNetCore.Components.Forms.Mapping.FormMappingError.Name.get -> string! Microsoft.AspNetCore.Components.Forms.Mapping.FormMappingError.Path.get -> string! Microsoft.AspNetCore.Components.Forms.Mapping.FormValueMappingContext -Microsoft.AspNetCore.Components.Forms.Mapping.FormValueMappingContext.FormValueMappingContext(string! mappingScopeName, string? restrictToFormName, System.Type! valueType, string! parameterName) -> void +Microsoft.AspNetCore.Components.Forms.Mapping.FormValueMappingContext.AcceptFormName.get -> string? +Microsoft.AspNetCore.Components.Forms.Mapping.FormValueMappingContext.AcceptMappingScopeName.get -> string! +Microsoft.AspNetCore.Components.Forms.Mapping.FormValueMappingContext.FormValueMappingContext(string! acceptMappingScopeName, string? acceptFormName, System.Type! valueType, string! parameterName) -> void Microsoft.AspNetCore.Components.Forms.Mapping.FormValueMappingContext.MapErrorToContainer.get -> System.Action? Microsoft.AspNetCore.Components.Forms.Mapping.FormValueMappingContext.MapErrorToContainer.set -> void -Microsoft.AspNetCore.Components.Forms.Mapping.FormValueMappingContext.MappingScopeName.get -> string! Microsoft.AspNetCore.Components.Forms.Mapping.FormValueMappingContext.OnError.get -> System.Action? Microsoft.AspNetCore.Components.Forms.Mapping.FormValueMappingContext.OnError.set -> void Microsoft.AspNetCore.Components.Forms.Mapping.FormValueMappingContext.ParameterName.get -> string! -Microsoft.AspNetCore.Components.Forms.Mapping.FormValueMappingContext.RestrictToFormName.get -> string? Microsoft.AspNetCore.Components.Forms.Mapping.FormValueMappingContext.Result.get -> object? Microsoft.AspNetCore.Components.Forms.Mapping.FormValueMappingContext.SetResult(object? result) -> void Microsoft.AspNetCore.Components.Forms.Mapping.FormValueMappingContext.ValueType.get -> System.Type! diff --git a/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTests/FormWithParentBindingContextTest.cs b/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTests/FormWithParentBindingContextTest.cs index f1db8257765d..b00ea8efcb34 100644 --- a/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTests/FormWithParentBindingContextTest.cs +++ b/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTests/FormWithParentBindingContextTest.cs @@ -968,15 +968,157 @@ public async Task CanHandleFormPostNonStreamingRenderingAsyncHandler() Browser.Exists(By.Id("pass")); } - private void DispatchToFormCore(DispatchToForm dispatch) + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void HandleErrorsOutsideErrorBoundary_OnInitialRender(bool suppressEnhancedNavigation, bool enableStreaming) + { + SuppressEnhancedNavigation(suppressEnhancedNavigation); + GoTo($"forms/error-outside-error-boundary{( enableStreaming ? "-streaming" : "" )}"); + + Browser.Exists(By.LinkText("Throw during initial render")).Click(); + AssertHasInternalServerError(suppressEnhancedNavigation); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void HandleErrorsOutsideErrorBoundary_SynchronouslyInSubmitEvent(bool suppressEnhancedNavigation, bool enableStreaming) + { + SuppressEnhancedNavigation(suppressEnhancedNavigation); + GoTo($"forms/error-outside-error-boundary{(enableStreaming ? "-streaming" : "")}"); + + Browser.Exists(By.Id("throw-sync")).Click(); + AssertHasInternalServerError(suppressEnhancedNavigation, enableStreaming); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void HandleErrorsOutsideErrorBoundary_AsynchronouslyInSubmitEvent(bool suppressEnhancedNavigation, bool enableStreaming) + { + SuppressEnhancedNavigation(suppressEnhancedNavigation); + GoTo($"forms/error-outside-error-boundary{(enableStreaming ? "-streaming" : "")}"); + + Browser.Exists(By.Id("throw-async")).Click(); + AssertHasInternalServerError(suppressEnhancedNavigation, enableStreaming); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void HandleErrorsInsideErrorBoundary_OnInitialRender(bool suppressEnhancedNavigation, bool enableStreaming) { - if (dispatch.SuppressEnhancedNavigation) + SuppressEnhancedNavigation(suppressEnhancedNavigation); + GoTo($"forms/error-in-error-boundary{(enableStreaming ? "-streaming" : "")}"); + + Browser.Exists(By.LinkText("Throw during initial render")).Click(); + + var errorBoundaryContent = Browser.Exists(By.Id("error-content")); + Assert.Contains("This is a deliberate error during initial render", errorBoundaryContent.Text); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void HandleErrorsInsideErrorBoundary_SynchronouslyInSubmitEvent(bool suppressEnhancedNavigation, bool enableStreaming) + { + SuppressEnhancedNavigation(suppressEnhancedNavigation); + GoTo($"forms/error-in-error-boundary{(enableStreaming ? "-streaming" : "")}"); + + Browser.Exists(By.Id("throw-sync")).Click(); + + var errorBoundaryContent = Browser.Exists(By.Id("error-content")); + Assert.Contains("This is a deliberate form-event synchronous error", errorBoundaryContent.Text); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void HandleErrorsInsideErrorBoundary_AsynchronouslyInSubmitEvent(bool suppressEnhancedNavigation, bool enableStreaming) + { + SuppressEnhancedNavigation(suppressEnhancedNavigation); + GoTo($"forms/error-in-error-boundary{(enableStreaming ? "-streaming" : "")}"); + + Browser.Exists(By.Id("throw-async")).Click(); + + var errorBoundaryContent = Browser.Exists(By.Id("error-content")); + Assert.Contains("This is a deliberate form-event asynchronous error", errorBoundaryContent.Text); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void CanPostRedirectGet_Synchronous(bool suppressEnhancedNavigation, bool enableStreaming) + { + SuppressEnhancedNavigation(suppressEnhancedNavigation); + GoTo($"forms/post-redirect-get{(enableStreaming ? "-streaming" : "")}"); + + Browser.Exists(By.Id("sync-redirect")).Click(); + Browser.Exists(By.Id("nav-home")); + Browser.True(() => Browser.Url.EndsWith("/nav", StringComparison.Ordinal)); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void CanPostRedirectGet_Asynchronous(bool suppressEnhancedNavigation, bool enableStreaming) + { + SuppressEnhancedNavigation(suppressEnhancedNavigation); + GoTo($"forms/post-redirect-get{(enableStreaming ? "-streaming" : "")}"); + + Browser.Exists(By.Id("async-redirect")).Click(); + Browser.Exists(By.Id("nav-home")); + Browser.True(() => Browser.Url.EndsWith("/nav", StringComparison.Ordinal)); + } + + private void SuppressEnhancedNavigation(bool shouldSuppress) + { + if (shouldSuppress) { GoTo(""); Browser.Equal("Hello", () => Browser.Exists(By.TagName("h1")).Text); ((IJavaScriptExecutor)Browser).ExecuteScript("sessionStorage.setItem('suppress-enhanced-navigation', 'true')"); } + } + private void AssertHasInternalServerError(bool suppressedEnhancedNavigation, bool streaming = false) + { + if (streaming) + { + Browser.True(() => Browser.FindElement(By.TagName("html")).Text.Contains("There was an unhandled exception on the current request")); + } + else if (suppressedEnhancedNavigation) + { + // Chrome's built-in error UI for a 500 response when there's no response content + Browser.Exists(By.Id("main-frame-error")); + } + else + { + // The UI generated by enhanced nav when there's no response content + Browser.Contains("Error: 500", () => Browser.Exists(By.TagName("html")).Text); + } + } + + private void DispatchToFormCore(DispatchToForm dispatch) + { + SuppressEnhancedNavigation(dispatch.SuppressEnhancedNavigation); GoTo(dispatch.Url); if (!dispatch.DispatchEvent && dispatch.ShouldCauseInternalServerError) @@ -1018,16 +1160,7 @@ private void DispatchToFormCore(DispatchToForm dispatch) if (dispatch.ShouldCauseInternalServerError) { - if (dispatch.SuppressEnhancedNavigation) - { - // Chrome's built-in error UI for a 500 response when there's no response content - Browser.Exists(By.Id("main-frame-error")); - } - else - { - // The UI generated by enhanced nav when there's no response content - Browser.Contains("Error: 500", () => Browser.Exists(By.TagName("html")).Text); - } + AssertHasInternalServerError(dispatch.SuppressEnhancedNavigation); } else if (dispatch.ShouldCauseBadRequest) { diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/Index.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/Index.razor index 4926c2e742f9..6282e54e5111 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/Index.razor +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/Index.razor @@ -2,6 +2,6 @@ Home -

Hello

+

Hello

This is a Razor Component endpoint.

diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ComponentThatThrows.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ComponentThatThrows.razor new file mode 100644 index 000000000000..c840fe50c42a --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ComponentThatThrows.razor @@ -0,0 +1,48 @@ +@inject NavigationManager Nav +@using Microsoft.AspNetCore.Components.Forms + +

Throw during initial render

+ +
+ + + + + +@if (loading) +{ +

Loading...

+} + +@code { + [SupplyParameterFromQuery] public bool ThrowDuringInitialRender { get; set; } + + [SupplyParameterFromForm] public string Scenario { get; set; } + + bool loading; + + protected override void OnInitialized() + { + if (ThrowDuringInitialRender) + { + throw new InvalidTimeZoneException("This is a deliberate error during initial render"); + } + } + + async Task TriggerError() + { + if (Scenario == "ThrowDuringEventSync") + { + throw new InvalidTimeZoneException("This is a deliberate form-event synchronous error"); + } + + loading = true; + await Task.Delay(500); + loading = false; + + if (Scenario == "ThrowDuringEventAsync") + { + throw new InvalidTimeZoneException("This is a deliberate form-event asynchronous error"); + } + } +} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ErrorInErrorBoundary.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ErrorInErrorBoundary.razor new file mode 100644 index 000000000000..b20448f844c4 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ErrorInErrorBoundary.razor @@ -0,0 +1,15 @@ +@page "/forms/error-in-error-boundary" +@using Microsoft.AspNetCore.Components.Forms + +

Error in error boundary

+ +

Demonstrates what happens with an unhandled exception during form post handling.

+ + + + + + +

The error boundary caught an exception with message: @context.Message

+
+
diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ErrorInErrorBoundaryStreaming.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ErrorInErrorBoundaryStreaming.razor new file mode 100644 index 000000000000..86f44f642cba --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ErrorInErrorBoundaryStreaming.razor @@ -0,0 +1,16 @@ +@page "/forms/error-in-error-boundary-streaming" +@attribute [StreamRendering(true)] +@using Microsoft.AspNetCore.Components.Forms + +

Error in error boundary (streaming)

+ +

Demonstrates what happens with an unhandled exception during form post handling.

+ + + + + + +

The error boundary caught an exception with message: @context.Message

+
+
diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ErrorOutsideErrorBoundary.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ErrorOutsideErrorBoundary.razor new file mode 100644 index 000000000000..3548c1f9a3d4 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ErrorOutsideErrorBoundary.razor @@ -0,0 +1,8 @@ +@page "/forms/error-outside-error-boundary" +@using Microsoft.AspNetCore.Components.Forms + +

Error outside error boundary

+ +

Demonstrates what happens with an unhandled exception during form post handling.

+ + diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ErrorOutsideErrorBoundaryStreaming.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ErrorOutsideErrorBoundaryStreaming.razor new file mode 100644 index 000000000000..e479d3cc3ac3 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ErrorOutsideErrorBoundaryStreaming.razor @@ -0,0 +1,9 @@ +@page "/forms/error-outside-error-boundary-streaming" +@attribute [StreamRendering(true)] +@using Microsoft.AspNetCore.Components.Forms + +

Error outside error boundary (streaming)

+ +

Demonstrates what happens with an unhandled exception during form post handling.

+ + diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/PostRedirectGet.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/PostRedirectGet.razor new file mode 100644 index 000000000000..50ea3fcf3f56 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/PostRedirectGet.razor @@ -0,0 +1,28 @@ +@page "/forms/post-redirect-get" +@using Microsoft.AspNetCore.Components.Forms +@inject NavigationManager Nav + +

Post/Redirect/Get

+ +
+ + + + +
+ + + + +@code { + void DoRedirection() + { + Nav.NavigateTo("nav"); + } + + async Task DoAsyncRedirection() + { + await Task.Delay(500); + Nav.NavigateTo("nav"); + } +} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/PostRedirectGetStreaming.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/PostRedirectGetStreaming.razor new file mode 100644 index 000000000000..de6448ec2069 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/PostRedirectGetStreaming.razor @@ -0,0 +1,29 @@ +@page "/forms/post-redirect-get-streaming" +@using Microsoft.AspNetCore.Components.Forms +@inject NavigationManager Nav +@attribute [StreamRendering(true)] + +

Post/Redirect/Get

+ +
+ + + + +
+ + + + +@code { + void DoRedirection() + { + Nav.NavigateTo("nav"); + } + + async Task DoAsyncRedirection() + { + await Task.Delay(500); + Nav.NavigateTo("nav"); + } +}