Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,22 @@ public Task SendFileAsync(string path, long offset, long? count, CancellationTok
_context.Response.Headers.ETag = "";
// Compute the new ETag, if this is a compressed asset, RuntimeStaticAssetResponseBodyFeature will update it.
_context.Response.Headers.ETag = GetETag(fileInfo);
_context.Response.Headers.ContentLength = fileInfo.Length;
_context.Response.Headers.LastModified = fileInfo.LastModified.ToString("ddd, dd MMM yyyy HH:mm:ss 'GMT'", CultureInfo.InvariantCulture);

// Send the modified asset as is.
return _original.SendFileAsync(fileInfo.PhysicalPath!, 0, fileInfo.Length, cancellationToken);
// For range requests (206 Partial Content), we preserve the original offset and count to send the correct range.
// For normal requests (200 OK), we send the entire file.
var isRangeRequest = _context.Response.StatusCode == StatusCodes.Status206PartialContent;
if (isRangeRequest)
{
// Send the requested range from the modified file.
return _original.SendFileAsync(fileInfo.PhysicalPath!, offset, count, cancellationToken);
}
else
{
// Send the entire modified file.
_context.Response.Headers.ContentLength = fileInfo.Length;
return _original.SendFileAsync(fileInfo.PhysicalPath!, 0, fileInfo.Length, cancellationToken);
}
}
}

Expand Down
66 changes: 66 additions & 0 deletions src/StaticAssets/test/StaticAssetsIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,72 @@ public async Task CanModifyAssetsOnTheFlyInDevelopment()
Directory.Delete(webRoot, true);
}

[Fact]
public async Task RangeRequestReturnsCorrectContentLengthForModifiedAssets()
{
// Arrange
var appName = nameof(RangeRequestReturnsCorrectContentLengthForModifiedAssets);
var (contentRoot, webRoot) = ConfigureAppPaths(appName);

CreateTestManifest(
appName,
webRoot,
[
new TestResource("sample.txt", "Hello, World!", false),
]);

var builder = WebApplication.CreateEmptyBuilder(new WebApplicationOptions
{
ApplicationName = appName,
ContentRootPath = contentRoot,
EnvironmentName = "Development",
WebRootPath = webRoot
});
builder.WebHost.UseSetting(StaticAssetDevelopmentRuntimeHandler.ReloadStaticAssetsAtRuntimeKey, "true");
builder.WebHost.ConfigureServices(services =>
{
services.AddRouting();
});
builder.WebHost.UseTestServer();

var app = builder.Build();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapStaticAssets();
});

await app.StartAsync();

var client = app.GetTestClient();

File.WriteAllText(Path.Combine(webRoot, "sample.txt"), "Hello, World! Modified");

// Act - Request first 5 bytes (Range: bytes=0-4)
var message = new HttpRequestMessage(HttpMethod.Get, "/sample.txt");
message.Headers.Range = new RangeHeaderValue(0, 4);
var response = await client.SendAsync(message);

// Assert - Should return 206 Partial Content with correct Content-Length of 5 bytes
Assert.NotNull(response);
Assert.Equal(HttpStatusCode.PartialContent, response.StatusCode);
Assert.Equal(5, response.Content.Headers.ContentLength);
Assert.Equal("Hello", await response.Content.ReadAsStringAsync());

// Act - Request bytes 7-11 (Range: bytes=7-11)
var message2 = new HttpRequestMessage(HttpMethod.Get, "/sample.txt");
message2.Headers.Range = new RangeHeaderValue(7, 11);
var response2 = await client.SendAsync(message2);

// Assert - Should return 206 Partial Content with correct Content-Length of 5 bytes
Assert.NotNull(response2);
Assert.Equal(HttpStatusCode.PartialContent, response2.StatusCode);
Assert.Equal(5, response2.Content.Headers.ContentLength);
Assert.Equal("World", await response2.Content.ReadAsStringAsync());

Directory.Delete(webRoot, true);
}

[Fact]
public async Task CanModifyAssetsWithCompressedVersionsOnTheFlyInDevelopment()
{
Expand Down
Loading