Skip to content

Explicitly implement SetParametersAsync #12254

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,9 @@ else

WeatherForecast[] forecasts;

public override Task SetParametersAsync(ParameterCollection parameters)
protected override void OnParametersSetting()
{
StartDate = DateTime.Now;
return base.SetParametersAsync(parameters);
}

protected override async Task OnParametersSetAsync()
Expand Down
55 changes: 48 additions & 7 deletions src/Components/Components/src/ComponentBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,30 @@ protected virtual Task OnInitializedAsync()

/// <summary>
/// Method invoked when the component has received parameters from its parent in
/// the render tree, and the incoming values have been assigned to properties.
/// the render tree, before the incoming values have been assigned to properties.
/// </summary>
protected virtual void OnParametersSetting()
{
}

/// <summary>
/// Method invoked when the component has received parameters from its parent in
/// the render tree, before the incoming values have been assigned to properties.
/// </summary>
/// <returns>A <see cref="Task"/> representing any asynchronous operation.</returns>
protected virtual Task OnParametersSettingAsync() => Task.CompletedTask;

/// <summary>
/// Method invoked when the component has received parameters from its parent in
/// the render tree, after the incoming values have been assigned to properties.
/// </summary>
protected virtual void OnParametersSet()
{
}

/// <summary>
/// Method invoked when the component has received parameters from its parent in
/// the render tree, and the incoming values have been assigned to properties.
/// the render tree, after the incoming values have been assigned to properties.
/// </summary>
/// <returns>A <see cref="Task"/> representing any asynchronous operation.</returns>
protected virtual Task OnParametersSetAsync()
Expand Down Expand Up @@ -175,22 +190,22 @@ void IComponent.Configure(RenderHandle renderHandle)
/// Method invoked to apply initial or updated parameters to the component.
/// </summary>
/// <param name="parameters">The parameters to apply.</param>
public virtual Task SetParametersAsync(ParameterCollection parameters)
Task IComponent.SetParametersAsync(ParameterCollection parameters)
Copy link
Member Author

Choose a reason for hiding this comment

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

Our idea of making this explicit with overrides is that you can't use the base keyword within in an explicit interface implementation. So it's not really possible to interact with by overriding or it would require a lot more design.

It seemed more natural to give you a before/after lifecycle model. The only use case I can really think of for before is to set default values, but it's otherwise SUPER painful to do.

{
parameters.SetParameterProperties(this);
Copy link
Member Author

Choose a reason for hiding this comment

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

This was another issue I discovered here (or maybe it's intentional and I just missed it).

The way this is currently implemented, Init runs after parameters are set. This means that you can read parameter values in Init and it works. That seems really surprising and I wonder how often it's misused. I assumed that it's valuable to separate these things so that we can keep the exact timing of init being called an implementation detail.

if (!_initialized)
{
_initialized = true;

return RunInitAndSetParametersAsync();
return RunInitAndSetParametersAsync(parameters);
}
else
{
return CallOnParametersSetAsync();

return SetParametersCoreAsync(parameters);
}
}

private async Task RunInitAndSetParametersAsync()
private async Task RunInitAndSetParametersAsync(ParameterCollection parameters)
{
OnInitialized();
var task = OnInitializedAsync();
Expand Down Expand Up @@ -223,9 +238,35 @@ private async Task RunInitAndSetParametersAsync()
// Don't call StateHasChanged here. CallOnParametersSetAsync should handle that for us.
}

await SetParametersCoreAsync(parameters);
}

private async Task SetParametersCoreAsync(ParameterCollection parameters)
{
await CallOnParametersSettingAsync();
parameters.SetParameterProperties(this);
await CallOnParametersSetAsync();
}

private Task CallOnParametersSettingAsync()
{
OnParametersSetting();
var task = OnParametersSettingAsync();
// If no async work is to be performed, i.e. the task has already ran to completion
// or was canceled by the time we got to inspect it, avoid going async and re-invoking
// StateHasChanged at the culmination of the async work.
var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&
task.Status != TaskStatus.Canceled;

// We always call StateHasChanged here as we want to trigger a rerender after OnParametersSet and
// the synchronous part of OnParametersSetAsync has run.
StateHasChanged();

return shouldAwaitTask ?
CallStateHasChangedOnAsyncCompletion(task) :
Task.CompletedTask;
}

private Task CallOnParametersSetAsync()
{
OnParametersSet();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace Microsoft.AspNetCore.Components.Forms
{
Expand Down Expand Up @@ -166,12 +165,9 @@ protected string CssClass
}
}


/// <inheritdoc />
public override Task SetParametersAsync(ParameterCollection parameters)
protected override void OnParametersSet()
{
parameters.SetParameterProperties(this);

if (EditContext == null)
{
// This is the first run
Expand Down Expand Up @@ -204,9 +200,6 @@ public override Task SetParametersAsync(ParameterCollection parameters)
throw new InvalidOperationException($"{GetType()} does not support changing the " +
$"{nameof(Forms.EditContext)} dynamically.");
}

// For derived components, retain the usual lifecycle with OnInit/OnParametersSet/etc.
return base.SetParametersAsync(ParameterCollection.Empty);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@page "/WithParameters/Name/{firstName}"
@page "/WithParameters/Name/{firstName}/LastName/{lastName}"
@implements IComponent
<div id="test-info">Your full name is @FirstName @LastName.</div>
<Links />

Expand All @@ -9,11 +10,10 @@

[Parameter] string LastName { get ; set; }

public override Task SetParametersAsync(ParameterCollection parameters)
protected override void OnParametersSetting()
{
// Manually reset parameters to defaults so we don't retain any from an earlier URL
FirstName = default;
LastName = default;
return base.SetParametersAsync(parameters);
}
}