1

I am trying to upload an image from one Asp net core backend to an other via refit.

await _repository.UploadImageAsync(userId,
    new StreamPart(file.OpenReadStream(), file.FileName, file.ContentType), extension);

The Api

[Put("/image/{userId}")]
Task<Guid> UploadImageAsync(Guid userId, StreamPart stream, string extension);

The receiving Controller

[HttpPut("image/{userId}")]
public async Task<IActionResult> UploadImageAsync([FromRoute] Guid userId, StreamPart stream, string extension)
{
    return await RunAsync(async () =>
    {
        var id = await _userImageManager.UploadImageAsync(userId, stream.Value, extension);

        return Ok(id);
    });
}

As soon as I run this i get the following exception:

Newtonsoft.Json.JsonSerializationException: Error getting value from 'ReadTimeout' on 'Microsoft.AspNetCore.Http.ReferenceReadStream'.
 ---> System.InvalidOperationException: Timeouts are not supported on this stream.
   at System.IO.Stream.get_ReadTimeout()
   at lambda_method(Closure , Object )
   at Newtonsoft.Json.Serialization.ExpressionValueProvider.GetValue(Object target)
   --- End of inner exception stack trace ---
   at Newtonsoft.Json.Serialization.ExpressionValueProvider.GetValue(Object target)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CalculatePropertyValues(JsonWriter writer, Object value, JsonContainerContract contract, JsonProperty member, JsonProperty property, JsonContract& memberContract, Object& memberValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType)
   at Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType)
   at Newtonsoft.Json.JsonSerializer.Serialize(JsonWriter jsonWriter, Object value, Type objectType)
   at Newtonsoft.Json.JsonConvert.SerializeObjectInternal(Object value, Type type, JsonSerializer jsonSerializer)
   at Newtonsoft.Json.JsonConvert.SerializeObject(Object value, Type type, JsonSerializerSettings settings)
   at Newtonsoft.Json.JsonConvert.SerializeObject(Object value, JsonSerializerSettings settings)
   at Refit.JsonContentSerializer.SerializeAsync[T](T item) in d:\a\1\s\Refit\JsonContentSerializer.cs:line 34
   at Refit.RequestBuilderImplementation.<>c__DisplayClass17_0.<<BuildRequestFactoryForMethod>b__0>d.MoveNext() in d:\a\1\s\Refit\RequestBuilderImplementation.cs:line 546
--- End of stack trace from previous location where exception was thrown ---
   at Refit.RequestBuilderImplementation.<>c__DisplayClass14_0`2.<<BuildCancellableTaskFuncForMethod>b__0>d.MoveNext() in d:\a\1\s\Refit\RequestBuilderImplementation.cs:line 243

I tried going the way described here but had no success. Is there anything I did wrong?

EDIT

Based on @TomO answer I edited my code, but I still get null for stream:

Api 1 (the sending part to Api 2):

public async Task<Guid> UploadImageAsync(Guid userId, IFormFile file)
{
    ...
    var stream = file.OpenReadStream();

    var streamPart = new StreamPart(stream, file.FileName, file.ContentType);
    var response = await _repository.UploadImageAsync(userId,
                streamPart, extension);
    ...
}

Api 2 (The receiver):

[HttpPost("image/{userId}")]
public async Task<IActionResult> UploadImageAsync([FromRoute] Guid userId, string description, IFormFile stream, string extension)
{
    ...
}

The Refit Api:

[Multipart]
[Post("/image/{userId}")]
Task<Guid> UploadImageAsync(Guid userId, [AliasAs("Description")] string description, [AliasAs("File")] StreamPart stream, string extension);
DirtyNative
  • 2,553
  • 2
  • 33
  • 58
  • Any luck with this? I'm struggling to handle a very similar situation. Can't seem to get a multipart form data upload with a file part. – TomO Apr 10 '20 at 02:31
  • Actually no, I paused the dev of this api until now, but haven't had luck – DirtyNative Apr 22 '20 at 20:12
  • I think you might want to change your refit argument list to AliasAs("stream") instead of "File" given your API is looking for a variable named "stream" and not "File" like my case - hopefully just a cut&paste error. Would certainly explain why your stream is coming in as null. – TomO Apr 27 '20 at 22:42
  • oops, yes, this was a copy paste & brain AFK error, sorry.. Renaming the variable fixed the issue! Thanks a lot! – DirtyNative Apr 28 '20 at 08:25

1 Answers1

2

I think the documentation (and the solution linked in the question) give good guidance. But here's what I got to work, anyhow:

Receiving API endpoint:

[HttpPost]
[Route("{*filePath:regex(^.*\\.[[^\\\\/]]+[[A-Za-z0-9]]$)}")]
public async Task<IActionResult> AddFile([FromRoute] AddFileRequest request,
                                         [FromForm] AddFileRequestDetails body,
                                         CancellationToken cancellationToken)

And the corresponding refit client call:

[Multipart]
[Post("/{**filePath}")]
Task AddFile(string filePath, [AliasAs("Description")] string description, [AliasAs("File")] StreamPart filepart);

The important thing here is that ALL of the member properties of the Form body(not including file) have to be decorated with the "AliasAs" attribute.

Calling on the sending side:

            System.IO.Stream FileStream = request.File.OpenReadStream();
            StreamPart sp = new StreamPart(FileStream, request.FileName);

            try
            {
                await _filesClient.AddFile(request.FilePath, request.Description, sp);
            }
            catch (ApiException ae)
            {
                ...
                throw new Exception("Refit FileClient API Exception", ae);
            }

Hope this helps the next person...

TomO
  • 458
  • 2
  • 9
  • 20
  • Still had no luck with your answer, what does the receiving Api controller look like? Does it receive a StreamPart or IFormFile? – DirtyNative Apr 27 '20 at 11:01
  • @DanielDirtyNativeMartin IFormFile. StreamPart is merely for the refit piece, but the controller should (at least in my case) stand alone as a standard Multipart Form upload. – TomO Apr 27 '20 at 15:09
  • The receiving API is the first codeblock, where AddFileRequest (the route object) just contains the string FilePath. The AddFileRequestDetails (form object) contains a string Description and IFormFile File. – TomO Apr 27 '20 at 15:16
  • Then I still don't get it to work.. I updated my question with the new code – DirtyNative Apr 27 '20 at 18:44