Skip to content

Commit 9b5402f

Browse files
Add Fortunes endpoint that returns RazorComponentResult to the Minimal project (#2041)
* Add Fortunes endpoint that returns RazorComponentResult * Add custom HtmlEncoder to DI * Move to BlazorSSR project
1 parent 80aab30 commit 9b5402f

File tree

7 files changed

+184
-9
lines changed

7 files changed

+184
-9
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Fortunes</title>
5+
</head>
6+
<body>
7+
<table>
8+
<tr><th>id</th><th>message</th></tr>
9+
@foreach (var item in Rows)
10+
{
11+
<tr><td>@item.Id</td><td>@item.Message</td></tr>
12+
}
13+
</table>
14+
</body>
15+
</html>
16+
@code {
17+
[Parameter]
18+
public required List<Fortune> Rows { get; set; }
19+
}

src/BenchmarksApps/TechEmpower/BlazorSSR/Database/Db.cs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public async Task<List<Fortune>> LoadFortunesRowsDapper()
2222
await using var connection = _dataSource.CreateConnection();
2323
var result = (await connection.QueryAsync<Fortune>($"SELECT id, message FROM fortune")).AsList();
2424

25-
result.Add(new Fortune { Id = 0, Message = "Additional fortune added at request time." });
25+
result.Add(new() { Id = 0, Message = "Additional fortune added at request time." });
2626
result.Sort(FortuneSortComparison);
2727

2828
return result;
@@ -37,11 +37,36 @@ public async Task<List<Fortune>> LoadFortunesRowsEf(AppDbContext dbContext)
3737
result.Add(fortune);
3838
}
3939

40-
result.Add(new Fortune { Id = 0, Message = "Additional fortune added at request time." });
40+
result.Add(new() { Id = 0, Message = "Additional fortune added at request time." });
4141
result.Sort(FortuneSortComparison);
4242

4343
return result;
4444
}
4545

46+
public Task<List<Fortune>> LoadFortunesRowsNoDb()
47+
{
48+
// Benchmark requirements explicitly prohibit pre-initializing the list size
49+
var result = new List<Fortune>
50+
{
51+
new() { Id = 1, Message = "fortune: No such file or directory" },
52+
new() { Id = 2, Message = "A computer scientist is someone who fixes things that aren't broken." },
53+
new() { Id = 3, Message = "After enough decimal places, nobody gives a damn." },
54+
new() { Id = 4, Message = "A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1" },
55+
new() { Id = 5, Message = "A computer program does what you tell it to do, not what you want it to do." },
56+
new() { Id = 6, Message = "Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen" },
57+
new() { Id = 7, Message = "Any program that runs right is obsolete." },
58+
new() { Id = 8, Message = "A list is only as strong as its weakest link. — Donald Knuth" },
59+
new() { Id = 9, Message = "Feature: A bug with seniority." },
60+
new() { Id = 10, Message = "Computers make very fast, very accurate mistakes." },
61+
new() { Id = 11, Message = "<script>alert(\"This should not be displayed in a browser alert box.\");</script>" },
62+
new() { Id = 12, Message = "フレームワークのベンチマーク" },
63+
new() { Id = 0, Message = "Additional fortune added at request time." }
64+
};
65+
66+
result.Sort(FortuneSortComparison);
67+
68+
return Task.FromResult(result);
69+
}
70+
4671
ValueTask IAsyncDisposable.DisposeAsync() => _dataSource.DisposeAsync();
4772
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System.Collections;
2+
using System.Diagnostics.CodeAnalysis;
3+
using BlazorSSR.Components;
4+
using BlazorSSR.Models;
5+
6+
namespace BlazorSSR;
7+
8+
internal readonly struct FortunesRazorParameters(List<Fortune> model) : IReadOnlyDictionary<string, object?>
9+
{
10+
private const string ModelKeyName = nameof(FortunesParameters.Rows);
11+
12+
private readonly KeyValuePair<string, object?> _modelKvp = new(ModelKeyName, model);
13+
14+
public object? this[string key] => KeyIsModel(key) ? model : null;
15+
16+
public IEnumerable<string> Keys { get; } = [ModelKeyName];
17+
18+
public IEnumerable<object?> Values { get; } = [model];
19+
20+
public int Count { get; } = 1;
21+
22+
public bool ContainsKey(string key) => KeyIsModel(key);
23+
24+
public IEnumerator<KeyValuePair<string, object?>> GetEnumerator() => new Enumerator(_modelKvp);
25+
26+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
27+
28+
public bool TryGetValue(string key, [MaybeNullWhen(false)] out object? value)
29+
{
30+
if (KeyIsModel(key))
31+
{
32+
value = model;
33+
return true;
34+
}
35+
value = default;
36+
return false;
37+
}
38+
39+
private static bool KeyIsModel(string key) => ModelKeyName.Equals(key, StringComparison.Ordinal);
40+
41+
private struct Enumerator(KeyValuePair<string, object?> kvp) : IEnumerator<KeyValuePair<string, object?>>
42+
{
43+
private bool _moved;
44+
45+
public readonly KeyValuePair<string, object?> Current { get; } = kvp;
46+
47+
readonly object IEnumerator.Current => Current;
48+
49+
public bool MoveNext()
50+
{
51+
if (_moved)
52+
{
53+
return false;
54+
}
55+
_moved = true;
56+
return true;
57+
}
58+
59+
public readonly void Dispose() { }
60+
61+
public void Reset() => throw new NotSupportedException();
62+
}
63+
}

src/BenchmarksApps/TechEmpower/BlazorSSR/Program.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
builder.Services.AddRazorComponents();
2626
builder.Services.AddSingleton(serviceProvider =>
2727
{
28-
// TODO: This custom configured HtmlEncoder won't actually be used until Blazor supports it: https://github.com/dotnet/aspnetcore/issues/47477
2928
var settings = new TextEncoderSettings(UnicodeRanges.BasicLatin, UnicodeRanges.Katakana, UnicodeRanges.Hiragana);
3029
settings.AllowCharacter('\u2014'); // allow EM DASH through
3130
return HtmlEncoder.Create(settings);
@@ -40,6 +39,18 @@
4039
app.MapGet("/direct/fortunes", () => new RazorComponentResult<Fortunes>());
4140
app.MapGet("/direct/fortunes-ef", () => new RazorComponentResult<FortunesEf>());
4241

42+
app.MapGet("/direct/fortunes/params", async (HttpContext context, Db db) => {
43+
var fortunes = await db.LoadFortunesRowsDapper();
44+
//var fortunes = await db.LoadFortunesRowsNoDb(); // Don't call the database
45+
var parameters = new Dictionary<string, object?> { { nameof(FortunesParameters.Rows), fortunes } };
46+
//var parameters = new FortunesRazorParameters(fortunes); // Custom parameters class to avoid allocating a Dictionary
47+
var result = new RazorComponentResult<FortunesParameters>(parameters)
48+
{
49+
PreventStreamingRendering = true
50+
};
51+
return result;
52+
});
53+
4354
app.Lifetime.ApplicationStarted.Register(() => Console.WriteLine("Application started. Press Ctrl+C to shut down."));
4455
app.Lifetime.ApplicationStopping.Register(() => Console.WriteLine("Application is shutting down..."));
4556

src/BenchmarksApps/TechEmpower/BlazorSSR/blazorssr.benchmarks.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,39 @@ scenarios:
5151
presetHeaders: html
5252
path: /fortunes-ef
5353

54+
fortunes-direct:
55+
db:
56+
job: postgresql
57+
application:
58+
job: blazorssr
59+
load:
60+
job: wrk
61+
variables:
62+
presetHeaders: html
63+
path: /direct/fortunes
64+
65+
fortunes-direct-ef:
66+
db:
67+
job: postgresql
68+
application:
69+
job: blazorssr
70+
load:
71+
job: wrk
72+
variables:
73+
presetHeaders: html
74+
path: /direct/fortunes-ef
75+
76+
fortunes-direct-params:
77+
db:
78+
job: postgresql
79+
application:
80+
job: blazorssr
81+
load:
82+
job: wrk
83+
variables:
84+
presetHeaders: html
85+
path: /direct/fortunes/params
86+
5487
profiles:
5588
# this profile uses the local folder as the source
5689
# instead of the public repository

src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Data.Common;
1+
using System.Data.Common;
22
using Dapper;
33
using Minimal.Models;
44

@@ -99,4 +99,29 @@ public async Task<List<Fortune>> LoadFortunesRows()
9999

100100
return result;
101101
}
102+
103+
public Task<List<Fortune>> LoadFortunesRowsNoDb()
104+
{
105+
// Benchmark requirements explicitly prohibit pre-initializing the list size
106+
var result = new List<Fortune>
107+
{
108+
new(1, "fortune: No such file or directory"),
109+
new(2, "A computer scientist is someone who fixes things that aren't broken."),
110+
new(3, "After enough decimal places, nobody gives a damn."),
111+
new(4, "A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1"),
112+
new(5, "A computer program does what you tell it to do, not what you want it to do."),
113+
new(6, "Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen"),
114+
new(7, "Any program that runs right is obsolete."),
115+
new(8, "A list is only as strong as its weakest link. — Donald Knuth"),
116+
new(9, "Feature: A bug with seniority."),
117+
new(10, "Computers make very fast, very accurate mistakes."),
118+
new(11, "<script>alert(\"This should not be displayed in a browser alert box.\");</script>"),
119+
new(12, "フレームワークのベンチマーク"),
120+
new(0, "Additional fortune added at request time.")
121+
};
122+
123+
result.Sort(FortuneSortComparison);
124+
125+
return Task.FromResult(result);
126+
}
102127
}

src/BenchmarksApps/TechEmpower/Minimal/Program.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System.Text.Encodings.Web;
22
using System.Text.Unicode;
33
using Microsoft.AspNetCore.Http.HttpResults;
4-
using RazorSlices;
54
using Minimal;
65
using Minimal.Database;
76
using Minimal.Models;
@@ -23,6 +22,7 @@
2322

2423
// Add services to the container.
2524
builder.Services.AddSingleton(new Db(appSettings));
25+
builder.Services.AddSingleton(CreateHtmlEncoder());
2626

2727
var app = builder.Build();
2828

@@ -38,10 +38,9 @@
3838

3939
app.MapGet("/db/result", async (Db db) => Results.Json(await db.LoadSingleQueryRow()));
4040

41-
var htmlEncoder = CreateHtmlEncoder();
42-
43-
app.MapGet("/fortunes", async (HttpContext context, Db db) => {
41+
app.MapGet("/fortunes", async (HttpContext context, Db db, HtmlEncoder htmlEncoder) => {
4442
var fortunes = await db.LoadFortunesRows();
43+
//var fortunes = await db.LoadFortunesRowsNoDb(); // Don't call the database
4544
var template = (RazorSliceHttpResult<List<Fortune>>)Fortunes.Create(fortunes);
4645
template.HtmlEncoder = htmlEncoder;
4746
return template;
@@ -65,4 +64,4 @@ static HtmlEncoder CreateHtmlEncoder()
6564
var settings = new TextEncoderSettings(UnicodeRanges.BasicLatin, UnicodeRanges.Katakana, UnicodeRanges.Hiragana);
6665
settings.AllowCharacter('\u2014'); // allow EM DASH through
6766
return HtmlEncoder.Create(settings);
68-
}
67+
}

0 commit comments

Comments
 (0)