Skip to content

Commit c443a3f

Browse files
committed
Add StreamResponse Test
1 parent 0b58e75 commit c443a3f

File tree

7 files changed

+216
-66
lines changed

7 files changed

+216
-66
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using Microsoft.AspNetCore.Http.Features;
2+
using Microsoft.AspNetCore.Mvc;
3+
using System.Text;
4+
5+
namespace WebApplicationTest.Controllers;
6+
7+
[ApiController]
8+
[Route("[controller]")]
9+
public class KestrelTestController : ControllerBase
10+
{
11+
private readonly IHttpContextAccessor _contextAccessor;
12+
13+
public KestrelTestController(IHttpContextAccessor contextAccessor)
14+
{
15+
_contextAccessor = contextAccessor;
16+
}
17+
18+
#region Methods
19+
20+
[HttpGet]
21+
[Route("DisableBuffering")]
22+
public async Task<IActionResult> DisableBufferingAsync(CancellationToken cancellation)
23+
{
24+
//curl http://localhost:5095/KestrelTest/DisableBuffering -v
25+
26+
var context = _contextAccessor.HttpContext!;
27+
var feature = context.Features.GetRequiredFeature<IHttpResponseBodyFeature>();
28+
feature.DisableBuffering(); // 无效果,掉不掉用都不使用响应缓冲区
29+
30+
context.Response.StatusCode = 200;
31+
context.Response.ContentType = "text/plain; charset=utf-8";
32+
//context.Response.ContentLength = null;
33+
34+
await feature.StartAsync(cancellation);
35+
36+
for (var i = 0; i < 5; ++i)
37+
{
38+
var line = $"this is line {i}\r\n";
39+
var bytes = Encoding.UTF8.GetBytes(line);
40+
// it seems context.Response.Body.WriteAsync() and
41+
// context.Response.BodyWriter.WriteAsync() work exactly the same
42+
var flushResult = await feature.Writer.WriteAsync(new ReadOnlyMemory<byte>(bytes), cancellation); //此时发送响应头
43+
if (flushResult.IsCompleted)
44+
{
45+
// 此处不会执行
46+
_ = await feature.Writer.FlushAsync(cancellation);
47+
}
48+
49+
await Task.Delay(1000, cancellation);
50+
}
51+
52+
await feature.CompleteAsync();
53+
54+
return Empty;
55+
}
56+
57+
[HttpGet]
58+
[Route("EnableBuffering")]
59+
public async Task<IActionResult> EnableBufferingAsync(CancellationToken cancellation)
60+
{
61+
//curl http://localhost:5095/KestrelTest/EnableBuffering -v
62+
63+
var builder = new StringBuilder();
64+
for (var i = 0; i < 5; ++i)
65+
{
66+
var s = $"this is line {i}\r\n";
67+
_ = builder.Append(s);
68+
await Task.Delay(1000, cancellation);
69+
}
70+
71+
return Ok(builder.ToString());
72+
}
73+
74+
#endregion
75+
76+
}
Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,64 @@
1-
using System.Text.Json;
2-
using System.Text.Json.Nodes;
31
using Microsoft.AspNetCore.Mvc;
42
using Microsoft.Extensions.Options;
3+
using System.Text.Json;
4+
using System.Text.Json.Nodes;
5+
6+
namespace WebApplicationTest.Controllers;
57

6-
namespace WebApplicationTest.Controllers
8+
[ApiController]
9+
[Route("[controller]")]
10+
public class WeatherForecastController : ControllerBase
711
{
8-
[ApiController]
9-
[Route("[controller]")]
10-
public class WeatherForecastController : ControllerBase
12+
13+
#region Methods
14+
15+
[HttpGet]
16+
[Route("GetOptions")]
17+
public IActionResult GetOptions(
18+
[FromServices] IOptionsSnapshot<SimpleOptions> simpleOptions,
19+
[FromServices] IOptionsSnapshot<ReadOnlyOptions> readonlyOptions,
20+
[FromServices] IOptionsSnapshot<PrivateOptions> privateOptions)
1121
{
12-
private readonly ILogger<WeatherForecastController> _logger;
13-
14-
public WeatherForecastController(ILogger<WeatherForecastController> logger)
15-
{
16-
_logger = logger;
17-
}
18-
19-
[HttpGet]
20-
[Route("GetOptions")]
21-
public IActionResult GetOptions([FromServices] IOptionsSnapshot<SimpleOptions> simpleOptions,
22-
[FromServices] IOptionsSnapshot<ReadOnlyOptions> readonlyOptions,
23-
[FromServices] IOptionsSnapshot<PrivateOptions> privateOptions)
24-
{
25-
var obj = new JsonObject(new KeyValuePair<string, JsonNode?>[]
26-
{
22+
var obj = new JsonObject(
23+
[
2724
new("simpleOptions", JsonNode.Parse(JsonSerializer.Serialize(simpleOptions.Value))),
2825
new("readonlyOptions", JsonNode.Parse(JsonSerializer.Serialize(readonlyOptions.Value))),
2926
new("privateOptions", JsonNode.Parse(JsonSerializer.Serialize(privateOptions.Value)))
30-
});
27+
]);
28+
29+
return Ok(obj);
30+
31+
//{
32+
// "simpleOptions": {
33+
// "InitArray": [
34+
// "123"
35+
// ],
36+
// "InitArray2": [
37+
// "123"
38+
// ],
39+
// "InitArray3": [
40+
// "123"
41+
// ],
42+
// "Parkings": [
43+
// "2323"
44+
// ]
45+
// },
3146

32-
return Ok(obj);
33-
}
47+
// "readonlyOptions": {
48+
// "Codes": null,
49+
// "Parkings": [
50+
// "123"
51+
// ]
52+
// },
53+
54+
// "privateOptions": {
55+
// "Parkings": [
56+
// "456"
57+
// ]
58+
// }
59+
//}
3460
}
61+
62+
#endregion
63+
3564
}

src/Tests/WebApplicationTest/Options.cs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,42 @@ namespace WebApplicationTest;
22

33
public class SimpleOptions
44
{
5-
public ICollection<string> Parkings { get; set; } = new List<string>();
65

7-
public IEnumerable<string> InitArray { get; set; } = Array.Empty<string>(); //Wrong!无法通过配置文件赋值
6+
#region Properties
87

9-
public string[] InitArray2 { get; set; } = Array.Empty<string>();
8+
public IEnumerable<string> InitArray { get; set; } = []; //Wrong!无法通过配置文件赋值
9+
10+
public string[] InitArray2 { get; set; } = [];
11+
12+
public string[] InitArray3 { get; set; } = null!; //禁止这样初始化,配置文件为空数组时属性会返回 null
13+
14+
public ICollection<string> Parkings { get; set; } = [];
15+
16+
#endregion
1017

11-
public string[] InitArray3 { get; set; } = null!; //配置文件为空数组,属性会返回 null 而不是空数组
1218
}
1319

1420
public class ReadOnlyOptions
1521
{
16-
public IReadOnlyCollection<string> Parkings { get; set; } = null!; //Readonly 必须初始化为 null
1722

18-
public IReadOnlyCollection<string> Codes { get; set; } = null!; //配置文件为空数组,属性会返回 null 而不是空数组
23+
#region Properties
24+
25+
public IReadOnlyCollection<string> Codes { get; set; } = null!; //禁止这样初始化,配置文件为空数组时属性会返回 null
26+
27+
public IReadOnlyCollection<string> Parkings { get; set; } = [];
28+
29+
#endregion
30+
31+
//Readonly 必须初始化为 null
1932
}
2033

21-
public class PrivateOptions
34+
public record PrivateOptions
2235
{
23-
public IReadOnlyCollection<string> Parkings { get; private set; } = null!; // = new List<string>();
36+
37+
#region Properties
38+
39+
public IReadOnlyCollection<string> Parkings { get; private set; } = null!; // 可以支持 private set
40+
41+
#endregion
42+
2443
}
Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,61 @@
1-
using WebApplicationTest;
1+
using Scalar.AspNetCore;
22

3-
var builder = WebApplication.CreateBuilder(args);
3+
namespace WebApplicationTest;
44

5-
// Add services to the container.
5+
public static class Program
6+
{
67

7-
var services = builder.Services;
8-
services.AddControllers();
9-
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
10-
services.AddEndpointsApiExplorer();
11-
services.AddSwaggerGen();
8+
#region Constants & Statics
129

13-
services.Configure<SimpleOptions>(builder.Configuration.GetSection("SimpleOptions"));
10+
private static void Main(string[] args)
11+
{
12+
var builder = WebApplication.CreateBuilder(args);
1413

15-
services.Configure<ReadOnlyOptions>(builder.Configuration.GetSection("ReadOnlyOptions"),
16-
options => options.BindNonPublicProperties = true);
14+
// Add services to the container.
1715

18-
services.Configure<PrivateOptions>(builder.Configuration.GetSection("PrivateOptions"),
19-
options => options.BindNonPublicProperties = true);
16+
var services = builder.Services;
17+
_ = services.AddControllers();
18+
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
19+
_ = services.AddEndpointsApiExplorer();
20+
_ = services.AddOpenApi();
2021

21-
var app = builder.Build();
22+
_ = services.AddHttpContextAccessor();
2223

23-
// Configure the HTTP request pipeline.
24-
if (app.Environment.IsDevelopment())
25-
{
26-
app.UseSwagger();
27-
app.UseSwaggerUI();
28-
}
24+
_ = services.Configure<SimpleOptions>(builder.Configuration.GetSection("SimpleOptions"));
25+
26+
_ = services.Configure<ReadOnlyOptions>(
27+
builder.Configuration.GetSection("ReadOnlyOptions"),
28+
options => options.BindNonPublicProperties = true);
29+
30+
_ = services.Configure<PrivateOptions>(
31+
builder.Configuration.GetSection("PrivateOptions"),
32+
options => options.BindNonPublicProperties = true);
33+
34+
_ = builder.WebHost
35+
.ConfigureKestrel(
36+
serverOptions =>
37+
{
38+
serverOptions.Limits.MaxResponseBufferSize = 10; // Disable response buffering limit
39+
});
2940

30-
app.UseHttpsRedirection();
41+
var app = builder.Build();
3142

32-
app.UseAuthorization();
43+
// Configure the HTTP request pipeline.
44+
if (app.Environment.IsDevelopment())
45+
{
46+
_ = app.MapOpenApi();
47+
_ = app.MapScalarApiReference();
48+
}
3349

34-
app.MapControllers();
50+
//_ = app.UseHttpsRedirection();
3551

36-
app.Run();
52+
_ = app.UseAuthorization();
53+
54+
_ = app.MapControllers();
55+
56+
app.Run();
57+
}
58+
59+
#endregion
60+
61+
}

src/Tests/WebApplicationTest/Properties/launchSettings.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{
1+
{
22
"$schema": "https://json.schemastore.org/launchsettings.json",
33
"iisSettings": {
44
"windowsAuthentication": false,
@@ -13,7 +13,7 @@
1313
"commandName": "Project",
1414
"dotnetRunMessages": true,
1515
"launchBrowser": true,
16-
"launchUrl": "swagger",
16+
"launchUrl": "scalar",
1717
"applicationUrl": "https://localhost:7095;http://localhost:5095",
1818
"environmentVariables": {
1919
"ASPNETCORE_ENVIRONMENT": "Development"
@@ -22,7 +22,7 @@
2222
"IIS Express": {
2323
"commandName": "IISExpress",
2424
"launchBrowser": true,
25-
"launchUrl": "swagger",
25+
"launchUrl": "scalar",
2626
"environmentVariables": {
2727
"ASPNETCORE_ENVIRONMENT": "Development"
2828
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22

33
<PropertyGroup>
4-
<TargetFramework>net6.0</TargetFramework>
4+
<TargetFramework>net9.0</TargetFramework>
55
<Nullable>enable</Nullable>
66
<ImplicitUsings>enable</ImplicitUsings>
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
10+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.10" />
11+
<PackageReference Include="Scalar.AspNetCore" Version="2.9.0" />
1112
</ItemGroup>
1213

1314
</Project>

src/Tests/WebApplicationTest/appsettings.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
"AllowedHosts": "*",
99
"SimpleOptions": {
1010
"Parkings": [ "2323" ],
11-
"InitArray": ["123"],
12-
"InitArray2": ["123"],
13-
"InitArray3": ["123"]
11+
"InitArray": [ "123" ],
12+
"InitArray2": [ "123" ],
13+
"InitArray3": [ "123" ]
1414
},
1515
"ReadOnlyOptions": {
16-
"Parkings": [ "123" ],
17-
"Codes": []
16+
"Parkings": [],
17+
"Codes": []
1818
},
1919
"PrivateOptions": {
2020
"Parkings": [ "456" ]

0 commit comments

Comments
 (0)