minio / minio-dotnet

MinIO Client SDK for .NET
https://docs.min.io/docs/dotnet-client-quickstart-guide.html
Apache License 2.0
578 stars 230 forks source link

How to get a Stream of an object? #775

Open njlr opened 1 year ago

njlr commented 1 year ago

There does not appear to be an API for getting a Stream of an object.

// Not real code
var args =
  GetObjectStreamArgs()
    .WithBucket(bucket)
    .WithObject(key)

var stream = await minioClient.GetObjectStreamAsync(args)

I came across GetObjectArgs::WithCallbackStream but it appears that the Stream contents are somehow tied to the lifespan of the Task that you return.

Obviously the following seems like a bad idea, although it does work!

var tcs = new TaskCompletionSource<Stream>()

var args =
  GetObjectArgs()
    .WithBucket(bucket)
    .WithObject(key)
    .WithCallbackStream((stream, ct) => {
       tcs.SetResult(stream)

       Task.Delay(TimeSpan.FromHours 48)
     })

var stream = await tcs.Task

// Work with the stream...

What is the intended approach here?

sprudel79 commented 1 year ago

Hi everyone, I would like to do the same as @njlr . I have tried also with TaskCompletionSource to get access to the stream but at that time it is already disposed. In the class "OperationHelper" I can see the method "GetObjectStreamAsync" which is used when executing the FuncCallback. However, it's using a "using" statement which will dispose the stream afterwards. Is it possible to offer a possibility to access the non-disposed stream? Thank you very much!

sprudel79 commented 1 year ago

I can confirm that the following has worked for me, however with the huge drawback to clone the entire project into my own solution.

After having cloned the project and adjusted the namespaces to distinguish the code from the original assembly, I have simply removed the using statement in the following method of the class OperationHelper.cs:

private async Task GetObjectStreamAsync(GetObjectArgs args, ObjectStat objectStat,
        Func<Stream, CancellationToken, Task> cb,
        CancellationToken cancellationToken = default)
    {
        var requestMessageBuilder = await CreateRequest(args).ConfigureAwait(false);
        var response =
            await ExecuteTaskAsync(NoErrorHandlers, requestMessageBuilder, cancellationToken: cancellationToken)
                .ConfigureAwait(false);
    }

Here's the code to access it then, please note I am using the newer Function callback:

var tcs = new TaskCompletionSource<Stream>();
            var objArgs = new MyMinio.GetObjectArgs().WithBucket(request.BucketName).WithObject(request.ObjectName)
                .WithCallbackStream((stream, cancellationToken) =>
                {
                    tcs.SetResult(stream);
                    return Task.CompletedTask;
                });

            var response = await _clientFacade.GetObjectStreamAsync(objArgs, cancellationToken);
            var streamResult = await tcs.Task;

            var fileStreamResult = new FileStreamResult(streamResult, "application/octet-stream")
            {
                FileDownloadName = fileName,
                EnableRangeProcessing = false
            };

Please also note that the _clientFacade is simply my own wrapper to the Minio Code. There I manage the instances of the MinioClient and the method simply calls the GetObjectAsync method from my cloned project code:

return await _myMinioClient.GetObjectAsync(args, cancellationToken).ConfigureAwait(false);

Doing so the stream won't be disposed when I forward it as a FileStreamResult to any consumer. The lifecycle of the Stream is controlled then by .NET framework itself. For all other Minio related methods (upload files, accessing buckets, etc.) I use the original Minio code from the external assembly.

njlr commented 1 year ago

I would love a function that explicitly returns a stream that the caller is expected to dispose, rather than using a TaskCompletionSource. This is something that the AWS S3 client offers.

Would the maintainers accept a PR for this?

martijn00 commented 1 year ago

@njlr can you open a PR for this?