| File: HttpResponse.cs | Web Access |
| Project: src\aspnetcore\src\Http\Http.Abstractions\src\Microsoft.AspNetCore.Http.Abstractions.csproj (Microsoft.AspNetCore.Http.Abstractions) |
// 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.Diagnostics.CodeAnalysis; using System.IO.Pipelines; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Shared; namespace Microsoft.AspNetCore.Http; /// <summary> /// Represents the outgoing side of an individual HTTP request. /// </summary> [DebuggerDisplay("{DebuggerToString(),nq}")] [DebuggerTypeProxy(typeof(HttpResponseDebugView))] public abstract class HttpResponse { private static readonly Func<object, Task> _callbackDelegate = callback => ((Func<Task>)callback)(); private static readonly Func<object, Task> _disposeDelegate = state => { // Prefer async dispose over dispose if (state is IAsyncDisposable asyncDisposable) { return asyncDisposable.DisposeAsync().AsTask(); } else if (state is IDisposable disposable) { disposable.Dispose(); } return Task.CompletedTask; }; /// <summary> /// Gets the <see cref="HttpContext"/> for this response. /// </summary> public abstract HttpContext HttpContext { get; } /// <summary> /// Gets or sets the HTTP response code. /// </summary> public abstract int StatusCode { get; set; } /// <summary> /// Gets the response headers. /// </summary> public abstract IHeaderDictionary Headers { get; } /// <summary> /// Gets or sets the response body <see cref="Stream"/>. /// </summary> public abstract Stream Body { get; set; } /// <summary> /// Gets the response body <see cref="PipeWriter"/> /// </summary> /// <value>The response body <see cref="PipeWriter"/>.</value> public virtual PipeWriter BodyWriter { get => throw new NotImplementedException(); } /// <summary> /// Gets or sets the value for the <c>Content-Length</c> response header. /// </summary> public abstract long? ContentLength { get; set; } /// <summary> /// Gets or sets the value for the <c>Content-Type</c> response header. /// </summary> public abstract string? ContentType { get; set; } /// <summary> /// Gets an object that can be used to manage cookies for this response. /// </summary> public abstract IResponseCookies Cookies { get; } /// <summary> /// Gets a value indicating whether response headers have been sent to the client. /// </summary> public abstract bool HasStarted { get; } /// <summary> /// Adds a delegate to be invoked just before response headers will be sent to the client. /// Callbacks registered here run in reverse order. /// </summary> /// <remarks> /// Callbacks registered here run in reverse order. The last one registered is invoked first. /// The reverse order is done to replicate the way middleware works, with the inner-most middleware looking at the /// response first. /// </remarks> /// <param name="callback">The delegate to execute.</param> /// <param name="state">A state object to capture and pass back to the delegate.</param> public abstract void OnStarting(Func<object, Task> callback, object state); /// <summary> /// Adds a delegate to be invoked just before response headers will be sent to the client. /// Callbacks registered here run in reverse order. /// </summary> /// <remarks> /// Callbacks registered here run in reverse order. The last one registered is invoked first. /// The reverse order is done to replicate the way middleware works, with the inner-most middleware looking at the /// response first. /// </remarks> /// <param name="callback">The delegate to execute.</param> public virtual void OnStarting(Func<Task> callback) => OnStarting(_callbackDelegate, callback); /// <summary> /// Adds a delegate to be invoked after the response has finished being sent to the client. /// </summary> /// <param name="callback">The delegate to invoke.</param> /// <param name="state">A state object to capture and pass back to the delegate.</param> public abstract void OnCompleted(Func<object, Task> callback, object state); /// <summary> /// Registers an object for disposal by the host once the request has finished processing. /// </summary> /// <param name="disposable">The object to be disposed.</param> public virtual void RegisterForDispose(IDisposable disposable) => OnCompleted(_disposeDelegate, disposable); /// <summary> /// Registers an object for asynchronous disposal by the host once the request has finished processing. /// </summary> /// <param name="disposable">The object to be disposed asynchronously.</param> public virtual void RegisterForDisposeAsync(IAsyncDisposable disposable) => OnCompleted(_disposeDelegate, disposable); /// <summary> /// Adds a delegate to be invoked after the response has finished being sent to the client. /// </summary> /// <param name="callback">The delegate to invoke.</param> public virtual void OnCompleted(Func<Task> callback) => OnCompleted(_callbackDelegate, callback); /// <summary> /// Returns a temporary redirect response (HTTP 302) to the client. /// </summary> /// <param name="location">The URL to redirect the client to. This must be properly encoded for use in http headers /// where only ASCII characters are allowed.</param> public virtual void Redirect([StringSyntax(StringSyntaxAttribute.Uri)] string location) => Redirect(location, permanent: false); /// <summary> /// Returns a redirect response (HTTP 301 or HTTP 302) to the client. /// </summary> /// <param name="location">The URL to redirect the client to. This must be properly encoded for use in http headers /// where only ASCII characters are allowed.</param> /// <param name="permanent"><c>True</c> if the redirect is permanent (301), otherwise <c>false</c> (302).</param> public abstract void Redirect([StringSyntax(StringSyntaxAttribute.Uri)] string location, bool permanent); /// <summary> /// Starts the response by calling OnStarting() and making headers unmodifiable. /// </summary> /// <param name="cancellationToken"></param> public virtual Task StartAsync(CancellationToken cancellationToken = default) { throw new NotImplementedException(); } /// <summary> /// Flush any remaining response headers, data, or trailers. /// This may throw if the response is in an invalid state such as a Content-Length mismatch. /// </summary> /// <returns></returns> public virtual Task CompleteAsync() { throw new NotImplementedException(); } internal string DebuggerToString() { return HttpContextDebugFormatter.ResponseToString(this, reasonPhrase: null); } private sealed class HttpResponseDebugView(HttpResponse response) { private readonly HttpResponse _response = response; public int StatusCode => _response.StatusCode; public IHeaderDictionary Headers => _response.Headers; public IHeaderDictionary? Trailers { get { var feature = _response.HttpContext.Features.Get<IHttpResponseTrailersFeature>(); return feature?.Trailers; } } public long? ContentLength => _response.ContentLength; public string? ContentType => _response.ContentType; public bool HasStarted => _response.HasStarted; } }