Jelajahi Sumber

Update aspnet core benchmarks (#8498)

* Update aspnet core benchmarks

* Fix stringy db arguments

* Fix _id serialization warning

* Fix test failures

* Fix Platform database run

* Fix plaintext/json

* Add MySQL benchmarks

* Fix MySQL benchmarks

* Fix MySQL fortunes and updates

* Restore synchronous path for plaintext and json

* Fix default database provider
Sébastien Ros 1 tahun lalu
induk
melakukan
82514f48af
100 mengubah file dengan 3987 tambahan dan 1316 penghapusan
  1. 30 10
      frameworks/CSharp/aspnetcore/Benchmarks.sln
  2. 0 4
      frameworks/CSharp/aspnetcore/Benchmarks/appsettings.postgresql.updates.json
  3. 0 6
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.cs
  4. 3 51
      frameworks/CSharp/aspnetcore/README.md
  5. 0 0
      frameworks/CSharp/aspnetcore/appsettings.mysql.json
  6. 0 0
      frameworks/CSharp/aspnetcore/appsettings.postgresql.json
  7. 0 20
      frameworks/CSharp/aspnetcore/aspcore-ado-my.dockerfile
  8. 0 20
      frameworks/CSharp/aspnetcore/aspcore-ado-pg-up.dockerfile
  9. 0 20
      frameworks/CSharp/aspnetcore/aspcore-ado-pg.dockerfile
  10. 0 17
      frameworks/CSharp/aspnetcore/aspcore-aot-ado-pg-up.dockerfile
  11. 0 17
      frameworks/CSharp/aspnetcore/aspcore-aot-ado-pg.dockerfile
  12. 0 18
      frameworks/CSharp/aspnetcore/aspcore-aot.dockerfile
  13. 0 20
      frameworks/CSharp/aspnetcore/aspcore-mvc-ado-my.dockerfile
  14. 0 20
      frameworks/CSharp/aspnetcore/aspcore-mvc-ado-pg-up.dockerfile
  15. 0 20
      frameworks/CSharp/aspnetcore/aspcore-mvc-ado-pg.dockerfile
  16. 0 20
      frameworks/CSharp/aspnetcore/aspcore-mvc-dap-my.dockerfile
  17. 0 20
      frameworks/CSharp/aspnetcore/aspcore-mvc-dap-pg-up.dockerfile
  18. 0 20
      frameworks/CSharp/aspnetcore/aspcore-mvc-dap-pg.dockerfile
  19. 0 20
      frameworks/CSharp/aspnetcore/aspcore-mvc-ef-pg.dockerfile
  20. 0 19
      frameworks/CSharp/aspnetcore/aspcore-mvc.dockerfile
  21. 0 20
      frameworks/CSharp/aspnetcore/aspcore-mw-ado-my.dockerfile
  22. 0 20
      frameworks/CSharp/aspnetcore/aspcore-mw-ado-pg-up.dockerfile
  23. 0 20
      frameworks/CSharp/aspnetcore/aspcore-mw-ado-pg.dockerfile
  24. 0 20
      frameworks/CSharp/aspnetcore/aspcore-mw-dap-my.dockerfile
  25. 0 20
      frameworks/CSharp/aspnetcore/aspcore-mw-dap-pg-up.dockerfile
  26. 0 20
      frameworks/CSharp/aspnetcore/aspcore-mw-dap-pg.dockerfile
  27. 0 20
      frameworks/CSharp/aspnetcore/aspcore-mw-ef-pg.dockerfile
  28. 0 19
      frameworks/CSharp/aspnetcore/aspcore-mw-json.dockerfile
  29. 0 19
      frameworks/CSharp/aspnetcore/aspcore-mw.dockerfile
  30. 0 21
      frameworks/CSharp/aspnetcore/aspcore.dockerfile
  31. 15 0
      frameworks/CSharp/aspnetcore/aspnetcore-minimal.dockerfile
  32. 15 0
      frameworks/CSharp/aspnetcore/aspnetcore-mvc.dockerfile
  33. 15 0
      frameworks/CSharp/aspnetcore/aspnetcore-mysql.dockerfile
  34. 15 0
      frameworks/CSharp/aspnetcore/aspnetcore.dockerfile
  35. 93 471
      frameworks/CSharp/aspnetcore/benchmark_config.json
  36. 23 304
      frameworks/CSharp/aspnetcore/config.toml
  37. 398 0
      frameworks/CSharp/aspnetcore/src/Minimal/.gitignore
  38. 9 0
      frameworks/CSharp/aspnetcore/src/Minimal/AppSettings.cs
  39. 31 0
      frameworks/CSharp/aspnetcore/src/Minimal/Database/BatchUpdateString.cs
  40. 120 0
      frameworks/CSharp/aspnetcore/src/Minimal/Database/Db.cs
  41. 17 0
      frameworks/CSharp/aspnetcore/src/Minimal/Minimal.csproj
  42. 6 0
      frameworks/CSharp/aspnetcore/src/Minimal/Models/Fortune.cs
  43. 9 0
      frameworks/CSharp/aspnetcore/src/Minimal/Models/ViewModel.cs
  44. 11 0
      frameworks/CSharp/aspnetcore/src/Minimal/Models/World.cs
  45. 7 0
      frameworks/CSharp/aspnetcore/src/Minimal/NuGet.Config
  46. 63 0
      frameworks/CSharp/aspnetcore/src/Minimal/Program.cs
  47. 6 0
      frameworks/CSharp/aspnetcore/src/Minimal/README.md
  48. 2 0
      frameworks/CSharp/aspnetcore/src/Minimal/Templates/Fortunes.cshtml
  49. 10 0
      frameworks/CSharp/aspnetcore/src/Minimal/Templates/_ViewImports.cshtml
  50. 8 0
      frameworks/CSharp/aspnetcore/src/Minimal/appsettings.Development.json
  51. 10 0
      frameworks/CSharp/aspnetcore/src/Minimal/appsettings.json
  52. 174 0
      frameworks/CSharp/aspnetcore/src/Minimal/minimal.benchmarks.yml
  53. 398 0
      frameworks/CSharp/aspnetcore/src/Mvc/.gitignore
  54. 6 0
      frameworks/CSharp/aspnetcore/src/Mvc/AppSettings.cs
  55. 18 0
      frameworks/CSharp/aspnetcore/src/Mvc/Controllers/FortunesController.cs
  56. 27 0
      frameworks/CSharp/aspnetcore/src/Mvc/Controllers/HomeController.cs
  57. 18 0
      frameworks/CSharp/aspnetcore/src/Mvc/Controllers/MultipleQueriesController.cs
  58. 18 0
      frameworks/CSharp/aspnetcore/src/Mvc/Controllers/SingleQueryController.cs
  59. 18 0
      frameworks/CSharp/aspnetcore/src/Mvc/Controllers/UpdatesController.cs
  60. 18 0
      frameworks/CSharp/aspnetcore/src/Mvc/Database/ApplicationDbContext.cs
  61. 87 0
      frameworks/CSharp/aspnetcore/src/Mvc/Database/Db.cs
  62. 28 0
      frameworks/CSharp/aspnetcore/src/Mvc/Models/Fortune.cs
  63. 20 0
      frameworks/CSharp/aspnetcore/src/Mvc/Models/World.cs
  64. 16 0
      frameworks/CSharp/aspnetcore/src/Mvc/Mvc.csproj
  65. 7 0
      frameworks/CSharp/aspnetcore/src/Mvc/NuGet.Config
  66. 44 0
      frameworks/CSharp/aspnetcore/src/Mvc/Program.cs
  67. 6 0
      frameworks/CSharp/aspnetcore/src/Mvc/README.md
  68. 12 0
      frameworks/CSharp/aspnetcore/src/Mvc/Views/Fortunes/Index.cshtml
  69. 7 0
      frameworks/CSharp/aspnetcore/src/Mvc/Views/Home/Index.cshtml
  70. 8 0
      frameworks/CSharp/aspnetcore/src/Mvc/appsettings.Development.json
  71. 9 0
      frameworks/CSharp/aspnetcore/src/Mvc/appsettings.json
  72. 119 0
      frameworks/CSharp/aspnetcore/src/Mvc/mvc.benchmarks.yml
  73. 15 0
      frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.Caching.cs
  74. 66 0
      frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.Fortunes.cs
  75. 235 0
      frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.HttpConnection.cs
  76. 40 0
      frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.Json.cs
  77. 41 0
      frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.MultipleQueries.cs
  78. 28 0
      frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.Plaintext.cs
  79. 40 0
      frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.SingleQuery.cs
  80. 40 0
      frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.Updates.cs
  81. 208 0
      frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.cs
  82. 62 0
      frameworks/CSharp/aspnetcore/src/Platform/BenchmarkConfigurationHelpers.cs
  83. 62 0
      frameworks/CSharp/aspnetcore/src/Platform/BufferExtensions.cs
  84. 142 0
      frameworks/CSharp/aspnetcore/src/Platform/BufferWriter.cs
  85. 241 0
      frameworks/CSharp/aspnetcore/src/Platform/ChunkedBufferWriter.cs
  86. 10 0
      frameworks/CSharp/aspnetcore/src/Platform/Configuration/AppSettings.cs
  87. 41 0
      frameworks/CSharp/aspnetcore/src/Platform/Data/BatchUpdateString.cs
  88. 13 0
      frameworks/CSharp/aspnetcore/src/Platform/Data/CachedWorld.cs
  89. 24 0
      frameworks/CSharp/aspnetcore/src/Platform/Data/FortuneUtf16.cs
  90. 24 0
      frameworks/CSharp/aspnetcore/src/Platform/Data/FortuneUtf8.cs
  91. 9 0
      frameworks/CSharp/aspnetcore/src/Platform/Data/JsonMessage.cs
  92. 31 0
      frameworks/CSharp/aspnetcore/src/Platform/Data/Random.cs
  93. 256 0
      frameworks/CSharp/aspnetcore/src/Platform/Data/RawDbMySqlConnector.cs
  94. 251 0
      frameworks/CSharp/aspnetcore/src/Platform/Data/RawDbNpgsql.cs
  95. 15 0
      frameworks/CSharp/aspnetcore/src/Platform/Data/World.cs
  96. 66 0
      frameworks/CSharp/aspnetcore/src/Platform/DateHeader.cs
  97. 6 0
      frameworks/CSharp/aspnetcore/src/Platform/Directory.Build.props
  98. 3 0
      frameworks/CSharp/aspnetcore/src/Platform/Directory.Build.targets
  99. 28 0
      frameworks/CSharp/aspnetcore/src/Platform/HttpApplication.cs
  100. 16 0
      frameworks/CSharp/aspnetcore/src/Platform/IHttpConnection.cs

+ 30 - 10
frameworks/CSharp/aspnetcore/Benchmarks.sln

@@ -3,24 +3,44 @@ Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio Version 17
 VisualStudioVersion = 17.0.31717.71
 MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlatformBenchmarks", "PlatformBenchmarks\PlatformBenchmarks.csproj", "{047A5FF4-56BB-4BEF-9DCF-9B6051365423}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Minimal", "src\Minimal\Minimal.csproj", "{6726997E-E741-4882-9307-066E7DC305AB}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "Benchmarks\Benchmarks.csproj", "{C65B0C4A-B242-4A03-AC80-4B1DC6C1DF57}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc", "src\Mvc\Mvc.csproj", "{03750EC4-B6AF-4F73-8674-5C37C7B61667}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Platform", "src\Platform\Platform.csproj", "{1D73EC03-15CC-4D7C-B919-09DEA12D6EEC}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug_Database|Any CPU = Debug_Database|Any CPU
 		Debug|Any CPU = Debug|Any CPU
+		Release_Database|Any CPU = Release_Database|Any CPU
 		Release|Any CPU = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{047A5FF4-56BB-4BEF-9DCF-9B6051365423}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{047A5FF4-56BB-4BEF-9DCF-9B6051365423}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{047A5FF4-56BB-4BEF-9DCF-9B6051365423}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{047A5FF4-56BB-4BEF-9DCF-9B6051365423}.Release|Any CPU.Build.0 = Release|Any CPU
-		{C65B0C4A-B242-4A03-AC80-4B1DC6C1DF57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{C65B0C4A-B242-4A03-AC80-4B1DC6C1DF57}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{C65B0C4A-B242-4A03-AC80-4B1DC6C1DF57}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{C65B0C4A-B242-4A03-AC80-4B1DC6C1DF57}.Release|Any CPU.Build.0 = Release|Any CPU
+		{6726997E-E741-4882-9307-066E7DC305AB}.Debug_Database|Any CPU.ActiveCfg = Debug|Any CPU
+		{6726997E-E741-4882-9307-066E7DC305AB}.Debug_Database|Any CPU.Build.0 = Debug|Any CPU
+		{6726997E-E741-4882-9307-066E7DC305AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6726997E-E741-4882-9307-066E7DC305AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6726997E-E741-4882-9307-066E7DC305AB}.Release_Database|Any CPU.ActiveCfg = Release|Any CPU
+		{6726997E-E741-4882-9307-066E7DC305AB}.Release_Database|Any CPU.Build.0 = Release|Any CPU
+		{6726997E-E741-4882-9307-066E7DC305AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6726997E-E741-4882-9307-066E7DC305AB}.Release|Any CPU.Build.0 = Release|Any CPU
+		{03750EC4-B6AF-4F73-8674-5C37C7B61667}.Debug_Database|Any CPU.ActiveCfg = Debug|Any CPU
+		{03750EC4-B6AF-4F73-8674-5C37C7B61667}.Debug_Database|Any CPU.Build.0 = Debug|Any CPU
+		{03750EC4-B6AF-4F73-8674-5C37C7B61667}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{03750EC4-B6AF-4F73-8674-5C37C7B61667}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{03750EC4-B6AF-4F73-8674-5C37C7B61667}.Release_Database|Any CPU.ActiveCfg = Release|Any CPU
+		{03750EC4-B6AF-4F73-8674-5C37C7B61667}.Release_Database|Any CPU.Build.0 = Release|Any CPU
+		{03750EC4-B6AF-4F73-8674-5C37C7B61667}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{03750EC4-B6AF-4F73-8674-5C37C7B61667}.Release|Any CPU.Build.0 = Release|Any CPU
+		{1D73EC03-15CC-4D7C-B919-09DEA12D6EEC}.Debug_Database|Any CPU.ActiveCfg = Debug_Database|Any CPU
+		{1D73EC03-15CC-4D7C-B919-09DEA12D6EEC}.Debug_Database|Any CPU.Build.0 = Debug_Database|Any CPU
+		{1D73EC03-15CC-4D7C-B919-09DEA12D6EEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{1D73EC03-15CC-4D7C-B919-09DEA12D6EEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{1D73EC03-15CC-4D7C-B919-09DEA12D6EEC}.Release_Database|Any CPU.ActiveCfg = Release_Database|Any CPU
+		{1D73EC03-15CC-4D7C-B919-09DEA12D6EEC}.Release_Database|Any CPU.Build.0 = Release_Database|Any CPU
+		{1D73EC03-15CC-4D7C-B919-09DEA12D6EEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{1D73EC03-15CC-4D7C-B919-09DEA12D6EEC}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 0 - 4
frameworks/CSharp/aspnetcore/Benchmarks/appsettings.postgresql.updates.json

@@ -1,4 +0,0 @@
-{
-  "ConnectionString": "Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;SSL Mode=Disable;Maximum Pool Size=18;NoResetOnClose=true;Enlist=false;Max Auto Prepare=4;Multiplexing=true;Write Coalescing Buffer Threshold Bytes=1000",
-  "Database": "postgresql"
-}

+ 0 - 6
frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.cs

@@ -57,13 +57,7 @@ public sealed partial class BenchmarkApplication
     }
 
 #if DATABASE
-#if NPGSQL
     private readonly static SliceFactory<List<FortuneUtf8>> FortunesTemplateFactory = RazorSlice.ResolveSliceFactory<List<FortuneUtf8>>("/Templates/FortunesUtf8.cshtml");
-#elif MYSQLCONNECTOR
-    private readonly static SliceFactory<List<FortuneUtf16>> FortunesTemplateFactory = RazorSlice.ResolveSliceFactory<List<FortuneUtf16>>("/Templates/FortunesUtf16.cshtml");
-#else
-#error "DATABASE defined by neither NPGSQL nor MYSQLCONNECTOR are defined"
-#endif
 #endif
 
     [ThreadStatic]

+ 3 - 51
frameworks/CSharp/aspnetcore/README.md

@@ -1,58 +1,10 @@
-# ASP.NET Core Tests on Windows and Linux
+# ASP.NET Core benchmarks
 
-See [.NET Core](http://dot.net) and [ASP.NET Core](https://github.com/aspnet) for more information.
-
-This includes tests for plaintext and json serialization.
+See [.NET Core](http://dot.net) and [ASP.NET Core](https://github.com/dotnet/aspnetcore) for more information.
 
 ## Infrastructure Software Versions
 
 **Language**
 
-* C# 7.0
-
-**Platforms**
-
-* .NET Core (Windows and Linux)
-
-**Web Servers**
-
-* [Kestrel](https://github.com/aspnet/KestrelHttpServer)
-* [HttpSys](https://github.com/aspnet/HttpSysServer)
-
-**Web Stack**
-
-* ASP.NET Core
-* ASP.NET Core MVC
-
-## Paths & Source for Tests
+* C# 8.0
 
-* [Plaintext](Benchmarks/Middleware/PlaintextMiddleware.cs): "/plaintext"
-* [Plaintext MVC](Benchmarks/Controllers/HomeController.cs): "/mvc/plaintext"
-* [JSON Serialization](Benchmarks/Middleware/JsonMiddleware.cs): "/json"
-* [JSON Serialization MVC](Benchmarks/Controllers/HomeController.cs): "/mvc/json"
-* [JSON Serialization Utf8Json](Benchmarks/Middleware/Utf8JsonMiddleware.cs): "/utf8json"
-* [JSON Serialization SpanJson](Benchmarks/Middleware/SpanJsonMiddleware.cs): "/spanjson"
-* [Single Query Raw](Benchmarks/Middleware/SingleQueryRawMiddleware.cs): "/db/raw"
-* [Single Query EF](Benchmarks/Middleware/SingleQueryEfMiddleware.cs): "/db/ef"
-* [Single Query Dapper](Benchmarks/Middleware/SingleQueryDapperMiddleware.cs): "/db/dapper"
-* [Single Query MVC Raw](Benchmarks/Controllers/SingleQueryController.cs): "/mvc/db/raw"
-* [Single Query MVC EF](Benchmarks/Controllers/SingleQueryController.cs): "/mvc/db/ef"
-* [Single Query MVC Dapper](Benchmarks/Controllers/SingleQueryController.cs): "/mvc/db/dapper"
-* [Multiple Queries Raw](Benchmarks/Middleware/MultipleQueriesRawMiddleware.cs): "/queries/raw"
-* [Multiple Queries EF](Benchmarks/Middleware/MultipleQueriesEfMiddleware.cs): "/queries/ef"
-* [Multiple Queries Dapper](Benchmarks/Middleware/MultipleQueriesDapperMiddleware.cs): "/queries/dapper"
-* [Multiple Queries MVC Raw](Benchmarks/Controllers/MultipleQueriesController.cs): "/mvc/queries/raw"
-* [Multiple Queries MVC EF](Benchmarks/Controllers/MultipleQueriesController.cs): "/mvc/queries/ef"
-* [Multiple Queries MVC Dapper](Benchmarks/Controllers/MultipleQueriesController.cs): "/mvc/queries/dapper"
-* [Data Updates Raw](Benchmarks/Middleware/MultipleUpdatesRawMiddleware.cs): "/updates/raw"
-* [Data Updates EF](Benchmarks/Middleware/MultipleUpdatesEfMiddleware.cs): "/updates/ef"
-* [Data Updates Dapper](Benchmarks/Middleware/MultipleUpdatesDapperMiddleware.cs): "/updates/dapper"
-* [Data Updates MVC Raw](Benchmarks/Controllers/MultipleUpdatesController.cs): "/mvc/updates/raw"
-* [Data Updates MVC EF](Benchmarks/Controllers/MultipleUpdatesController.cs): "/mvc/updates/ef"
-* [Data Updates MVC Dapper](Benchmarks/Controllers/MultipleUpdatesController.cs): "/mvc/updates/dapper"
-* [Fortunes Raw](Benchmarks/Middleware/FortunesRawMiddleware.cs): "/fortunes/raw"
-* [Fortunes EF](Benchmarks/Middleware/FortunesEfMiddleware.cs): "/fortunes/ef"
-* [Fortunes Dapper](Benchmarks/Middleware/FortunesDapperMiddleware.cs): "/fortunes/dapper"
-* [Fortunes MVC Raw](Benchmarks/Controllers/FortunesController.cs): "/mvc/fortunes/raw"
-* [Fortunes MVC EF](Benchmarks/Controllers/FortunesController.cs): "/mvc/fortunes/ef"
-* [Fortunes MVC Dapper](Benchmarks/Controllers/FortunesController.cs): "/mvc/fortunes/dapper"

+ 0 - 0
frameworks/CSharp/aspnetcore/Benchmarks/appsettings.mysql.json → frameworks/CSharp/aspnetcore/appsettings.mysql.json


+ 0 - 0
frameworks/CSharp/aspnetcore/Benchmarks/appsettings.postgresql.json → frameworks/CSharp/aspnetcore/appsettings.postgresql.json


+ 0 - 20
frameworks/CSharp/aspnetcore/aspcore-ado-my.dockerfile

@@ -1,20 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY PlatformBenchmarks .
-RUN dotnet publish -c Release -o out /p:DatabaseProvider=MySqlConnector
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY PlatformBenchmarks/appsettings.mysql.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "PlatformBenchmarks.dll"]

+ 0 - 20
frameworks/CSharp/aspnetcore/aspcore-ado-pg-up.dockerfile

@@ -1,20 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY PlatformBenchmarks .
-RUN dotnet publish -c Release -o out /p:DatabaseProvider=Npgsql
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY PlatformBenchmarks/appsettings.postgresql.updates.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "PlatformBenchmarks.dll"]

+ 0 - 20
frameworks/CSharp/aspnetcore/aspcore-ado-pg.dockerfile

@@ -1,20 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY PlatformBenchmarks .
-RUN dotnet publish -c Release -o out /p:DatabaseProvider=Npgsql
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY PlatformBenchmarks/appsettings.postgresql.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "PlatformBenchmarks.dll"]

+ 0 - 17
frameworks/CSharp/aspnetcore/aspcore-aot-ado-pg-up.dockerfile

@@ -1,17 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-RUN apt-get update
-RUN apt-get -yqq install clang zlib1g-dev
-WORKDIR /app
-COPY PlatformBenchmarks .
-RUN dotnet publish -c Release -o out -p:PublishAot=true -p:DatabaseProvider=Npgsql
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY PlatformBenchmarks/appsettings.postgresql.updates.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["./PlatformBenchmarks"]

+ 0 - 17
frameworks/CSharp/aspnetcore/aspcore-aot-ado-pg.dockerfile

@@ -1,17 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-RUN apt-get update
-RUN apt-get -yqq install clang zlib1g-dev
-WORKDIR /app
-COPY PlatformBenchmarks .
-RUN dotnet publish -c Release -o out -p:PublishAot=true -p:DatabaseProvider=Npgsql
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY PlatformBenchmarks/appsettings.postgresql.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["./PlatformBenchmarks"]

+ 0 - 18
frameworks/CSharp/aspnetcore/aspcore-aot.dockerfile

@@ -1,18 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-RUN apt-get update
-RUN apt-get -yqq install clang zlib1g-dev
-WORKDIR /app
-COPY PlatformBenchmarks .
-RUN dotnet publish -c Release -o out -p:PublishAot=true
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-ENV DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS 1
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY Benchmarks/appsettings.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["./PlatformBenchmarks"]

+ 0 - 20
frameworks/CSharp/aspnetcore/aspcore-mvc-ado-my.dockerfile

@@ -1,20 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY Benchmarks .
-RUN dotnet publish -c Release -o out
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY Benchmarks/appsettings.mysql.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "Benchmarks.dll", "scenarios=MvcDbSingleQueryRaw,MvcDbMultiQueryRaw,MvcDbMultiUpdateRaw,MvcDbFortunesRaw"]

+ 0 - 20
frameworks/CSharp/aspnetcore/aspcore-mvc-ado-pg-up.dockerfile

@@ -1,20 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY Benchmarks .
-RUN dotnet publish -c Release -o out
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY Benchmarks/appsettings.postgresql.updates.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "Benchmarks.dll", "scenarios=MvcDbMultiUpdateRaw"]

+ 0 - 20
frameworks/CSharp/aspnetcore/aspcore-mvc-ado-pg.dockerfile

@@ -1,20 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY Benchmarks .
-RUN dotnet publish -c Release -o out
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY Benchmarks/appsettings.postgresql.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "Benchmarks.dll", "scenarios=MvcDbSingleQueryRaw,MvcDbMultiQueryRaw,MvcDbFortunesRaw"]

+ 0 - 20
frameworks/CSharp/aspnetcore/aspcore-mvc-dap-my.dockerfile

@@ -1,20 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY Benchmarks .
-RUN dotnet publish -c Release -o out
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY Benchmarks/appsettings.mysql.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "Benchmarks.dll", "scenarios=MvcDbSingleQueryDapper,MvcDbMultiQueryDapper,MvcDbMultiUpdateDapper,MvcDbFortunesDapper"]

+ 0 - 20
frameworks/CSharp/aspnetcore/aspcore-mvc-dap-pg-up.dockerfile

@@ -1,20 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY Benchmarks .
-RUN dotnet publish -c Release -o out
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY Benchmarks/appsettings.postgresql.updates.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "Benchmarks.dll", "scenarios=MvcDbMultiUpdateDapper"]

+ 0 - 20
frameworks/CSharp/aspnetcore/aspcore-mvc-dap-pg.dockerfile

@@ -1,20 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY Benchmarks .
-RUN dotnet publish -c Release -o out
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY Benchmarks/appsettings.postgresql.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "Benchmarks.dll", "scenarios=MvcDbSingleQueryDapper,MvcDbMultiQueryDapper,MvcDbFortunesDapper"]

+ 0 - 20
frameworks/CSharp/aspnetcore/aspcore-mvc-ef-pg.dockerfile

@@ -1,20 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY Benchmarks .
-RUN dotnet publish -c Release -o out
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY Benchmarks/appsettings.postgresql.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "Benchmarks.dll", "scenarios=MvcDbSingleQueryEf,MvcDbMultiQueryEf,MvcDbFortunesEf"]

+ 0 - 19
frameworks/CSharp/aspnetcore/aspcore-mvc.dockerfile

@@ -1,19 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY Benchmarks .
-RUN dotnet publish -c Release -o out
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "Benchmarks.dll", "scenarios=mvcplaintext,mvcjson"]

+ 0 - 20
frameworks/CSharp/aspnetcore/aspcore-mw-ado-my.dockerfile

@@ -1,20 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY Benchmarks .
-RUN dotnet publish -c Release -o out
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY Benchmarks/appsettings.mysql.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "Benchmarks.dll", "scenarios=DbSingleQueryRaw,DbMultiQueryRaw,DbMultiUpdateRaw,DbFortunesRaw"]

+ 0 - 20
frameworks/CSharp/aspnetcore/aspcore-mw-ado-pg-up.dockerfile

@@ -1,20 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY Benchmarks .
-RUN dotnet publish -c Release -o out
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY Benchmarks/appsettings.postgresql.updates.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "Benchmarks.dll", "scenarios=DbMultiUpdateRaw"]

+ 0 - 20
frameworks/CSharp/aspnetcore/aspcore-mw-ado-pg.dockerfile

@@ -1,20 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY Benchmarks .
-RUN dotnet publish -c Release -o out
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY Benchmarks/appsettings.postgresql.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "Benchmarks.dll", "scenarios=DbSingleQueryRaw,DbMultiQueryRaw,DbFortunesRaw"]

+ 0 - 20
frameworks/CSharp/aspnetcore/aspcore-mw-dap-my.dockerfile

@@ -1,20 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY Benchmarks .
-RUN dotnet publish -c Release -o out
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY Benchmarks/appsettings.mysql.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "Benchmarks.dll", "scenarios=DbSingleQueryDapper,DbMultiQueryDapper,DbMultiUpdateDapper,DbFortunesDapper"]

+ 0 - 20
frameworks/CSharp/aspnetcore/aspcore-mw-dap-pg-up.dockerfile

@@ -1,20 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY Benchmarks .
-RUN dotnet publish -c Release -o out
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY Benchmarks/appsettings.postgresql.updates.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "Benchmarks.dll", "scenarios=DbMultiUpdateDapper"]

+ 0 - 20
frameworks/CSharp/aspnetcore/aspcore-mw-dap-pg.dockerfile

@@ -1,20 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY Benchmarks .
-RUN dotnet publish -c Release -o out
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY Benchmarks/appsettings.postgresql.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "Benchmarks.dll", "scenarios=DbSingleQueryDapper,DbMultiQueryDapper,DbFortunesDapper"]

+ 0 - 20
frameworks/CSharp/aspnetcore/aspcore-mw-ef-pg.dockerfile

@@ -1,20 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY Benchmarks .
-RUN dotnet publish -c Release -o out
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY Benchmarks/appsettings.postgresql.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "Benchmarks.dll", "scenarios=DbSingleQueryEf,DbMultiQueryEf,DbFortunesEf"]

+ 0 - 19
frameworks/CSharp/aspnetcore/aspcore-mw-json.dockerfile

@@ -1,19 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY Benchmarks .
-RUN dotnet publish -c Release -o out
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "Benchmarks.dll", "scenarios=json"]

+ 0 - 19
frameworks/CSharp/aspnetcore/aspcore-mw.dockerfile

@@ -1,19 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY Benchmarks .
-RUN dotnet publish -c Release -o out
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "Benchmarks.dll", "scenarios=plaintext"]

+ 0 - 21
frameworks/CSharp/aspnetcore/aspcore.dockerfile

@@ -1,21 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:7.0.100 AS build
-WORKDIR /app
-COPY PlatformBenchmarks .
-RUN dotnet publish -c Release -o out
-
-FROM mcr.microsoft.com/dotnet/aspnet:7.0.0 AS runtime
-ENV ASPNETCORE_URLS http://+:8080
-ENV DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS 1
-
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
-
-WORKDIR /app
-COPY --from=build /app/out ./
-COPY Benchmarks/appsettings.json ./appsettings.json
-
-EXPOSE 8080
-
-ENTRYPOINT ["dotnet", "PlatformBenchmarks.dll"]

+ 15 - 0
frameworks/CSharp/aspnetcore/aspnetcore-minimal.dockerfile

@@ -0,0 +1,15 @@
+FROM mcr.microsoft.com/dotnet/sdk:8.0.100-rc.2 AS build
+WORKDIR /app
+COPY src/Minimal .
+RUN dotnet publish -c Release -o out
+
+FROM mcr.microsoft.com/dotnet/aspnet:8.0.0-rc.2 AS runtime
+ENV URLS http://+:8080
+
+WORKDIR /app
+COPY --from=build /app/out ./
+COPY appsettings.postgresql.json ./appsettings.json
+
+EXPOSE 8080
+
+ENTRYPOINT ["dotnet", "Minimal.dll"]

+ 15 - 0
frameworks/CSharp/aspnetcore/aspnetcore-mvc.dockerfile

@@ -0,0 +1,15 @@
+FROM mcr.microsoft.com/dotnet/sdk:8.0.100-rc.2 AS build
+WORKDIR /app
+COPY src/Mvc .
+RUN dotnet publish -c Release -o out
+
+FROM mcr.microsoft.com/dotnet/aspnet:8.0.0-rc.2 AS runtime
+ENV URLS http://+:8080
+
+WORKDIR /app
+COPY --from=build /app/out ./
+COPY appsettings.postgresql.json ./appsettings.json
+
+EXPOSE 8080
+
+ENTRYPOINT ["dotnet", "Mvc.dll"]

+ 15 - 0
frameworks/CSharp/aspnetcore/aspnetcore-mysql.dockerfile

@@ -0,0 +1,15 @@
+FROM mcr.microsoft.com/dotnet/sdk:8.0.100-rc.2 AS build
+WORKDIR /app
+COPY src/Platform .
+RUN dotnet publish -c Release -o out /p:DatabaseProvider=MySqlConnector
+
+FROM mcr.microsoft.com/dotnet/aspnet:8.0.0-rc.2 AS runtime
+ENV URLS http://+:8080
+
+WORKDIR /app
+COPY --from=build /app/out ./
+COPY appsettings.mysql.json ./appsettings.json
+
+EXPOSE 8080
+
+ENTRYPOINT ["dotnet", "Platform.dll"]

+ 15 - 0
frameworks/CSharp/aspnetcore/aspnetcore.dockerfile

@@ -0,0 +1,15 @@
+FROM mcr.microsoft.com/dotnet/sdk:8.0.100-rc.2 AS build
+WORKDIR /app
+COPY src/Platform .
+RUN dotnet publish -c Release -o out /p:DatabaseProvider=Npgsql
+
+FROM mcr.microsoft.com/dotnet/aspnet:8.0.0-rc.2 AS runtime
+ENV URLS http://+:8080
+
+WORKDIR /app
+COPY --from=build /app/out ./
+COPY appsettings.postgresql.json ./appsettings.json
+
+EXPOSE 8080
+
+ENTRYPOINT ["dotnet", "Platform.dll"]

+ 93 - 471
frameworks/CSharp/aspnetcore/benchmark_config.json

@@ -1,474 +1,96 @@
 {
-  "framework": "aspcore",
-  "tests": [{
-    "default": {
-      "plaintext_url": "/plaintext",
-      "json_url": "/json",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Platform",
-      "database": "None",
-      "framework": "ASP.NET Core",
-      "language": "C#",
-      "orm": "Raw",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [Platform]",
-      "notes": "",
-      "versus": "aspcore"
-    },
-    "aot": {
-      "plaintext_url": "/plaintext",
-      "json_url": "/json",
-      "port": 8080,
-      "approach": "Stripped",
-      "classification": "Platform",
-      "database": "None",
-      "framework": "ASP.NET Core",
-      "language": "C#",
-      "orm": "Raw",
-      "platform": ".NET",
-      "flavor": "NativeAOT",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [Platform, NativeAOT]",
-      "notes": "",
-      "versus": "aspcore-aot"
-    },
-    "ado-pg": {
-      "fortune_url": "/fortunes",
-      "db_url": "/db",
-      "query_url": "/queries/",
-      "cached_query_url": "/cached-worlds/",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Platform",
-      "database": "Postgres",
-      "framework": "ASP.NET Core",
-      "language": "C#",
-      "orm": "Raw",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [Platform, Pg]",
-      "notes": "",
-      "versus": "aspcore-ado-pg"
-    },
-    "aot-ado-pg": {
-      "fortune_url": "/fortunes",
-      "db_url": "/db",
-      "query_url": "/queries/",
-      "cached_query_url": "/cached-worlds/",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Platform",
-      "database": "Postgres",
-      "framework": "ASP.NET Core",
-      "language": "C#",
-      "orm": "Raw",
-      "platform": ".NET",
-      "flavor": "NativeAOT",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [Platform, NativeAOT, Pg]",
-      "notes": "",
-      "versus": "aspcore-aot-ado-pg"
-    },
-    "ado-pg-up": {
-      "update_url": "/updates/",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Platform",
-      "database": "Postgres",
-      "framework": "ASP.NET Core",
-      "language": "C#",
-      "orm": "Raw",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [Platform, Pg]",
-      "notes": "",
-      "versus": "aspcore-ado-pg-up"
-    },
-    "aot-ado-pg-up": {
-      "update_url": "/updates/",
-      "port": 8080,
-      "approach": "Stripped",
-      "classification": "Platform",
-      "database": "Postgres",
-      "framework": "ASP.NET Core",
-      "language": "C#",
-      "orm": "Raw",
-      "platform": ".NET",
-      "flavor": "NativeAOT",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [Platform, NativeAOT, Pg]",
-      "notes": "",
-      "versus": "aspcore-aot-ado-pg-up"
-    },
-    "mw": {
-      "plaintext_url": "/plaintext",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Micro",
-      "database": "None",
-      "framework": "ASP.NET Core [Middleware]",
-      "language": "C#",
-      "orm": "Raw",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [Middleware]",
-      "notes": "",
-      "versus": "aspcore"
-    },
-    "mw-json": {
-      "json_url": "/json",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Micro",
-      "database": "None",
-      "framework": "ASP.NET Core [Middleware]",
-      "language": "C#",
-      "orm": "Raw",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [Middleware]",
-      "notes": "",
-      "versus": "aspcore"
-    },
-    "mw-ado-pg": {
-      "db_url": "/db/raw",
-      "query_url": "/queries/raw?queries=",
-      "fortune_url": "/fortunes/raw",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Micro",
-      "database": "Postgres",
-      "framework": "ASP.NET Core [Middleware]",
-      "language": "C#",
-      "orm": "Raw",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [Middleware, Pg]",
-      "notes": "",
-      "versus": "aspcore-ado-pg"
-    },
-    "mw-ado-pg-up": {
-      "update_url": "/updates/raw?queries=",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Micro",
-      "database": "Postgres",
-      "framework": "ASP.NET Core [Middleware]",
-      "language": "C#",
-      "orm": "Raw",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [Middleware, Pg]",
-      "notes": "",
-      "versus": "aspcore-ado-pg"
-    },
-    "mw-ef-pg": {
-      "db_url": "/db/ef",
-      "query_url": "/queries/ef?queries=",
-      "fortune_url": "/fortunes/ef",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Micro",
-      "database": "Postgres",
-      "framework": "ASP.NET Core [Middleware]",
-      "language": "C#",
-      "orm": "Full",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [Middleware, Pg, EF]",
-      "notes": "",
-      "versus": "aspcore-ado-pg"
-    },
-    "mw-dap-pg": {
-      "db_url": "/db/dapper",
-      "query_url": "/queries/dapper?queries=",
-      "fortune_url": "/fortunes/dapper",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Micro",
-      "database": "Postgres",
-      "framework": "ASP.NET Core [Middleware]",
-      "language": "C#",
-      "orm": "Micro",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [Middleware, Pg, Dapper]",
-      "notes": "",
-      "versus": "aspcore-ado-pg"
-    },
-    "mw-dap-pg-up": {
-      "update_url": "/updates/dapper?queries=",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Micro",
-      "database": "Postgres",
-      "framework": "ASP.NET Core [Middleware]",
-      "language": "C#",
-      "orm": "Micro",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [Middleware, Pg, Dapper]",
-      "notes": "",
-      "versus": "aspcore-ado-pg"
-    },
-    "mw-ado-my": {
-      "db_url": "/db/raw",
-      "query_url": "/queries/raw?queries=",
-      "update_url": "/updates/raw?queries=",
-      "fortune_url": "/fortunes/raw",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Micro",
-      "database": "MySQL",
-      "framework": "ASP.NET Core [Middleware]",
-      "language": "C#",
-      "orm": "Raw",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [Middleware, My]",
-      "notes": "",
-      "versus": "aspcore-ado-my"
-    },
-    "mw-dap-my": {
-      "db_url": "/db/dapper",
-      "query_url": "/queries/dapper?queries=",
-      "update_url": "/updates/dapper?queries=",
-      "fortune_url": "/fortunes/dapper",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Micro",
-      "database": "MySQL",
-      "framework": "ASP.NET Core [Middleware]",
-      "language": "C#",
-      "orm": "Micro",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [Middleware, My, Dapper]",
-      "notes": "",
-      "versus": "aspcore-ado-my"
-    },    
-    "mvc": {
-      "plaintext_url": "/mvc/plaintext",
-      "json_url": "/mvc/json",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Fullstack",
-      "database": "None",
-      "framework": "ASP.NET Core [MVC]",
-      "language": "C#",
-      "orm": "Raw",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "aspcore-mvc",
-      "notes": "",
-      "versus": "aspcore"
-    },
-    "mvc-ado-pg": {
-      "db_url": "/mvc/db/raw",
-      "query_url": "/mvc/queries/raw?queries=",
-      "fortune_url": "/mvc/fortunes/raw",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Fullstack",
-      "database": "Postgres",
-      "framework": "ASP.NET Core [MVC]",
-      "language": "C#",
-      "orm": "Raw",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [MVC, Pg]",
-      "notes": "",
-      "versus": "aspcore-ado-pg"
-    },
-    "mvc-ado-pg-up": {
-      "update_url": "/mvc/updates/raw?queries=",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Fullstack",
-      "database": "Postgres",
-      "framework": "ASP.NET Core [MVC]",
-      "language": "C#",
-      "orm": "Raw",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [MVC, Pg]",
-      "notes": "",
-      "versus": "aspcore-ado-pg"
-    },
-    "mvc-ef-pg": {
-      "db_url": "/mvc/db/ef",
-      "query_url": "/mvc/queries/ef?queries=",
-      "fortune_url": "/mvc/fortunes/ef",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Fullstack",
-      "database": "Postgres",
-      "framework": "ASP.NET Core [MVC]",
-      "language": "C#",
-      "orm": "Full",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [MVC, Pg, EF]",
-      "notes": "",
-      "versus": "aspcore-ado-pg"
-    },
-    "mvc-dap-pg": {
-      "db_url": "/mvc/db/dapper",
-      "query_url": "/mvc/queries/dapper?queries=",
-      "fortune_url": "/mvc/fortunes/dapper",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Fullstack",
-      "database": "Postgres",
-      "framework": "ASP.NET Core [MVC]",
-      "language": "C#",
-      "orm": "Micro",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [MVC, Pg, Dapper]",
-      "notes": "",
-      "versus": "aspcore-ado-pg"
-    },
-    "mvc-dap-pg-up": {
-      "update_url": "/mvc/updates/dapper?queries=",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Fullstack",
-      "database": "Postgres",
-      "framework": "ASP.NET Core [MVC]",
-      "language": "C#",
-      "orm": "Micro",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [MVC, Pg, Dapper]",
-      "notes": "",
-      "versus": "aspcore-ado-pg"
-    },
-    "mvc-ado-my": {
-      "db_url": "/mvc/db/raw",
-      "query_url": "/mvc/queries/raw?queries=",
-      "update_url": "/mvc/updates/raw?queries=",
-      "fortune_url": "/mvc/fortunes/raw",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Fullstack",
-      "database": "MySQL",
-      "framework": "ASP.NET Core [MVC]",
-      "language": "C#",
-      "orm": "Raw",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [MVC, My]",
-      "notes": "",
-      "versus": "aspcore-ado-my"
-    },
-    "mvc-dap-my": {
-      "db_url": "/mvc/db/dapper",
-      "query_url": "/mvc/queries/dapper?queries=",
-      "update_url": "/mvc/updates/dapper?queries=",
-      "fortune_url": "/mvc/fortunes/dapper",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Fullstack",
-      "database": "MySQL",
-      "framework": "ASP.NET Core [MVC]",
-      "language": "C#",
-      "orm": "Micro",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [MVC, My, Dapper]",
-      "notes": "",
-      "versus": "aspcore-ado-my"
-    },
-    "ado-my": {
-      "fortune_url": "/fortunes",
-      "db_url": "/db",
-      "query_url": "/queries/",
-      "update_url": "/updates/",
-      "cached_query_url": "/cached-worlds/",
-      "port": 8080,
-      "approach": "Realistic",
-      "classification": "Platform",
-      "database": "MySQL",
-      "framework": "ASP.NET Core",
-      "language": "C#",
-      "orm": "Raw",
-      "platform": ".NET",
-      "flavor": "CoreCLR",
-      "webserver": "Kestrel",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "ASP.NET Core [Platform, My]",
-      "notes": "",
-      "versus": "aspcore-ado-my"
+  "framework": "aspnetcore",
+  "tests": [
+    {
+      "default": {
+        "plaintext_url": "/plaintext",
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries/",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates/",
+        "cached_query_url": "/cached-worlds/",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Platform",
+        "database": "Postgres",
+        "framework": "ASP.NET Core",
+        "language": "C#",
+        "orm": "Raw",
+        "platform": ".NET",
+        "flavor": "CoreCLR",
+        "webserver": "Kestrel",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "ASP.NET Core [Platform, Pg]",
+        "notes": ""
+      },
+      "minimal": {
+        "plaintext_url": "/plaintext",
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries/",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates/",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "ASP.NET Core [Minimal APIs]",
+        "language": "C#",
+        "orm": "Micro",
+        "platform": ".NET",
+        "flavor": "CoreCLR",
+        "webserver": "Kestrel",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "ASP.NET Core [Minimal APIs, Pg, Dapper]",
+        "notes": "",
+        "versus": "aspnetcore"
+      },
+      "mvc": {
+        "plaintext_url": "/plaintext",
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries/",
+        "fortune_url": "/fortunes",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Fullstack",
+        "database": "Postgres",
+        "framework": "ASP.NET Core [MVC]",
+        "language": "C#",
+        "orm": "Full",
+        "platform": ".NET",
+        "flavor": "CoreCLR",
+        "webserver": "Kestrel",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "ASP.NET Core [MVC, Pg, EF]",
+        "notes": "",
+        "versus": "aspnetcore"
+      },
+      "mysql": {
+        "db_url": "/db",
+        "query_url": "/queries/",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates/",
+        "cached_query_url": "/cached-worlds/",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Platform",
+        "database": "MySQL",
+        "framework": "ASP.NET Core",
+        "language": "C#",
+        "orm": "Raw",
+        "platform": ".NET",
+        "flavor": "CoreCLR",
+        "webserver": "Kestrel",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "ASP.NET Core [Platform, My]",
+        "notes": ""
+      }
     }
-  }]
+  ]
 }

+ 23 - 304
frameworks/CSharp/aspnetcore/config.toml

@@ -1,10 +1,13 @@
 [framework]
 name = "aspnetcore"
 
-[ado-pg]
+[main]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
 urls.db = "/db"
 urls.query = "/queries/"
 urls.fortune = "/fortunes"
+urls.update = "/updates/"
 urls.cached_query = "/cached-worlds/"
 approach = "Realistic"
 classification = "Platform"
@@ -14,338 +17,54 @@ os = "Linux"
 orm = "Raw"
 platform = ".NET"
 webserver = "Kestrel"
-versus = "aspcore-ado-pg"
+versus = "None"
 
-[aot-ado-pg]
+[minimal]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
 urls.db = "/db"
 urls.query = "/queries/"
 urls.fortune = "/fortunes"
-urls.cached_query = "/cached-worlds/"
-approach = "Stripped"
-classification = "Platform"
-database = "Postgres"
-database_os = "Linux"
-os = "Linux"
-orm = "Raw"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore-aot-ado-pg"
-
-[ado-my]
-urls.db = "/db"
-urls.query = "/queries/"
 urls.update = "/updates/"
-urls.fortune = "/fortunes"
 urls.cached_query = "/cached-worlds/"
 approach = "Realistic"
-classification = "Platform"
-database = "MySQL"
-database_os = "Linux"
-os = "Linux"
-orm = "Raw"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore-ado-my"
-
-[mw-ef-pg]
-urls.db = "/db/ef"
-urls.query = "/queries/ef?queries="
-urls.fortune = "/fortunes/ef"
-approach = "Realistic"
 classification = "Micro"
 database = "Postgres"
 database_os = "Linux"
 os = "Linux"
-orm = "Full"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore-ado-pg"
-
-[mvc-ef-pg]
-urls.db = "/mvc/db/ef"
-urls.query = "/mvc/queries/ef?queries="
-urls.fortune = "/mvc/fortunes/ef"
-approach = "Realistic"
-classification = "Fullstack"
-database = "Postgres"
-database_os = "Linux"
-os = "Linux"
-orm = "Full"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore-ado-pg"
-
-[mw-json]
-urls.json = "/json"
-approach = "Realistic"
-classification = "Micro"
-database = "None"
-database_os = "Linux"
-os = "Linux"
-orm = "Raw"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore"
-
-[mvc]
-urls.plaintext = "/mvc/plaintext"
-urls.json = "/mvc/json"
-approach = "Realistic"
-classification = "Fullstack"
-database = "None"
-database_os = "Linux"
-os = "Linux"
-orm = "Raw"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore"
-
-[mw-ado-pg]
-urls.db = "/db/raw"
-urls.query = "/queries/raw?queries="
-urls.fortune = "/fortunes/raw"
-approach = "Realistic"
-classification = "Micro"
-database = "Postgres"
-database_os = "Linux"
-os = "Linux"
-orm = "Raw"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore-ado-pg"
-
-[mvc-ado-pg]
-urls.db = "/mvc/db/raw"
-urls.query = "/mvc/queries/raw?queries="
-urls.fortune = "/mvc/fortunes/raw"
-approach = "Realistic"
-classification = "Fullstack"
-database = "Postgres"
-database_os = "Linux"
-os = "Linux"
-orm = "Raw"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore-ado-pg"
-
-[mw-ado-my]
-urls.db = "/db/raw"
-urls.query = "/queries/raw?queries="
-urls.update = "/updates/raw?queries="
-urls.fortune = "/fortunes/raw"
-approach = "Realistic"
-classification = "Micro"
-database = "MySQL"
-database_os = "Linux"
-os = "Linux"
-orm = "Raw"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore-ado-my"
-
-[mvc-ado-my]
-urls.db = "/mvc/db/raw"
-urls.query = "/mvc/queries/raw?queries="
-urls.update = "/mvc/updates/raw?queries="
-urls.fortune = "/mvc/fortunes/raw"
-approach = "Realistic"
-classification = "Fullstack"
-database = "MySQL"
-database_os = "Linux"
-os = "Linux"
-orm = "Raw"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore-ado-my"
-
-[mvc-ado-pg-up]
-urls.update = "/mvc/updates/raw?queries="
-approach = "Realistic"
-classification = "Fullstack"
-database = "Postgres"
-database_os = "Linux"
-os = "Linux"
-orm = "Raw"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore-ado-pg"
-
-[mw-ado-pg-up]
-urls.update = "/updates/raw?queries="
-approach = "Realistic"
-classification = "Micro"
-database = "Postgres"
-database_os = "Linux"
-os = "Linux"
-orm = "Raw"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore-ado-pg"
-
-[mvc-dap-pg]
-urls.db = "/mvc/db/dapper"
-urls.query = "/mvc/queries/dapper?queries="
-urls.fortune = "/mvc/fortunes/dapper"
-approach = "Realistic"
-classification = "Fullstack"
-database = "Postgres"
-database_os = "Linux"
-os = "Linux"
-orm = "Micro"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore-ado-pg"
-
-[mw-dap-pg]
-urls.db = "/db/dapper"
-urls.query = "/queries/dapper?queries="
-urls.fortune = "/fortunes/dapper"
-approach = "Realistic"
-classification = "Micro"
-database = "Postgres"
-database_os = "Linux"
-os = "Linux"
-orm = "Micro"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore-ado-pg"
-
-[mvc-dap-my]
-urls.db = "/mvc/db/dapper"
-urls.query = "/mvc/queries/dapper?queries="
-urls.update = "/mvc/updates/dapper?queries="
-urls.fortune = "/mvc/fortunes/dapper"
-approach = "Realistic"
-classification = "Fullstack"
-database = "MySQL"
-database_os = "Linux"
-os = "Linux"
 orm = "Micro"
 platform = ".NET"
 webserver = "Kestrel"
-versus = "aspcore-ado-my"
-
-[mw-dap-my]
-urls.db = "/db/dapper"
-urls.query = "/queries/dapper?queries="
-urls.update = "/updates/dapper?queries="
-urls.fortune = "/fortunes/dapper"
-approach = "Realistic"
-classification = "Micro"
-database = "MySQL"
-database_os = "Linux"
-os = "Linux"
-orm = "Micro"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore-ado-my"
-
-[mw-ef-pg-up]
-urls.update = "/updates/ef?queries="
-approach = "Realistic"
-classification = "Micro"
-database = "Postgres"
-database_os = "Linux"
-os = "Linux"
-orm = "Full"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore-ado-pg"
-
-[mvc-ef-pg-up]
-urls.update = "/mvc/updates/ef?queries="
-approach = "Realistic"
-classification = "Fullstack"
-database = "Postgres"
-database_os = "Linux"
-os = "Linux"
-orm = "Full"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore-ado-pg"
-
-[main]
-urls.plaintext = "/plaintext"
-urls.json = "/json"
-approach = "Realistic"
-classification = "Platform"
-database = "None"
-database_os = "Linux"
-os = "Linux"
-orm = "Raw"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore"
+versus = "aspnetcore"
 
-[aot]
+[mvc]
 urls.plaintext = "/plaintext"
 urls.json = "/json"
-approach = "Stripped"
-classification = "Platform"
-database = "None"
-database_os = "Linux"
-os = "Linux"
-orm = "Raw"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore-aot"
-
-[mvc-dap-pg-up]
-urls.update = "/mvc/updates/dapper?queries="
+urls.db = "/db"
+urls.query = "/queries/"
+urls.fortune = "/fortunes"
+urls.cached_query = "/cached-worlds/"
 approach = "Realistic"
 classification = "Fullstack"
 database = "Postgres"
 database_os = "Linux"
 os = "Linux"
-orm = "Micro"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore-ado-pg"
-
-[mw-dap-pg-up]
-urls.update = "/updates/dapper?queries="
-approach = "Realistic"
-classification = "Micro"
-database = "Postgres"
-database_os = "Linux"
-os = "Linux"
-orm = "Micro"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore-ado-pg"
-
-[mw]
-urls.plaintext = "/plaintext"
-approach = "Realistic"
-classification = "Micro"
-database = "None"
-database_os = "Linux"
-os = "Linux"
-orm = "Raw"
+orm = "Full"
 platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore"
+webserver = "aspnetcore"
 
-[ado-pg-up]
+[mysql]
+urls.db = "/db"
+urls.query = "/queries/"
+urls.fortune = "/fortunes"
 urls.update = "/updates/"
+urls.cached_query = "/cached-worlds/"
 approach = "Realistic"
 classification = "Platform"
-database = "Postgres"
-database_os = "Linux"
-os = "Linux"
-orm = "Raw"
-platform = ".NET"
-webserver = "Kestrel"
-versus = "aspcore-ado-pg-up"
-
-[aot-ado-pg-up]
-urls.update = "/updates/"
-approach = "Stripped"
-classification = "Platform"
-database = "Postgres"
+database = "MySQL"
 database_os = "Linux"
 os = "Linux"
 orm = "Raw"
 platform = ".NET"
 webserver = "Kestrel"
-versus = "aspcore-ado-pg-up"
+versus = "aspnetcore"

+ 398 - 0
frameworks/CSharp/aspnetcore/src/Minimal/.gitignore

@@ -0,0 +1,398 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml

+ 9 - 0
frameworks/CSharp/aspnetcore/src/Minimal/AppSettings.cs

@@ -0,0 +1,9 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+namespace Minimal;
+
+public class AppSettings
+{
+    public string? ConnectionString { get; set; }
+}

+ 31 - 0
frameworks/CSharp/aspnetcore/src/Minimal/Database/BatchUpdateString.cs

@@ -0,0 +1,31 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+using System.Text;
+
+namespace Minimal.Database;
+
+internal class BatchUpdateString
+{
+    private const int MaxBatch = 500;
+
+    private static readonly string[] _queries = new string[MaxBatch + 1];
+
+    public static string Query(int batchSize)
+    {
+        if (_queries[batchSize] != null)
+        {
+            return _queries[batchSize];
+        }
+
+        var lastIndex = batchSize - 1;
+
+        var sb = new StringBuilder();
+
+        sb.Append("UPDATE world SET randomNumber = temp.randomNumber FROM (VALUES ");
+        Enumerable.Range(0, lastIndex).ToList().ForEach(i => sb.Append($"(@Id_{i}, @Rn_{i}), "));
+        sb.Append($"(@Id_{lastIndex}, @Rn_{lastIndex}) ORDER BY 1) AS temp(id, randomNumber) WHERE temp.id = world.id");
+
+        return _queries[batchSize] = sb.ToString();
+    }
+}

+ 120 - 0
frameworks/CSharp/aspnetcore/src/Minimal/Database/Db.cs

@@ -0,0 +1,120 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+using System.Buffers.Text;
+using System.Data.Common;
+using Dapper;
+using Minimal.Models;
+
+namespace Minimal.Database;
+
+public class Db
+{
+    private static readonly Comparison<Fortune> FortuneSortComparison = (a, b) => string.CompareOrdinal(a.Message, b.Message);
+
+    private readonly DbProviderFactory _dbProviderFactory;
+    private readonly string _connectionString;
+
+    public Db(AppSettings appSettings)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(appSettings.ConnectionString);
+
+        _dbProviderFactory = Npgsql.NpgsqlFactory.Instance;
+        _connectionString = appSettings.ConnectionString;
+    }
+
+    public async Task<World> LoadSingleQueryRow()
+    {
+        await using var db = _dbProviderFactory.CreateConnection();
+        db!.ConnectionString = _connectionString;
+
+        // Note: Don't need to open connection if only doing one thing; let dapper do it
+        return await ReadSingleRow(db);
+    }
+
+    static Task<World> ReadSingleRow(DbConnection db)
+    {
+        return db.QueryFirstOrDefaultAsync<World>(
+                "SELECT id, randomnumber FROM world WHERE id = @Id",
+                new { Id = Random.Shared.Next(1, 10001) });
+    }
+
+    public async Task<World[]> LoadMultipleQueriesRows(string? parameter)
+    {
+        var count = ParseQueries(parameter);
+
+        var results = new World[count];
+        await using var db = _dbProviderFactory.CreateConnection();
+
+        db!.ConnectionString = _connectionString;
+        await db.OpenAsync();
+
+        for (var i = 0; i < count; i++)
+        {
+            results[i] = await ReadSingleRow(db);
+        }
+
+        return results;
+    }
+
+    public async Task<World[]> LoadMultipleUpdatesRows(string? parameter)
+    {
+        var count = ParseQueries(parameter);
+
+        var parameters = new Dictionary<string, object>();
+
+        await using var db = _dbProviderFactory.CreateConnection();
+
+        db!.ConnectionString = _connectionString;
+        await db.OpenAsync();
+
+        var results = new World[count];
+        for (var i = 0; i < count; i++)
+        {
+            results[i] = await ReadSingleRow(db);
+        }
+
+        for (var i = 0; i < count; i++)
+        {
+            var randomNumber = Random.Shared.Next(1, 10001);
+            parameters[$"@Rn_{i}"] = randomNumber;
+            parameters[$"@Id_{i}"] = results[i].Id;
+
+            results[i].RandomNumber = randomNumber;
+        }
+
+        await db.ExecuteAsync(BatchUpdateString.Query(count), parameters);
+        return results;
+    }
+
+    public async Task<List<Fortune>> LoadFortunesRows()
+    {
+        List<Fortune> result;
+
+        await using var db = _dbProviderFactory.CreateConnection();
+
+        db!.ConnectionString = _connectionString;
+
+        // Note: don't need to open connection if only doing one thing; let dapper do it
+        result = (await db.QueryAsync<Fortune>("SELECT id, message FROM fortune")).AsList();
+
+        result.Add(new Fortune(0, "Additional fortune added at request time."));
+        result.Sort(FortuneSortComparison);
+
+        return result;
+    }
+
+    private static int ParseQueries(string? parameter)
+    {
+        if (!int.TryParse(parameter, out int queries))
+        {
+            queries = 1;
+        }
+        else
+        {
+            queries = Math.Clamp(queries, 1, 500);
+        }
+
+        return queries;
+    }
+}

+ 17 - 0
frameworks/CSharp/aspnetcore/src/Minimal/Minimal.csproj

@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <LangVersion>latest</LangVersion>
+    <UserSecretsId>38063504-d08c-495a-89c9-daaad2f60f31</UserSecretsId>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Npgsql" Version="8.0.0-rc.2" />
+    <PackageReference Include="Dapper" Version="2.1.11" />
+    <PackageReference Include="RazorSlices" Version="0.7.0" />
+  </ItemGroup>
+
+</Project>

+ 6 - 0
frameworks/CSharp/aspnetcore/src/Minimal/Models/Fortune.cs

@@ -0,0 +1,6 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+namespace Minimal.Models;
+
+public record Fortune(int Id, string Message);

+ 9 - 0
frameworks/CSharp/aspnetcore/src/Minimal/Models/ViewModel.cs

@@ -0,0 +1,9 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+namespace Minimal.Models;
+
+public class ViewModel
+{
+    public required List<Fortune> Fortunes { get; init; }
+}

+ 11 - 0
frameworks/CSharp/aspnetcore/src/Minimal/Models/World.cs

@@ -0,0 +1,11 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+namespace Minimal.Models;
+
+public struct World
+{
+    public int Id { get; set; }
+
+    public int RandomNumber { get; set; }
+}

+ 7 - 0
frameworks/CSharp/aspnetcore/src/Minimal/NuGet.Config

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <packageSources>
+    <clear />
+    <add key="NuGet" value="https://api.nuget.org/v3/index.json" />
+  </packageSources>
+</configuration>

+ 63 - 0
frameworks/CSharp/aspnetcore/src/Minimal/Program.cs

@@ -0,0 +1,63 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+using System.Text.Encodings.Web;
+using System.Text.Unicode;
+using Microsoft.AspNetCore.Http.HttpResults;
+using RazorSlices;
+using Minimal;
+using Minimal.Database;
+using Minimal.Models;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Disable logging as this is not required for the benchmark
+builder.Logging.ClearProviders();
+
+builder.WebHost.ConfigureKestrel(options =>
+{
+     options.AllowSynchronousIO = true;
+});
+
+// Load custom configuration
+var appSettings = new AppSettings();
+builder.Configuration.Bind(appSettings);
+
+// Add services to the container.
+builder.Services.AddSingleton(new Db(appSettings));
+
+var app = builder.Build();
+
+app.MapGet("/plaintext", () => "Hello, World!");
+
+app.MapGet("/plaintext/result", () => Results.Text("Hello, World!"));
+
+app.MapGet("/json", () => new { message = "Hello, World!" });
+
+app.MapGet("/db", async (Db db) => await db.LoadSingleQueryRow());
+
+var createFortunesTemplate = RazorSlice.ResolveSliceFactory<List<Fortune>>("/Templates/Fortunes.cshtml");
+var htmlEncoder = CreateHtmlEncoder();
+
+app.MapGet("/fortunes", async (HttpContext context, Db db) => {
+    var fortunes = await db.LoadFortunesRows();
+    var template = (RazorSliceHttpResult<List<Fortune>>)createFortunesTemplate(fortunes);
+    template.HtmlEncoder = htmlEncoder;
+    return template;
+});
+
+app.MapGet("/queries/{count?}", async (Db db, string? count) => await db.LoadMultipleQueriesRows(count));
+
+app.MapGet("/updates/{count?}", async (Db db, string? count) => await db.LoadMultipleUpdatesRows(count));
+
+app.Lifetime.ApplicationStarted.Register(() => Console.WriteLine("Application started. Press Ctrl+C to shut down."));
+app.Lifetime.ApplicationStopping.Register(() => Console.WriteLine("Application is shutting down..."));
+
+app.Run();
+
+static HtmlEncoder CreateHtmlEncoder()
+{
+    var settings = new TextEncoderSettings(UnicodeRanges.BasicLatin, UnicodeRanges.Katakana, UnicodeRanges.Hiragana);
+    settings.AllowCharacter('\u2014'); // allow EM DASH through
+    return HtmlEncoder.Create(settings);
+}

+ 6 - 0
frameworks/CSharp/aspnetcore/src/Minimal/README.md

@@ -0,0 +1,6 @@
+This folder contains an idiomatic version of an ASP.NET Minimal APIs implementation of TechEmpower scenarios.
+
+### Implementation details
+
+- Logging is disabled
+- Database access uses Dapper

+ 2 - 0
frameworks/CSharp/aspnetcore/src/Minimal/Templates/Fortunes.cshtml

@@ -0,0 +1,2 @@
+@inherits RazorSliceHttpResult<List<Fortune>>
+<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>@foreach (var item in Model){<tr><td>@WriteNumber(item.Id, default, CultureInfo.InvariantCulture, false)</td><td>@item.Message</td></tr>}</table></body></html>

+ 10 - 0
frameworks/CSharp/aspnetcore/src/Minimal/Templates/_ViewImports.cshtml

@@ -0,0 +1,10 @@
+@inherits RazorSliceHttpResult
+
+@using System.Globalization;
+@using Microsoft.AspNetCore.Razor;
+@using Microsoft.AspNetCore.Http.HttpResults;
+@using RazorSlices;
+@using Minimal.Models;
+
+@tagHelperPrefix __disable_tagHelpers__:
+@removeTagHelper *, Microsoft.AspNetCore.Mvc.Razor

+ 8 - 0
frameworks/CSharp/aspnetcore/src/Minimal/appsettings.Development.json

@@ -0,0 +1,8 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  }
+}

+ 10 - 0
frameworks/CSharp/aspnetcore/src/Minimal/appsettings.json

@@ -0,0 +1,10 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  },
+  "AllowedHosts": "*",
+  "ConnectionString": "This should be set in user secrets or environment config"
+}

+ 174 - 0
frameworks/CSharp/aspnetcore/src/Minimal/minimal.benchmarks.yml

@@ -0,0 +1,174 @@
+imports:
+  - https://raw.githubusercontent.com/dotnet/crank/main/src/Microsoft.Crank.Jobs.Wrk/wrk.yml
+  - https://github.com/aspnet/Benchmarks/blob/main/scenarios/aspnet.profiles.standard.yml?raw=true
+
+variables:
+    serverPort: 5000
+    
+jobs:
+  minimal:
+    source:
+      repository: https://github.com/aspnet/benchmarks.git
+      branchOrCommit: main
+      project: src/BenchmarksApps/TechEmpower/Minimal/Minimal.csproj
+    readyStateText: Application started.
+    arguments: "--urls {{serverScheme}}://{{serverAddress}}:{{serverPort}}"
+    variables:
+      serverScheme: http
+    environmentVariables:
+    # [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Development password, not confidential")]
+      connectionString: Server={{databaseServer}};Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;Maximum Pool Size=18;Enlist=false;Max Auto Prepare=4;Multiplexing=true;Write Coalescing Buffer Threshold Bytes=1000
+
+  postgresql:
+    source:
+      repository: https://github.com/TechEmpower/FrameworkBenchmarks.git
+      branchOrCommit: master
+      dockerFile: toolset/databases/postgres/postgres.dockerfile
+      dockerImageName: postgres_te
+      dockerContextDirectory: toolset/databases/postgres
+    readyStateText: ready to accept connections
+    noClean: true
+
+scenarios:
+  plaintext:
+    application:
+      job: minimal
+    load:
+      job: wrk
+      variables:
+        presetHeaders: plaintext
+        path: /plaintext
+        pipeline: 16
+
+  plaintext_result:
+    application:
+      job: minimal
+    load:
+      job: wrk
+      variables:
+        presetHeaders: plaintext
+        path: /plaintext/result
+        pipeline: 16
+
+  json:
+    application:
+      job: minimal
+    load:
+      job: wrk
+      variables:
+        presetHeaders: json
+        path: /json
+
+  json_result:
+    application:
+      job: minimal
+    load:
+      job: wrk
+      variables:
+        presetHeaders: json
+        path: /json/result
+
+  fortunes:
+    db:
+      job: postgresql
+    application:
+      job: minimal
+    load:
+      job: wrk
+      variables:
+        presetHeaders: html
+        path: /fortunes
+
+  fortunes_result:
+    db:
+      job: postgresql
+    application:
+      job: minimal
+    load:
+      job: wrk
+      variables:
+        presetHeaders: html
+        path: /fortunes/result
+
+  single_query:
+    db:
+      job: postgresql
+    application:
+      job: minimal
+    load:
+      job: wrk
+      variables:
+        presetHeaders: json
+        path: /db
+        connections: 512
+
+  single_query_result:
+    db:
+      job: postgresql
+    application:
+      job: minimal
+    load:
+      job: wrk
+      variables:
+        presetHeaders: json
+        path: /db/result
+        connections: 512
+
+  multiple_queries:
+    db:
+      job: postgresql
+    application:
+      job: minimal
+    load:
+      job: wrk
+      variables:
+        presetHeaders: json
+        path: /queries/20
+        connections: 512
+
+  multiple_queries_result:
+    db:
+      job: postgresql
+    application:
+      job: minimal
+    load:
+      job: wrk
+      variables:
+        presetHeaders: json
+        path: /queries/20/result
+        connections: 512
+
+  updates:
+    db:
+      job: postgresql
+    application:
+      job: minimal
+    load:
+      job: wrk
+      variables:
+        presetHeaders: json
+        path: /updates/20
+        connections: 512
+
+  updates_result:
+    db:
+      job: postgresql
+    application:
+      job: minimal
+    load:
+      job: wrk
+      variables:
+        presetHeaders: json
+        path: /updates/20/result
+        connections: 512
+
+profiles:
+  # this profile uses the local folder as the source 
+  # instead of the public repository
+  source:
+    agents:
+      main:
+        source:
+          localFolder: .
+          respository: ''
+          project: Minimal.csproj

+ 398 - 0
frameworks/CSharp/aspnetcore/src/Mvc/.gitignore

@@ -0,0 +1,398 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml

+ 6 - 0
frameworks/CSharp/aspnetcore/src/Mvc/AppSettings.cs

@@ -0,0 +1,6 @@
+namespace Mvc;
+
+public class AppSettings
+{
+    public string? ConnectionString { get; set; }
+}

+ 18 - 0
frameworks/CSharp/aspnetcore/src/Mvc/Controllers/FortunesController.cs

@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+using Microsoft.AspNetCore.Mvc;
+using Mvc.Database;
+
+namespace Mvc.Controllers;
+
+public class FortunesController : Controller
+{
+    [Route("fortunes")]
+    public async Task<IActionResult> Index([FromServices] Db db)
+    {
+        var fortunes = await db.LoadFortunesRows();
+
+        return View(fortunes);
+    }
+}

+ 27 - 0
frameworks/CSharp/aspnetcore/src/Mvc/Controllers/HomeController.cs

@@ -0,0 +1,27 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+using Microsoft.AspNetCore.Mvc;
+
+namespace Mvc.Controllers;
+
+public class HomeController : Controller
+{
+    public IActionResult Index()
+    {
+        return View();
+    }
+
+    [HttpGet("plaintext")]
+    public string Plaintext()
+    {
+        return "Hello, World!";
+    }
+
+    [HttpGet("json")]
+    [Produces("application/json")]
+    public object Json()
+    {
+        return new { message = "Hello, World!" };
+    }
+}

+ 18 - 0
frameworks/CSharp/aspnetcore/src/Mvc/Controllers/MultipleQueriesController.cs

@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+using Microsoft.AspNetCore.Mvc;
+using Mvc.Database;
+using Mvc.Models;
+
+namespace Mvc.Controllers;
+
+public class MultipleQueriesController : Controller
+{
+    [Route("queries/{count?}")]
+    [Produces("application/json")]
+    public Task<World[]> Index([FromServices] Db db, int count = 1)
+    {
+        return db.LoadMultipleQueriesRows(count);
+    }
+}

+ 18 - 0
frameworks/CSharp/aspnetcore/src/Mvc/Controllers/SingleQueryController.cs

@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+using Microsoft.AspNetCore.Mvc;
+using Mvc.Database;
+using Mvc.Models;
+
+namespace Mvc.Controllers;
+
+public class SingleQueryController : Controller
+{
+    [Route("db")]
+    [Produces("application/json")]
+    public Task<World> Index([FromServices] Db db)
+    {
+        return db.LoadSingleQueryRow();
+    }
+}

+ 18 - 0
frameworks/CSharp/aspnetcore/src/Mvc/Controllers/UpdatesController.cs

@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+using Microsoft.AspNetCore.Mvc;
+using Mvc.Database;
+using Mvc.Models;
+
+namespace Mvc.Controllers;
+
+public class UpdatesController : Controller
+{
+    [Route("updates/{count?}")]
+    [Produces("application/json")]
+    public Task<World[]> Index([FromServices] Db db, int count = 1)
+    {
+        return db.LoadMultipleUpdatesRows(count);
+    }
+}

+ 18 - 0
frameworks/CSharp/aspnetcore/src/Mvc/Database/ApplicationDbContext.cs

@@ -0,0 +1,18 @@
+using Microsoft.EntityFrameworkCore;
+using Mvc.Models;
+
+namespace Mvc.Database;
+
+public sealed class ApplicationDbContext : DbContext
+{
+    public ApplicationDbContext(DbContextOptions options)
+        : base(options)
+    {
+        ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
+        ChangeTracker.AutoDetectChangesEnabled = false;
+    }
+
+    public required DbSet<Fortune> Fortunes { get; set; }
+
+    public required DbSet<World> Worlds { get; set; }
+}

+ 87 - 0
frameworks/CSharp/aspnetcore/src/Mvc/Database/Db.cs

@@ -0,0 +1,87 @@
+using Microsoft.EntityFrameworkCore;
+using Mvc.Models;
+
+namespace Mvc.Database;
+
+public sealed class Db
+{
+    private readonly ApplicationDbContext _dbContext;
+
+    public Db(ApplicationDbContext dbContext)
+    {
+        _dbContext = dbContext;
+    }
+
+    private static readonly Func<ApplicationDbContext, int, Task<World>> _firstWorldQuery
+        = EF.CompileAsyncQuery((ApplicationDbContext context, int id)
+            => context.Worlds.First(w => w.Id == id));
+
+    public Task<World> LoadSingleQueryRow()
+    {
+        var id = Random.Shared.Next(1, 10001);
+
+        return _firstWorldQuery(_dbContext, id);
+    }
+
+    public async Task<World[]> LoadMultipleQueriesRows(int count)
+    {
+        count = count < 1 ? 1 : count > 500 ? 500 : count;
+
+        var result = new World[count];
+
+        for (var i = 0; i < count; i++)
+        {
+            var id = Random.Shared.Next(1, 10001);
+
+            result[i] = await _firstWorldQuery(_dbContext, id);
+        }
+
+        return result;
+    }
+
+    private static readonly Func<ApplicationDbContext, int, Task<World>> _firstWorldTrackedQuery
+        = EF.CompileAsyncQuery((ApplicationDbContext context, int id)
+            => context.Worlds.AsTracking().First(w => w.Id == id));
+
+    public async Task<World[]> LoadMultipleUpdatesRows(int count)
+    {
+        count = count < 1 ? 1 : count > 500 ? 500 : count;
+
+        var results = new World[count];
+
+        for (var i = 0; i < count; i++)
+        {
+            var id = Random.Shared.Next(1, 10001);
+            var result = await _firstWorldTrackedQuery(_dbContext, id);
+            result.RandomNumber = Random.Shared.Next(1, 10001);
+
+            // Per the rules, always send an update, even if the random new value is the same as the current value.
+            _dbContext.Entry(result).State = EntityState.Modified;
+            
+            results[i] = result;
+        }
+
+        await _dbContext.SaveChangesAsync();
+
+        return results;
+    }
+
+    private static readonly Func<ApplicationDbContext, IAsyncEnumerable<Fortune>> _fortunesQuery
+        = EF.CompileAsyncQuery((ApplicationDbContext context) => context.Fortunes);
+
+    public async Task<IEnumerable<Fortune>> LoadFortunesRows()
+    {
+        var result = new List<Fortune>();
+
+        await foreach (var fortune in _fortunesQuery(_dbContext))
+        {
+            result.Add(fortune);
+        }
+
+        result.Add(new Fortune { Message = "Additional fortune added at request time." });
+
+        result.Sort();
+
+        return result;
+    }
+}

+ 28 - 0
frameworks/CSharp/aspnetcore/src/Mvc/Models/Fortune.cs

@@ -0,0 +1,28 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Runtime.Serialization;
+
+namespace Mvc.Models;
+
+[Table("fortune")]
+public class Fortune : IComparable<Fortune>, IComparable
+{
+    [Column("id")]
+    public int Id { get; set; }
+
+    [Column("message")]
+    [StringLength(2048)]
+    [IgnoreDataMember]
+    [Required]
+    public string? Message { get; set; }
+
+    public int CompareTo(object? obj)
+    {
+        return CompareTo((Fortune?)obj);
+    }
+
+    public int CompareTo(Fortune? other)
+    {
+        return string.CompareOrdinal(Message, other?.Message);
+    }
+}

+ 20 - 0
frameworks/CSharp/aspnetcore/src/Mvc/Models/World.cs

@@ -0,0 +1,20 @@
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Runtime.Serialization;
+using System.Text.Json.Serialization;
+
+namespace Mvc.Models;
+
+[Table("world")]
+public class World
+{
+    [Column("id")]
+    public int Id { get; set; }
+
+    [IgnoreDataMember]
+    [NotMapped]
+    [JsonIgnore]
+    public int _Id { get; set; }
+
+    [Column("randomnumber")]
+    public int RandomNumber { get; set; }
+}

+ 16 - 0
frameworks/CSharp/aspnetcore/src/Mvc/Mvc.csproj

@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <LangVersion>latest</LangVersion>
+    <UserSecretsId>574ff97c-fe74-4b0d-af25-8d7200a036f5</UserSecretsId>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Npgsql" Version="8.0.0-rc.2" />
+    <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0-rc.2" />
+  </ItemGroup>
+
+</Project>

+ 7 - 0
frameworks/CSharp/aspnetcore/src/Mvc/NuGet.Config

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <packageSources>
+    <clear />
+    <add key="NuGet" value="https://api.nuget.org/v3/index.json" />
+  </packageSources>
+</configuration>

+ 44 - 0
frameworks/CSharp/aspnetcore/src/Mvc/Program.cs

@@ -0,0 +1,44 @@
+using System.Text.Encodings.Web;
+using System.Text.Unicode;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Storage;
+using Mvc;
+using Mvc.Database;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Remove logging as this is not required for the benchmark
+builder.Logging.ClearProviders();
+
+// Load custom configuration
+var appSettings = new AppSettings();
+builder.Configuration.Bind(appSettings);
+
+// Add services to the container.
+builder.Services.AddControllersWithViews();
+builder.Services.AddTransient<Db>();
+builder.Services.AddSingleton(appSettings);
+builder.Services.AddDbContextPool<ApplicationDbContext>(
+    options => options
+        .UseNpgsql(appSettings.ConnectionString, o => o.ExecutionStrategy(d => new NonRetryingExecutionStrategy(d)))
+        .EnableThreadSafetyChecks(false)
+        );
+builder.Services.AddSingleton(serviceProvider =>
+{
+    var settings = new TextEncoderSettings(UnicodeRanges.BasicLatin, UnicodeRanges.Katakana, UnicodeRanges.Hiragana);
+    settings.AllowCharacter('\u2014'); // allow EM DASH through
+    return HtmlEncoder.Create(settings);
+});
+
+var app = builder.Build();
+
+app.UseRouting();
+
+app.MapControllerRoute(
+    name: "default",
+    pattern: "{controller=Home}/{action=Index}/{id?}");
+
+app.Lifetime.ApplicationStarted.Register(() => Console.WriteLine("Application started. Press Ctrl+C to shut down."));
+app.Lifetime.ApplicationStopping.Register(() => Console.WriteLine("Application is shutting down..."));
+
+app.Run();

+ 6 - 0
frameworks/CSharp/aspnetcore/src/Mvc/README.md

@@ -0,0 +1,6 @@
+This folder contains a canonical version of an ASP.NET MVC implementation of TechEmpower scenarios.
+
+### Implementation details
+
+- Logging is disabled
+- Database access uses Entity Framework

+ 12 - 0
frameworks/CSharp/aspnetcore/src/Mvc/Views/Fortunes/Index.cshtml

@@ -0,0 +1,12 @@
+@using Mvc.Models
+@model List<Fortune>
+<!DOCTYPE html>
+<html>
+<head><title>Fortunes</title></head>
+<body><table><tr><th>id</th><th>message</th></tr>
+@foreach (var item in Model)
+{
+    <tr><td>@item.Id</td><td>@item.Message</td></tr>
+}
+</table></body>
+</html>

+ 7 - 0
frameworks/CSharp/aspnetcore/src/Mvc/Views/Home/Index.cshtml

@@ -0,0 +1,7 @@
+<div class="text-center">
+    <h1 class="display-4">TechEmpower ASP.NET Core MVC benchmarks</h1>
+    <ul>
+        <li><a href="~/plaintext">Plaintext</a></li>
+        <li><a href="~/json">Json</a></li>
+    </ul>
+</div>

+ 8 - 0
frameworks/CSharp/aspnetcore/src/Mvc/appsettings.Development.json

@@ -0,0 +1,8 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  }
+}

+ 9 - 0
frameworks/CSharp/aspnetcore/src/Mvc/appsettings.json

@@ -0,0 +1,9 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  },
+  "AllowedHosts": "*"
+}

+ 119 - 0
frameworks/CSharp/aspnetcore/src/Mvc/mvc.benchmarks.yml

@@ -0,0 +1,119 @@
+imports:
+  - https://raw.githubusercontent.com/dotnet/crank/main/src/Microsoft.Crank.Jobs.Wrk/wrk.yml
+  - https://github.com/aspnet/Benchmarks/blob/main/scenarios/aspnet.profiles.standard.yml?raw=true
+
+variables:
+    serverPort: 5000
+    
+jobs:
+  mvc:
+    source:
+      repository: https://github.com/aspnet/benchmarks.git
+      branchOrCommit: main
+      project: src/BenchmarksApps/TechEmpower/Mvc/Mvc.csproj
+    readyStateText: Application started.
+    arguments: "--urls {{serverScheme}}://{{serverAddress}}:{{serverPort}}"
+    variables:
+      serverScheme: http
+    environmentVariables:
+    # [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Development password, not confidential")]
+      connectionString: Server={{databaseServer}};Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;Maximum Pool Size=18;Enlist=false;Max Auto Prepare=4;Multiplexing=true;Write Coalescing Buffer Threshold Bytes=1000
+
+  postgresql:
+    source:
+      repository: https://github.com/TechEmpower/FrameworkBenchmarks.git
+      branchOrCommit: master
+      dockerFile: toolset/databases/postgres/postgres.dockerfile
+      dockerImageName: postgres_te
+      dockerContextDirectory: toolset/databases/postgres
+    readyStateText: ready to accept connections
+    noClean: true
+
+scenarios:
+  plaintext:
+    application:
+      job: mvc
+    load:
+      job: wrk
+      variables:
+        presetHeaders: plaintext
+        path: /plaintext
+        pipeline: 16
+
+  json:
+    application:
+      job: mvc
+    load:
+      job: wrk
+      variables:
+        presetHeaders: json
+        path: /json
+
+  fortunes:
+    db:
+      job: postgresql
+    application:
+      job: mvc
+    load:
+      job: wrk
+      variables:
+        presetHeaders: html
+        path: /fortunes
+
+  fortunes_dapper:
+    db:
+      job: postgresql
+    application:
+      job: mvc
+    load:
+      job: wrk
+      variables:
+        presetHeaders: html
+        path: /fortunes/dapper
+
+  single_query:
+    db:
+      job: postgresql
+    application:
+      job: mvc
+    load:
+      job: wrk
+      variables:
+        presetHeaders: json
+        path: /db
+        connections: 512
+
+  multiple_queries:
+    db:
+      job: postgresql
+    application:
+      job: mvc
+    load:
+      job: wrk
+      variables:
+        presetHeaders: json
+        path: /queries/20
+        connections: 512
+
+  updates:
+    db:
+      job: postgresql
+    application:
+      job: mvc
+    load:
+      job: wrk
+      variables:
+        presetHeaders: json
+        path: /updates/20
+        connections: 512
+
+profiles:
+  # this profile uses the local folder as the source 
+  # instead of the public repository
+  source:
+    agents:
+      main:
+        source:
+          localFolder: .
+          respository: ''
+          project: Mvc.csproj

+ 15 - 0
frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.Caching.cs

@@ -0,0 +1,15 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.IO.Pipelines;
+using System.Threading.Tasks;
+
+namespace PlatformBenchmarks;
+
+public partial class BenchmarkApplication
+{
+    private static async Task Caching(PipeWriter pipeWriter, int count)
+    {
+        OutputMultipleQueries(pipeWriter, await RawDb.LoadCachedQueries(count), SerializerContext.CachedWorldArray);
+    }
+}

+ 66 - 0
frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.Fortunes.cs

@@ -0,0 +1,66 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.IO.Pipelines;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using RazorSlices;
+
+namespace PlatformBenchmarks;
+
+public partial class BenchmarkApplication
+{
+    private async Task FortunesRaw(PipeWriter pipeWriter)
+    {
+        await OutputFortunes(
+            pipeWriter,
+            await RawDb.LoadFortunesRows(),
+            FortunesTemplateFactory);
+    }
+
+    private ValueTask OutputFortunes<TModel>(PipeWriter pipeWriter, TModel model, SliceFactory<TModel> templateFactory)
+    {
+        // Render headers
+        var preamble = """
+            HTTP/1.1 200 OK
+            Server: K
+            Content-Type: text/html; charset=utf-8
+            Transfer-Encoding: chunked
+            """u8;
+        var headersLength = preamble.Length + DateHeader.HeaderBytes.Length;
+        var headersSpan = pipeWriter.GetSpan(headersLength);
+        preamble.CopyTo(headersSpan);
+        DateHeader.HeaderBytes.CopyTo(headersSpan[preamble.Length..]);
+        pipeWriter.Advance(headersLength);
+
+        // Render body
+        var template = templateFactory(model);
+        // Kestrel PipeWriter span size is 4K, headers above already written to first span & template output is ~1350 bytes,
+        // so 2K chunk size should result in only a single span and chunk being used.
+        var chunkedWriter = GetChunkedWriter(pipeWriter, chunkSizeHint: 2048);
+        var renderTask = template.RenderAsync(chunkedWriter, null, HtmlEncoder);
+
+        if (renderTask.IsCompletedSuccessfully)
+        {
+            renderTask.GetAwaiter().GetResult();
+            EndTemplateRendering(chunkedWriter, template);
+            return ValueTask.CompletedTask;
+        }
+
+        return AwaitTemplateRenderTask(renderTask, chunkedWriter, template);
+    }
+
+    private static async ValueTask AwaitTemplateRenderTask(ValueTask renderTask, ChunkedBufferWriter<WriterAdapter> chunkedWriter, RazorSlice template)
+    {
+        await renderTask;
+        EndTemplateRendering(chunkedWriter, template);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static void EndTemplateRendering(ChunkedBufferWriter<WriterAdapter> chunkedWriter, RazorSlice template)
+    {
+        chunkedWriter.End();
+        ReturnChunkedWriter(chunkedWriter);
+        template.Dispose();
+    }
+}

+ 235 - 0
frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.HttpConnection.cs

@@ -0,0 +1,235 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Buffers;
+using System.IO.Pipelines;
+using System.Runtime.CompilerServices;
+using System.Text.Encodings.Web;
+using System.Text.Unicode;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
+
+namespace PlatformBenchmarks;
+
+public partial class BenchmarkApplication : IHttpConnection
+{
+    private State _state;
+
+    public PipeReader Reader { get; set; }
+    public PipeWriter Writer { get; set; }
+
+    private HtmlEncoder HtmlEncoder { get; } = CreateHtmlEncoder();
+
+    private HttpParser<ParsingAdapter> Parser { get; } = new HttpParser<ParsingAdapter>();
+
+    public async Task ExecuteAsync()
+    {
+        try
+        {
+            await ProcessRequestsAsync();
+
+            Reader.Complete();
+        }
+        catch (Exception ex)
+        {
+            Reader.Complete(ex);
+        }
+        finally
+        {
+            Writer.Complete();
+        }
+    }
+
+    private bool ParseHttpRequest(ref SequenceReader<byte> reader, bool isCompleted)
+    {
+        var state = _state;
+
+        if (state == State.StartLine)
+        {
+            if (Parser.ParseRequestLine(new ParsingAdapter(this), ref reader))
+            {
+                state = State.Headers;
+            }
+        }
+
+        if (state == State.Headers)
+        {
+            var success = Parser.ParseHeaders(new ParsingAdapter(this), ref reader);
+
+            if (success)
+            {
+                state = State.Body;
+            }
+        }
+
+        if (state != State.Body && isCompleted)
+        {
+            ThrowUnexpectedEndOfData();
+        }
+
+        _state = state;
+        return true;
+    }
+
+    private async Task ProcessRequestsAsync()
+    {
+        while (true)
+        {
+            var readResult = await Reader.ReadAsync(default);
+            var buffer = readResult.Buffer;
+            var isCompleted = readResult.IsCompleted;
+
+            if (buffer.IsEmpty && isCompleted)
+            {
+                return;
+            }
+
+            if (!HandleRequests(buffer, isCompleted))
+            {
+                await ProcessRequestAsync();
+            }
+
+            await Writer.FlushAsync(default);
+        }
+    }
+
+    private bool HandleRequests(in ReadOnlySequence<byte> buffer, bool isCompleted)
+    {
+        var reader = new SequenceReader<byte>(buffer);
+        var hasWriter = false;
+        BufferWriter<WriterAdapter> writer = default;
+
+        while (true)
+        {
+            if (!ParseHttpRequest(ref reader, isCompleted))
+            {
+                return false;
+            }
+
+            // Only create the local writer if the request is Plaintext or Json
+            
+            if (!hasWriter)
+            {
+                hasWriter = true;
+                writer = GetWriter(Writer, sizeHint: 160 * 16); // 160*16 is for Plaintext, for Json 160 would be enough
+            }
+
+            if (_state == State.Body)
+            {
+                if (!ProcessRequest(ref writer))
+                {
+                    return false;
+                }
+
+                _state = State.StartLine;
+
+                if (!reader.End)
+                {
+                    // More input data to parse
+                    continue;
+                }
+            }
+
+            // No more input or incomplete data, Advance the Reader
+            Reader.AdvanceTo(reader.Position, buffer.End);
+            break;
+        }
+
+        if (hasWriter)
+        { 
+            writer.Commit();
+        }
+
+        return true;
+    }
+
+    private static HtmlEncoder CreateHtmlEncoder()
+    {
+        var settings = new TextEncoderSettings(UnicodeRanges.BasicLatin, UnicodeRanges.Katakana, UnicodeRanges.Hiragana);
+        settings.AllowCharacter('\u2014');  // allow EM DASH through
+        return HtmlEncoder.Create(settings);
+    }
+
+    public void OnStaticIndexedHeader(int index)
+    {
+    }
+
+    public void OnStaticIndexedHeader(int index, ReadOnlySpan<byte> value)
+    {
+    }
+
+    public void OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
+    {
+    }
+    public void OnHeadersComplete(bool endStream)
+    {
+    }
+
+    private static void ThrowUnexpectedEndOfData()
+    {
+        throw new InvalidOperationException("Unexpected end of data!");
+    }
+
+    private enum State
+    {
+        StartLine,
+        Headers,
+        Body
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static BufferWriter<WriterAdapter> GetWriter(PipeWriter pipeWriter, int sizeHint)
+        => new(new(pipeWriter), sizeHint);
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static ChunkedBufferWriter<WriterAdapter> GetChunkedWriter(PipeWriter pipeWriter, int chunkSizeHint)
+    {
+        var writer = ChunkedWriterPool.Get();
+        writer.SetOutput(new WriterAdapter(pipeWriter), chunkSizeHint);
+        return writer;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static void ReturnChunkedWriter(ChunkedBufferWriter<WriterAdapter> writer) => ChunkedWriterPool.Return(writer);
+
+    private struct WriterAdapter : IBufferWriter<byte>
+    {
+        public PipeWriter Writer;
+
+        public WriterAdapter(PipeWriter writer)
+            => Writer = writer;
+
+        public void Advance(int count)
+            => Writer.Advance(count);
+
+        public Memory<byte> GetMemory(int sizeHint = 0)
+            => Writer.GetMemory(sizeHint);
+
+        public Span<byte> GetSpan(int sizeHint = 0)
+            => Writer.GetSpan(sizeHint);
+    }
+
+    private struct ParsingAdapter : IHttpRequestLineHandler, IHttpHeadersHandler
+    {
+        public BenchmarkApplication RequestHandler;
+
+        public ParsingAdapter(BenchmarkApplication requestHandler)
+            => RequestHandler = requestHandler;
+
+        public void OnStaticIndexedHeader(int index)
+            => RequestHandler.OnStaticIndexedHeader(index);
+
+        public void OnStaticIndexedHeader(int index, ReadOnlySpan<byte> value)
+            => RequestHandler.OnStaticIndexedHeader(index, value);
+
+        public void OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
+            => RequestHandler.OnHeader(name, value);
+
+        public void OnHeadersComplete(bool endStream)
+            => RequestHandler.OnHeadersComplete(endStream);
+
+        public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
+            => RequestHandler.OnStartLine(versionAndMethod, targetPath, startLine);
+    }
+}

+ 40 - 0
frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.Json.cs

@@ -0,0 +1,40 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Buffers;
+using System.IO.Pipelines;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+namespace PlatformBenchmarks;
+
+public partial class BenchmarkApplication
+{
+    private readonly static uint _jsonPayloadSize = (uint)JsonSerializer.SerializeToUtf8Bytes(
+        new JsonMessage { message = "Hello, World!" },
+        SerializerContext.JsonMessage
+        ).Length;
+
+    private static ReadOnlySpan<byte> _jsonPreamble =>
+        "HTTP/1.1 200 OK\r\n"u8 +
+        "Server: K\r\n"u8 +
+        "Content-Type: application/json\r\n"u8 +
+        "Content-Length: 27"u8;
+
+    private static void Json(ref BufferWriter<WriterAdapter> writer, IBufferWriter<byte> bodyWriter)
+    {
+        writer.Write(_jsonPreamble);
+
+        // Date header
+        writer.Write(DateHeader.HeaderBytes);
+
+        writer.Commit();
+
+        var utf8JsonWriter = t_writer ??= new Utf8JsonWriter(bodyWriter, new JsonWriterOptions { SkipValidation = true });
+        utf8JsonWriter.Reset(bodyWriter);
+
+        // Body
+        JsonSerializer.Serialize(utf8JsonWriter, new JsonMessage { message = "Hello, World!" }, SerializerContext.JsonMessage);
+    }
+}

+ 41 - 0
frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.MultipleQueries.cs

@@ -0,0 +1,41 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.IO.Pipelines;
+using System.Text.Json;
+using System.Threading.Tasks;
+using System.Text.Json.Serialization.Metadata;
+
+namespace PlatformBenchmarks;
+
+public partial class BenchmarkApplication
+{
+    private static async Task MultipleQueries(PipeWriter pipeWriter, int count)
+    {
+        OutputMultipleQueries(pipeWriter, await RawDb.LoadMultipleQueriesRows(count), SerializerContext.WorldArray);
+    }
+
+    private static void OutputMultipleQueries<TWord>(PipeWriter pipeWriter, TWord[] rows, JsonTypeInfo<TWord[]> jsonTypeInfo)
+    {
+        var writer = GetWriter(pipeWriter, sizeHint: 160 * rows.Length); // in reality it's 152 for one
+
+        writer.Write(_dbPreamble);
+
+        var lengthWriter = writer;
+        writer.Write(_contentLengthGap);
+
+        // Date header
+        writer.Write(DateHeader.HeaderBytes);
+
+        writer.Commit();
+
+        var utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true });
+        utf8JsonWriter.Reset(pipeWriter);
+
+        // Body
+        JsonSerializer.Serialize(utf8JsonWriter, rows, jsonTypeInfo);
+
+        // Content-Length
+        lengthWriter.WriteNumeric((uint)utf8JsonWriter.BytesCommitted);
+    }
+}

+ 28 - 0
frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.Plaintext.cs

@@ -0,0 +1,28 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO.Pipelines;
+using System.Threading.Tasks;
+
+namespace PlatformBenchmarks;
+
+public partial class BenchmarkApplication
+{
+    private static ReadOnlySpan<byte> _plaintextPreamble =>
+        "HTTP/1.1 200 OK\r\n"u8 +
+        "Server: K\r\n"u8 +
+        "Content-Type: text/plain\r\n"u8 +
+        "Content-Length: 13"u8;
+
+    private static void PlainText(ref BufferWriter<WriterAdapter> writer)
+    {
+        writer.Write(_plaintextPreamble);
+
+        // Date header
+        writer.Write(DateHeader.HeaderBytes);
+
+        // Body
+        writer.Write(_plainTextBody);
+    }
+}

+ 40 - 0
frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.SingleQuery.cs

@@ -0,0 +1,40 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.IO.Pipelines;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+namespace PlatformBenchmarks;
+
+public partial class BenchmarkApplication
+{
+    private static async Task SingleQuery(PipeWriter pipeWriter)
+    {
+        OutputSingleQuery(pipeWriter, await RawDb.LoadSingleQueryRow());
+    }
+
+    private static void OutputSingleQuery(PipeWriter pipeWriter, World row)
+    {
+        var writer = GetWriter(pipeWriter, sizeHint: 180); // in reality it's 150
+
+        writer.Write(_dbPreamble);
+
+        var lengthWriter = writer;
+        writer.Write(_contentLengthGap);
+
+        // Date header
+        writer.Write(DateHeader.HeaderBytes);
+
+        writer.Commit();
+
+        var utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true });
+        utf8JsonWriter.Reset(pipeWriter);
+
+        // Body
+        JsonSerializer.Serialize(utf8JsonWriter, row, SerializerContext.World);
+
+        // Content-Length
+        lengthWriter.WriteNumeric((uint)utf8JsonWriter.BytesCommitted);
+    }
+}

+ 40 - 0
frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.Updates.cs

@@ -0,0 +1,40 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.IO.Pipelines;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+namespace PlatformBenchmarks;
+
+public partial class BenchmarkApplication
+{
+    private static async Task Updates(PipeWriter pipeWriter, int count)
+    {
+        OutputUpdates(pipeWriter, await RawDb.LoadMultipleUpdatesRows(count));
+    }
+
+    private static void OutputUpdates(PipeWriter pipeWriter, World[] rows)
+    {
+        var writer = GetWriter(pipeWriter, sizeHint: 120 * rows.Length); // in reality it's 112 for one
+
+        writer.Write(_dbPreamble);
+
+        var lengthWriter = writer;
+        writer.Write(_contentLengthGap);
+
+        // Date header
+        writer.Write(DateHeader.HeaderBytes);
+        
+        writer.Commit();
+
+        var utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true });
+        utf8JsonWriter.Reset(pipeWriter);
+
+        // Body
+        JsonSerializer.Serialize(utf8JsonWriter, rows, SerializerContext.WorldArray);
+
+        // Content-Length
+        lengthWriter.WriteNumeric((uint)utf8JsonWriter.BytesCommitted);
+    }
+}

+ 208 - 0
frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.cs

@@ -0,0 +1,208 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Buffers.Text;
+using System.Collections.Generic;
+using System.IO.Pipelines;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
+using Microsoft.Extensions.ObjectPool;
+using RazorSlices;
+
+namespace PlatformBenchmarks
+{
+    public sealed partial class BenchmarkApplication
+    {
+        public static ReadOnlySpan<byte> ApplicationName => "Kestrel Platform-Level Application"u8;
+
+        private static ReadOnlySpan<byte> _crlf => "\r\n"u8;
+        private static ReadOnlySpan<byte> _eoh => "\r\n\r\n"u8; // End Of Headers
+        private static ReadOnlySpan<byte> _http11OK => "HTTP/1.1 200 OK\r\n"u8;
+        private static ReadOnlySpan<byte> _http11NotFound => "HTTP/1.1 404 Not Found\r\n"u8;
+        private static ReadOnlySpan<byte> _headerServer => "Server: K"u8;
+        private static ReadOnlySpan<byte> _headerContentLength => "Content-Length: "u8;
+        private static ReadOnlySpan<byte> _headerContentLengthZero => "Content-Length: 0"u8;
+        private static ReadOnlySpan<byte> _headerContentTypeText => "Content-Type: text/plain"u8;
+        private static ReadOnlySpan<byte> _headerContentTypeJson => "Content-Type: application/json"u8;
+        private static ReadOnlySpan<byte> _headerContentTypeHtml => "Content-Type: text/html; charset=UTF-8"u8;
+
+        private static ReadOnlySpan<byte> _dbPreamble => 
+            "HTTP/1.1 200 OK\r\n"u8 +
+            "Server: K\r\n"u8 +
+            "Content-Type: application/json\r\n"u8 +
+            "Content-Length: "u8;
+
+        private static ReadOnlySpan<byte> _plainTextBody => "Hello, World!"u8;
+        private static ReadOnlySpan<byte> _contentLengthGap => "    "u8;
+
+        public static RawDb RawDb { get; set; }
+
+        private static readonly DefaultObjectPool<ChunkedBufferWriter<WriterAdapter>> ChunkedWriterPool
+            = new(new ChunkedWriterObjectPolicy());
+
+        private sealed class ChunkedWriterObjectPolicy : IPooledObjectPolicy<ChunkedBufferWriter<WriterAdapter>>
+        {
+            public ChunkedBufferWriter<WriterAdapter> Create() => new();
+
+            public bool Return(ChunkedBufferWriter<WriterAdapter> writer)
+            {
+                writer.Reset();
+                return true;
+            }
+        }
+
+#if NPGSQL
+        private readonly static SliceFactory<List<FortuneUtf8>> FortunesTemplateFactory = RazorSlice.ResolveSliceFactory<List<FortuneUtf8>>("/Templates/FortunesUtf8.cshtml");
+#else
+        private readonly static SliceFactory<List<FortuneUtf16>> FortunesTemplateFactory = RazorSlice.ResolveSliceFactory<List<FortuneUtf16>>("/Templates/FortunesUtf16.cshtml");
+#endif
+
+        [ThreadStatic]
+        private static Utf8JsonWriter t_writer;
+
+        private static readonly JsonContext SerializerContext = JsonContext.Default;
+
+        [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)]
+        [JsonSerializable(typeof(JsonMessage))]
+        [JsonSerializable(typeof(CachedWorld[]))]
+        [JsonSerializable(typeof(World[]))]
+        private partial class JsonContext : JsonSerializerContext
+        {
+        }
+
+        public static class Paths
+        {
+            public static ReadOnlySpan<byte> Json => "/json"u8;
+            public static ReadOnlySpan<byte> Plaintext => "/plaintext"u8;
+            public static ReadOnlySpan<byte> SingleQuery => "/db"u8;
+            public static ReadOnlySpan<byte> FortunesRaw => "/fortunes"u8;
+            public static ReadOnlySpan<byte> Updates => "/updates/"u8;
+            public static ReadOnlySpan<byte> MultipleQueries => "/queries/"u8;
+            public static ReadOnlySpan<byte> Caching => "/cached-worlds/"u8;
+        }
+
+        private RequestType _requestType;
+        private int _queries;
+
+        public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
+        {
+            _requestType = versionAndMethod.Method == HttpMethod.Get ? GetRequestType(startLine.Slice(targetPath.Offset, targetPath.Length), ref _queries) : RequestType.NotRecognized;
+        }
+
+        private static RequestType GetRequestType(ReadOnlySpan<byte> path, ref int queries)
+        {
+            if (path.Length == 10 && path.SequenceEqual(Paths.Plaintext))
+            {
+                return RequestType.PlainText;
+            }
+            else if (path.Length == 5 && path.SequenceEqual(Paths.Json))
+            {
+                return RequestType.Json;
+            }
+            else if (path.Length == 3 && path[0] == '/' && path[1] == 'd' && path[2] == 'b')
+            {
+                return RequestType.SingleQuery;
+            }
+            else if (path.Length >= 9 && path[1] == 'f' && path.StartsWith(Paths.FortunesRaw))
+            {
+                return RequestType.FortunesRaw;
+            }
+            else if (path.Length >= 15 && path[1] == 'c' && path.StartsWith(Paths.Caching))
+            {
+                queries = ParseQueries(path.Slice(15));
+                return RequestType.Caching;
+            }
+            else if (path.Length >= 9 && path[1] == 'u' && path.StartsWith(Paths.Updates))
+            {
+                queries = ParseQueries(path.Slice(9));
+                return RequestType.Updates;
+            }
+            else if (path.Length >= 9 && path[1] == 'q' && path.StartsWith(Paths.MultipleQueries))
+            {
+                queries = ParseQueries(path.Slice(9));
+                return RequestType.MultipleQueries;
+            }
+
+            return RequestType.NotRecognized;
+        }
+
+        private static int ParseQueries(ReadOnlySpan<byte> parameter)
+        {
+            if (!Utf8Parser.TryParse(parameter, out int queries, out _))
+            {
+                queries = 1;
+            }
+            else
+            {
+                queries = Math.Clamp(queries, 1, 500);
+            }
+
+            return queries;
+        }
+
+        private Task ProcessRequestAsync() => _requestType switch
+        {
+            RequestType.FortunesRaw => FortunesRaw(Writer),
+            RequestType.SingleQuery => SingleQuery(Writer),
+            RequestType.Caching => Caching(Writer, _queries),
+            RequestType.Updates => Updates(Writer, _queries),
+            RequestType.MultipleQueries => MultipleQueries(Writer, _queries),
+            _ => Default(Writer)
+        };
+
+        private bool ProcessRequest(ref BufferWriter<WriterAdapter> writer)
+        {
+            if (_requestType == RequestType.PlainText)
+            {
+                PlainText(ref writer);
+            }
+            else if (_requestType == RequestType.Json)
+            {
+                Json(ref writer, Writer);
+            }
+            else
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        private static Task Default(PipeWriter pipeWriter)
+        {
+            var writer = GetWriter(pipeWriter, sizeHint: _defaultPreamble.Length + DateHeader.HeaderBytes.Length);
+            Default(ref writer);
+            writer.Commit();
+            return Task.CompletedTask;
+        }
+
+        private static ReadOnlySpan<byte> _defaultPreamble =>
+            "HTTP/1.1 200 OK\r\n"u8 +
+            "Server: K"u8 + "\r\n"u8 +
+            "Content-Type: text/plain"u8 +
+            "Content-Length: 0"u8;
+
+        private static void Default(ref BufferWriter<WriterAdapter> writer)
+        {
+            writer.Write(_defaultPreamble);
+
+            // Date header
+            writer.Write(DateHeader.HeaderBytes);
+        }
+
+        private enum RequestType
+        {
+            NotRecognized,
+            PlainText,
+            Json,
+            FortunesRaw,
+            SingleQuery,
+            Caching,
+            Updates,
+            MultipleQueries
+        }
+    }
+}

+ 62 - 0
frameworks/CSharp/aspnetcore/src/Platform/BenchmarkConfigurationHelpers.cs

@@ -0,0 +1,62 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Net;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Configuration;
+
+namespace PlatformBenchmarks;
+
+public static class BenchmarkConfigurationHelpers
+{
+    public static IWebHostBuilder UseBenchmarksConfiguration(this IWebHostBuilder builder, IConfiguration configuration)
+    {
+        builder.UseConfiguration(configuration);
+
+        // Handle the transport type
+        var webHost = builder.GetSetting("KestrelTransport");
+
+        Console.WriteLine($"Transport: {webHost}");
+
+        builder.UseSockets(options =>
+        {
+            if (int.TryParse(builder.GetSetting("threadCount"), out var threadCount))
+            {
+                options.IOQueueCount = threadCount;
+            }
+
+            options.WaitForDataBeforeAllocatingBuffer = false;
+
+            Console.WriteLine($"Options: WaitForData={options.WaitForDataBeforeAllocatingBuffer}, IOQueue={options.IOQueueCount}");
+        });
+
+        return builder;
+    }
+
+    public static IPEndPoint CreateIPEndPoint(this IConfiguration config)
+    {
+        var url = config["server.urls"] ?? config["urls"];
+
+        if (string.IsNullOrEmpty(url))
+        {
+            return new IPEndPoint(IPAddress.Loopback, 8080);
+        }
+
+        var address = BindingAddress.Parse(url);
+
+        IPAddress ip;
+
+        if (string.Equals(address.Host, "localhost", StringComparison.OrdinalIgnoreCase))
+        {
+            ip = IPAddress.Loopback;
+        }
+        else if (!IPAddress.TryParse(address.Host, out ip))
+        {
+            ip = IPAddress.IPv6Any;
+        }
+
+        return new IPEndPoint(ip, address.Port);
+    }
+}

+ 62 - 0
frameworks/CSharp/aspnetcore/src/Platform/BufferExtensions.cs

@@ -0,0 +1,62 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace PlatformBenchmarks;
+
+// Same as KestrelHttpServer\src\Kestrel.Core\Internal\Http\PipelineExtensions.cs
+// However methods accept T : struct, IBufferWriter<byte> rather than PipeWriter.
+// This allows a struct wrapper to turn CountingBufferWriter into a non-shared generic,
+// while still offering the WriteNumeric extension.
+
+public static class BufferExtensions
+{
+    private const int _maxULongByteLength = 20;
+
+    [ThreadStatic]
+    private static byte[] _numericBytesScratch;
+
+    internal static void WriteUtf8String<T>(ref this BufferWriter<T> buffer, string text)
+         where T : struct, IBufferWriter<byte>
+    {
+        var byteCount = Encoding.UTF8.GetByteCount(text);
+        buffer.Ensure(byteCount);
+        byteCount = Encoding.UTF8.GetBytes(text.AsSpan(), buffer.Span);
+        buffer.Advance(byteCount);
+    }
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    internal static void WriteNumericMultiWrite<T>(ref this BufferWriter<T> buffer, uint number)
+         where T : IBufferWriter<byte>
+    {
+        const byte AsciiDigitStart = (byte)'0';
+
+        var value = number;
+        var position = _maxULongByteLength;
+        var byteBuffer = NumericBytesScratch;
+        do
+        {
+            // Consider using Math.DivRem() if available
+            var quotient = value / 10;
+            byteBuffer[--position] = (byte)(AsciiDigitStart + (value - quotient * 10)); // 0x30 = '0'
+            value = quotient;
+        }
+        while (value != 0);
+
+        var length = _maxULongByteLength - position;
+        buffer.Write(new ReadOnlySpan<byte>(byteBuffer, position, length));
+    }
+
+    private static byte[] NumericBytesScratch => _numericBytesScratch ?? CreateNumericBytesScratch();
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static byte[] CreateNumericBytesScratch()
+    {
+        var bytes = new byte[_maxULongByteLength];
+        _numericBytesScratch = bytes;
+        return bytes;
+    }
+}

+ 142 - 0
frameworks/CSharp/aspnetcore/src/Platform/BufferWriter.cs

@@ -0,0 +1,142 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+
+namespace PlatformBenchmarks;
+
+public ref struct BufferWriter<T> where T : IBufferWriter<byte>
+{
+    private T _output;
+    private Span<byte> _span;
+    private int _buffered;
+
+    public BufferWriter(T output, int sizeHint)
+    {
+        _buffered = 0;
+        _output = output;
+        _span = output.GetSpan(sizeHint);
+    }
+
+    public Span<byte> Span => _span;
+
+    public T Output => _output;
+
+    public int Buffered => _buffered;
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Commit()
+    {
+        var buffered = _buffered;
+        if (buffered > 0)
+        {
+            _buffered = 0;
+            _output.Advance(buffered);
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Advance(int count)
+    {
+        _buffered += count;
+        _span = _span.Slice(count);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Write(scoped ReadOnlySpan<byte> source)
+    {
+        if (_span.Length >= source.Length)
+        {
+            source.CopyTo(_span);
+            Advance(source.Length);
+        }
+        else
+        {
+            WriteMultiBuffer(source);
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Ensure(int count = 1)
+    {
+        if (_span.Length < count)
+        {
+            EnsureMore(count);
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private void EnsureMore(int count = 0)
+    {
+        if (_buffered > 0)
+        {
+            Commit();
+        }
+
+        _span = _output.GetSpan(count);
+    }
+
+    private void WriteMultiBuffer(scoped ReadOnlySpan<byte> source)
+    {
+        while (source.Length > 0)
+        {
+            if (_span.Length == 0)
+            {
+                EnsureMore();
+            }
+
+            var writable = Math.Min(source.Length, _span.Length);
+            source.Slice(0, writable).CopyTo(_span);
+            source = source.Slice(writable);
+            Advance(writable);
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal void WriteNumeric(uint number)
+    {
+        const byte AsciiDigitStart = (byte)'0';
+
+        var span = this.Span;
+
+        // Fast path, try copying to the available memory directly
+        var advanceBy = 0;
+        if (span.Length >= 3)
+        {
+            if (number < 10)
+            {
+                span[0] = (byte)(number + AsciiDigitStart);
+                advanceBy = 1;
+            }
+            else if (number < 100)
+            {
+                var tens = (byte)((number * 205u) >> 11); // div10, valid to 1028
+
+                span[0] = (byte)(tens + AsciiDigitStart);
+                span[1] = (byte)(number - (tens * 10) + AsciiDigitStart);
+                advanceBy = 2;
+            }
+            else if (number < 1000)
+            {
+                var digit0 = (byte)((number * 41u) >> 12); // div100, valid to 1098
+                var digits01 = (byte)((number * 205u) >> 11); // div10, valid to 1028
+
+                span[0] = (byte)(digit0 + AsciiDigitStart);
+                span[1] = (byte)(digits01 - (digit0 * 10) + AsciiDigitStart);
+                span[2] = (byte)(number - (digits01 * 10) + AsciiDigitStart);
+                advanceBy = 3;
+            }
+        }
+
+        if (advanceBy > 0)
+        {
+            Advance(advanceBy);
+        }
+        else
+        {
+            BufferExtensions.WriteNumericMultiWrite(ref this, number);
+        }
+    }
+}

+ 241 - 0
frameworks/CSharp/aspnetcore/src/Platform/ChunkedBufferWriter.cs

@@ -0,0 +1,241 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+using System;
+using System.Buffers;
+using System.Buffers.Text;
+using System.Diagnostics;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace PlatformBenchmarks;
+
+internal sealed class ChunkedBufferWriter<TWriter> : IBufferWriter<byte> where TWriter : IBufferWriter<byte>
+{
+    private const int DefaultChunkSizeHint = 2048;
+    private static readonly StandardFormat DefaultHexFormat = GetHexFormat(DefaultChunkSizeHint);
+    private static ReadOnlySpan<byte> ChunkTerminator => "\r\n"u8;
+
+    private TWriter _output;
+    private int _chunkSizeHint;
+    private StandardFormat _hexFormat = DefaultHexFormat;
+    private Memory<byte> _currentFullChunk;
+    private Memory<byte> _currentChunk;
+    private int _buffered;
+    private bool _ended = false;
+
+    public Memory<byte> Memory => _currentChunk;
+
+    public TWriter Output => _output;
+
+    public int Buffered => _buffered;
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void SetOutput(TWriter output, int chunkSizeHint = DefaultChunkSizeHint)
+    {
+        _buffered = 0;
+        _chunkSizeHint = chunkSizeHint;
+        _output = output;
+
+        StartNewChunk(chunkSizeHint, isFirst: true);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Reset()
+    {
+        _buffered = 0;
+        _output = default;
+        _ended = false;
+        _hexFormat = DefaultHexFormat;
+        _currentFullChunk = default;
+        _currentChunk = default;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Advance(int count)
+    {
+        ThrowIfEnded();
+
+        _buffered += count;
+        _currentChunk = _currentChunk[count..];
+    }
+
+    public Memory<byte> GetMemory(int sizeHint = 0)
+    {
+        ThrowIfEnded();
+
+        if (_currentChunk.Length <= sizeHint)
+        {
+            EnsureMore(sizeHint);
+        }
+        return _currentChunk;
+    }
+
+    public Span<byte> GetSpan(int sizeHint = 0) => GetMemory(sizeHint).Span;
+
+    public void End()
+    {
+        ThrowIfEnded();
+
+        CommitCurrentChunk(isFinal: true);
+
+        _ended = true;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static StandardFormat GetHexFormat(int maxValue)
+    {
+        var hexDigitCount = CountHexDigits(maxValue);
+
+        return new StandardFormat('X', (byte)hexDigitCount);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static int CountHexDigits(int n) => n <= 16 ? 1 : (BitOperations.Log2((uint)n) >> 2) + 1;
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void StartNewChunk(int sizeHint, bool isFirst = false)
+    {
+        ThrowIfEnded();
+
+        // Header is like:
+        // 520\r\n
+
+        var oldFullChunkHexLength = -1;
+        if (!isFirst)
+        {
+            oldFullChunkHexLength = CountHexDigits(_currentFullChunk.Length);
+        }
+        _currentFullChunk = _output.GetMemory(Math.Max(_chunkSizeHint, sizeHint));
+        var newFullChunkHexLength = CountHexDigits(_currentFullChunk.Length);
+
+        var currentFullChunkSpan = _currentFullChunk.Span;
+
+        // Write space for HEX digits
+        currentFullChunkSpan[..newFullChunkHexLength].Fill(48); // 48 == '0'
+
+        // Write header terminator
+        var terminator = "\r\n"u8;
+        terminator.CopyTo(currentFullChunkSpan[newFullChunkHexLength..]);
+        var chunkHeaderLength = newFullChunkHexLength + terminator.Length;
+        _currentChunk = _currentFullChunk[chunkHeaderLength..];
+
+        if ((!isFirst && oldFullChunkHexLength != newFullChunkHexLength) || (isFirst && DefaultChunkSizeHint != _chunkSizeHint))
+        {
+            // Update HEX format if changed
+            _hexFormat = GetHexFormat(_currentFullChunk.Length);
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void CommitCurrentChunk(bool isFinal = false, int sizeHint = 0)
+    {
+        ThrowIfEnded();
+
+        var contentLength = _buffered;
+        
+        if (contentLength > 0)
+        {
+            // Update the chunk header
+            var chunkLengthHexDigitsLength = CountHexDigits(contentLength);
+            var span = _currentFullChunk.Span;
+            if (!Utf8Formatter.TryFormat(contentLength, span, out var bytesWritten, _hexFormat))
+            {
+                throw new NotSupportedException("Chunk size too large");
+            }
+            Debug.Assert(chunkLengthHexDigitsLength == bytesWritten, "HEX formatting math problem.");
+            var headerLength = chunkLengthHexDigitsLength + 2;
+
+            // Total chunk length: content length as HEX string + \r\n + content + \r\n
+            var spanOffset = headerLength + contentLength;
+            var chunkTotalLength = spanOffset + ChunkTerminator.Length;
+
+            Debug.Assert(span.Length >= chunkTotalLength, "Bad chunk size calculation.");
+
+            // Write out the chunk terminator
+            ChunkTerminator.CopyTo(span[spanOffset..]);
+            spanOffset = chunkTotalLength;
+            
+            if (!isFinal)
+            {
+                _output.Advance(chunkTotalLength);
+                StartNewChunk(sizeHint);
+            }
+            else
+            {
+                // Write out final chunk (zero-length chunk)
+                var terminator = "0\r\n\r\n"u8;
+                if ((spanOffset + terminator.Length) <= span.Length)
+                {
+                    // There's space for the final chunk in the current span
+                    terminator.CopyTo(span[spanOffset..]);
+                    _output.Advance(chunkTotalLength + terminator.Length);
+                }
+                else
+                {
+                    // Final chunk doesn't fit in current span so just write it directly after advancing the writer
+                    _output.Advance(chunkTotalLength);
+                    _output.Write(terminator);
+                }
+            }
+            
+            _buffered = 0;
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Write(ReadOnlySpan<byte> source)
+    {
+        ThrowIfEnded();
+
+        if (_currentChunk.Length >= (source.Length + ChunkTerminator.Length))
+        {
+            source.CopyTo(_currentChunk.Span);
+            Advance(source.Length);
+        }
+        else
+        {
+            WriteMultiBuffer(source);
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private void EnsureMore(int count = 0)
+    {
+        if (count > (_currentChunk.Length - _buffered - ChunkTerminator.Length))
+        {
+            if (_buffered > 0)
+            {
+                CommitCurrentChunk(isFinal: false, count);
+            }
+            else
+            {
+                StartNewChunk(count);
+            }
+        }
+    }
+
+    private void WriteMultiBuffer(ReadOnlySpan<byte> source)
+    {
+        while (source.Length > 0)
+        {
+            if ((_currentChunk.Length - ChunkTerminator.Length) == 0)
+            {
+                EnsureMore();
+            }
+
+            var writable = Math.Min(source.Length, _currentChunk.Length - ChunkTerminator.Length);
+            source[..writable].CopyTo(_currentChunk.Span);
+            source = source[writable..];
+            Advance(writable);
+        }
+    }
+
+    private void ThrowIfEnded()
+    {
+        if (_ended)
+        {
+            throw new InvalidOperationException("Cannot use the writer after calling End().");
+        }
+    }
+}

+ 10 - 0
frameworks/CSharp/aspnetcore/src/Platform/Configuration/AppSettings.cs

@@ -0,0 +1,10 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace PlatformBenchmarks
+{
+    public sealed class AppSettings
+    {
+        public string ConnectionString { get; set; }
+    }
+}

+ 41 - 0
frameworks/CSharp/aspnetcore/src/Platform/Data/BatchUpdateString.cs

@@ -0,0 +1,41 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Linq;
+
+namespace PlatformBenchmarks;
+
+internal sealed class BatchUpdateString
+{
+    private const int MaxBatch = 500;
+
+    internal static readonly string[] ParamNames = Enumerable.Range(0, MaxBatch * 2).Select(i => $"@p{i}").ToArray();
+
+    private static string[] _queries = new string[MaxBatch + 1];
+
+    public static string Query(int batchSize)
+        => _queries[batchSize] is null
+            ? CreateBatch(batchSize)
+            : _queries[batchSize];
+
+    private static string CreateBatch(int batchSize)
+    {
+        var sb = StringBuilderCache.Acquire();
+
+#if NPGSQL
+        sb.Append("UPDATE world SET randomNumber = temp.randomNumber FROM (VALUES ");
+        var c = 1;
+        for (var i = 0; i < batchSize; i++)
+        {
+            if (i > 0)
+                sb.Append(", ");
+            sb.Append($"(${c++}, ${c++})");
+        }
+        sb.Append(" ORDER BY 1) AS temp(id, randomNumber) WHERE temp.id = world.id");
+#else
+        Enumerable.Range(0, batchSize).ToList().ForEach(i => sb.Append($"UPDATE world SET randomnumber = @Random_{i} WHERE id = @Id_{i};"));
+#endif
+
+        return _queries[batchSize] = StringBuilderCache.GetStringAndRelease(sb);
+    }
+}

+ 13 - 0
frameworks/CSharp/aspnetcore/src/Platform/Data/CachedWorld.cs

@@ -0,0 +1,13 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace PlatformBenchmarks;
+
+public sealed class CachedWorld
+{
+    public int Id { get; set; }
+
+    public int RandomNumber { get; set; }
+
+    public static implicit operator CachedWorld(World world) => new CachedWorld { Id = world.Id, RandomNumber = world.RandomNumber };
+}

+ 24 - 0
frameworks/CSharp/aspnetcore/src/Platform/Data/FortuneUtf16.cs

@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace PlatformBenchmarks;
+
+public readonly struct FortuneUtf16 : IComparable<FortuneUtf16>, IComparable
+{
+    public FortuneUtf16(int id, string message)
+    {
+        Id = id;
+        Message = message;
+    }
+
+    public int Id { get; }
+
+    public string Message { get; }
+
+    public int CompareTo(object obj) => throw new InvalidOperationException("The non-generic CompareTo should not be used");
+
+    // Performance critical, using culture insensitive comparison
+    public int CompareTo(FortuneUtf16 other) => string.CompareOrdinal(Message, other.Message);
+}

+ 24 - 0
frameworks/CSharp/aspnetcore/src/Platform/Data/FortuneUtf8.cs

@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace PlatformBenchmarks;
+
+public readonly struct FortuneUtf8 : IComparable<FortuneUtf8>, IComparable
+{
+    public FortuneUtf8(int id, byte[] message)
+    {
+        Id = id;
+        Message = message;
+    }
+
+    public int Id { get; }
+
+    public byte[] Message { get; }
+
+    public int CompareTo(object obj) => throw new InvalidOperationException("The non-generic CompareTo should not be used");
+
+    // Performance critical, using culture insensitive comparison
+    public int CompareTo(FortuneUtf8 other) => Message.AsSpan().SequenceCompareTo(other.Message.AsSpan());
+}

+ 9 - 0
frameworks/CSharp/aspnetcore/src/Platform/Data/JsonMessage.cs

@@ -0,0 +1,9 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace PlatformBenchmarks;
+
+public struct JsonMessage
+{
+    public string message { get; set; }
+}

+ 31 - 0
frameworks/CSharp/aspnetcore/src/Platform/Data/Random.cs

@@ -0,0 +1,31 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading;
+
+namespace PlatformBenchmarks;
+
+public sealed class ConcurrentRandom
+{
+    private static int nextSeed = 0;
+
+    // Random isn't thread safe
+    [ThreadStatic]
+    private static Random _random;
+
+    private static Random Random => _random ?? CreateRandom();
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static Random CreateRandom()
+    {
+        _random = new Random(Interlocked.Increment(ref nextSeed));
+        return _random;
+    }
+
+    public int Next(int minValue, int maxValue)
+    {
+        return Random.Next(minValue, maxValue);
+    }
+}

+ 256 - 0
frameworks/CSharp/aspnetcore/src/Platform/Data/RawDbMySqlConnector.cs

@@ -0,0 +1,256 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#if MYSQLCONNECTOR
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Caching.Memory;
+using MySqlConnector;
+
+namespace PlatformBenchmarks;
+
+// Is semantically identical to RawDbNpgsql.cs.
+// If you are changing RawDbMySqlConnector.cs, also consider changing RawDbNpgsql.cs.
+public sealed class RawDb
+{
+    private readonly ConcurrentRandom _random;
+    private readonly string _connectionString;
+    private readonly MemoryCache _cache
+        = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromMinutes(60) });
+
+    private readonly MySqlDataSource _dataSource;
+
+    public RawDb(ConcurrentRandom random, AppSettings appSettings)
+    {
+        _random = random;
+        _connectionString = appSettings.ConnectionString;
+        _dataSource = new MySqlDataSource(appSettings.ConnectionString);
+    }
+
+    public async Task<World> LoadSingleQueryRow()
+    {
+        using var db = await _dataSource.OpenConnectionAsync();
+        
+        var (cmd, _) = await CreateReadCommandAsync(db);
+        using var command = cmd;
+
+        return await ReadSingleRow(cmd);
+    }
+
+    public Task<CachedWorld[]> LoadCachedQueries(int count)
+    {
+        var result = new CachedWorld[count];
+        var cacheKeys = _cacheKeys;
+        var cache = _cache;
+        var random = _random;
+        for (var i = 0; i < result.Length; i++)
+        {
+            var id = random.Next(1, 10001);
+            var key = cacheKeys[id];
+            if (cache.TryGetValue(key, out var cached))
+            {
+                result[i] = (CachedWorld)cached;
+            }
+            else
+            {
+                return LoadUncachedQueries(id, i, count, this, result);
+            }
+        }
+
+        return Task.FromResult(result);
+
+        static async Task<CachedWorld[]> LoadUncachedQueries(int id, int i, int count, RawDb rawdb, CachedWorld[] result)
+        {
+            using var db = await rawdb._dataSource.OpenConnectionAsync();
+
+            var (cmd, idParameter) = await rawdb.CreateReadCommandAsync(db);
+            using var command = cmd;
+            async Task<CachedWorld> create(ICacheEntry _) => await rawdb.ReadSingleRow(cmd);
+
+            var cacheKeys = _cacheKeys;
+            var key = cacheKeys[id];
+
+            idParameter.Value = id;
+
+            for (; i < result.Length; i++)
+            {
+                result[i] = await rawdb._cache.GetOrCreateAsync(key, create);
+
+                id = rawdb._random.Next(1, 10001);
+                idParameter.Value = id;
+                key = cacheKeys[id];
+            }
+
+            return result;
+        }
+    }
+
+    public async Task PopulateCache()
+    {
+        using (var db = new MySqlConnection(_connectionString))
+        {
+            await db.OpenAsync();
+
+            var (cmd, idParameter) = await CreateReadCommandAsync(db);
+            using (cmd)
+            {
+                var cacheKeys = _cacheKeys;
+                var cache = _cache;
+                for (var i = 1; i < 10001; i++)
+                {
+                    idParameter.Value = i;
+                    cache.Set<CachedWorld>(cacheKeys[i], await ReadSingleRow(cmd));
+                }
+            }
+        }
+
+        Console.WriteLine("Caching Populated");
+    }
+
+    public async Task<World[]> LoadMultipleQueriesRows(int count)
+    {
+        var results = new World[count];
+
+        using var connection = await _dataSource.OpenConnectionAsync();
+
+        using var batch = new MySqlBatch(connection);
+
+        for (var i = 0; i < count; i++)
+        {
+            batch.BatchCommands.Add(new MySqlBatchCommand()
+            {
+                CommandText = "SELECT id, randomnumber FROM world WHERE id = @id",
+                Parameters = { new MySqlParameter("@id", _random.Next(1, 10001)) }
+            });
+        }
+
+        using var reader = await batch.ExecuteReaderAsync();
+
+        for (var i = 0; i < count; i++)
+        {
+            await reader.ReadAsync();
+            results[i] = new World { Id = reader.GetInt32(0), RandomNumber = reader.GetInt32(1) };
+            await reader.NextResultAsync();
+        }
+
+        return results;
+    }
+
+    public async Task<World[]> LoadMultipleUpdatesRows(int count)
+    {
+        var results = new World[count];
+
+        using var connection = await _dataSource.OpenConnectionAsync();
+
+        var (queryCmd, queryParameter) = await CreateReadCommandAsync(connection);
+        using (queryCmd)
+        {
+            for (var i = 0; i < results.Length; i++)
+            {
+                results[i] = await ReadSingleRow(queryCmd);
+                queryParameter.Value = _random.Next(1, 10001);
+            }
+        }
+
+        using (var updateCmd = new MySqlCommand(BatchUpdateString.Query(count), connection))
+        {
+            for (var i = 0; i < results.Length; i++)
+            {
+                var randomNumber = _random.Next(1, 10001);
+
+                updateCmd.Parameters.AddWithValue($"@Id_{i}", results[i].Id);
+                updateCmd.Parameters.AddWithValue($"@Random_{i}", randomNumber);
+
+                results[i].RandomNumber = randomNumber;
+            }
+
+            await updateCmd.ExecuteNonQueryAsync();
+        }
+
+        return results;
+    }
+
+    public async Task<List<FortuneUtf16>> LoadFortunesRows()
+    {
+        // Benchmark requirements explicitly prohibit pre-initializing the list size
+        var result = new List<FortuneUtf16>();
+
+        using var connection = await _dataSource.OpenConnectionAsync();
+
+        using var cmd = new MySqlCommand("SELECT id, message FROM fortune", connection);
+        using var rdr = await cmd.ExecuteReaderAsync();
+
+        while (await rdr.ReadAsync())
+        {
+            result.Add(new FortuneUtf16
+            (
+                id: rdr.GetInt32(0),
+                message: rdr.GetString(1)
+            ));
+        }
+
+        result.Add(new FortuneUtf16(id: 0, AdditionalFortune));
+        result.Sort();
+
+        return result;
+    }
+
+    private const string AdditionalFortune = "Additional fortune added at request time.";
+
+    private async Task<(MySqlCommand readCmd, MySqlParameter idParameter)> CreateReadCommandAsync(MySqlConnection connection)
+    {
+        var cmd = new MySqlCommand("SELECT id, randomnumber FROM world WHERE id = @Id", connection);
+        var parameter = new MySqlParameter("@Id", _random.Next(1, 10001));
+
+        cmd.Parameters.Add(parameter);
+
+        await cmd.PrepareAsync();
+            
+        return (cmd, parameter);
+    }
+        
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private async Task<World> ReadSingleRow(MySqlCommand cmd)
+    {
+        using (var rdr = await cmd.ExecuteReaderAsync(System.Data.CommandBehavior.SingleRow))
+        {
+            await rdr.ReadAsync();
+
+            return new World
+            {
+                Id = rdr.GetInt32(0),
+                RandomNumber = rdr.GetInt32(1)
+            };
+        }
+    }
+
+    private MySqlConnection CreateConnection() => _dataSource.CreateConnection();
+
+    private static readonly object[] _cacheKeys = Enumerable.Range(0, 10001).Select((i) => new CacheKey(i)).ToArray();
+
+    public sealed class CacheKey : IEquatable<CacheKey>
+    {
+        private readonly int _value;
+
+        public CacheKey(int value)
+            => _value = value;
+
+        public bool Equals(CacheKey key)
+            => key._value == _value;
+
+        public override bool Equals(object obj)
+            => ReferenceEquals(obj, this);
+
+        public override int GetHashCode()
+            => _value;
+
+        public override string ToString()
+            => _value.ToString();
+    }
+}
+
+#endif

+ 251 - 0
frameworks/CSharp/aspnetcore/src/Platform/Data/RawDbNpgsql.cs

@@ -0,0 +1,251 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#if NPGSQL
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Caching.Memory;
+using Npgsql;
+
+namespace PlatformBenchmarks;
+
+public sealed class RawDb
+{
+    private readonly ConcurrentRandom _random;
+    private readonly MemoryCache _cache
+        = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromMinutes(60) });
+
+    private readonly NpgsqlDataSource _dataSource;
+
+    public RawDb(ConcurrentRandom random, AppSettings appSettings)
+    {
+        _random = random;
+        _dataSource = NpgsqlDataSource.Create(appSettings.ConnectionString);
+    }
+
+    public async Task<World> LoadSingleQueryRow()
+    {
+        using var connection = await _dataSource.OpenConnectionAsync();
+
+        var (cmd, _) = CreateReadCommand(connection);
+        using var command = cmd;
+
+        return await ReadSingleRow(cmd);
+    }
+
+    public Task<CachedWorld[]> LoadCachedQueries(int count)
+    {
+        var result = new CachedWorld[count];
+        var cacheKeys = _cacheKeys;
+        var cache = _cache;
+        var random = _random;
+        for (var i = 0; i < result.Length; i++)
+        {
+            var id = random.Next(1, 10001);
+            var key = cacheKeys[id];
+            if (cache.TryGetValue(key, out var cached))
+            {
+                result[i] = (CachedWorld)cached;
+            }
+            else
+            {
+                return LoadUncachedQueries(id, i, count, this, result);
+            }
+        }
+
+        return Task.FromResult(result);
+
+        static async Task<CachedWorld[]> LoadUncachedQueries(int id, int i, int count, RawDb rawdb, CachedWorld[] result)
+        {
+            using var connection = await rawdb._dataSource.OpenConnectionAsync();
+
+            var (cmd, idParameter) = rawdb.CreateReadCommand(connection);
+            using var command = cmd;
+            async Task<CachedWorld> create(ICacheEntry _) => await ReadSingleRow(cmd);
+
+            var cacheKeys = _cacheKeys;
+            var key = cacheKeys[id];
+
+            idParameter.TypedValue = id;
+
+            for (; i < result.Length; i++)
+            {
+                result[i] = await rawdb._cache.GetOrCreateAsync(key, create);
+
+                id = rawdb._random.Next(1, 10001);
+                idParameter.TypedValue = id;
+                key = cacheKeys[id];
+            }
+
+            return result;
+        }
+    }
+
+    public async Task PopulateCache()
+    {
+        using var connection = await _dataSource.OpenConnectionAsync();
+
+        var (cmd, idParameter) = CreateReadCommand(connection);
+        using var command = cmd;
+
+        var cacheKeys = _cacheKeys;
+        var cache = _cache;
+        for (var i = 1; i < 10001; i++)
+        {
+            idParameter.TypedValue = i;
+            cache.Set<CachedWorld>(cacheKeys[i], await ReadSingleRow(cmd));
+        }
+
+        Console.WriteLine("Caching Populated");
+    }
+
+    public async Task<World[]> LoadMultipleQueriesRows(int count)
+    {
+        var results = new World[count];
+
+        using var connection = await _dataSource.OpenConnectionAsync();
+
+        using var batch = new NpgsqlBatch(connection)
+        {
+            // Inserts a PG Sync message between each statement in the batch, required for compliance with
+            // TechEmpower general test requirement 7
+            // https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Framework-Tests-Overview
+            EnableErrorBarriers = true
+        };
+
+        for (var i = 0; i < count; i++)
+        {
+            batch.BatchCommands.Add(new()
+            {
+                CommandText = "SELECT id, randomnumber FROM world WHERE id = $1",
+                Parameters = { new NpgsqlParameter<int> { TypedValue = _random.Next(1, 10001) } }
+            });
+        }
+
+        using var reader = await batch.ExecuteReaderAsync();
+
+        for (var i = 0; i < count; i++)
+        {
+            await reader.ReadAsync();
+            results[i] = new World { Id = reader.GetInt32(0), RandomNumber = reader.GetInt32(1) };
+            await reader.NextResultAsync();
+        }
+
+        return results;
+    }
+
+    public async Task<World[]> LoadMultipleUpdatesRows(int count)
+    {
+        var results = new World[count];
+
+        using var connection = CreateConnection();
+        await connection.OpenAsync();
+
+        var (queryCmd, queryParameter) = CreateReadCommand(connection);
+        using (queryCmd)
+        {
+            for (var i = 0; i < results.Length; i++)
+            {
+                results[i] = await ReadSingleRow(queryCmd);
+                queryParameter.TypedValue = _random.Next(1, 10001);
+            }
+        }
+
+        using (var updateCmd = new NpgsqlCommand(BatchUpdateString.Query(count), connection))
+        {
+            for (var i = 0; i < results.Length; i++)
+            {
+                var randomNumber = _random.Next(1, 10001);
+
+                updateCmd.Parameters.Add(new NpgsqlParameter<int> { TypedValue = results[i].Id });
+                updateCmd.Parameters.Add(new NpgsqlParameter<int> { TypedValue = randomNumber });
+
+                results[i].RandomNumber = randomNumber;
+            }
+
+            await updateCmd.ExecuteNonQueryAsync();
+        }
+
+        return results;
+    }
+
+    public async Task<List<FortuneUtf8>> LoadFortunesRows()
+    {
+        // Benchmark requirements explicitly prohibit pre-initializing the list size
+        var result = new List<FortuneUtf8>();
+
+        using var connection = await _dataSource.OpenConnectionAsync();
+
+        using var cmd = new NpgsqlCommand("SELECT id, message FROM fortune", connection);
+        using var rdr = await cmd.ExecuteReaderAsync();
+
+        while (await rdr.ReadAsync())
+        {
+            result.Add(new FortuneUtf8
+            (
+                id: rdr.GetInt32(0),
+                message: rdr.GetFieldValue<byte[]>(1)
+            ));
+        }
+
+        result.Add(new FortuneUtf8(id: 0, AdditionalFortune));
+        result.Sort();
+
+        return result;
+    }
+
+    private readonly byte[] AdditionalFortune = "Additional fortune added at request time."u8.ToArray();
+
+    private (NpgsqlCommand readCmd, NpgsqlParameter<int> idParameter) CreateReadCommand(NpgsqlConnection connection)
+    {
+        var cmd = new NpgsqlCommand("SELECT id, randomnumber FROM world WHERE id = $1", connection);
+        var parameter = new NpgsqlParameter<int> { TypedValue = _random.Next(1, 10001) };
+
+        cmd.Parameters.Add(parameter);
+
+        return (cmd, parameter);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static async Task<World> ReadSingleRow(NpgsqlCommand cmd)
+    {
+        using var rdr = await cmd.ExecuteReaderAsync(System.Data.CommandBehavior.SingleRow);
+        await rdr.ReadAsync();
+
+        return new World
+        {
+            Id = rdr.GetInt32(0),
+            RandomNumber = rdr.GetInt32(1)
+        };
+    }
+
+    private NpgsqlConnection CreateConnection() => _dataSource.CreateConnection();
+
+    private static readonly object[] _cacheKeys = Enumerable.Range(0, 10001).Select((i) => new CacheKey(i)).ToArray();
+
+    public sealed class CacheKey : IEquatable<CacheKey>
+    {
+        private readonly int _value;
+
+        public CacheKey(int value)
+            => _value = value;
+
+        public bool Equals(CacheKey key)
+            => key._value == _value;
+
+        public override bool Equals(object obj)
+            => ReferenceEquals(obj, this);
+
+        public override int GetHashCode()
+            => _value;
+
+        public override string ToString()
+            => _value.ToString();
+    }
+}
+
+#endif

+ 15 - 0
frameworks/CSharp/aspnetcore/src/Platform/Data/World.cs

@@ -0,0 +1,15 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Runtime.InteropServices;
+
+namespace PlatformBenchmarks
+{
+    [StructLayout(LayoutKind.Sequential, Size = 8)]
+    public struct World
+    {
+        public int Id { get; set; }
+
+        public int RandomNumber { get; set; }
+    }
+}

+ 66 - 0
frameworks/CSharp/aspnetcore/src/Platform/DateHeader.cs

@@ -0,0 +1,66 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Buffers.Text;
+using System.Diagnostics;
+using System.Threading;
+
+namespace PlatformBenchmarks;
+
+/// <summary>
+/// Manages the generation of the date header value.
+/// </summary>
+internal static class DateHeader
+{
+    const int prefixLength = 8; // "\r\nDate: ".Length
+    const int dateTimeRLength = 29; // Wed, 14 Mar 2018 14:20:00 GMT
+    const int suffixLength = 2; // crlf
+    const int suffixIndex = dateTimeRLength + prefixLength;
+
+    private static readonly Timer s_timer = new((s) => {
+        SetDateValues(DateTimeOffset.UtcNow);
+    }, null, 1000, 1000);
+
+    private static byte[] s_headerBytesMaster = new byte[prefixLength + dateTimeRLength + 2 * suffixLength];
+    private static byte[] s_headerBytesScratch = new byte[prefixLength + dateTimeRLength + 2 * suffixLength];
+
+    static DateHeader()
+    {
+        var utf8 = "\r\nDate: "u8;
+
+        utf8.CopyTo(s_headerBytesMaster);
+        utf8.CopyTo(s_headerBytesScratch);
+        s_headerBytesMaster[suffixIndex] = (byte)'\r';
+        s_headerBytesMaster[suffixIndex + 1] = (byte)'\n';
+        s_headerBytesMaster[suffixIndex + 2] = (byte)'\r';
+        s_headerBytesMaster[suffixIndex + 3] = (byte)'\n';
+        s_headerBytesScratch[suffixIndex] = (byte)'\r';
+        s_headerBytesScratch[suffixIndex + 1] = (byte)'\n';
+        s_headerBytesScratch[suffixIndex + 2] = (byte)'\r';
+        s_headerBytesScratch[suffixIndex + 3] = (byte)'\n';
+
+        SetDateValues(DateTimeOffset.UtcNow);
+        SyncDateTimer();
+    }
+
+    public static void SyncDateTimer()
+    {
+        s_timer.Change(1000, 1000);
+    }
+
+    public static ReadOnlySpan<byte> HeaderBytes => s_headerBytesMaster;
+
+    private static void SetDateValues(DateTimeOffset value)
+    {
+        lock (s_headerBytesScratch)
+        {
+            if (!Utf8Formatter.TryFormat(value, s_headerBytesScratch.AsSpan(prefixLength), out var written, 'R'))
+            {
+                throw new Exception("date time format failed");
+            }
+            Debug.Assert(written == dateTimeRLength);
+            (s_headerBytesScratch, s_headerBytesMaster) = (s_headerBytesMaster, s_headerBytesScratch);
+        }
+    }
+}

+ 6 - 0
frameworks/CSharp/aspnetcore/src/Platform/Directory.Build.props

@@ -0,0 +1,6 @@
+<Project>
+  <PropertyGroup>
+    <Configurations>Debug;Release;Debug_Database;Release_Database</Configurations>
+    <IsDatabase Condition="$(Configuration.EndsWith('_Database'))">true</IsDatabase>
+  </PropertyGroup>
+</Project>

+ 3 - 0
frameworks/CSharp/aspnetcore/src/Platform/Directory.Build.targets

@@ -0,0 +1,3 @@
+<!-- This file prevents any other Directory.Build.targets from a parent folder to be loaded -->
+<Project>
+</Project>

+ 28 - 0
frameworks/CSharp/aspnetcore/src/Platform/HttpApplication.cs

@@ -0,0 +1,28 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Connections;
+
+namespace PlatformBenchmarks;
+
+public static class HttpApplicationConnectionBuilderExtensions
+{
+    public static IConnectionBuilder UseHttpApplication<TConnection>(this IConnectionBuilder builder) where TConnection : IHttpConnection, new()
+    {
+        return builder.Use(next => new HttpApplication<TConnection>().ExecuteAsync);
+    }
+}
+
+public sealed class HttpApplication<TConnection> where TConnection : IHttpConnection, new()
+{
+    public Task ExecuteAsync(ConnectionContext connection)
+    {
+        var httpConnection = new TConnection
+        {
+            Reader = connection.Transport.Input,
+            Writer = connection.Transport.Output
+        };
+        return httpConnection.ExecuteAsync();
+    }
+}

+ 16 - 0
frameworks/CSharp/aspnetcore/src/Platform/IHttpConnection.cs

@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.IO.Pipelines;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
+
+namespace PlatformBenchmarks;
+
+public interface IHttpConnection : IHttpHeadersHandler, IHttpRequestLineHandler
+{
+    PipeReader Reader { get; set; }
+    PipeWriter Writer { get; set; }
+
+    Task ExecuteAsync();
+}

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini