-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Closed
Labels
DoneThis issue has been fixedThis issue has been fixedapi-suggestionEarly API idea and discussion, it is NOT ready for implementationEarly API idea and discussion, it is NOT ready for implementationarea-blazorIncludes: Blazor, Razor ComponentsIncludes: Blazor, Razor Components
Milestone
Description
Background and Motivation
The current input textarea doesn't perform well with large amounts of text within Blazor Server. Due to binding/validation requirements, each keypress could lead to the entire text needing to be transferred to the server (ie 20k+ chars) which leads to unresponsiveness. This new InputLargeTextArea allows for async streaming based access to the textarea and forgoes binding/validation.
Proposed API
namespace Microsoft.AspNetCore.Components.Forms
{
/// <summary>
/// A multiline input component for editing large <see cref="string"/> values. It supports asynchronous
/// content access without binding or validation.
/// </summary>
public class InputLargeTextArea : ComponentBase, IInputLargeTextAreaJsCallbacks, IDisposable
{
/// <summary>
/// Gets or sets the event callback that will be invoked when the textarea content changes.
/// </summary>
[Parameter]
public EventCallback<InputLargeTextAreaChangeEventArgs> OnChange { get; set; }
/// <summary>
/// Retrieves the textarea value asynchronously.
/// </summary>
/// <param name="maxLength">The maximum length of content to fetch from the textarea.</param>
/// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> used to relay cancellation of the request.</param>
/// <returns>A <see cref="System.IO.StreamReader"/> which facilitates reading of the textarea value.</returns>
public ValueTask<StreamReader> GetTextAsync(int maxLength = 50_000, CancellationToken cancellationToken = default);
/// <summary>
/// Sets the textarea value asynchronously.
/// </summary>
/// <param name="newValue">A <see cref="System.IO.StreamWriter"/> used to set the value of the textarea.</param>
/// <param name="leaveTextAreaEnabled">Don't disable the textarea while set text is in progress.</param>
/// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> used to relay cancellation of the request.</param>
public ValueTask SetTextAsync(StreamWriter newValue, bool leaveTextAreaEnabled = false, CancellationToken cancellationToken = default);
}
/// <summary>
/// Provides information for a text change event on a <see cref="InputLargeTextArea" /> component.
/// </summary>
public class InputLargeTextAreaChangeEventArgs
{
/// <summary>
/// Gets the <see cref="InputLargeTextArea" /> whose text has changed.
/// </summary>
public InputLargeTextArea Source { get; set; }
/// <summary>
/// Gets the length of the updated text in characters.
/// </summary>
public long TextLength { get; set; }
}
}Usage Examples
@using System.IO
@using System.Text
<InputLargeTextArea @ref="TextArea" OnChange="TextAreaChanged" />
@code {
InputLargeTextArea TextArea;
public async Task GetTextAsync()
{
var streamReader = await TextArea.GetTextAsync();
GetTextResult = await streamReader.ReadToEndAsync();
StateHasChanged();
}
public async Task SetTextAsync()
{
var memoryStream = new MemoryStream();
var streamWriter = new StreamWriter(memoryStream);
await streamWriter.WriteAsync(new string('c', 50_000));
await streamWriter.FlushAsync();
await TextArea.SetTextAsync(streamWriter);
}
public void TextAreaChanged(InputLargeTextAreaChangeEventArgs args)
{
// args.Length represents the new textarea value length
}
}Alternative Designs
Alternatively, to get past the 32k SignalR message size limit, we can take a Stream based approach:
[Old approach]
/// <summary>
/// Retrieves the textarea value asynchronously.
/// </summary>
/// <returns>The string value of the textarea.</returns>
public ValueTask<string> GetTextStreamAsync();
/// <summary>
/// Sets the textarea value asynchronously.
/// </summary>
/// <param name="newValue">The new content to set for the textarea.</param>
public ValueTask SetTextAsync(string newValue);Questions
This section added by @SteveSandersonMS just to capture some ongoing thoughts:
- For
SetTextAsync,- Should the developer really pass a StreamWriter? Why not pass the underlying stream from it? If there are cases like setting an initial value from data in a file or other stream source, you wouldn't have a StreamWriter - you'd just want to supply a stream. And if you do have a StreamWriter you can also pass the .BaseStream from it, so that seems more flexible. I know this means the developer becomes responsible for ensuring the stream data actually is UTF8-encoded text, but that seems like a reasonable tradeoff.
- Could we eliminate the explicit
leaveTextAreaEnabledparameter and instead trigger the disabling behavior based on whether the initial chunk is "complete" vs whether more data is yet to arrive asynchronously?
- Should there also be an
InitialTextparameter? I'm probably inclined to go with "no" just to avoid having too many different ways to do things, but we should bear in mind that the usage pattern for setting some initial text from a string will be pretty indirect, as you'll need to use@refand then add anOnAfterRenderAsync, and then doif (firstRender)and inside that construct a stream from your string and supply that. Lots of steps!
Risks
Potential issue with >32k chars (SignalR message size limit) in the textbox.
Metadata
Metadata
Assignees
Labels
DoneThis issue has been fixedThis issue has been fixedapi-suggestionEarly API idea and discussion, it is NOT ready for implementationEarly API idea and discussion, it is NOT ready for implementationarea-blazorIncludes: Blazor, Razor ComponentsIncludes: Blazor, Razor Components