Skip to content
代码片段 群组 项目
未验证 提交 71327921 编辑于 作者: Chris Sainty's avatar Chris Sainty 提交者: GitHub
浏览文件

Modified EditForm to return _fixedEditContext via the EditContext parameter (#24007)

* Modified EditForm to return _fixedEditContext via the EditContext parameter. Also added some tests to cover the new functionality

* Swapped to boolean to track provided EditContext

* Patched ref assembly

* Simplified setting _hasSetEditContextExplicitly

* Renamed _fixedEditContext to _editContext

* Updated null check in OnParametersSet

* Simplified check for EditContext updates based on Model changes
上级 1455aaef
No related branches found
No related tags found
无相关合并请求
...@@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Components.Forms ...@@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Components.Forms
[Microsoft.AspNetCore.Components.ParameterAttribute] [Microsoft.AspNetCore.Components.ParameterAttribute]
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.Forms.EditContext>? ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.Forms.EditContext>? ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute] [Microsoft.AspNetCore.Components.ParameterAttribute]
public Microsoft.AspNetCore.Components.Forms.EditContext? EditContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public Microsoft.AspNetCore.Components.Forms.EditContext? EditContext { get { throw null; } set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute] [Microsoft.AspNetCore.Components.ParameterAttribute]
public object? Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public object? Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute] [Microsoft.AspNetCore.Components.ParameterAttribute]
......
...@@ -16,7 +16,8 @@ namespace Microsoft.AspNetCore.Components.Forms ...@@ -16,7 +16,8 @@ namespace Microsoft.AspNetCore.Components.Forms
{ {
private readonly Func<Task> _handleSubmitDelegate; // Cache to avoid per-render allocations private readonly Func<Task> _handleSubmitDelegate; // Cache to avoid per-render allocations
private EditContext? _fixedEditContext; private EditContext? _editContext;
private bool _hasSetEditContextExplicitly;
/// <summary> /// <summary>
/// Constructs an instance of <see cref="EditForm"/>. /// Constructs an instance of <see cref="EditForm"/>.
...@@ -36,7 +37,16 @@ namespace Microsoft.AspNetCore.Components.Forms ...@@ -36,7 +37,16 @@ namespace Microsoft.AspNetCore.Components.Forms
/// also supply <see cref="Model"/>, since the model value will be taken /// also supply <see cref="Model"/>, since the model value will be taken
/// from the <see cref="EditContext.Model"/> property. /// from the <see cref="EditContext.Model"/> property.
/// </summary> /// </summary>
[Parameter] public EditContext? EditContext { get; set; } [Parameter]
public EditContext? EditContext
{
get => _editContext;
set
{
_editContext = value;
_hasSetEditContextExplicitly = value != null;
}
}
/// <summary> /// <summary>
/// Specifies the top-level model object for the form. An edit context will /// Specifies the top-level model object for the form. An edit context will
...@@ -73,11 +83,16 @@ namespace Microsoft.AspNetCore.Components.Forms ...@@ -73,11 +83,16 @@ namespace Microsoft.AspNetCore.Components.Forms
/// <inheritdoc /> /// <inheritdoc />
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
if ((EditContext == null) == (Model == null)) if (_hasSetEditContextExplicitly && Model != null)
{ {
throw new InvalidOperationException($"{nameof(EditForm)} requires a {nameof(Model)} " + throw new InvalidOperationException($"{nameof(EditForm)} requires a {nameof(Model)} " +
$"parameter, or an {nameof(EditContext)} parameter, but not both."); $"parameter, or an {nameof(EditContext)} parameter, but not both.");
} }
else if (!_hasSetEditContextExplicitly && Model == null)
{
throw new InvalidOperationException($"{nameof(EditForm)} requires either a {nameof(Model)} " +
$"parameter, or an {nameof(EditContext)} parameter, please provide one of these.");
}
// If you're using OnSubmit, it becomes your responsibility to trigger validation manually // If you're using OnSubmit, it becomes your responsibility to trigger validation manually
// (e.g., so you can display a "pending" state in the UI). In that case you don't want the // (e.g., so you can display a "pending" state in the UI). In that case you don't want the
...@@ -89,31 +104,31 @@ namespace Microsoft.AspNetCore.Components.Forms ...@@ -89,31 +104,31 @@ namespace Microsoft.AspNetCore.Components.Forms
$"{nameof(EditForm)}, do not also supply {nameof(OnValidSubmit)} or {nameof(OnInvalidSubmit)}."); $"{nameof(EditForm)}, do not also supply {nameof(OnValidSubmit)} or {nameof(OnInvalidSubmit)}.");
} }
// Update _fixedEditContext if we don't have one yet, or if they are supplying a // Update _editContext if we don't have one yet, or if they are supplying a
// potentially new EditContext, or if they are supplying a different Model // potentially new EditContext, or if they are supplying a different Model
if (_fixedEditContext == null || EditContext != null || Model != _fixedEditContext.Model) if (Model != null && Model != _editContext?.Model)
{ {
_fixedEditContext = EditContext ?? new EditContext(Model!); _editContext = new EditContext(Model!);
} }
} }
/// <inheritdoc /> /// <inheritdoc />
protected override void BuildRenderTree(RenderTreeBuilder builder) protected override void BuildRenderTree(RenderTreeBuilder builder)
{ {
Debug.Assert(_fixedEditContext != null); Debug.Assert(_editContext != null);
// If _fixedEditContext changes, tear down and recreate all descendants. // If _editContext changes, tear down and recreate all descendants.
// This is so we can safely use the IsFixed optimization on CascadingValue, // This is so we can safely use the IsFixed optimization on CascadingValue,
// optimizing for the common case where _fixedEditContext never changes. // optimizing for the common case where _editContext never changes.
builder.OpenRegion(_fixedEditContext.GetHashCode()); builder.OpenRegion(_editContext.GetHashCode());
builder.OpenElement(0, "form"); builder.OpenElement(0, "form");
builder.AddMultipleAttributes(1, AdditionalAttributes); builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "onsubmit", _handleSubmitDelegate); builder.AddAttribute(2, "onsubmit", _handleSubmitDelegate);
builder.OpenComponent<CascadingValue<EditContext>>(3); builder.OpenComponent<CascadingValue<EditContext>>(3);
builder.AddAttribute(4, "IsFixed", true); builder.AddAttribute(4, "IsFixed", true);
builder.AddAttribute(5, "Value", _fixedEditContext); builder.AddAttribute(5, "Value", _editContext);
builder.AddAttribute(6, "ChildContent", ChildContent?.Invoke(_fixedEditContext)); builder.AddAttribute(6, "ChildContent", ChildContent?.Invoke(_editContext));
builder.CloseComponent(); builder.CloseComponent();
builder.CloseElement(); builder.CloseElement();
...@@ -122,26 +137,26 @@ namespace Microsoft.AspNetCore.Components.Forms ...@@ -122,26 +137,26 @@ namespace Microsoft.AspNetCore.Components.Forms
private async Task HandleSubmitAsync() private async Task HandleSubmitAsync()
{ {
Debug.Assert(_fixedEditContext != null); Debug.Assert(_editContext != null);
if (OnSubmit.HasDelegate) if (OnSubmit.HasDelegate)
{ {
// When using OnSubmit, the developer takes control of the validation lifecycle // When using OnSubmit, the developer takes control of the validation lifecycle
await OnSubmit.InvokeAsync(_fixedEditContext); await OnSubmit.InvokeAsync(_editContext);
} }
else else
{ {
// Otherwise, the system implicitly runs validation on form submission // Otherwise, the system implicitly runs validation on form submission
var isValid = _fixedEditContext.Validate(); // This will likely become ValidateAsync later var isValid = _editContext.Validate(); // This will likely become ValidateAsync later
if (isValid && OnValidSubmit.HasDelegate) if (isValid && OnValidSubmit.HasDelegate)
{ {
await OnValidSubmit.InvokeAsync(_fixedEditContext); await OnValidSubmit.InvokeAsync(_editContext);
} }
if (!isValid && OnInvalidSubmit.HasDelegate) if (!isValid && OnInvalidSubmit.HasDelegate)
{ {
await OnInvalidSubmit.InvokeAsync(_fixedEditContext); await OnInvalidSubmit.InvokeAsync(_editContext);
} }
} }
} }
......
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Xunit;
namespace Microsoft.AspNetCore.Components.Forms
{
public class EditFormTest
{
[Fact]
public async Task ThrowsIfBothEditContextAndModelAreSupplied()
{
// Arrange
var editForm = new EditForm
{
EditContext = new EditContext(new TestModel()),
Model = new TestModel()
};
var testRenderer = new TestRenderer();
var componentId = testRenderer.AssignRootComponentId(editForm);
// Act/Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => testRenderer.RenderRootComponentAsync(componentId));
Assert.StartsWith($"{nameof(EditForm)} requires a {nameof(EditForm.Model)} parameter, or an {nameof(EditContext)} parameter, but not both.", ex.Message);
}
[Fact]
public async Task ThrowsIfBothEditContextAndModelAreNull()
{
// Arrange
var editForm = new EditForm();
var testRenderer = new TestRenderer();
var componentId = testRenderer.AssignRootComponentId(editForm);
// Act/Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => testRenderer.RenderRootComponentAsync(componentId));
Assert.StartsWith($"{nameof(EditForm)} requires either a {nameof(EditForm.Model)} parameter, or an {nameof(EditContext)} parameter, please provide one of these.", ex.Message);
}
[Fact]
public async Task ReturnsEditContextWhenModelParameterUsed()
{
// Arrange
var model = new TestModel();
var rootComponent = new TestEditFormHostComponent
{
Model = model
};
var editFormComponent = await RenderAndGetTestEditFormComponentAsync(rootComponent);
// Act
var returnedEditContext = editFormComponent.EditContext;
// Assert
Assert.NotNull(returnedEditContext);
Assert.Same(model, returnedEditContext.Model);
}
[Fact]
public async Task ReturnsEditContextWhenEditContextParameterUsed()
{
// Arrange
var editContext = new EditContext(new TestModel());
var rootComponent = new TestEditFormHostComponent
{
EditContext = editContext
};
var editFormComponent = await RenderAndGetTestEditFormComponentAsync(rootComponent);
// Act
var returnedEditContext = editFormComponent.EditContext;
// Assert
Assert.Same(editContext, returnedEditContext);
}
private static EditForm FindEditFormComponent(CapturedBatch batch)
=> batch.ReferenceFrames
.Where(f => f.FrameType == RenderTreeFrameType.Component)
.Select(f => f.Component)
.OfType<EditForm>()
.Single();
private static async Task<EditForm> RenderAndGetTestEditFormComponentAsync(TestEditFormHostComponent hostComponent)
{
var testRenderer = new TestRenderer();
var componentId = testRenderer.AssignRootComponentId(hostComponent);
await testRenderer.RenderRootComponentAsync(componentId);
return FindEditFormComponent(testRenderer.Batches.Single());
}
class TestModel
{
public string StringProperty { get; set; }
}
class TestEditFormHostComponent : AutoRenderComponent
{
public EditContext EditContext { get; set; }
public TestModel Model { get; set; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenComponent<EditForm>(0);
builder.AddAttribute(1, "Model", Model);
builder.AddAttribute(2, "EditContext", EditContext);
builder.CloseComponent();
}
}
}
}
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册