Skip to content

Commit a0224b1

Browse files
Decouple scope prefixing from EditForm so it works identically with plain <form>
1 parent 1044221 commit a0224b1

File tree

9 files changed

+60
-42
lines changed

9 files changed

+60
-42
lines changed

src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.EventDispatch.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Components.Endpoints;
1010
internal partial class EndpointHtmlRenderer
1111
{
1212
private readonly Dictionary<(int ComponentId, int FrameIndex), string> _namedSubmitEventsByLocation = new();
13-
private readonly Dictionary<string, (int ComponentId, int FrameIndex)> _namedSubmitEventsByAssignedName = new(StringComparer.Ordinal);
13+
private readonly Dictionary<string, (int ComponentId, int FrameIndex)> _namedSubmitEventsByScopeQualifiedName = new(StringComparer.Ordinal);
1414

1515
internal Task DispatchSubmitEventAsync(string? handlerName)
1616
{
@@ -21,7 +21,7 @@ internal Task DispatchSubmitEventAsync(string? handlerName)
2121
throw new InvalidOperationException("Cannot dispatch the POST request to the Razor Component endpoint, because the POST data does not specify which form is being submitted. To fix this, ensure form elements have an @onsubmit:name attribute with any unique value, or pass a FormHandlerName parameter if using EditForm.");
2222
}
2323

24-
if (!_namedSubmitEventsByAssignedName.TryGetValue(handlerName, out var frameLocation))
24+
if (!_namedSubmitEventsByScopeQualifiedName.TryGetValue(handlerName, out var frameLocation))
2525
{
2626
// This may happen if you deploy an app update and someone still on the old page submits a form,
2727
// or if you're dynamically building the UI and the submitted form doesn't exist the next time
@@ -47,9 +47,9 @@ private void UpdateNamedEvents(in RenderBatch renderBatch)
4747
if (string.Equals(removedEntry.EventType, "onsubmit", StringComparison.Ordinal))
4848
{
4949
var location = (removedEntry.ComponentId, removedEntry.FrameIndex);
50-
if (_namedSubmitEventsByLocation.Remove(location, out var assignedName))
50+
if (_namedSubmitEventsByLocation.Remove(location, out var scopeQualifiedName))
5151
{
52-
_namedSubmitEventsByAssignedName.Remove(assignedName);
52+
_namedSubmitEventsByScopeQualifiedName.Remove(scopeQualifiedName);
5353
}
5454
}
5555
}
@@ -62,19 +62,22 @@ private void UpdateNamedEvents(in RenderBatch renderBatch)
6262
for (var i = 0; i < addedCount; i++)
6363
{
6464
ref var addedEntry = ref addedArray[i];
65-
if (string.Equals(addedEntry.EventType, "onsubmit", StringComparison.Ordinal) && addedEntry.AssignedName is string assignedName)
65+
if (string.Equals(addedEntry.EventType, "onsubmit", StringComparison.Ordinal)
66+
&& addedEntry.AssignedName is string assignedName
67+
&& TryGetScopeQualifiedEventName(addedEntry.ComponentId, assignedName, out var scopeQualifiedName))
6668
{
6769
var location = (addedEntry.ComponentId, addedEntry.FrameIndex);
68-
if (_namedSubmitEventsByAssignedName.TryAdd(assignedName, location))
70+
71+
if (_namedSubmitEventsByScopeQualifiedName.TryAdd(scopeQualifiedName, location))
6972
{
70-
_namedSubmitEventsByLocation.Add(location, assignedName);
73+
_namedSubmitEventsByLocation.Add(location, scopeQualifiedName);
7174
}
7275
else
7376
{
7477
// We could allow multiple events with the same name, since they are all tracked separately. However
7578
// this is most likely a mistake on the developer's part so we will consider it an error.
76-
var existingEntry = _namedSubmitEventsByAssignedName[assignedName];
77-
throw new InvalidOperationException($"There is more than one named event with the name '{assignedName}'. Ensure named events have unique names. The following components both use this name:"
79+
var existingEntry = _namedSubmitEventsByScopeQualifiedName[scopeQualifiedName];
80+
throw new InvalidOperationException($"There is more than one named event with the name '{scopeQualifiedName}'. Ensure named events have unique names. The following components both use this name:"
7881
+ $"\n - {GenerateComponentPath(existingEntry.ComponentId)}"
7982
+ $"\n - {GenerateComponentPath(addedEntry.ComponentId)}");
8083
}

src/Components/Web/src/Forms/AntiforgeryToken.cs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using Microsoft.AspNetCore.Components.Rendering;
5+
using Microsoft.Extensions.DependencyInjection;
56

67
namespace Microsoft.AspNetCore.Components.Forms;
78

@@ -14,34 +15,34 @@ public class AntiforgeryToken : IComponent
1415
private bool _hasRendered;
1516
private AntiforgeryRequestToken? _requestToken;
1617

17-
[Inject] AntiforgeryStateProvider Antiforgery { get; set; } = default!;
18+
[Inject] IServiceProvider Services { get; set; } = default!;
1819

1920
void IComponent.Attach(RenderHandle renderHandle)
2021
{
2122
_handle = renderHandle;
22-
_requestToken = Antiforgery.GetAntiforgeryToken();
23+
_requestToken = Services.GetService<AntiforgeryStateProvider>()?.GetAntiforgeryToken();
2324
}
2425

2526
Task IComponent.SetParametersAsync(ParameterView parameters)
2627
{
2728
if (!_hasRendered)
2829
{
2930
_hasRendered = true;
30-
_handle.Render(RenderField);
31+
if (_requestToken != null)
32+
{
33+
_handle.Render(RenderField);
34+
}
3135
}
3236

3337
return Task.CompletedTask;
3438
}
3539

3640
private void RenderField(RenderTreeBuilder builder)
3741
{
38-
if (_requestToken != null)
39-
{
40-
builder.OpenElement(0, "input");
41-
builder.AddAttribute(1, "type", "hidden");
42-
builder.AddAttribute(2, "name", _requestToken.FormFieldName);
43-
builder.AddAttribute(3, "value", _requestToken.Value);
44-
builder.CloseElement();
45-
}
42+
builder.OpenElement(0, "input");
43+
builder.AddAttribute(1, "type", "hidden");
44+
builder.AddAttribute(2, "name", _requestToken!.FormFieldName);
45+
builder.AddAttribute(3, "value", _requestToken.Value);
46+
builder.CloseElement();
4647
}
4748
}

src/Components/Web/src/Forms/EditForm.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
142142
// to include the handler and antiforgery token in the post data
143143
if (MappingContext != null)
144144
{
145-
var combinedFormName = MappingContext.GetCombinedFormName(FormHandlerName) ?? string.Empty;
146-
builder.AddNamedEvent(5, "onsubmit", combinedFormName);
145+
builder.AddNamedEvent(5, "onsubmit", FormHandlerName ?? string.Empty);
147146
RenderSSRFormHandlingChildren(builder, 6);
148147
}
149148

src/Components/Web/src/Forms/Mapping/FormMappingContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public IEnumerable<FormMappingError> GetAllErrors(string formName)
9292
_errorsByFormName?.TryGetValue(formName, out var formErrors) == true &&
9393
formErrors.TryGetValue(key, out var mappingError) ? mappingError.AttemptedValue : null;
9494

95-
internal string GetCombinedFormName(string? formHandlerName)
95+
internal string GetScopeQualifiedFormName(string? formHandlerName)
9696
{
9797
if (string.IsNullOrEmpty(Name))
9898
{

src/Components/Web/src/Forms/Mapping/IFormValueMapper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ public interface IFormValueMapper
1212
/// Determines whether the specified value type can be mapped.
1313
/// </summary>
1414
/// <param name="valueType">The <see cref="Type"/> for the value to map.</param>
15-
/// <param name="formName">The form name to map data from or null to only validate the type can be mapped.</param>
15+
/// <param name="scopeQualifiedFormName">The form name to map data from or null to only validate the type can be mapped.</param>
1616
/// <returns><c>true</c> if the value type can be mapped; otherwise, <c>false</c>.</returns>
17-
bool CanMap(Type valueType, string? formName = null);
17+
bool CanMap(Type valueType, string? scopeQualifiedFormName = null);
1818

1919
/// <summary>
2020
/// Maps the form value with the specified name to a value of the specified type.

src/Components/Web/src/Forms/Mapping/SupplyParameterFromFormValueProvider.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ public bool CanSupplyValue(in CascadingParameterInfo parameterInfo)
4040
// We also supply values for [SupplyValueFromForm]
4141
if (_formValueMapper is not null && parameterInfo.Attribute is SupplyParameterFromFormAttribute supplyParameterFromFormAttribute)
4242
{
43-
var combinedFormName = _mappingContext.GetCombinedFormName(supplyParameterFromFormAttribute.Handler);
44-
return _formValueMapper.CanMap(parameterInfo.PropertyType, combinedFormName);
43+
var scopeQualifiedFormName = _mappingContext.GetScopeQualifiedFormName(supplyParameterFromFormAttribute.Handler);
44+
return _formValueMapper.CanMap(parameterInfo.PropertyType, scopeQualifiedFormName);
4545
}
4646

4747
return false;
@@ -73,15 +73,15 @@ void ICascadingValueSupplier.Unsubscribe(ComponentState subscriber, in Cascading
7373
internal static object? GetFormPostValue(IFormValueMapper formValueMapper, FormMappingContext? mappingContext, in CascadingParameterInfo parameterInfo, SupplyParameterFromFormAttribute supplyParameterFromFormAttribute)
7474
{
7575
Debug.Assert(mappingContext != null);
76-
var combinedFormName = mappingContext.GetCombinedFormName(supplyParameterFromFormAttribute.Handler);
76+
var scopeQualifiedFormName = mappingContext.GetScopeQualifiedFormName(supplyParameterFromFormAttribute.Handler);
7777

7878
var parameterName = parameterInfo.Attribute.Name ?? parameterInfo.PropertyName;
7979
var handler = ((SupplyParameterFromFormAttribute)parameterInfo.Attribute).Handler;
8080
Action<string, FormattableString, string?> errorHandler = string.IsNullOrEmpty(handler) ?
8181
mappingContext.AddError :
82-
(name, message, value) => mappingContext.AddError(combinedFormName, parameterName, message, value);
82+
(name, message, value) => mappingContext.AddError(scopeQualifiedFormName, parameterName, message, value);
8383

84-
var context = new FormValueMappingContext(combinedFormName, parameterInfo.PropertyType, parameterName)
84+
var context = new FormValueMappingContext(scopeQualifiedFormName, parameterInfo.PropertyType, parameterName)
8585
{
8686
OnError = errorHandler,
8787
MapErrorToContainer = mappingContext.AttachParentValue

src/Components/Web/src/HtmlRendering/StaticHtmlRenderer.HtmlWriting.cs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Diagnostics;
5+
using System.Diagnostics.CodeAnalysis;
56
using System.Text.Encodings.Web;
67
using Microsoft.AspNetCore.Components.Forms;
78
using Microsoft.AspNetCore.Components.RenderTree;
@@ -171,11 +172,8 @@ private void RenderHiddenFieldForNamedSubmitEvent(int componentId, TextWriter ou
171172
ref var enclosingElementFrame = ref frames.Array[enclosingElementFrameIndex];
172173
if (string.Equals(enclosingElementFrame.ElementName, "form", StringComparison.OrdinalIgnoreCase))
173174
{
174-
if (FindFormMappingContext(componentId) is { } formMappingContext)
175+
if (TryGetScopeQualifiedEventName(componentId, frames.Array[namedEventFramePosition].NamedEventAssignedName, out var combinedFormName))
175176
{
176-
var combinedFormName = formMappingContext.GetCombinedFormName(
177-
frames.Array[namedEventFramePosition].NamedEventAssignedName);
178-
179177
output.Write("<input type=\"hidden\" name=\"handler\" value=\"");
180178
_htmlEncoder.Encode(output, combinedFormName);
181179
output.Write("\" />");
@@ -184,6 +182,28 @@ private void RenderHiddenFieldForNamedSubmitEvent(int componentId, TextWriter ou
184182
}
185183
}
186184

185+
/// <summary>
186+
/// Gets the fully scope-qualified name for a named event, if the component is within
187+
/// a <see cref="FormMappingContext"/> (whether or not that mapping context is named).
188+
/// </summary>
189+
/// <param name="componentId">The ID of the component that defines a named event.</param>
190+
/// <param name="eventName">The name assigned to the named event.</param>
191+
/// <param name="scopeQualifiedEventName">The scope-qualified event name.</param>
192+
/// <returns>A flag to indicate whether a value could be produced.</returns>
193+
protected bool TryGetScopeQualifiedEventName(int componentId, string eventName, [NotNullWhen(true)] out string? scopeQualifiedEventName)
194+
{
195+
if (FindFormMappingContext(componentId) is { } formMappingContext)
196+
{
197+
scopeQualifiedEventName = formMappingContext.GetScopeQualifiedFormName(eventName);
198+
return true;
199+
}
200+
else
201+
{
202+
scopeQualifiedEventName = null;
203+
return false;
204+
}
205+
}
206+
187207
private FormMappingContext? FindFormMappingContext(int forComponentId)
188208
{
189209
var componentState = GetComponentState(forComponentId);

src/Components/Web/src/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,14 @@ Microsoft.AspNetCore.Components.Forms.Mapping.FormValueMappingContext.Result.get
5353
Microsoft.AspNetCore.Components.Forms.Mapping.FormValueMappingContext.SetResult(object? result) -> void
5454
Microsoft.AspNetCore.Components.Forms.Mapping.FormValueMappingContext.ValueType.get -> System.Type!
5555
Microsoft.AspNetCore.Components.Forms.Mapping.IFormValueMapper
56-
Microsoft.AspNetCore.Components.Forms.Mapping.IFormValueMapper.CanMap(System.Type! valueType, string? formName = null) -> bool
56+
Microsoft.AspNetCore.Components.Forms.Mapping.IFormValueMapper.CanMap(System.Type! valueType, string? scopeQualifiedFormName = null) -> bool
5757
Microsoft.AspNetCore.Components.Forms.Mapping.IFormValueMapper.Map(Microsoft.AspNetCore.Components.Forms.Mapping.FormValueMappingContext! context) -> void
5858
Microsoft.AspNetCore.Components.Forms.Mapping.SupplyParameterFromFormServiceCollectionExtensions
5959
Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer
6060
Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer.BeginRenderingComponent(Microsoft.AspNetCore.Components.IComponent! component, Microsoft.AspNetCore.Components.ParameterView initialParameters) -> Microsoft.AspNetCore.Components.Web.HtmlRendering.HtmlRootComponent
6161
Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer.BeginRenderingComponent(System.Type! componentType, Microsoft.AspNetCore.Components.ParameterView initialParameters) -> Microsoft.AspNetCore.Components.Web.HtmlRendering.HtmlRootComponent
6262
Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer.StaticHtmlRenderer(System.IServiceProvider! serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
63+
Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer.TryGetScopeQualifiedEventName(int componentId, string! eventName, out string? scopeQualifiedEventName) -> bool
6364
Microsoft.AspNetCore.Components.RenderTree.WebRenderer.WaitUntilAttachedAsync() -> System.Threading.Tasks.Task!
6465
Microsoft.AspNetCore.Components.SupplyParameterFromFormAttribute
6566
Microsoft.AspNetCore.Components.SupplyParameterFromFormAttribute.Handler.get -> string?

src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ActionForm.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,7 @@ void RenderFormContents(RenderTreeBuilder builder, FormMappingContext? bindingCo
5252

5353
if (bindingContext != null)
5454
{
55-
builder.AddNamedEvent(5, "onsubmit", bindingContext.Name);
56-
57-
builder.OpenElement(2, "input");
58-
builder.AddAttribute(3, "type", "hidden");
59-
builder.AddAttribute(4, "name", "handler");
60-
builder.AddAttribute(5, "value", bindingContext.Name);
61-
builder.CloseElement();
55+
builder.AddNamedEvent(5, "onsubmit", "");
6256
}
6357

6458
builder.AddContent(6, ChildContent?.Invoke(bindingContext));

0 commit comments

Comments
 (0)