Skip to content

Commit aa7e1d0

Browse files
Merge pull request #19 from KristofferStrube/feature/read-async-should-respect-max-size-of-buffer
Added test project and test for new logic of ReadAsync method.
2 parents 7c3b01e + 70463e6 commit aa7e1d0

File tree

20 files changed

+537
-10
lines changed

20 files changed

+537
-10
lines changed

Blazor.Streams.sln

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KristofferStrube.Blazor.Str
77
EndProject
88
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KristofferStrube.Blazor.Streams.WasmExample", "samples\KristofferStrube.Blazor.Streams.WasmExample\KristofferStrube.Blazor.Streams.WasmExample.csproj", "{43126A7F-10BE-41C9-BB23-EAA52F96CA05}"
99
EndProject
10+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
11+
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorServer", "tests\BlazorServer\BlazorServer.csproj", "{9049D654-2EA4-5D01-BC0A-F26D2803F0EF}"
13+
EndProject
14+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTests", "tests\IntegrationTests\IntegrationTests.csproj", "{FF77485D-96D1-B1AA-B58F-3986BE3ADB27}"
15+
EndProject
1016
Global
1117
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1218
Debug|Any CPU = Debug|Any CPU
1319
Release|Any CPU = Release|Any CPU
1420
EndGlobalSection
15-
GlobalSection(SolutionProperties) = preSolution
16-
HideSolutionNode = FALSE
17-
EndGlobalSection
1821
GlobalSection(ProjectConfigurationPlatforms) = postSolution
1922
{C055529E-49A5-4BA6-9049-9D0FF4CBA7BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
2023
{C055529E-49A5-4BA6-9049-9D0FF4CBA7BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
@@ -24,9 +27,23 @@ Global
2427
{43126A7F-10BE-41C9-BB23-EAA52F96CA05}.Debug|Any CPU.Build.0 = Debug|Any CPU
2528
{43126A7F-10BE-41C9-BB23-EAA52F96CA05}.Release|Any CPU.ActiveCfg = Release|Any CPU
2629
{43126A7F-10BE-41C9-BB23-EAA52F96CA05}.Release|Any CPU.Build.0 = Release|Any CPU
30+
{9049D654-2EA4-5D01-BC0A-F26D2803F0EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31+
{9049D654-2EA4-5D01-BC0A-F26D2803F0EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
32+
{9049D654-2EA4-5D01-BC0A-F26D2803F0EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
33+
{9049D654-2EA4-5D01-BC0A-F26D2803F0EF}.Release|Any CPU.Build.0 = Release|Any CPU
34+
{FF77485D-96D1-B1AA-B58F-3986BE3ADB27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35+
{FF77485D-96D1-B1AA-B58F-3986BE3ADB27}.Debug|Any CPU.Build.0 = Debug|Any CPU
36+
{FF77485D-96D1-B1AA-B58F-3986BE3ADB27}.Release|Any CPU.ActiveCfg = Release|Any CPU
37+
{FF77485D-96D1-B1AA-B58F-3986BE3ADB27}.Release|Any CPU.Build.0 = Release|Any CPU
38+
EndGlobalSection
39+
GlobalSection(SolutionProperties) = preSolution
40+
HideSolutionNode = FALSE
2741
EndGlobalSection
2842
GlobalSection(NestedProjects) = preSolution
29-
{C055529E-49A5-4BA6-9049-9D0FF4CBA7BF} = {09D72B7A-1CB1-49BA-9312-2358E79A7DCE}
30-
{43126A7F-10BE-41C9-BB23-EAA52F96CA05} = {BC97D913-7EFD-4215-AB60-0039CB00703D}
43+
{9049D654-2EA4-5D01-BC0A-F26D2803F0EF} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
44+
{FF77485D-96D1-B1AA-B58F-3986BE3ADB27} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
45+
EndGlobalSection
46+
GlobalSection(ExtensibilityGlobals) = postSolution
47+
SolutionGuid = {314CF5E5-BA34-4279-80CE-C6F5C6689C29}
3148
EndGlobalSection
3249
EndGlobal

src/KristofferStrube.Blazor.Streams/ReadableStream/ReadableStream.Stream.cs

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Microsoft.JSInterop;
1+
using KristofferStrube.Blazor.WebIDL;
2+
using Microsoft.JSInterop;
23

34
namespace KristofferStrube.Blazor.Streams;
45

@@ -44,17 +45,53 @@ public override void Write(byte[] buffer, int offset, int count)
4445
throw new InvalidOperationException($"Writing to {nameof(ReadableStream)} is not supported as it is meant for reading.");
4546
}
4647

48+
private byte[]? additionalDataRead;
49+
4750
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
4851
{
52+
// If there is more than the desired buffer size available from previous reads then use those bytes.
53+
if (additionalDataRead?.Length > buffer.Length)
54+
{
55+
additionalDataRead[..buffer.Length].CopyTo(buffer);
56+
additionalDataRead = additionalDataRead[buffer.Length..];
57+
return buffer.Length;
58+
}
59+
60+
// If there is exactly enough data from previous reads, then use those bytes and clear the old data.
61+
if (additionalDataRead?.Length == buffer.Length)
62+
{
63+
additionalDataRead.CopyTo(buffer);
64+
additionalDataRead = null;
65+
return buffer.Length;
66+
}
67+
68+
// There is some data left from previous reads but not enough to fill the buffer.
69+
int bytesCopiedFromExcessDataReadPreviously = 0;
70+
if (additionalDataRead is not null)
71+
{
72+
additionalDataRead.CopyTo(buffer);
73+
bytesCopiedFromExcessDataReadPreviously = additionalDataRead.Length;
74+
additionalDataRead = null;
75+
}
76+
4977
reader ??= await GetDefaultReaderAsync();
5078
ReadableStreamReadResult read = await reader.ReadAsync();
5179
if (!await read.GetDoneAsync())
5280
{
5381
IJSObjectReference jSValue = await read.GetValueAsync();
54-
IJSObjectReference helper = await helperTask.Value;
55-
int length = await helper.InvokeAsync<int>("getAttribute", jSValue, "length");
56-
(await helper.InvokeAsync<byte[]>("byteArray", jSValue)).CopyTo(buffer);
57-
return length;
82+
await using Uint8Array value = await Uint8Array.CreateAsync(JSRuntime, jSValue);
83+
byte[] data = await value.GetAsArrayAsync();
84+
int bytesNeededToFillBuffer = buffer.Length - bytesCopiedFromExcessDataReadPreviously;
85+
if (data.Length > bytesNeededToFillBuffer)
86+
{
87+
data[..bytesNeededToFillBuffer].CopyTo(buffer[bytesCopiedFromExcessDataReadPreviously..]);
88+
additionalDataRead = data[bytesNeededToFillBuffer..];
89+
}
90+
else
91+
{
92+
data.CopyTo(buffer[bytesCopiedFromExcessDataReadPreviously..]);
93+
}
94+
return Math.Min(data.Length, buffer.Length);
5895
}
5996
await reader.ReleaseLockAsync();
6097
reader = null;

tests/BlazorServer/App.razor

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Router AppAssembly="@typeof(App).Assembly">
2+
<Found Context="routeData">
3+
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
4+
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
5+
</Found>
6+
<NotFound>
7+
<PageTitle>Not found</PageTitle>
8+
<LayoutView Layout="@typeof(MainLayout)">
9+
<p role="alert">Sorry, there's nothing at this address.</p>
10+
</LayoutView>
11+
</NotFound>
12+
</Router>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<ProjectReference Include="..\..\src\KristofferStrube.Blazor.Streams\KristofferStrube.Blazor.Streams.csproj" />
11+
</ItemGroup>
12+
13+
</Project>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+

2+
namespace BlazorServer;
3+
4+
public class EvaluationContext
5+
{
6+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@inherits LayoutComponentBase
2+
3+
<main> @Body </main>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
@page "/"
2+
3+
<span data-testid="result">@result</span>
4+
5+
@code {
6+
string? result;
7+
8+
[Inject]
9+
public required EvaluationContext EvaluationContext { get; set; }
10+
11+
protected override void OnAfterRender(bool firstRender)
12+
{
13+
if (!firstRender) return;
14+
result = "done";
15+
StateHasChanged();
16+
}
17+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
@page "/"
2+
@using Microsoft.AspNetCore.Components.Web
3+
@namespace BlazorServer.Pages
4+
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
5+
6+
<!DOCTYPE html>
7+
<html lang="en">
8+
<head>
9+
<meta charset="utf-8" />
10+
<base href="~/" />
11+
<link href="css/site.css" rel="stylesheet" />
12+
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
13+
</head>
14+
<body>
15+
<component type="typeof(App)" render-mode="ServerPrerendered" />
16+
17+
<div id="blazor-error-ui">
18+
<environment include="Staging,Production">
19+
An error has occurred. This application may no longer respond until reloaded.
20+
</environment>
21+
<environment include="Development">
22+
An unhandled exception has occurred. See browser dev tools for details.
23+
</environment>
24+
<a href="" class="reload">Reload</a>
25+
<a class="dismiss">🗙</a>
26+
</div>
27+
28+
<script src="_framework/blazor.server.js"></script>
29+
</body>
30+
</html>

tests/BlazorServer/Program.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace BlazorServer;
2+
3+
public class Program
4+
{
5+
private static async Task Main(string[] args)
6+
{
7+
var host = BuildWebHost(args, _ => { });
8+
await host.RunAsync();
9+
}
10+
11+
public static IHost BuildWebHost(string[] args, Action<IServiceCollection> configureServices)
12+
=> Host.CreateDefaultBuilder(args)
13+
.ConfigureWebHostDefaults(builder =>
14+
{
15+
builder.UseStaticWebAssets();
16+
builder.UseStartup<Startup>();
17+
builder.ConfigureServices(configureServices);
18+
})
19+
.Build();
20+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"iisSettings": {
3+
"iisExpress": {
4+
"applicationUrl": "http://localhost:1729",
5+
"sslPort": 44325
6+
}
7+
},
8+
"profiles": {
9+
"http": {
10+
"commandName": "Project",
11+
"dotnetRunMessages": true,
12+
"launchBrowser": true,
13+
"applicationUrl": "http://localhost:5151",
14+
"environmentVariables": {
15+
"ASPNETCORE_ENVIRONMENT": "Development"
16+
}
17+
},
18+
"https": {
19+
"commandName": "Project",
20+
"dotnetRunMessages": true,
21+
"launchBrowser": true,
22+
"applicationUrl": "https://localhost:7131;http://localhost:5151",
23+
"environmentVariables": {
24+
"ASPNETCORE_ENVIRONMENT": "Development"
25+
}
26+
},
27+
"IIS Express": {
28+
"commandName": "IISExpress",
29+
"launchBrowser": true,
30+
"environmentVariables": {
31+
"ASPNETCORE_ENVIRONMENT": "Development"
32+
}
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)