Skip to content
Merged
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
@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31612.314
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InputLargeTextArea", "src\InputLargeTextArea\InputLargeTextArea.csproj", "{D3C10569-1320-4C66-B76B-FC02BDC9010F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleServerApp", "SampleServerApp\SampleServerApp.csproj", "{8162C3A5-8054-4647-B597-64A01027EC24}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D3C10569-1320-4C66-B76B-FC02BDC9010F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D3C10569-1320-4C66-B76B-FC02BDC9010F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D3C10569-1320-4C66-B76B-FC02BDC9010F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D3C10569-1320-4C66-B76B-FC02BDC9010F}.Release|Any CPU.Build.0 = Release|Any CPU
{8162C3A5-8054-4647-B597-64A01027EC24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8162C3A5-8054-4647-B597-64A01027EC24}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8162C3A5-8054-4647-B597-64A01027EC24}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8162C3A5-8054-4647-B597-64A01027EC24}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {130EF2A6-BAD0-430B-9B31-D35406A46F67}
EndGlobalSection
EndGlobal
43 changes: 43 additions & 0 deletions samples/aspnetcore/blazor/InputLargeTextArea/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
## Blazor `InputLargeTextArea` Component Sample
A multiline input component for Blazor Server to enable editing large string values. Supports async content access without binding and without validations.

### Example:
```csharp
<InputLargeTextArea id="largeTextArea" @ref="TextArea" OnChange="TextAreaChanged" />


@code {
InputLargeTextArea? TextArea;

public async Task GetTextAsync()
{
var streamReader = await TextArea!.GetTextAsync(maxLength: 50_000);
var textFromInputLargeTextArea = await streamReader.ReadToEndAsync();
}

public async Task SetTextAsync()
{
var textToWrite = new string('c', 50_000);

var memoryStream = new MemoryStream();
var streamWriter = new StreamWriter(memoryStream);
await streamWriter.WriteAsync(textToWrite);
await streamWriter.FlushAsync();
await TextArea!.SetTextAsync(streamWriter);
}

public void TextAreaChanged(InputLargeTextAreaChangeEventArgs args)
{
LastChangedLength = args.Length;
}
}
```

## Why?
Using Blazor Server's `InputTextArea` with large (ex. 20K chars) amounts of text can lead to a degraded user experience due to the constant round-trip communication to/from the server to enable binding and validations. This component provides an asynchronous ability to get & set the text area content. This approach **is not optimal** due to the additional complexity working with `StreamReader`/`StreamWriter` APIs, as well as the (large) amount of memory allocations which may occur when encoding/decoding the `UTF-8` `string`/`textarea` content into `byte`s. Due to these concerns, we've made this available as a sample instead of adding it to the core framework.

Note: If you're encountering slowdowns specifically in complex components or Blazor WebAssembly, we recommend reviewing the [Blazor WebAssembly Performance Best Practices](https://docs.microsoft.com/en-us/aspnet/core/blazor/webassembly-performance-best-practices?view=aspnetcore-6.0#avoid-rerendering-after-handling-events-without-state-changes) which detail rendering optimizations.

## Setup
1. Add a package reference to `InputLargeTextArea`.
2. Add `<script src="_content/InputLargeTextArea/js/InputLargeTextArea.js"></script>` to your `_Layout.cshtml` on Blazor Server.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@page "/"
@using Microsoft.AspNetCore.Components.Forms

<PageTitle>Input Large Text Area</PageTitle>

<InputLargeTextArea id="largeTextArea" @ref="TextArea" OnChange="TextAreaChanged" />

<br />

<button id="setTextBtn" @onclick="SetTextAsync">SetTextAsync</button>
<button id="getTextBtn" @onclick="GetTextAsync">GetTextAsync</button>

<hr />

<h3>Last Changed:</h3>
Length: <p id="lastChangedLength">@LastChangedLength</p>

<h3>Get Text Result:</h3>
<p id="getTextResult">@GetTextResult</p>
<p id="getTextError">@GetTextError</p>


@code {
public long LastChangedLength { get; set; }
public string GetTextResult { get; set; } = string.Empty;
public string GetTextError { get; set; } = string.Empty;

InputLargeTextArea? TextArea;

public async Task GetTextAsync()
{
var streamReader = await TextArea!.GetTextAsync(maxLength: 50_000);
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)
{
LastChangedLength = args.Length;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@page "/"
@namespace SampleServerApp.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = "_Layout";
}

<component type="typeof(App)" render-mode="ServerPrerendered" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@using Microsoft.AspNetCore.Components.Web
@namespace SampleServerApp.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="~/" />
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
<link href="css/site.css" rel="stylesheet" />
<link href="SampleServerApp.styles.css" rel="stylesheet" />
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
</head>
<body>
@RenderBody()

<div id="blazor-error-ui">
<environment include="Staging,Production">
An error has occurred. This application may no longer respond until reloaded.
</environment>
<environment include="Development">
An unhandled exception has occurred. See browser dev tools for details.
</environment>
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>

<script src="_content/InputLargeTextArea/js/InputLargeTextArea.js"></script>
<script src="_framework/blazor.server.js"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();


app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

app.Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\src\InputLargeTextArea\InputLargeTextArea.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@inherits LayoutComponentBase

<PageTitle>SampleServerApp</PageTitle>

<div class="page">
<div class="sidebar">
<NavMenu />
</div>

<main>
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>

<article class="content px-4">
@Body
</article>
</main>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}

main {
flex: 1;
}

.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}

.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}

.top-row ::deep a, .top-row .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
}

.top-row a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}

@media (max-width: 640.98px) {
.top-row:not(.auth) {
display: none;
}

.top-row.auth {
justify-content: space-between;
}

.top-row a, .top-row .btn-link {
margin-left: 0;
}
}

@media (min-width: 641px) {
.page {
flex-direction: row;
}

.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}

.top-row {
position: sticky;
top: 0;
z-index: 1;
}

.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">SampleServerApp</a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
Home
</NavLink>
</div>
</nav>
</div>

@code {
private bool collapseNavMenu = true;

private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;

private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}
Loading