Blazor newbie here.
I'm trying to read files locally (browser-side) and do something with them in C#.
I'm using the <input type="file" onchange="@OnChange" />
tag. In my OnChange method I can only receive the filename, but cannot read it:
void OnChange(UIEventArgs eventArgs) {
var changeEventArgs = (UIChangeEventArgs)eventArgs;
var filepath = changeEventArgs.Value.ToString(); // returns C:\fakepath\test.txt
data = System.IO.File.ReadAllText(filepath); // System.IO.FileNotFoundException: Could not find file "/C:\fakepath\test.txt"
}
How can I do this? An alternative would be to implement a javascript handler, then use HTML5 File API to read the file, then pass the raw bytes over to a C# method via custom C# binding. Would that be the way to go, or should this be easier?
I managed to get this working, but it's a major hack: Read the file in javascript, convert to base64 and pass it over to C#.
<input type="file" onchange="handleFileSelect(this.files);" />
function handleFileSelect(files) {
var file = files[0];
var reader = new FileReader();
reader.onloadend = function (evt) {
if (evt.target.readyState == FileReader.DONE) { // DONE == 2
let messageAsDotNetString = Blazor.platform.toDotNetString(arrayBufferToBase64(evt.target.result));
const decompileMethod = Blazor.platform.findMethod(
'Client', // Assembly name
'Client', // Namespace
'MyClass', // Class name
'MyMethod' // Method name
);
Blazor.platform.callMethod(
decompileMethod, // Method handle
null, // Target
[messageAsDotNetString] // Arguments
);
}
};
var blob = file.slice(0, file.size);
reader.readAsArrayBuffer(blob);
}
public static class MyClass {
public static void MyMethod(string base64) {
byte[] bytes = Convert.FromBase64String(base64);
using (var sr = new MemoryStream(bytes)) {
// do stuff here
}
}
}
Obviously this isn't optimal: If I was able to pass not only strings, but also blobs, to C#, that would be an improvement. However, I'd much rather directly use C# System.IO.File
apis to read files. Is there any way to do this yet?
In your Blazor.platform.callMethod
call, you're choosing to send the data as a string. It doesn't have to be a string. You can work with arbitrary data types. For example, you could first call a method that returns an empty byte array of a given length, and then your JS-side code could copy the binary data into it, and then you could call your MyMethod
with the arg type being a byte[]
.
I recognize that this is nonobvious and doing it is quite advanced. We don't have any docs about this level of JS interop yet (the closest being some combination of the Emscripten and Mono docs). So for now unless this is really damaging the responsiveness of your app, I think your existing base64 solution is probably good to stick with.
However, I'd much rather directly use C# System.IO.File apis to read files
This is something we've seriously considered. The tricky bit is that unless absolutely 100% of the System.IO.File APIs can map to exactly equivalent JS APIs, then it's just a recipe for obscure bugs. For example, if some third-party NuGet package tries to do things with System.IO.File but the runtime doesn't exactly simulate what happens on the desktop, then it would fail and developers would have no obvious way to make sense of why. It's much safer to say that web browsers don't have a traditional filesystem, but rather there are some different HTML5 "file" APIs you can use. Then developers will have to account for those differences in their designs.
@discostu105 : Since 0.4.0, its possible to skip the base64 step as Blazor.platform.toUint8Array
was introduced. I made an example here.
@Tewr Great. Thanks for getting back to me. I'll give it a try :).
@discostu105 Please take a look at the following video where I demo a Blazor interop for handling files. https://youtu.be/-IuZQeZ10Uw
Here's the package I used: https://www.nuget.org/packages/Tewr.Blazor.FileReader/
Most helpful comment
In your
Blazor.platform.callMethod
call, you're choosing to send the data as a string. It doesn't have to be a string. You can work with arbitrary data types. For example, you could first call a method that returns an empty byte array of a given length, and then your JS-side code could copy the binary data into it, and then you could call yourMyMethod
with the arg type being abyte[]
.I recognize that this is nonobvious and doing it is quite advanced. We don't have any docs about this level of JS interop yet (the closest being some combination of the Emscripten and Mono docs). So for now unless this is really damaging the responsiveness of your app, I think your existing base64 solution is probably good to stick with.
This is something we've seriously considered. The tricky bit is that unless absolutely 100% of the System.IO.File APIs can map to exactly equivalent JS APIs, then it's just a recipe for obscure bugs. For example, if some third-party NuGet package tries to do things with System.IO.File but the runtime doesn't exactly simulate what happens on the desktop, then it would fail and developers would have no obvious way to make sense of why. It's much safer to say that web browsers don't have a traditional filesystem, but rather there are some different HTML5 "file" APIs you can use. Then developers will have to account for those differences in their designs.