Záznam ze Vzdělávacího okénka HAVIT, kde Jirka Kanda ukazoval, jak na validací formulářů v Blazoru:
Category Archives: Blazor
Cancellation Tokens [Jiří Kanda, Vzdělávací okénko, 5.5.2021]
Záznam ze Vzdělávacího okénka HAVIT, kde Jirka Kanda ukazoval využití a práci s CancellationTokens v asynchronním programování.
Blazor – životní cyklus komponent [Jiří Kanda, Vzdělávací okénko, 24.3.2021]
Záznam ze Vzdělávacího okénka HAVIT, kde Jirka Kanda ukazoval životní cyklus Razor (Blazor) komponent:
Havit.Blazor – HxGrid [Jiří Kanda, Vzdělávací okénko, 24.3.2021]
Záznam ze Vzdělávacího okénka HAVIT, kde Jirka Kanda ukazoval komponentu HxGrid z balíčku Havit.Blazor:
I[Async]Disposable [Jiří Kanda, Vzdělávací okénko, 17.3.2021]
Záznam ze Vzdělávacího okénka HAVIT, kde Jirka Kanda mluvil o IDisposable
, IAsyncDisposable
a vztahu mezi nimi.
Havit.Blazor stack onboarding [Robert Haken, Vzdělávací okénko, 16.3.2021]
Záznam ze Vzdělávacího okénka HAVIT z 16. března 2021, kde nabízím pohled do nitra naší kuchyně – vstupní seznámení s naším Havit.Blazor stackem.
Nahrávka je publikována na našem HAVIT YouTube Channelu.
HxInputFile – rozšíření Blazor InputFile komponenty o přímý (nativní) upload a progress-indikaci
V .NET 5. dostal Blazor InputFile komponentu, která zpřístupňuje vybrané soubory v podobě streamu (IBrowserFile.OpenReadStream
).
V případě Blazor Serveru je to celkem použitelné, implementace InputFile protlačí obsah souboru na server pomocí SignalR téměř plnou rychlostí (odhadem o 30% pomaleji než nativní HTTP upload). V případě Blazor WebAssembly je ale implementace přenosu dat na server ponechána na vývojářích a možnosti nejsou růžové.
Blazor dokáže přes unmarshalled interop dostat obsah souboru do WASM velmi rychle (cca 100MB/s), jenže vyšťouchat větší soubor z WASM na server je o dost horší a pro větší soubory nepoužitelné:
- HttpClient (v době psaní tohoto postu) nepodporuje request-streaming. I když použijete StreamContent, celý request je nejprve nabufferován do paměti a pak teprve tlačen na server.
- HttpClient musí data z WASM dostat ven.
Přímý (nativní) upload
Abychom překonali limity InputFile komponenty ve WASM, můžeme soubory uploadovat na server přímo z JavaScriptu pomocí XMLHttpRequest (fetch()
API se na to moc nehodí, protože zatím nedává rozumné informace o progressu):
var data = new FormData();
data.append('file', file, file.name);
var request = new XMLHttpRequest();
request.open('POST', uploadEndpointUrl, true);
request.send(data);
Progress indikace
XMLHttpRequest
nám dává hezké API v podobě události onprogress
:
request.upload.onprogress = function (e) {
// e.loaded - bytes already uploaded
// e.total - total upload size (slightly bigger than the file size)
};
HxInputFile / HxInputFileCore
Když to dáme celé dohromady, můžeme vytvořit komponentu zděděnou z InputFile
. Říkejme jí HxInputFileCore (a k tomu HxInputFile, který už je Bootstrap podobou s drobnými vylepšeními kolem).
Klíčové části zdrojového kódu komponenty pak budou:
public partial class HxInputFileCore : InputFile, IAsyncDisposable
{
[Parameter] public string UploadUrl { get; set; }
[Parameter] public EventCallback<UploadProgressEventArgs> OnProgress { get; set; }
[Parameter] public EventCallback<FileUploadedEventArgs> OnFileUploaded { get; set; }
[Parameter] public EventCallback<UploadCompletedEventArgs> OnUploadCompleted { get; set; }
[Parameter] public bool Multiple { get; set; }
[Parameter] public string Id { get; set; } = "hx" + Guid.NewGuid().ToString("N");
[Inject] protected IJSRuntime JSRuntime { get; set; }
private DotNetObjectReference<HxInputFileCore> dotnetObjectReference;
private IJSObjectReference jsModule;
public HxInputFileCore()
{
dotnetObjectReference = DotNetObjectReference.Create(this);
}
protected override void OnParametersSet()
{
base.OnParametersSet();
// TODO Temporary hack as base implementation of InputFile does not expose ElementReference (vNext: https://github.com/dotnet/aspnetcore/blob/main/src/Components/Web/src/Forms/InputFile.cs)
AdditionalAttributes ??= new Dictionary<string, object>();
AdditionalAttributes["id"] = this.Id;
AdditionalAttributes["multiple"] = this.Multiple;
}
public async Task StartUploadAsync(string accessToken = null)
{
jsModule ??= await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./_content/Havit.Blazor.Components.Web/hxinputfilecore.js");
await jsModule.InvokeVoidAsync("upload", Id, dotnetObjectReference, this.UploadUrl, accessToken);
}
[JSInvokable("HxInputFileCore_HandleUploadProgress")]
public async Task HandleUploadProgress(int fileIndex, string fileName, long loaded, long total)
{
var uploadProgress = new UploadProgressEventArgs() { /*...*/ };
await OnProgress.InvokeAsync(uploadProgress);
}
[JSInvokable("HxInputFileCore_HandleFileUploaded")]
public async Task HandleFileUploaded(int fileIndex, string fileName, long fileSize, string fileType, long fileLastModified, int responseStatus, string responseText)
{
var fileUploaded = new FileUploadedEventArgs() { /* ... */ };
await OnFileUploaded.InvokeAsync(fileUploaded);
}
[JSInvokable("HxInputFileCore_HandleUploadCompleted")]
public async Task HandleUploadCompleted(int fileCount, long totalSize)
{
var uploadCompleted = new UploadCompletedEventArgs() { /* ... */
};
await OnUploadCompleted.InvokeAsync(uploadCompleted);
}
public async ValueTask DisposeAsync()
{
// ...
}
}
…a podpůrný JavaScript k tomu:
export function upload(inputElementId, hxInputFileDotnetObjectReference, uploadEndpointUrl, accessToken) {
var inputElement = document.getElementById(inputElementId);
var dotnetReference = hxInputFileDotnetObjectReference;
var files = inputElement.files;
var totalSize = 0;
var uploadedCounter = 0;
for (var i = 0; i < files.length; i++) {
(function (curr) {
var index = curr;
var file = files[curr];
totalSize = totalSize + file.size;
var data = new FormData();
data.append('file', file, file.name);
var request = new XMLHttpRequest();
request.open('POST', uploadEndpointUrl, true);
if (accessToken) {
request.setRequestHeader('Authorization', 'Bearer ' + accessToken);
}
request.upload.onprogress = function (e) {
dotnetReference.invokeMethodAsync('HxInputFileCore_HandleUploadProgress', index, file.name, e.loaded, e.total);
};
request.onreadystatechange = function () {
if (request.readyState === 4) {
dotnetReference.invokeMethodAsync('HxInputFileCore_HandleFileUploaded', index, file.name, file.size, file.type, file.lastModified, request.status, request.responseText);
};
uploadedCounter++;
if (uploadedCounter === files.length) {
dotnetReference.invokeMethodAsync('HxInputFileCore_HandleUploadCompleted', files.length, totalSize);
}
}
request.send(data);
}(i));
}
}
Komponenty jsou součástí open-source balíčku Havit.Blazor, který najdete na GitHub.
Použití
<HxInputFile @ref="hxInputFileComponent" Label="HxInputFile" UploadUrl="/file-upload-streamed/" OnProgress="HandleProgress" OnFileUploaded="HandleFileUploaded" OnUploadCompleted="HandleUploadCompleted" Multiple="true" />
<HxButton Text="Upload" OnClick="HandleUploadClick" />
@code
{
private HxInputFile hxInputFileComponent;
private async Task HandleUploadClick()
{
files.Clear();
string accessToken = null;
var accessTokenResult = await ... // use IAccessTokenProvider
await hxInputFileComponent.StartUploadAsync(accessToken);
}
private Task HandleProgress(UploadProgressEventArgs progress)
{
// indicate progress here
}
private Task HandleFileUploaded(FileUploadedEventArgs fileUploaded)
{
// individual file uploaded
}
private Task HandleUploadCompleted(UploadCompletedEventArgs uploadCompleted)
{
// all files uploaded
}
}
TODOs
První verze komponenty neřeší některé detaily. Bude potřeba ještě:
- Limit velikosti souborů
- Limit počtu paralelně uploadovaných souborů
- Vylepšit obsluhu chyb
- …?
Odkazy
- havit/Havit.Blazor: Libraries of components and supportive classes for Blazor development. (github.com)
- Havit.Blazor/HxInputFileCore.cs at master · havit/Havit.Blazor (github.com)
- Havit.Blazor/HxInputFile.cs at master · havit/Havit.Blazor (github.com)
- NuGet Gallery | Havit.Blazor.Components.Web – HxInputFileCore
- NuGet Gallery | Havit.Blazor.Components.Web.Bootstrap – HxInputFile
Viz též
- File uploads with Blazor (stevensanderson.com) – původní
BlazorInputFile
, než bylo přidáno do Blazoru - [browser][wasm] Request Streaming upload via http handler · Issue #36634 · dotnet/runtime (github.com)
- WebAssemblyHttpHandler buffers StreamContent into browser memory · Issue #19969 · mono/mono (github.com)
Líbí se Vám Blazor?
Blazor – Stav světa a co se chystá v .NET5 – záznam, slides, dema [WUG Days Online 2020]
Záznam z přednášky pro konferenci WUG Days Online 2020, kterou pořádalo sdružení WUG Česká Republika ve dnech 21. až 23. října 2020.
HAVIT & Blazor – pohled do kuchyně [Jiří Kanda, Vzdělávací okénko, 29.7.2020]
Záznam ze Vzdělávacího okénka HAVIT, kde Jirka Kanda ukazoval, jaký stack máme v HAVITu připravený pro vývoj aplikací v Blazor.
Blazor – Razor Class Libraries [Daniel Vršek, Vzdělávací okénko, 22.4.2020]
Záznam ze Vzdělávacího okénka HAVIT z 22. dubna 2020, kde Daniel Vršek povídal o vytváření knihoven komponent pro Blazor.
Nahrávka je publikována na našem HAVIT YouTube Channelu.