File: Internal\DefaultHttpResponse.cs
Web Access
Project: src\src\Http\Http\src\Microsoft.AspNetCore.Http.csproj (Microsoft.AspNetCore.Http)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.IO.Pipelines;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Shared;
using Microsoft.AspNetCore.WebUtilities;
 
namespace Microsoft.AspNetCore.Http;
 
// DebuggerDisplayAttribute is inherited but we're replacing it on this implementation to include reason phrase.
[DebuggerDisplay("{DebuggerToString(),nq}")]
internal sealed class DefaultHttpResponse : HttpResponse
{
    // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
    private static readonly Func<IFeatureCollection, IHttpResponseFeature?> _nullResponseFeature = f => null;
    private static readonly Func<IFeatureCollection, IHttpResponseBodyFeature?> _nullResponseBodyFeature = f => null;
    private static readonly Func<IFeatureCollection, IResponseCookiesFeature?> _newResponseCookiesFeature = f => new ResponseCookiesFeature(f);
 
    private readonly DefaultHttpContext _context;
    private FeatureReferences<FeatureInterfaces> _features;
 
    public DefaultHttpResponse(DefaultHttpContext context)
    {
        _context = context;
        _features.Initalize(context.Features);
    }
 
    public void Initialize()
    {
        _features.Initalize(_context.Features);
    }
 
    public void Initialize(int revision)
    {
        _features.Initalize(_context.Features, revision);
    }
 
    public void Uninitialize()
    {
        _features = default;
    }
 
    private IHttpResponseFeature HttpResponseFeature =>
        _features.Fetch(ref _features.Cache.Response, _nullResponseFeature)!;
 
    private IHttpResponseBodyFeature HttpResponseBodyFeature =>
        _features.Fetch(ref _features.Cache.ResponseBody, _nullResponseBodyFeature)!;
 
    private IResponseCookiesFeature ResponseCookiesFeature =>
        _features.Fetch(ref _features.Cache.Cookies, _newResponseCookiesFeature)!;
 
    public override HttpContext HttpContext { get { return _context; } }
 
    public override int StatusCode
    {
        get { return HttpResponseFeature.StatusCode; }
        set { HttpResponseFeature.StatusCode = value; }
    }
 
    public override IHeaderDictionary Headers
    {
        get { return HttpResponseFeature.Headers; }
    }
 
    public override Stream Body
    {
        get { return HttpResponseBodyFeature.Stream; }
        set
        {
            var otherFeature = _features.Collection.GetRequiredFeature<IHttpResponseBodyFeature>();
 
            if (otherFeature is StreamResponseBodyFeature streamFeature
                && streamFeature.PriorFeature != null
                && object.ReferenceEquals(value, streamFeature.PriorFeature.Stream))
            {
                // They're reverting the stream back to the prior one. Revert the whole feature.
                _features.Collection.Set(streamFeature.PriorFeature);
                return;
            }
 
            _features.Collection.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(value, otherFeature));
        }
    }
 
    public override long? ContentLength
    {
        get { return Headers.ContentLength; }
        set { Headers.ContentLength = value; }
    }
 
    public override string? ContentType
    {
        get
        {
            return Headers.ContentType;
        }
        set
        {
            if (string.IsNullOrEmpty(value))
            {
                HttpResponseFeature.Headers.ContentType = default;
            }
            else
            {
                HttpResponseFeature.Headers.ContentType = value;
            }
        }
    }
 
    public override IResponseCookies Cookies
    {
        get { return ResponseCookiesFeature.Cookies; }
    }
 
    public override bool HasStarted
    {
        get { return HttpResponseFeature.HasStarted; }
    }
 
    public override PipeWriter BodyWriter
    {
        get { return HttpResponseBodyFeature.Writer; }
    }
 
    public override void OnStarting(Func<object, Task> callback, object state)
    {
        ArgumentNullException.ThrowIfNull(callback);
 
        HttpResponseFeature.OnStarting(callback, state);
    }
 
    public override void OnCompleted(Func<object, Task> callback, object state)
    {
        ArgumentNullException.ThrowIfNull(callback);
 
        HttpResponseFeature.OnCompleted(callback, state);
    }
 
    public override void Redirect(string location, bool permanent)
    {
        if (permanent)
        {
            HttpResponseFeature.StatusCode = 301;
        }
        else
        {
            HttpResponseFeature.StatusCode = 302;
        }
 
        Headers.Location = location;
    }
 
    public override Task StartAsync(CancellationToken cancellationToken = default)
    {
        if (HasStarted)
        {
            return Task.CompletedTask;
        }
 
        return HttpResponseBodyFeature.StartAsync(cancellationToken);
    }
 
    public override Task CompleteAsync() => HttpResponseBodyFeature.CompleteAsync();
 
    internal string DebuggerToString()
    {
        // DebuggerToString is also on this type because this project has access to ReasonPhrases.
        return HttpContextDebugFormatter.ResponseToString(this, ReasonPhrases.GetReasonPhrase(StatusCode));
    }
 
    struct FeatureInterfaces
    {
        public IHttpResponseFeature? Response;
        public IHttpResponseBodyFeature? ResponseBody;
        public IResponseCookiesFeature? Cookies;
    }
}