How to retrieve the current response body length?

Here is the code for tracking the content length during stream writing. The ContentLengthTracker class is designed for sharing the content length value between other classes. The code is published at https://github.com/ycrumeyrolle/Throttling/blob/master/src/Throttling/Internal/ContentLengthTrackingStream.cs

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

public class ContentLengthTracker
{
    public long ContentLength { get; set; }
}

public class ContentLengthTrackingStream : Stream
{
    private readonly Stream _inner;
    private readonly ContentLengthTracker _tracker;

    public ContentLengthTrackingStream(Stream inner, ContentLengthTracker tracker)
    {
        if (inner == null)
        {
            throw new ArgumentNullException(nameof(inner));
        }

        if (tracker == null)
        {
            throw new ArgumentNullException(nameof(tracker));
        }

        _inner = inner;
        _tracker = tracker;
    }

    public override bool CanRead
        => _inner.CanRead;

    public override bool CanSeek
        => _inner.CanSeek;

    public override bool CanWrite
        => _inner.CanWrite;

    public override long Length
        => _inner.Length;

    public override long Position
    {
        get => _inner.Position;
        set => _inner.Position = value;
    }

    public override bool CanTimeout
        => _inner.CanTimeout;

    public override int ReadTimeout
    {
        get => _inner.ReadTimeout;
        set => _inner.ReadTimeout = value;
    }

    public override int WriteTimeout
    {
        get => _inner.WriteTimeout;
        set => _inner.WriteTimeout = value;
    }

    public ContentLengthTracker Tracker
        => _tracker;

    public override void Flush()
        => _inner.Flush();

    public override Task FlushAsync(CancellationToken cancellationToken)
        => _inner.FlushAsync(cancellationToken);

    public override int Read(byte[] buffer, int offset, int count)
        => _inner.Read(buffer, offset, count);

    public async override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        => await _inner.ReadAsync(buffer, offset, count, cancellationToken);

    public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
        => _inner.BeginRead(buffer, offset, count, callback, state);

    public override int EndRead(IAsyncResult asyncResult)
    {
        Task<int> task = asyncResult as Task<int>;
        if (task != null)
        {
            return task.GetAwaiter().GetResult();
        }

        return _inner.EndRead(asyncResult);
    }

    public override long Seek(long offset, SeekOrigin origin)
        => _inner.Seek(offset, origin);

    public override void SetLength(long value)
       => _inner.SetLength(value);

    public override void Write(byte[] buffer, int offset, int count)
    {
        _tracker.ContentLength += count - offset;
        _inner.Write(buffer, offset, count);
    }

    public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
    {
        // _tracker.ContentLength += count - offset; // This is incorrect
        _tracker.ContentLength += count;
        return _inner.BeginWrite(buffer, offset, count, callback, state);
    }

    public override void EndWrite(IAsyncResult asyncResult)
        => _inner.EndWrite(asyncResult);

    public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
    {
        // _tracker.ContentLength += count - offset; // This is incorrect
        _tracker.ContentLength += count;
        return _inner.WriteAsync(buffer, offset, count, cancellationToken);
    }

    public override void WriteByte(byte value)
    {
        _tracker.ContentLength++;
        _inner.WriteByte(value);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _inner.Dispose();
        }
    }
}

I assume you are trying to get the ContentLength from within a middleware?

Here is an example Middleware. It should be added to to the pipeline (startup.cs) before any response generating middleware such as useMVC or useStaticFiles.

public class ContentLengthMiddleware
{
    RequestDelegate _next;

    public ContentLengthMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        using (var buffer = new MemoryStream())
        {
            var request = context.Request;
            var response = context.Response;

            var bodyStream = response.Body;
            response.Body = buffer;

            await _next(context);
            Debug.WriteLine($"{request.Path} ({response.ContentType}) Content-Length: {response.ContentLength ?? buffer.Length}");
            buffer.Position = 0;
            await buffer.CopyToAsync(bodyStream);
        }
    }
}

For reasons that are beyond my understanding when returning static files (png, js, etc) the response body will be empty however ContentLength is set that is why I used the response.ContentLength ?? buffer.Length.

(Note to mod: sorry about the duplicate answer to two questions. The other answer was incorrectly posted, too many tabs open. I deleted it and reposted the answer here).

Tags:

Asp.Net Core