Skip to content

Commit

Permalink
Add HttpResponse.WriteFile/TransmitFile
Browse files Browse the repository at this point in the history
These APIs are essentially the same, except that they had different memory characteristics. TransmitFile did not buffer, while WriteFile did. On ASP.NET Core, these will just delegate to IHttpResponseBodyFeature.SendFileAsync.

NOTE: This implementation will call Task.GetAwaiter().GetResult(). We could potentially expose an async API for this on the ASP.NET Core side as a stepping stone if we want.

Part of #91
  • Loading branch information
twsouthwick committed Jul 11, 2022
1 parent 47bb10e commit 5d105c3
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 7 deletions.
42 changes: 37 additions & 5 deletions src/Microsoft.AspNetCore.SystemWebAdapters/HttpResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,28 @@ public int StatusCode
set => _response.StatusCode = value;
}

public string StatusDescription
public int SubStatusCode { get; set; } = -1;

public string? StatusDescription
{
get => _response.HttpContext.Features.GetRequired<IHttpResponseFeature>().ReasonPhrase ?? ReasonPhrases.GetReasonPhrase(_response.StatusCode);
set => _response.HttpContext.Features.GetRequired<IHttpResponseFeature>().ReasonPhrase = value;
}

public NameValueCollection Headers => _headers ??= _response.Headers.ToNameValueCollection();

public void ClearHeaders()
{
StatusCode = 200;
SubStatusCode = 0;
StatusDescription = null;
ContentType = "text/html";
Charset = Encoding.UTF8.WebName;

_response.Headers.Clear();
_cookies?.Clear();
}

public bool TrySkipIisCustomErrors
{
get => _response.HttpContext.Features.GetRequired<IStatusCodePagesFeature>().Enabled;
Expand All @@ -61,10 +75,9 @@ public bool TrySkipIisCustomErrors

public Stream OutputStream => _response.Body;

public HttpCookieCollection Cookies
{
get => _cookies ??= new(this);
}
public HttpCookieCollection Cookies => _cookies ??= new(this);

public void AppendCookie(HttpCookie cookie) => Cookies.Add(cookie);

public bool SuppressContent
{
Expand Down Expand Up @@ -177,6 +190,16 @@ public void AppendHeader(string name, string value)

public void Write(object obj) => Output.Write(obj);

public void BinaryWrite(byte[] buffer)
{
if (buffer is null)
{
throw new ArgumentNullException(nameof(buffer));
}

OutputStream.Write(buffer, 0, buffer.Length);
}

public void Clear()
{
_response.Clear();
Expand All @@ -195,6 +218,15 @@ public void ClearContent()
}
}

public void WriteFile(string filename)
=> TransmitFile(filename);

public void TransmitFile(string filename)
=> TransmitFile(filename, 0, -1);

public void TransmitFile(string filename, long offset, long length)
=> _response.SendFileAsync(filename, offset, length >= 0 ? length : null).GetAwaiter().GetResult();

[return: NotNullIfNotNull("response")]
public static implicit operator HttpResponse?(HttpResponseCore? response) => response?.GetAdapter();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public virtual int StatusCode
set => throw new NotImplementedException();
}

public virtual string StatusDescription
public virtual string? StatusDescription
{
get => throw new NotImplementedException();
set => throw new NotImplementedException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public override int StatusCode
set => _response.StatusCode = value;
}

public override string StatusDescription
public override string? StatusDescription
{
get => _response.StatusDescription;
set => _response.StatusDescription = value;
Expand Down
7 changes: 7 additions & 0 deletions src/Microsoft.AspNetCore.SystemWebAdapters/Ref.Standard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,17 +221,24 @@ internal HttpResponse() { }
public System.IO.Stream OutputStream { get { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");} }
public int StatusCode { get { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");} set { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");} }
public string StatusDescription { get { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");} set { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");} }
public int SubStatusCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");} [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");} }
public bool SuppressContent { get { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");} set { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");} }
public bool TrySkipIisCustomErrors { get { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");} set { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");} }
public void AddHeader(string name, string value) { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
public void AppendCookie(System.Web.HttpCookie cookie) { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
public void AppendHeader(string name, string value) { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
public void BinaryWrite(byte[] buffer) { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
public void Clear() { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
public void ClearContent() { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
public void ClearHeaders() { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
public void End() { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
public void SetCookie(System.Web.HttpCookie cookie) { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
public void TransmitFile(string filename) { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
public void TransmitFile(string filename, long offset, long length) { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
public void Write(char ch) { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
public void Write(object obj) { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
public void Write(string s) { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
public void WriteFile(string filename) { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
}
public partial class HttpResponseBase
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,4 +378,44 @@ public void Cookies()
// Assert
Assert.Same(cookies1, cookies2);
}

[Fact]
public void WriteFile()
=> SendFileTest((response, file) => response.WriteFile(file));

[Fact]
public void TransmitFile()
=> SendFileTest((response, file) => response.TransmitFile(file));

[Fact]
public void TransmitFileArgs()
=> SendFileTest((response, file, offset, length) => response.TransmitFile(file, offset, length!.Value), 30, 3);

private static void SendFileTest(Action<HttpResponse, string> action)
=> SendFileTest((response, file, offset, length) => action(response, file), 0, default);

private static void SendFileTest(Action<HttpResponse, string, long, long?> action, long offset, long? length)
{
// Arrange
const string FileName = "somefile.txt";

var responsebody = new Mock<IHttpResponseBodyFeature>();

var features = new Mock<IFeatureCollection>();
features.Setup(f => f.Get<IHttpResponseBodyFeature>()).Returns(responsebody.Object);

var context = new Mock<HttpContextCore>();
context.Setup(c => c.Features).Returns(features.Object);

var responseCore = new Mock<HttpResponseCore>();
responseCore.Setup(r => r.HttpContext).Returns(context.Object);

var response = new HttpResponse(responseCore.Object);

// Act
action(response, FileName, offset, length);

// Assert
responsebody.Verify(r => r.SendFileAsync(FileName, offset, length, default), Times.Once);
}
}

0 comments on commit 5d105c3

Please sign in to comment.