Záznam ze Vzdělávacího okénka HAVIT z 10. března 2021, kde jsem povídal o Refit – knihovně umožňující snadné konzumování REST API z .NET pomocí strong-API.
Nahrávka je publikována na našem HAVIT YouTube Channelu.
Záznam ze Vzdělávacího okénka HAVIT z 10. března 2021, kde jsem povídal o Refit – knihovně umožňující snadné konzumování REST API z .NET pomocí strong-API.
Nahrávka je publikována na našem HAVIT YouTube Channelu.
Záznam ze Vzdělávacího okénka HAVIT, kde Ondra Václavek povídal o HTTPS, Let’s Encrypt, ACME a dalších tématech z oblasti.
Záznam ze Vzdělávacího okénka HAVIT, kde Jirka Kanda ukazoval, jak nasazujeme aplikace z Azure DevOps Services.
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é:
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);
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)
};
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.
<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
}
}
První verze komponenty neřeší některé detaily. Bude potřeba ještě:
BlazorInputFile
, než bylo přidáno do BlazoruZáznam ze Vzdělávacího okénka HAVIT, kde Lukáš Rada mluvil o nových možnostech v jazyce TypeScript verzi 4.
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.
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.
Záznam ze Vzdělávacího okénka HAVIT, kde Lukáš Rada mluvil o Azure Static Web Apps (aktuálně v Preview), které nabízejí možnost snadno hostovat statický frontend ve spojení s Azure Functions pro API (backend).
Záznam ze Vzdělávacího okénka HAVIT z 8. července 2020, kde nám Jirka Gregor ukazoval vytvoření Chrome Extension pro Chrome/EDGE, kterým zobrazuje stavy ticketů v JIRA (Service Desk) u Azure DevOps work-items a obarvování položek v ADOS dle vlastních pravidel.
Záznam z přednášky pro konferenci SQL Server Bootcamp 2020, kterou pořádalo sdružení WUG Česká Republika ve dnech 12. a 13. září 2020
https://github.com/hakenr/SqlInjectionDemo
https://github.com/hakenr/SqlConnectionPoolingDemo