none
Reading HttpContent bytes fails inside DelegatingHandler when multiple content types present RRS feed

  • Question

  • 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?

    I'm using .NET 4.5.2

    Monday, August 22, 2016 12:44 AM

Answers

  • 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.

    Monday, September 26, 2016 3:17 AM

All replies

  • Hi Gavin,

    Thank you for posting in the MSDN Forum.

    I'm trying to involve some senior engineers into this issue and it will take some time. Your patience will be greatly appreciated. 

    Sorry for any inconvenience and have a nice day!

    Best Regards,

    Edward


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.


    Tuesday, August 23, 2016 7:28 AM
  • Hi Edward,

    I'm grateful for Microsoft to take the time to look into the issue - much appreciated.

    Cheers,

    Gavin

    Tuesday, August 23, 2016 8:30 AM
  • Hi Gavin,

    What kind of issue do you face when using Memory Stream?

    Cheers,

    Zhiqing


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.

    Friday, September 2, 2016 8:26 AM
  • Hi Zhiqing,

    When the indicated line is hit the CopyToAsync method doesn't ever return a result, and no error occurs.

    Cheers,

    Gavin

    Monday, September 5, 2016 3:53 AM
  • Hi Gavin,

    According to the problem description, I think we need to give it a try to get the crash dump. You can follow the below steps. For dump reviewing, it is out of forum support. I suggest to submit a email-based support and phone-based support to Microsoft support team.

     1. Install DebugDiag
        https://www.microsoft.com/en-us/download/details.aspx?id=49924

     2. Click Add Rule and choose Crash
     
     3. Choose A specific process

     4. Choose the relevant process and click Next

     5. Choose Full Userdump in Action type for unconfigured first chance exceptions
      
     6. Click Yes in the pop-up window
     
     7. Click Next
     
     8. Choose Activate the rule now
     
     9. Wait until the issue reoccurs and check whether the dump is generated.

    Here is an article for your reference

    https://blogs.msdn.microsoft.com/kaushal/2012/05/09/using-debugdiag-to-capture-a-dump-on-first-chance-exception/

    BR,

    Zhiqing


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.

    Thursday, September 15, 2016 1:35 PM
  • 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.

    Monday, September 26, 2016 3:17 AM
  • Hi Gavin,

    Could you expand on the changes you made the SendAsync method? I'm guessing we're using the same HMAC examples as I'm struggling with posted content hanging in the ComputeHash task too.

    Thanks,

    Karl

    Monday, September 26, 2016 5:34 AM
  • Hi Gavin,

    Could you expand on the changes you made the SendAsync method? I'm guessing we're using the same HMAC examples as I'm struggling with posted content hanging in the ComputeHash task too.

    Thanks,

    Karl

    Hi Karl, check out the changes I made here - https://github.com/gavinharriss/WebAPI.HMAC/blob/master/WebAPI.HMAC/Http/HMACDelegatingHandler.cs

    Hope that helps.


    Monday, September 26, 2016 5:46 AM
  • Hi Gavin,

    Much appreciated, but I'm still having issues with that ComputeHash task. If I use the CopyToAsync method, as you are doing, the posted content doesn't reach the API Controller. My understanding is that with certain methods you can only read the content once and then it's gone.

    The ReadAsByteArrayAsync and ReadAsStringAsync methods don't have that problem, but they hang. However, I'm now thinking they only hang on the second call. The first time, the ComputeHash task will complete, but subsequent attempts fail. I'm also noticing that that if you put a breakpoint before reading the content, it works.

    I'm assuming it's got something to do with threads and tasks, but I'm at a loss.

    Using CopyToAsync, does your posted content actually reach your API?

    Thanks,

    Karl


    Monday, September 26, 2016 10:27 PM
  • Hi Karl,

    Everything works for me now, but to be fair I'm no longer posting a file. At some point along the way through various attempted work-arounds I started reading my xml file in and posting it as string content as part of a request class with everything in I need using:

    var response = HttpClient.PostAsJsonAsync(apiResource, request).Result;

    This is was preferable to the consumer of the API anyway so it remained in place.

    Hope that helps, might give you ideas for a work-around in your own code.

    Best of luck,

    Gavin

    Monday, September 26, 2016 10:36 PM
  • I finally got this working by:

    a) adding ConfigureAwait(false) to all await calls in HmacHelper.cs. That resolved the hangs, which I assume were deadlocks of some kind.

    and

    b) Using ReadAsStringAsync in the ComputeHash method, instead of copying to a memory stream. That ensured that the posted HTTP content reached the controller intact.

    Thanks for your help, Gavin.

    Tuesday, September 27, 2016 5:53 PM