Category Archives: Blazor

Blazor – Validace formulářů [Jiří Kanda, Vzdělávací okénko, 2.6.2021]

Záznam ze Vzdělávacího okénka HAVIT, kde Jirka Kanda ukazoval, jak na validací formulářů v Blazoru:

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:

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é:

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

Viz též

Líbí se Vám Blazor?

Přidejte se k nám…

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.

DEMA: https://github.com/hakenr/BlazorNet5Demos

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.