Skip to content

Commit d6a3c16

Browse files
Extended sample of FileReader
1 parent 6a6c769 commit d6a3c16

6 files changed

Lines changed: 230 additions & 5 deletions

File tree

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
@page "/FileReaderSample"
2+
@using System.Text
3+
4+
@inject IJSRuntime JSRuntime
5+
@inject HttpClient HttpClient
6+
7+
<PageTitle>FileAPI - FileReader</PageTitle>
8+
9+
<h2>Using the FileReader to load an image in different ways.</h2>
10+
In this sample we download an image using the <code>HttpClient</code> and create a new <code>Blob</code> from that.
11+
We then use the methods of the <code>FileReader</code> interface to read the blob in different ways and convert those different results to an image.
12+
<br />
13+
<div class="px-1 py-1">
14+
<button class="btn btn-primary" @onclick=ReadAsArrayBufferAsync>Read as byte array</button>
15+
<button class="btn btn-primary" @onclick=ReadAsBinaryStringAsync>Read as binary string</button>
16+
<button class="btn btn-primary" @onclick=ReadAsTextAsync>Read as text</button>
17+
<button class="btn btn-primary" @onclick=ReadAsDataURLAsync>Read as data URL</button>
18+
</div>
19+
<textarea @bind=@log style="height:30vh;width:100%;">
20+
</textarea>
21+
<br />
22+
<img src="@imageUrl" style="max-width:100%; max-height:30vh;" />
23+
24+
@code {
25+
string log = "";
26+
private string imageUrl = "";
27+
private Blob? blob;
28+
29+
protected override async Task OnInitializedAsync()
30+
{
31+
var imageBytes = await HttpClient.GetByteArrayAsync($"images/mountain.jpg");
32+
blob = await Blob.CreateAsync(
33+
JSRuntime,
34+
blobParts: new BlobPart[] { new(imageBytes) },
35+
options: new() { Type = "image/png" }
36+
);
37+
}
38+
39+
private async Task GetProgressAsync(ProgressEvent eventArgs, string prepend)
40+
{
41+
var progress = await eventArgs.GetLengthComputableAsync() ? $"({await eventArgs.GetLoadedAsync()}/{await eventArgs.GetTotalAsync()})" : "";
42+
log += $"{prepend}: {progress}\n";
43+
StateHasChanged();
44+
}
45+
46+
public async Task ReadAsArrayBufferAsync()
47+
{
48+
log = "";
49+
var fileReader = await FileReader.CreateAsync(JSRuntime);
50+
fileReader.OnLoadStart = async (e) => GetProgressAsync(e, "OnLoadStart");
51+
fileReader.OnProgress = async (e) => GetProgressAsync(e, "OnProgress");
52+
fileReader.OnLoad = async (e) => GetProgressAsync(e, "OnLoad");
53+
fileReader.OnAbort = async (e) => GetProgressAsync(e, "OnAbort");
54+
fileReader.OnError = async (e) => GetProgressAsync(e, "OnError");
55+
fileReader.OnLoadEnd = async (e) =>
56+
{
57+
imageUrl = "data:image/png;base64," + Convert.ToBase64String(await fileReader.GetResultAsByteArrayAsync());
58+
GetProgressAsync(e, "OnLoadEnd");
59+
};
60+
await fileReader.ReadAsArrayBufferAsync(blob);
61+
}
62+
63+
public async Task ReadAsBinaryStringAsync()
64+
{
65+
log = "";
66+
var fileReader = await FileReader.CreateAsync(JSRuntime);
67+
fileReader.OnLoadStart = async (e) => GetProgressAsync(e, "OnLoadStart");
68+
fileReader.OnProgress = async (e) => GetProgressAsync(e, "OnProgress");
69+
fileReader.OnLoad = async (e) => GetProgressAsync(e, "OnLoad");
70+
fileReader.OnAbort = async (e) => GetProgressAsync(e, "OnAbort");
71+
fileReader.OnError = async (e) => GetProgressAsync(e, "OnError");
72+
fileReader.OnLoadEnd = async (e) =>
73+
{
74+
var bytes = (await fileReader.GetResultAsStringAsync()).Select(c => (byte)c).ToArray();
75+
imageUrl = "data:image/png;base64," + Convert.ToBase64String(bytes);
76+
GetProgressAsync(e, "OnLoadEnd");
77+
};
78+
await fileReader.ReadAsBinaryStringAsync(blob);
79+
}
80+
81+
public async Task ReadAsTextAsync()
82+
{
83+
log = "We can't read an image as text. ;)";
84+
imageUrl = "";
85+
StateHasChanged();
86+
}
87+
88+
public async Task ReadAsDataURLAsync()
89+
{
90+
log = "";
91+
var fileReader = await FileReader.CreateAsync(JSRuntime);
92+
fileReader.OnLoadStart = async (e) => GetProgressAsync(e, "OnLoadStart");
93+
fileReader.OnProgress = async (e) => GetProgressAsync(e, "OnProgress");
94+
fileReader.OnLoad = async (e) => GetProgressAsync(e, "OnLoad");
95+
fileReader.OnAbort = async (e) => GetProgressAsync(e, "OnAbort");
96+
fileReader.OnError = async (e) => GetProgressAsync(e, "OnError");
97+
fileReader.OnLoadEnd = async (e) =>
98+
{
99+
imageUrl = await fileReader.GetResultAsStringAsync();
100+
GetProgressAsync(e, "OnLoadEnd");
101+
};
102+
await fileReader.ReadAsDataURLAsync(blob);
103+
}
104+
}

samples/KristofferStrube.Blazor.FileAPI.WasmExample/Pages/Index.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<PageTitle>FileAPI - Index</PageTitle>
99

1010
<h2>Loading an image as a Blob</h2>
11-
In this sample we download an image using the <code>HttpClient</code> and create a new <code>Blob</code> from that.
11+
In this sample we download an image using the <code>HttpClient</code> and create a new <code>File</code> from that.
1212
Using the methods from the <code>URL</code> interface we construct a <code>Blob URL</code> for the image and use that as the source for the img-tag below.
1313
<br />
1414
<img src="@blobURL" style="max-width:100%; max-height:50vh;" />

samples/KristofferStrube.Blazor.FileAPI.WasmExample/Pages/Slice.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Here we can see how we can slice a <code>Blob</code>. We construct a <code>Blob<
1010
<b>Slice(20, 30):</b> @slice20_30
1111
</div>
1212
<div>
13-
<b>SliceAsync(end: 20):</b> @slice_end_10
13+
<b>SliceAsync(end: 10):</b> @slice_end_10
1414
</div>
1515
<div>
1616
<b>SliceAsync(start: 40):</b> @slice_start_40

samples/KristofferStrube.Blazor.FileAPI.WasmExample/Shared/NavMenu.razor

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
<span class="oi oi-crop" aria-hidden="true"></span> Slice
2020
</NavLink>
2121
</div>
22+
<div class="nav-item px-3">
23+
<NavLink class="nav-link" href="FileReaderSample">
24+
<span class="oi oi-arrow-bottom" aria-hidden="true"></span> FileReader
25+
</NavLink>
26+
</div>
2227
<div class="nav-item px-3">
2328
<NavLink class="nav-link" href="Status">
2429
<span class="oi oi-warning" aria-hidden="true"></span> API Coverage Status

src/KristofferStrube.Blazor.FileAPI/FileReader.cs

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Microsoft.JSInterop;
2+
using System.Text.Json.Serialization;
23

34
namespace KristofferStrube.Blazor.FileAPI;
45

@@ -27,15 +28,21 @@ public static async Task<FileReader> CreateAsync(IJSRuntime jSRuntime)
2728
{
2829
IJSObjectReference helper = await jSRuntime.GetHelperAsync();
2930
IJSObjectReference jSInstance = await helper.InvokeAsync<IJSObjectReference>("constructFileReader");
30-
return new FileReader(jSRuntime, jSInstance);
31+
var fileReader = new FileReader(jSRuntime, jSInstance);
32+
await helper.InvokeVoidAsync("registerEventHandlers", fileReader, jSInstance);
33+
return fileReader;
3134
}
3235

3336
/// <summary>
3437
/// Constructs a wrapper instance for a given JS Instance of a <see cref="FileReader"/>.
3538
/// </summary>
3639
/// <param name="jSRuntime">An <see cref="IJSRuntime"/> instance.</param>
3740
/// <param name="jSReference">A JS reference to an existing <see cref="FileReader"/>.</param>
38-
internal FileReader(IJSRuntime jSRuntime, IJSObjectReference jSReference) : base(jSRuntime, jSReference) { }
41+
internal FileReader(IJSRuntime jSRuntime, IJSObjectReference jSReference) : base(jSRuntime, jSReference) {
42+
ObjRef = DotNetObjectReference.Create(this);
43+
}
44+
45+
public DotNetObjectReference<FileReader> ObjRef { get; init; }
3946

4047
public async Task ReadAsArrayBufferAsync(Blob blob)
4148
{
@@ -69,6 +76,102 @@ public async Task AbortAsync(Blob blob)
6976
public async Task<ushort> GetReadyStateAsync()
7077
{
7178
IJSObjectReference helper = await helperTask.Value;
72-
return await helper.InvokeAsync<ushort>("readyState");
79+
return await helper.InvokeAsync<ushort>("getAttribute", JSReference, "readyState");
80+
}
81+
82+
/// <summary>
83+
/// Checks whether the result is a either a string or a byte array.
84+
/// </summary>
85+
/// <returns>Either the type of <see langword="string"/> or type of <see cref="byte"/>[].</returns>
86+
public async Task<Type?> GetResultTypeAsync()
87+
{
88+
IJSObjectReference helper = await helperTask.Value;
89+
var isArrayBuffer = await helper.InvokeAsync<bool>("isArrayBuffer", JSReference);
90+
return isArrayBuffer ? typeof(byte[]) : typeof(string);
91+
}
92+
93+
public async Task<string?> GetResultAsStringAsync()
94+
{
95+
IJSObjectReference helper = await helperTask.Value;
96+
return await helper.InvokeAsync<string>("getAttribute", JSReference, "result");
97+
}
98+
99+
public async Task<byte[]?> GetResultAsByteArrayAsync()
100+
{
101+
IJSObjectReference helper = await helperTask.Value;
102+
var jSResult = await helper.InvokeAsync<IJSObjectReference>("getAttribute", JSReference, "result");
103+
return await helper.InvokeAsync<byte[]>("arrayBuffer", jSResult);
104+
}
105+
106+
/// <summary>
107+
/// Gets the error object reference which will be null if no error occured.
108+
/// </summary>
109+
/// <returns>A nullable IJSObjectReference because it was out of scope to wrap the Exception API.</returns>
110+
public async Task<IJSObjectReference?> GetErrorAsync()
111+
{
112+
IJSObjectReference helper = await helperTask.Value;
113+
return await helper.InvokeAsync<IJSObjectReference?>("getAttribute", JSReference, "error");
114+
}
115+
116+
[JsonIgnore]
117+
public Func<ProgressEvent, Task>? OnLoadStart { get; set; }
118+
119+
[JsonIgnore]
120+
public Func<ProgressEvent, Task>? OnProgress { get; set; }
121+
122+
[JsonIgnore]
123+
public Func<ProgressEvent, Task>? OnLoad { get; set; }
124+
125+
[JsonIgnore]
126+
public Func<ProgressEvent, Task>? OnAbort { get; set; }
127+
128+
[JsonIgnore]
129+
public Func<ProgressEvent, Task>? OnError { get; set; }
130+
131+
[JsonIgnore]
132+
public Func<ProgressEvent, Task>? OnLoadEnd { get; set; }
133+
134+
[JSInvokable]
135+
public async Task InvokeOnLoadStart(IJSObjectReference jsProgressEvent)
136+
{
137+
if (OnLoadStart is null) return;
138+
await OnLoadStart.Invoke(new ProgressEvent(jSRuntime, jsProgressEvent));
139+
}
140+
141+
[JSInvokable]
142+
public async Task InvokeOnProgress(IJSObjectReference jsProgressEvent)
143+
{
144+
if (OnProgress is null) return;
145+
await OnProgress.Invoke(new ProgressEvent(jSRuntime, jsProgressEvent));
146+
}
147+
148+
[JSInvokable]
149+
public async Task InvokeOnLoad(IJSObjectReference jsProgressEvent)
150+
{
151+
if (OnLoad is null) return;
152+
await OnLoad.Invoke(new ProgressEvent(jSRuntime, jsProgressEvent));
153+
}
154+
155+
[JSInvokable]
156+
public async Task InvokeOnAbort(IJSObjectReference jsProgressEvent)
157+
{
158+
if (OnAbort is null) return;
159+
await OnAbort.Invoke(new ProgressEvent(jSRuntime, jsProgressEvent));
160+
}
161+
162+
[JSInvokable]
163+
public async Task InvokeOnError(IJSObjectReference jsProgressEvent)
164+
{
165+
if (OnError is null) return;
166+
await OnError.Invoke(new ProgressEvent(jSRuntime, jsProgressEvent));
167+
}
168+
169+
[JSInvokable]
170+
public async Task InvokeOnLoadEnd(IJSObjectReference jsProgressEvent)
171+
{
172+
if (OnLoadEnd is null) return;
173+
await OnLoadEnd.Invoke(new ProgressEvent(jSRuntime, jsProgressEvent));
73174
}
175+
176+
74177
}

src/KristofferStrube.Blazor.FileAPI/wwwroot/KristofferStrube.Blazor.FileAPI.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,17 @@ export function constructFile(blobParts, fileName, options) {
1515

1616
export function constructFileReader() {
1717
return new FileReader();
18+
}
19+
20+
export function registerEventHandlers(fileReader, jSInstance) {
21+
jSInstance.addEventListener('loadstart', (e) => fileReader.objRef.invokeMethodAsync('InvokeOnLoadStart', DotNet.createJSObjectReference(e)));
22+
jSInstance.addEventListener('progress', (e) => fileReader.objRef.invokeMethodAsync('InvokeOnProgress', DotNet.createJSObjectReference(e)));
23+
jSInstance.addEventListener('load', (e) => fileReader.objRef.invokeMethodAsync('InvokeOnLoad', DotNet.createJSObjectReference(e)));
24+
jSInstance.addEventListener('abort', (e) => fileReader.objRef.invokeMethodAsync('InvokeOnAbort', DotNet.createJSObjectReference(e)));
25+
jSInstance.addEventListener('error', (e) => fileReader.objRef.invokeMethodAsync('InvokeOnError', DotNet.createJSObjectReference(e)));
26+
jSInstance.addEventListener('loadend', (e) => fileReader.objRef.invokeMethodAsync('InvokeOnLoadEnd', DotNet.createJSObjectReference(e)));
27+
}
28+
29+
export function isArrayBuffer(fileReader) {
30+
return (fileReader.result instanceof ArrayBuffer)
1831
}

0 commit comments

Comments
 (0)