1

I'm trying to implement HMAC security for an API. Everything works fine until I try to post data values alongside a file in a MultipartFormDataContent.

The HttpClient DelegatingHandler fails silently when the async line of code to read bytes is hit.

Here's the code building the request:

private FileOutputViewModel GetApiOutput(Uri apiResource, string filename, byte[] file, IDictionary<string, string> extraParameters)
{
    FileOutputViewModel result = new FileOutputViewModel();

    if (file != null)
    {
        using (var content = new MultipartFormDataContent())
        {
            if (extraParameters != null)
            {
                foreach (var param in extraParameters)
                {
                    content.Add(new StringContent(param.Value), param.Key); // <- If I don't have this, everything works fine
                }
            }

            var fileContent = new ByteArrayContent(file);
            fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
            {
                FileName = filename
            };
            content.Add(fileContent);

            var response = HttpClient.PostAsync(apiResource.ToString(), content).Result;

            result.Output = JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().Result);

            result.Filename = Path.GetFileName(filename);
        }
    }

    return result;
}

If I don't use the DelegatingHandler everything works fine, but the HMAC security isn't implemented for the request so is rejected on the API's end.

If I don't add the data values using StringContent items alongside the file then there's no problem reading the bytes. But I'm left with an incomplete request as I need to pass more info along with the file.

The line of code that fails in the DelegatingHandler is indicated below:

private static async Task<byte[]> ComputeHash(HttpContent httpContent)
{
    using (var md5 = MD5.Create())
    {
        byte[] hash = null;
        if (httpContent != null)
        {
            var ms = new MemoryStream();
            await httpContent.CopyToAsync(ms); // <- Fails here
            ms.Seek(0, SeekOrigin.Begin);

            var content = ms.ToArray();
            if (content.Length != 0)
            {
                hash = md5.ComputeHash(content);
            }
        }
        return hash;
    }
}

Originally the failing line was:

var content = await httpContent.ReadAsByteArrayAsync();

but this failed with even just the file on its own (previous Stackoverflow question). Using a MemoryStream was one step forward but hasn't got me all the way.

Any ideas how I might be able to work around this issue?

Community
  • 1
  • 1
Gavin
  • 5,629
  • 7
  • 44
  • 86

1 Answers1

1

Seems this was caused by having an async signature for the System.Net.Http.DelegatingHandler.SendAsync method. Originally the delegate override was:

protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)

when I adapted the code so I could change it to:

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)

everything started to work as expected.

Seems there must be threading issues with this part of the .NET framework. There's some other workarounds described here if you need to try other work-arounds: https://social.msdn.microsoft.com/Forums/vstudio/en-US/55f5571d-fe94-4b68-b1d4-bfb91fd721dd/reading-httpcontent-bytes-fails-inside-delegatinghandler-when-multiple-content-types-present?forum=wcf

Gavin
  • 5,629
  • 7
  • 44
  • 86