Zaid-Ajaj / Fable.Remoting

Type-safe communication layer (RPC-style) for F# featuring Fable and .NET Apps
https://zaid-ajaj.github.io/Fable.Remoting/
MIT License
272 stars 55 forks source link

Upload file along with metadata (file name, client's id etc.) #339

Closed picolino closed 9 months ago

picolino commented 1 year ago

I've implemented file upload / download logic using this tutorial:
https://zaid-ajaj.github.io/Fable.Remoting/#/advanced/file-upload-and-download

But one thing stays a little unclear for me. Can I make it possible to pass current context from client to server along with file?

I've implemented api for file uploading this way:

// Client side

// update function
| UploadFile (name:string, fileBinaryContent:byte[]) ->
    let file = { Id = Guid.NewGuid(); Name = name; Content = fileBinaryContent }
    let upload = api.uploadFile model.CurrentContext.Id
    model, Cmd.OfAsync.perform upload file FileUploaded
// Server side

// api definition
let api : IFilesApi = {
    uploadFile = fun id file -> async { /*file save logic*/ }
}

But every time I trying to send file this way - Content field of passed file record is empty. I assume this is correct logic, because record trying to be converted to JSON and pass to server with Content-Type: application/json, but file is a binary, so serializer/browser/etc. blocks it or something.

One thing I want to achieve - is to pass some context along with file content, just to identify file entity on the server side to place it where it needs to be placed with correct id, name.

Few thoughts:

I could save file with two steps (two requests). First to send and save file metadata (id, name etc.) and then save file with that entity as a separated request with byte[] content only. This way seems like mostly proper way to do that, but how to pass generated id from first request to the second?

I could just serialize all data I need to pass to the server into byte[] (for example few first bytes will be an id, next bytes name etc.) and then deserialize it on the server side, but how to serialize to binary proper way on the client side for Fable.Remoting? BinaryFormatter has been deprecated by Microsoft: https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide

I could pass some metadata (id, name etc) into URL directly, but I don't see any ways to override api endpoint route by-method.

picolino commented 1 year ago

I came up with one implementation that could work in this case.

Firstly, we upload some file to the server on specific url (for example if you have images storage it could be IImageStorageApi.uploadImage function) using only one content: byte[] argument.

This method returns file ID, showing how we can access this file in the storage we saved it before.

Then using this file ID we could link this file to another entity (using another http request to other api).

This is good workaround, I think. I'll try it.

kerams commented 1 year ago

I do this outside of Remoting with a regular Giraffe PUT (or GET for downloads) handler. The request metadata is serialized with Thoth.Json, base64 URL-encoded and then passed in the URL. It would be tricky to integrate this into Remoting for several reasons.

Zaid-Ajaj commented 9 months ago

Hi @picolino I think the workaround you came with above should be good enough. Otherwise, like @kerams said, you can always implement a specialized route that does what you need it to do 😄