Pathoschild / FluentHttpClient

A modern async HTTP client for REST APIs. Its fluent interface lets you send an HTTP request and parse the response in one go.
MIT License
342 stars 52 forks source link

When using WithBody(p => p.FileUpload(filePath)) the file is blocked for deletion #129

Open GuilhermeGHN opened 1 month ago

GuilhermeGHN commented 1 month ago

After making a simple request by sending a file, it is blocked for manipulation by another process, for example deleting the file itself.

I believe these methods would need some treatment to avoid this problem:

Pathoschild commented 1 month ago

Hi! The file stream is owned by the request message, so we'll need to dispose the request at some point. That would be a breaking change though, since it'll prevent any further access to the request (e.g. when handling the response). I'm not sure off-hand how best to handle that; we'll probably need some API changes.

As a temporary workaround, you can dispose it yourself since you know it won't be accessed later. For example:

IResponse response = null;
try
{
    response = await client
        .PostAsync("api")
        .WithBody(p => p.FileUpload(...))
        .AsResponse();

    return response.As<TModel>();
}
finally
{
    response?.Message.RequestMessage.Dispose();
    response?.Message.Dispose();
}
GuilhermeGHN commented 1 month ago

Hello!

I understood the problem, I am already using the following approach:

IResponse response;

using (var fs = File.OpenRead(file))
{
    var files = new List<KeyValuePair<string, Stream>>
    {
        new KeyValuePair<string, Stream>("filename", fs)
    };

    response = await client
        .PostAsync("api")
        .WithBody(p => p.FileUpload(files))
        .AsResponse();

    fs.Flush();
}

Regarding the solution without a break, I took a look at FileUpload and found that the following adjustment might be possible:

private Stream GetFileStream(FileInfo file)
{
    var memoryStream = new MemoryStream();

    using (var fileStream = file.OpenRead())
    {
        fileStream.CopyTo(memoryStream);
    }

    return memoryStream;
}

public HttpContent FileUpload(IEnumerable<FileInfo> files)
{
    return this.FileUpload(
        files.Select(file => file.Exists
            ? new KeyValuePair<string, Stream>(file.Name, this.GetFileStream(file))
            : throw new FileNotFoundException($"There's no file matching path '{file.FullName}'.")
        )
    );
}

This way, the methods I mentioned initially would be detached from the files sent as parameters, while the method that receives IEnumerable<KeyValuePair<string, Stream>> files would be left as is, being the responsibility of whoever sent the Streams to manage.

I hope I helped in some way.