0

In ASP.NET WebAPi 2 code, I have a delegate handler

public class RequestHandler1 : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var formData2 = await ReadContent(request);
        return await base.SendAsync(request, cancellationToken);
    }

    private static async Task<string> ReadContent(HttpRequestMessage request)
    {
        using (var ms = new MemoryStream())
        {
            await request.Content.CopyToAsync(ms);
            ms.Seek(0, SeekOrigin.Begin);

            using (var sr = new StreamReader(ms, Encoding.UTF8, true, 100, true))
            {
                return sr.ReadToEnd();
            }
        }
    }

    private static async Task<string> ReadContent3(HttpRequestMessage request)
    {
        var text = await request.Content.ReadAsStringAsync();
        return text;
    }
}

The issue is related to HttpContent.ReadAsStringAsync causes request to hang (or other strange behaviours) but it was never properly answered in that thread.

By the time I call return await base.SendAsync(request, cancellationToken); it just hangs. it does not matter if I call ReadContent or ReadContent3

Any more suggestions?

hardywang
  • 4,864
  • 11
  • 65
  • 101
  • 3
    global search in your code and see if there is any .Result or .Wait() call. If there is then chances are you are having a deadlock issue – Steve Dec 13 '19 at 20:00
  • It's likely a similar problem to this answer: https://stackoverflow.com/a/15022170/3626160. You call to SendAsync, which blocks the request context while awaiting ReadContent. But inside ReadContent, CopyToAsync(), ReadToEnd() or ReadAsStringAsync() is waiting for the context to be free to get the content. Therefore since your content readers are waiting on the request context to be freed by the same method that's waiting on them to return a value, you have a deadlock. – TheAtomicOption Dec 13 '19 at 20:14
  • @Steve you are right, there are some other .Result elsewhere in other classes. Once I fixed them, it works – hardywang Dec 13 '19 at 21:21

1 Answers1

0

try this code

public class CustomLogHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var logMetadata = await BuildRequestMetadata(request);
        var response = await base.SendAsync(request, cancellationToken);
        logMetadata = await BuildResponseMetadata(logMetadata, response);
        await SendToLog(logMetadata);
        return response;
    }
    private async Task<LogMetadata> BuildRequestMetadata(HttpRequestMessage request)
    {
        LogMetadata log = new LogMetadata
        {
            RequestMethod = request.Method.Method,
            RequestTimestamp = DateTime.Now,
            RequestUri = request.RequestUri.ToString(),
            RequestContent = await request.Content.ReadAsStringAsync(),
        };
        return log;
    }
    private async Task<LogMetadata> BuildResponseMetadata(LogMetadata logMetadata, HttpResponseMessage response)
    {
        logMetadata.ResponseStatusCode = response.StatusCode;
        logMetadata.ResponseTimestamp = DateTime.Now;
        logMetadata.ResponseContentType = response.Content == null ? string.Empty : response.Content.Headers.ContentType.MediaType;
        logMetadata.Response = await response.Content.ReadAsStringAsync();
        return logMetadata;
    }
    private async Task<bool> SendToLog(LogMetadata logMetadata)
    {
        try
        {
            //write your code
        }
        catch
        {
            return false;
        }
        return true;
    }

}
Sunny Jangid
  • 578
  • 4
  • 19
  • How is this supposed to help? First of all, `ReadAsStringAsync` doesn't hang. Second, this handler isn't even used when reading from the Content. It's *only* used to log the request and response. If `ReadAsStringAsync` blocked (which it doesn't) that `await response.Content.ReadAsStringAsync();` in `BuildResponseMetadata` would block as well – Panagiotis Kanavos Feb 07 '20 at 10:29
  • What this code does though, is introduce bugs. Nothing says the response is a string, and yet `BuildResponseMetadata` tries to read *all* of it as a string. If the response is big, this will block until everything is read. It will cache the entire response string in memory, wasting RAM and eliminating one of the most important benefits of HttpClient. And worst of all, it will consume the *entire* response stream before the caller had a change to see it. This will break any caller that expected to read from the stream – Panagiotis Kanavos Feb 07 '20 at 10:32