good examples

alphons <alphons@heijden.com> 10 Mar 2026, 09:39
3e95e2a294f01cd4993ce1419fa01f9bd63f4f12
17 files changed
  • tests/VanDerHeijden.JsonBodyProviderTest/Controllers/ComplexController.cs
  • tests/VanDerHeijden.JsonBodyProviderTest/Controllers/SpeedController.cs
  • tests/VanDerHeijden.JsonBodyProviderTest/Program.cs
  • tests/VanDerHeijden.JsonBodyProviderTest/VanDerHeijden.JsonBodyProviderTest.csproj
  • tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Default.css
  • tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Default.js
  • tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Index.html
  • tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/ResetAll.css
  • tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Scripts/errorhandler.js
  • tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Scripts/macros.js
  • tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Scripts/netproxy.js
  • tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/SpeedTest.js
  • tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Ui.css
  • tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/css/netproxyspinner.css
  • tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/favicon.ico
  • tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/images/netproxyspinner.gif
  • tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/speedtest.html
diff --git a/tests/VanDerHeijden.JsonBodyProviderTest/Controllers/ComplexController.cs b/tests/VanDerHeijden.JsonBodyProviderTest/Controllers/ComplexController.cs
new file mode 100644
index 0000000..1aa9220
--- /dev/null
+++ b/tests/VanDerHeijden.JsonBodyProviderTest/Controllers/ComplexController.cs
@@ -0,0 +1,611 @@
+using Microsoft.AspNetCore.Mvc;
+
+#nullable disable
+
+namespace VanDerHeijden.JsonBodyProviderTest.Controllers;
+
+[Route("~/api")]
+public class ApiController : ControllerBase
+{
+ [HttpGet("~/api/HelloWorld")]
+ public async Task<IActionResult> HelloWorld()
+ {
+ // Forces a session cooky
+ HttpContext.Session.SetString("Hello", "World");
+
+ await HttpContext.Session.CommitAsync();
+
+ HttpContext.Response.Cookies.Append("mycookie", "1234");
+
+ return Ok();
+ }
+
+ [HttpGet("~/api/GetHeader")]
+ public IActionResult GetHeader(string Accept)
+ {
+ return Ok(new
+ {
+ Value = Accept
+ });
+ }
+
+ [HttpGet("~/api/QueryString")]
+ public IActionResult QueryString(string question)
+ {
+ return Ok(new
+ {
+ Value = question
+ });
+ }
+
+ [HttpGet("index")]
+ // 1.0
+ public async Task<IActionResult> Index(List<string> list)
+ {
+ await Task.Yield();
+
+ // Using SomeValueProviderFactory
+ // Returned list "a", b", "b", "c" <- repeat-bug
+ // Expected list "a", b", null, "c"
+
+ return Ok();
+ }
+
+ // 2.1
+ [HttpPost("SimpleString1")]
+ public async Task<IActionResult> SimpleString1(string user)
+ {
+ await Task.Yield();
+ return Ok(new
+ {
+ user
+ });
+ }
+
+ [HttpPost("SimpleString2")]
+ public async Task<IActionResult> SimpleString2([FromBody] string user)
+ {
+ await Task.Yield();
+ return Ok(new
+ {
+ user
+ });
+ }
+
+ public class ModelOfString
+ {
+ public string user { get; set; }
+ }
+
+ [HttpPost("SimpleString3")]
+ public async Task<IActionResult> SimpleString3(ModelOfString model)
+ {
+ await Task.Yield();
+ return Ok(new
+ {
+ model?.user
+ });
+ }
+
+ [HttpPost("~/api/SimpleString4")]
+ public async Task<IActionResult> SimpleString4([FromBody] ModelOfString model)
+ {
+ await Task.Yield();
+ return Ok(new
+ {
+ model?.user
+ });
+ }
+
+ // 2.2
+ [HttpPost("~/api/ArrayOfStrings1")]
+ public async Task<IActionResult> ArrayOfStrings1(List<string> users)
+ {
+ await Task.Yield();
+ return Ok(new
+ {
+ users
+ });
+ }
+
+ [HttpPost("~/api/ArrayOfStrings2")]
+ public async Task<IActionResult> ArrayOfStrings2([FromBody] List<string> users)
+ {
+ await Task.Yield();
+ return Ok(new
+ {
+ users
+ });
+ }
+
+ public class ModelArrayOfStrings
+ {
+ public List<string> users { get; set; }
+ }
+
+ [HttpPost("~/api/ArrayOfStrings3")]
+ public async Task<IActionResult> ArrayOfStrings3(ModelArrayOfStrings model)
+ {
+ await Task.Yield();
+ return Ok(new
+ {
+ model?.users
+ });
+ }
+
+ [HttpPost("~/api/ArrayOfStrings4")]
+ public async Task<IActionResult> ArrayOfStrings4([FromBody] ModelArrayOfStrings model)
+ {
+ await Task.Yield();
+ return Ok(new
+ {
+ model?.users
+ });
+ }
+
+ // 2.3
+
+ public class ModelArrayOfArrayOfStrings
+ {
+ public List<List<string>> users { get; set; }
+ }
+
+ [HttpPost("~/api/ArrayOfArrayOfStrings1")]
+ public async Task<IActionResult> ArrayOfArrayOfStrings1(ModelArrayOfArrayOfStrings model)
+ {
+ await Task.Yield();
+ return Ok(new
+ {
+ model?.users
+ });
+ }
+
+ [HttpPost("~/api/ArrayOfArrayOfStrings2")]
+ public async Task<IActionResult> ArrayOfArrayOfStrings2([FromBody] ModelArrayOfArrayOfStrings model)
+ {
+ await Task.Yield();
+ return Ok(new
+ {
+ model?.users
+ });
+ }
+
+ [HttpPost("~/api/TwoParameters1")]
+ public async Task<IActionResult> TwoParameters1(string a, string b)
+ {
+ await Task.Yield();
+ return Ok(new
+ {
+ a,
+ b
+ });
+ }
+
+ [HttpPost("~/api/TwoParameters2")]
+ public async Task<IActionResult> TwoParameters2([FromBody] string a, string b)
+ {
+ await Task.Yield();
+ return Ok(new
+ {
+ a,
+ b
+ });
+ }
+
+ [HttpPost("~/api/TwoParameters3")]
+ public async Task<IActionResult> TwoParameters3([FromBody] ModelOfString a, ModelOfString b)
+ {
+ await Task.Yield();
+ return Ok(new
+ {
+ a = a.user,
+ b = b.user
+ });
+ }
+
+ // Solution 1, problem of the repeater bug
+
+ [HttpPost("~/api/ListOfDoubles")]
+ public async Task<IActionResult> ListOfDoubles(List<double?> list)
+ {
+ await Task.Yield();
+ return Ok(new
+ {
+ list
+ });
+ }
+
+ public class ApiModel
+ {
+ public string Name { get; set; }
+ public List<List<Usert>> Users { get; set; }
+
+ public override string ToString() => Name;
+ }
+
+
+ [HttpGet("~/api/GetSomeSessionValue")]
+ public IActionResult GetSomeSessionValue(string Hello)
+ {
+ return Ok(new
+ {
+ Value = Hello
+ });
+ }
+
+
+ [HttpGet("~/api/NotANumber")]
+ public IActionResult NotANumber()
+ {
+ return Ok(
+ new
+ {
+ Value = double.NaN
+ });
+ }
+
+
+ [HttpPost("~/api/ComplexTest")]
+ public async Task<IActionResult> ComplexTest(
+ ApiModel SomeParameter4,
+ string SomeParameter5)
+ {
+ await Task.Yield();
+
+ return Ok(new
+ {
+
+ });
+ }
+
+ [HttpPost("~/api/DemoProposal/{SomeParameter2}")]
+ public async Task<IActionResult> DemoProposal(
+ [FromHeader(Name = "Referer")] string SomeParameter1,
+ [FromRoute] string SomeParameter2,
+ [FromQuery] string SomeParameter3,
+ [FromBody] ApiModel SomeParameter4,
+ [FromBody] string SomeParameter5,
+ [FromQuery] string SomeParameter6)
+ {
+ await Task.Yield();
+
+ return Ok(new
+ {
+ SomeParameter1,
+ SomeParameter2,
+ SomeParameter3,
+ SomeParameter4,
+ SomeParameter5,
+ SomeParameter6
+ });
+ }
+
+
+ [HttpPost("~/api/DemoProposal2/{SomeParameter2}")]
+ public async Task<IActionResult> DemoProposal2(
+ string Referer,
+ string SomeParameter2,
+ string SomeParameter3,
+ ApiModel SomeParameter4,
+ string SomeParameter5,
+ string SomeParameter6)
+ {
+ await Task.Yield();
+
+ return Ok(new
+ {
+ Referer,
+ SomeParameter2,
+ SomeParameter3,
+ SomeParameter4,
+ SomeParameter5,
+ SomeParameter6
+ });
+ }
+
+ [HttpPost("~/api/ComplexString")]
+ public async Task<IActionResult> ComplexString(string Name)
+ {
+ await Task.Yield();
+
+ return Ok(new
+ {
+ Name
+ });
+ }
+
+ [HttpPost("~/api/ComplexDouble")]
+ public async Task<IActionResult> ComplexDouble(double? F)
+ {
+ await Task.Yield();
+
+ return Ok(new
+ {
+ F
+ });
+ }
+
+ [HttpPost("~/api/ComplexStringInt")]
+ public async Task<IActionResult> ComplexStringInt(string Name, int A)
+ {
+ await Task.Yield();
+
+ return Ok(new
+ {
+ Name,
+ A
+ });
+ }
+
+ [HttpPost("~/api/ComplexListOfStrings")]
+ public async Task<IActionResult> ComplexListOfStrings(List<string> ListOfStrings)
+ {
+ await Task.Yield();
+
+ return Ok(new
+ {
+ ListOfStrings
+ });
+ }
+
+ [HttpPost("~/api/ComplexListOfInts")]
+ public async Task<IActionResult> ComplexListOfInts(List<int?> ListOfInts)
+ {
+ await Task.Yield();
+
+ return Ok(new
+ {
+ ListOfInts
+ });
+ }
+
+ [HttpPost("~/api/ComplexListNullableDouble")]
+ public async Task<IActionResult> ComplexListNullableDouble(List<double?> list)
+ {
+ await Task.Yield();
+
+ return Ok(new
+ {
+ list
+ });
+ }
+
+ [HttpPost("~/api/ComplexListObjecs")]
+ public async Task<IActionResult> ComplexListObjecs(List<string> list)
+ {
+ await Task.Yield();
+
+ return Ok(new
+ {
+ list
+ });
+ }
+
+ [HttpPost("~/api/ComplexStringList")]
+ public async Task<IActionResult> ComplexStringList(string Name, List<string> list)
+ {
+ await Task.Yield();
+
+ return Ok(new
+ {
+ Name,
+ list
+ });
+ }
+
+ public class ObjectB
+ {
+ public List<ObjectA> List { get; set; }
+
+ public string Name { get; set; }
+ }
+
+ public class ObjectA
+ {
+ public string a { get; set; }
+ public string b { get; set; }
+ }
+
+ [HttpPost("~/api/ComplexSingleObject")]
+ public async Task<IActionResult> ComplexSingleObject(ObjectA AA)
+ {
+ await Task.Yield();
+
+ return Ok(new
+ {
+ AA
+ });
+ }
+
+ [HttpPost("~/api/ComplexArray")]
+ public async Task<IActionResult> ComplexArray([FromBody] ObjectA[] list)
+ {
+ await Task.Yield();
+
+ return Ok(new
+ {
+ list
+ });
+ }
+
+ [HttpPost("~/api/ComplexObjectArray")]
+ public async Task<IActionResult> ComplexObjectArray(ObjectB objB)
+ {
+ await Task.Yield();
+
+ return Ok(new
+ {
+ objB
+ });
+ }
+
+ public class ObjectC
+ {
+ public string Name { get; set; }
+ public List<List<string>> Users { get; set; }
+ }
+
+ [HttpPost("~/api/ComplexArrayArray")]
+ public async Task<IActionResult> ComplexArrayArray(string Group, ObjectC List)
+ {
+ await Task.Yield();
+
+ return Ok(new
+ {
+ Group,
+ List
+ });
+ }
+
+ public class Usert
+ {
+ public string Name { get; set; }
+
+ public List<string> Alias { get; set; }
+ }
+
+ public class ObjectD
+ {
+ public string Name { get; set; }
+ public List<List<Usert>> Users { get; set; }
+ }
+
+ [HttpPost("~/api/ComplexArrayArrayClass")]
+ public async Task<IActionResult> ComplexArrayArrayClass(bool Testing, bool Relaxed, string Group, ObjectD GroupInfo)
+ {
+ await Task.Yield();
+
+ return Ok(new
+ {
+ Testing,
+ Relaxed,
+ Group,
+ GroupInfo
+ });
+ }
+
+
+ /// <summary>
+ /// Uploads have default a maximum of 30MByte presenting upload example of 2.5GB
+ ///
+ /// For IIS Limit maxAllowedContentLength in Web.config (in the root of the app, not in wwwroot content folder!)
+ ///
+ /// </summary>
+ /// <param name="formFile"></param>
+ /// <returns></returns>
+ [HttpPost("~/api/Upload")]
+ [RequestSizeLimit(2_500_000_000)]
+ [RequestFormLimits(MultipartBodyLengthLimit = 2_500_000_000)]
+ public async Task<IActionResult> Upload(List<IFormFile> files, string Form1)
+ {
+ if(files == null)
+ return BadRequest("No file uploaded or file is empty.");
+
+ if (files.Count == 0)
+ return BadRequest("No file uploaded or file is empty.");
+
+ try
+ {
+ var Length = files.Count;
+ var filePath = Path.GetTempFileName();
+
+ using (var stream = new FileStream(filePath, FileMode.Create))
+ await files[0].CopyToAsync(stream);
+
+ return Ok(new
+ {
+ Form1,
+ Length,
+ filePath
+ });
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, new { Message = "An error occurred while uploading the file.", Error = ex.Message });
+ }
+
+ }
+
+ public enum AddressEnum
+ {
+ Unknown,
+ Living,
+ Working
+ }
+
+ public enum StatusEnum
+ {
+ Unknown,
+ Active,
+ Passive
+ }
+
+ [HttpGet("~/api/GetEnums")]
+ public async Task<IActionResult> GetEnums()
+ {
+ await Task.Yield();
+ return Ok(new
+ {
+ StatusEnum.Active,
+ AddressEnum.Working
+ });
+ }
+
+ public class Product
+ {
+ public StatusEnum StatusType { get; set; }
+ public AddressEnum AddressType { get; set; }
+ }
+
+ [HttpPost("~/api/PostEnum")]
+ public async Task<IActionResult> PostEnum(StatusEnum status)
+ {
+ await Task.Yield();
+ return Ok();
+ }
+
+ [HttpPost("~/api/PostEnums")]
+ public async Task<IActionResult> PostEnums(Product product)
+ {
+ await Task.Yield();
+ return Ok();
+ }
+
+
+ [HttpPost("~/api/PostEnumAsString")]
+ public async Task<IActionResult> PostEnumAsString(StatusEnum status)
+ {
+ await Task.Yield();
+ return Ok(new
+ {
+ Status = status.ToString(),
+ Value = (int)status
+ });
+ }
+
+
+ /// <summary>
+ ///
+ /// </summary>
+ /// <param name="mycookie">the name of the var must the name of the cookie</param>
+ /// <returns></returns>
+ [HttpGet("~/api/GetCookie")]
+ public IActionResult GetCookie(string mycookie)
+ {
+ return Ok(new
+ {
+ Value = mycookie
+ });
+ }
+
+ [HttpPost("~/api/PostIt")]
+ public IActionResult PostIt(string Name, int Age)
+ {
+
+ return Ok(new
+ {
+ Name,
+ Age
+ });
+ }
+}
diff --git a/tests/VanDerHeijden.JsonBodyProviderTest/Controllers/SpeedController.cs b/tests/VanDerHeijden.JsonBodyProviderTest/Controllers/SpeedController.cs
new file mode 100644
index 0000000..3fa11c0
--- /dev/null
+++ b/tests/VanDerHeijden.JsonBodyProviderTest/Controllers/SpeedController.cs
@@ -0,0 +1,87 @@
+using Microsoft.AspNetCore.Mvc;
+
+
+namespace VanDerHeijden.JsonBodyProviderTest.Controllers;
+
+[Route("~/api")]
+public class SpeedController : ControllerBase
+{
+ public class Usert
+ {
+ public string Name { get; set; } = string.Empty;
+
+ public List<string> Alias { get; set; } = [];
+ }
+
+ public class ApiModel
+ {
+ public string Name { get; set; } = string.Empty;
+ public List<List<Usert>> Users { get; set; } = [];
+
+ public override string ToString() => Name;
+ }
+
+ [HttpPost("~/api/Speedtest/{SomeParameter2}")]
+ public IActionResult Speedtest(
+ string Referer,
+ string SomeParameter2,
+ string SomeParameter3,
+ ApiModel SomeParameter4,
+ double SomeParameter5,
+ int SomeParameter6)
+ {
+ return Ok(new
+ {
+ Referer,
+ SomeParameter2,
+ SomeParameter3,
+ SomeParameter4,
+ SomeParameter5,
+ SomeParameter6
+ });
+ }
+
+
+ // Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
+ public class Address
+ {
+ public string street { get; set; }
+ public string city { get; set; }
+ public string zipcode { get; set; }
+ }
+
+ public class Metadata
+ {
+ public int totalCount { get; set; }
+ public DateTime generatedAt { get; set; }
+ }
+
+ public class Preferences
+ {
+ public string theme { get; set; }
+ public bool notifications { get; set; }
+ }
+
+ public class LargeObject
+ {
+ public List<User> users { get; set; }
+ public Metadata metadata { get; set; }
+ }
+
+ public class User
+ {
+ public int id { get; set; }
+ public string name { get; set; }
+ public string email { get; set; }
+ public Address address { get; set; }
+ public Preferences preferences { get; set; }
+ }
+
+
+ [HttpPost("~/api/LargeJson")]
+ public IActionResult LargeJson(LargeObject largeJson)
+ {
+ return Ok();
+ }
+
+}
diff --git a/tests/VanDerHeijden.JsonBodyProviderTest/Program.cs b/tests/VanDerHeijden.JsonBodyProviderTest/Program.cs
index 15dc5b2..22832ac 100644
--- a/tests/VanDerHeijden.JsonBodyProviderTest/Program.cs
+++ b/tests/VanDerHeijden.JsonBodyProviderTest/Program.cs
@@ -1,13 +1,30 @@
using VanDerHeijden.JsonBodyProvider;
-var builder = WebApplication.CreateBuilder(args);
+var builder = WebApplication.CreateBuilder(new WebApplicationOptions
+{
+ // the 'real' root of the application
+ ContentRootPath = AppDomain.CurrentDomain.BaseDirectory
+});
-builder.Services
- .AddControllers()
- .Services
- .AddJsonBodyProvider(CorrectLists: true);
+builder.Services.AddMvcCore();
+
+builder.Services.AddJsonBodyProvider(CorrectLists: true, ParseCookies: true, ParseHeaders: true);
+
+builder.Services.AddDistributedMemoryCache();
+
+builder.Services.AddSession();
var app = builder.Build();
+app.UseRouting();
+
+app.UseSession();
+
+app.UseDefaultFiles();
+
+app.UseStaticFiles();
+
app.MapControllers();
+
app.Run();
+
diff --git a/tests/VanDerHeijden.JsonBodyProviderTest/VanDerHeijden.JsonBodyProviderTest.csproj b/tests/VanDerHeijden.JsonBodyProviderTest/VanDerHeijden.JsonBodyProviderTest.csproj
index db27ba6..4dba3f7 100644
--- a/tests/VanDerHeijden.JsonBodyProviderTest/VanDerHeijden.JsonBodyProviderTest.csproj
+++ b/tests/VanDerHeijden.JsonBodyProviderTest/VanDerHeijden.JsonBodyProviderTest.csproj
@@ -1,13 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
- <PropertyGroup>
- <TargetFramework>net10.0</TargetFramework>
- <Nullable>enable</Nullable>
- <ImplicitUsings>enable</ImplicitUsings>
- </PropertyGroup>
-
- <ItemGroup>
- <ProjectReference Include="..\..\src\VanDerHeijden.JsonBodyProvider\VanDerHeijden.JsonBodyProvider.csproj" />
- </ItemGroup>
+ <PropertyGroup>
+ <TargetFramework>net10.0</TargetFramework>
+ <Nullable>enable</Nullable>
+ <ImplicitUsings>enable</ImplicitUsings>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\VanDerHeijden.JsonBodyProvider\VanDerHeijden.JsonBodyProvider.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Content Update="wwwroot\*">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Update="wwwroot\*\*">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ </ItemGroup>
</Project>
diff --git a/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Default.css b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Default.css
new file mode 100644
index 0000000..831ebe3
--- /dev/null
+++ b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Default.css
@@ -0,0 +1,201 @@
+html
+{
+ box-sizing: border-box;
+}
+
+*, *:before, *:after
+{
+ box-sizing: inherit;
+}
+
+body
+{
+ line-height: 1;
+ font-family: Arial;
+ padding: 10px;
+}
+
+a
+{
+ text-decoration: none;
+ color: dimgrey;
+}
+
+ a:hover
+ {
+ color: #444;
+ }
+
+#NetProxySpinner
+{
+ display: none;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ margin-top: -25px;
+ margin-left: -25px;
+ z-index: 1;
+}
+
+
+.panel
+{
+ display: none;
+}
+
+.active input
+{
+ background-color: red;
+}
+
+.hideMe
+{
+ display: none;
+}
+
+.stopfloating
+{
+ clear: both;
+}
+
+.Error
+{
+ color: red;
+}
+
+body
+{
+ font-family: 'Arial';
+}
+
+.Flexing
+{
+ display: flex;
+ flex-wrap: wrap;
+}
+
+#video
+{
+ width: 640px;
+ height: 480px;
+}
+
+video::-webkit-media-controls-timeline
+{
+ display:none;
+}
+
+video::-webkit-media-controls-current-time-display
+{
+ display: none;
+}
+
+video::-webkit-media-controls-time-remaining-display
+{
+ display: none;
+}
+
+body
+{
+ padding: 20px;
+ overflow-x: auto;
+}
+
+.Hidden
+{
+ display: none;
+}
+
+
+#Title
+{
+ display: inline-block;
+ font-size: 120%;
+ font-weight: bold;
+}
+
+.AdminPanel .ui-input-text
+{
+ margin-top: 0;
+ padding: 10px;
+}
+
+.Navigate td
+{
+ width: 100px;
+}
+
+.TableNormal
+{
+ border-collapse: separate;
+ border-spacing: 0;
+ border: 1px solid #ddd;
+ border-radius: 10px;
+ padding: 4px;
+ min-width: 600px;
+}
+
+ .TableNormal td, .TableNormal th
+ {
+ border: 1px solid #ddd;
+ padding: 8px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ height: 26px;
+ }
+
+ .TableNormal tr:nth-child(even)
+ {
+ background-color: #f2f2f2;
+ }
+
+ .TableNormal th
+ {
+ padding-top: 12px;
+ padding-bottom: 12px;
+ text-align: left;
+ background-color: #4CAF50;
+ color: white;
+ }
+
+ .TableNormal tr:hover
+ {
+ background-color: #c0c0ff;
+ cursor: pointer;
+ }
+
+
+tbody.t_Users td.p_Id
+{
+ color: red;
+}
+
+.Search .ui-input-text
+{
+ position: relative;
+ top: -14px;
+ margin: 0px;
+}
+
+.Search #Title
+{
+ position: relative;
+ top: -14px;
+}
+
+#Result{
+ font-family:'Courier New';
+}
+
+div.error
+{
+ width:800px;
+ background-color:pink;
+}
+
+div.ok
+{
+ width: 800px;
+ background-color: lightgreen;
+}
diff --git a/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Default.js b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Default.js
new file mode 100644
index 0000000..1a40486
--- /dev/null
+++ b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Default.js
@@ -0,0 +1,361 @@
+const result = $id('Result');
+var r, result1, result2;
+
+onReady(async () =>
+{
+ PageEvents();
+ await InitAsync();
+});
+
+function PageEvents()
+{
+ document.on("click", async function (e)
+ {
+ if (e.target.id && typeof window[e.target.id] === "function")
+ {
+ const func = window[e.target.id].call(e, e);
+ if (func instanceof Promise)
+ await func;
+ }
+ });
+
+ $id("UploadExample").on("change", function (e)
+ {
+ StartUpload(e);
+ });
+}
+
+async function InitAsync()
+{
+ await netproxyasync("./api/HelloWorld"); // Makes session (and cookie)
+}
+
+function C(s, jscript)
+{
+ if (eval(jscript))
+ result.innerHTML += "<div class='ok'>" + s + " :: " + jscript + '</div>\n';
+ else
+ result.innerHTML += "<div class='error'>" + s + " :: " + jscript + '</div>\n';
+}
+
+async function UnitTest()
+{
+ result.innerText = '';
+
+ var user = { user: 'alphons' };
+
+ r = await netproxyasync("./api/GetCookie");
+ C("Cookies", "r.Value == '1234'");
+ r = await netproxyasync("./api/GetHeader");
+ C("Headers (Accept)", "r.Value == '*/*'");
+ r = await netproxyasync("./api/QueryString?question=answer");
+ C("QueryString", "r.Value == 'answer'");
+
+ var formData = new FormData();
+ formData.append("Name", "Alphons");
+ formData.append("Age", "25")
+ r = await netproxyasync("/api/PostIt", formData);
+ C("Post Form Data", "r.Name == 'Alphons' && r.Age ==25");
+
+ r = await netproxyasync("./api/SimpleString1", user);
+ C("SimpleString1", "r.user == 'alphons'");
+ r = await netproxyasync("./api/SimpleString2", user);
+ C("SimpleString2", "r.user == 'alphons'");
+ r = await netproxyasync("./api/SimpleString3", { user: 'alphons' });
+ C("SimpleString3", "r.user == 'alphons'");
+ r = await netproxyasync("./api/SimpleString4", { user: 'alphons' });
+ C("SimpleString4", "r.user == 'alphons'");
+
+ r = await netproxyasync("./api/PostEnumAsString", { status: 'Active' });
+ C("PostEnumAsString", "r.Status == 'Active' && r.Value == 1");
+
+ var users = { "users": ["admins", "editors", null, "sisters"] };
+
+ r = await netproxyasync("./api/ArrayOfStrings1", users);
+ C("ArrayOfStrings1", "r.users && r.users.length == 4");
+ r = await netproxyasync("./api/ArrayOfStrings2", ["admins", "editors", null, "sisters"]);
+ C("ArrayOfStrings2", "r.users && r.users.length == 4");
+ r = await netproxyasync("./api/ArrayOfStrings3", users);
+ C("ArrayOfStrings3", "r.users && r.users.length == 4");
+ r = await netproxyasync("./api/ArrayOfStrings4", users);
+ C("ArrayOfStrings4", "r.users && r.users.length == 4");
+
+ var arrayofarrayofstrings = { "users": [["admins", "0"], ["editors", "1"], null, ["sisters", "3"]] };
+ r = await netproxyasync("./api/ArrayOfArrayOfStrings1", arrayofarrayofstrings);
+ C("ArrayOfArrayOfStrings1", "r.users && r.users.length == 4");
+ r = await netproxyasync("./api/ArrayOfArrayOfStrings2", arrayofarrayofstrings);
+ C("ArrayOfArrayOfStrings2", "r.users && r.users.length == 4");
+
+ r = await netproxyasync("./api/TwoParameters1", { a: 'aa aa', b: 'bbb bbb' });
+ C("TwoParameters1", "r.a =='aa aa' && r.b =='bbb bbb'");
+ r = await netproxyasync("./api/TwoParameters2", { a: 'aa aa', b: 'bbb bbb' });
+ C("TwoParameters2", "r.a =='aa aa' && r.b =='bbb bbb'");
+ r = await netproxyasync("./api/TwoParameters3", { a: { user: 'aa aa' }, b: { user: 'bbb bbb' } });
+ C("TwoParameters3", "r.a =='aa aa' && r.b =='bbb bbb'");
+
+ r = await netproxyasync("./api/ListOfDoubles", { list: [1.2, 3.4, 5.6, null, 7.8] });
+ C("ListOfDoubles", "r.list[0] == 1.2");
+ C("ListOfDoubles", "r.list[1] == 3.4");
+ C("ListOfDoubles", "r.list[2] == 5.6");
+ C("ListOfDoubles", "r.list[3] == null");
+ C("ListOfDoubles", "r.list[4] == 7.8");
+
+ //r = await netproxyasync("./api/ComplexArrayArray",
+ // {
+ // Group: "groupy",
+ // List:
+ // {
+ // Name: "My Name is",
+ // "Users":
+ // [
+ // ["admins", "0"],
+ // ["editors", "1"],
+ // null,
+ // ["sisters", "3"]
+ // ]
+ // }
+ // });
+
+ r = await netproxyasync("./api/ComplexDouble", { F: 123.456 });
+ C("ComplexDouble", "r.F == 123.456");
+
+ r = await netproxyasync("./api/ComplexDouble", { F: '123.456' }); // Needs JsonNumberHandling.AllowReadingFromString
+ C("ComplexDouble", "r.F == 123.456");
+
+ r = await netproxyasync("./api/ComplexSingleObject", { a: 'aaa', b: 'bbb' });
+ C("ComplexSingleObject", "r.AA.a== 'aaa'");
+ C("ComplexSingleObject", "r.AA.b== 'bbb'");
+
+ r = await netproxyasync("./api/ComplexString", { Name: 'This is a test' });
+ C("ComplexString", "r.Name == 'This is a test'");
+
+ r = await netproxyasync("./api/ComplexStringInt", { Name: 'This is a test', A: 123 });
+ C("ComplexStringInt", "r.Name == 'This is a test'");
+ C("ComplexStringInt", "r.A == 123");
+
+ r = await netproxyasync("./api/ComplexListOfStrings", { ListOfStrings: ['a', '', 'b', null, 'c'] });
+ C("ComplexList", "r.ListOfStrings[0]== 'a'");
+ C("ComplexList", "r.ListOfStrings[1]== ''");
+ C("ComplexList", "r.ListOfStrings[2]== 'b'");
+ C("ComplexList", "r.ListOfStrings[3]== null");
+ C("ComplexList", "r.ListOfStrings[4]== 'c'");
+
+ r = await netproxyasync("./api/ComplexListOfInts", { ListOfInts: [0, 1, 2, null, 4] });
+ C("ComplexListInt", "r.ListOfInts[0]== 0");
+ C("ComplexListInt", "r.ListOfInts[1]== 1");
+ C("ComplexListInt", "r.ListOfInts[2]== 2");
+ C("ComplexListInt", "r.ListOfInts[3]== null");
+ C("ComplexListInt", "r.ListOfInts[4]== 4");
+
+ r = await netproxyasync("./api/ComplexArray", [{ a: 'a', b: null }, null, { a: 'c', b: 'd' }]);
+ C("ComplexArray", "r.list.length == 3");
+ if (r.list.length == 3)
+ {
+ C("ComplexArray", "r.list[0].a== 'a'")
+ C("ComplexArray", "r.list[0].b== null");
+ C("ComplexArray", "r.list[1]== null");
+ C("ComplexArray", "r.list[2].a== 'c'");
+ C("ComplexArray", "r.list[2].b== 'd'");
+ }
+
+ r = await netproxyasync("./api/ComplexArrayArray",
+ {
+ Group: "groupy",
+ List:
+ {
+ Name: "My Name is",
+ "Users":
+ [
+ ["admins", "0"],
+ ["editors", "1"],
+ null,
+ ["sisters", "3"]
+ ]
+ }
+ });
+ C("ComplexArrayArray", "r.Group== 'groupy'");
+ C("ComplexArrayArray", "r.List.Name== 'My Name is'");
+ C("ComplexArrayArray", "r.List.Users.length== 4");
+ if (r.List.Users.length > 0)
+ {
+ C("ComplexArrayArray", "r.List.Users[0][0]== 'admins'");
+ C("ComplexArrayArray", "r.List.Users[0][1]== '0'");
+ C("ComplexArrayArray", "r.List.Users[1][0]== 'editors'");
+ C("ComplexArrayArray", "r.List.Users[1][1]== '1'");
+ C("ComplexArrayArray", "r.List.Users[2]== null");
+ }
+ if (r.List.Users.length == 4)
+ {
+ C("ComplexArrayArray", "r.List.Users[3][0]== 'sisters'");
+ C("ComplexArrayArray", "r.List.Users[3][1]== '3'");
+ }
+
+ r = await netproxyasync("./api/ComplexArrayArrayClass",
+ {
+ Testing: true,
+ Relaxed: false,
+ Group: "Nice Group",
+ GroupInfo:
+ {
+ Name: "My Name is",
+ "Users":
+ [
+ [{ Name: "User00", Alias: ['aliasa', 'aliasb', 'aliasc'] }, { Name: "User01" }],
+ [{ Name: "User10" }, { Name: "User11" }],
+ [{ Name: "User20" }, { Name: "User21" }]
+ ]
+ }
+ });
+ C("ComplexArrayArrayClass", "r.Testing== true");
+ C("ComplexArrayArrayClass", "r.Relaxed== false");
+ C("ComplexArrayArrayClass", "r.Group== 'Nice Group'");
+ C("ComplexArrayArrayClass", "r.GroupInfo.Name== 'My Name is'");
+ C("ComplexArrayArrayClass", "r.GroupInfo.Users.length== 3");
+ if (r.GroupInfo.Users.length > 0)
+ {
+ C("ComplexArrayArrayClass", "r.GroupInfo.Users[0][0].Name== 'User00'");
+ C("ComplexArrayArrayClass", "r.GroupInfo.Users[0][0].Alias.length== 3");
+ C("ComplexArrayArrayClass", "r.GroupInfo.Users[0][1].Name== 'User01'");
+ C("ComplexArrayArrayClass", "r.GroupInfo.Users[0][1].Alias== null");
+ C("ComplexArrayArrayClass", "r.GroupInfo.Users[1][0].Name== 'User10'");
+ C("ComplexArrayArrayClass", "r.GroupInfo.Users[1][0].Alias== null");
+ C("ComplexArrayArrayClass", "r.GroupInfo.Users[1][1].Name== 'User11'");
+ C("ComplexArrayArrayClass", "r.GroupInfo.Users[1][1].Alias== null");
+ C("ComplexArrayArrayClass", "r.GroupInfo.Users[2][0].Name== 'User20'");
+ C("ComplexArrayArrayClass", "r.GroupInfo.Users[2][0].Alias== null");
+ C("ComplexArrayArrayClass", "r.GroupInfo.Users[2][1].Name== 'User21'");
+ C("ComplexArrayArrayClass", "r.GroupInfo.Users[2][1].Alias== null");
+ C("ComplexArrayArrayClass", "r.GroupInfo.Users[0][0].Alias[0]== 'aliasa'");
+ C("ComplexArrayArrayClass", "r.GroupInfo.Users[0][0].Alias[1]== 'aliasb'");
+ C("ComplexArrayArrayClass", "r.GroupInfo.Users[0][0].Alias[2]== 'aliasc'");
+ }
+ // Checking, run till end
+
+ netproxy("./api/GetEnums", null, function ()
+ {
+ var data = this;
+ });
+
+ var product = { StatusType: 1, AddressType: 2 };
+
+ netproxy("./api/PostEnums", { product: product }, function ()
+ {
+ var data = this;
+ });
+
+ netproxy("./api/PostEnum", { status: 1 }, function ()
+ {
+ var data = this;
+ });
+
+ netproxy("./api/NotANumber", null, function ()
+ {
+ var a = this.Value;
+ });
+
+ try
+ {
+ r = await netproxyasync("./api/NotANumber");
+ }
+ catch (e)
+ {
+ }
+
+ C("NotANumber", "r.Value == 'NaN'");
+
+ C("ready (must be false and red)", "true == false");
+}
+
+
+//netproxy("./api/ComplexDouble", { F: null });
+//netproxy("./api/ComplexDouble", { F: 123.456 });
+//netproxy("./api/ComplexString", { Name: null });
+//netproxy("./api/ComplexString", { Name: 12.34 });
+//netproxy("./api/ComplexString", { Name: 'abc def' });
+//netproxy("./api/ComplexStringInt", { Name: 'abc def', A : 123 });
+//netproxy("./api/ComplexList", { list: [ "a", "b" , "", null, "d"] });
+//netproxy("./api/ComplexList", { list: ["a", "", "b", null, "c"] }); // "a" null "b" "b" "c" WTF?
+//netproxy("./api/ComplexListNullableDouble", { list: [1.2, 3.4, 5.6, null, 7.8] });
+//netproxy("./api/ComplexListObjecs", { list: [1.2, null, 3 ] }); // numbers to string, using CultureInfo!
+//netproxy("./api/ComplexStringList", { Name: 'abc def', list: [ 'a', 'b' , 'c'] });
+//netproxy("./api/ComplexObjectArray", { objB: { Name: 'Alphons', List: [{ a: 'a', b: 'b' }, { a: 'c', b: 'd' }] } });
+
+
+
+function ProgressHandler(event)
+{
+ var percent = 0;
+ var position = event.loaded || event.position;
+ var total = event.total;
+ if (event.lengthComputable)
+ percent = Math.ceil(position / total * 100);
+ result.innerText = "Uploading " + percent + "%";
+}
+
+function StartUpload(e)
+{
+ var file = e.target.files[0];
+
+ result.innerText = 'Uploading';
+
+ var formData = new FormData();
+
+ formData.append("files", file, file.name);
+ formData.append("files", file, file.name);
+ formData.append("Form1", "another var");
+
+ netproxy("/api/Upload", formData, function ()
+ {
+ result.innerText = 'Ready Length:' + this.Length + " ExtraValue:" + this.Form1 + " path:" + this.filePath;
+ }, window.NetProxyErrorHandler, ProgressHandler);
+}
+
+async function MultiBinderTest()
+{
+ result.innerText = '';
+ result1 = await netproxyasync("./api/DemoProposal/two?SomeParameter3=three&SomeParameter6=six",
+ {
+ "SomeParameter4": // Now the beast has a name
+ {
+ Name: "four",
+ "Users":
+ [
+ [{ Name: "User00", Alias: ['aliasa', 'aliasb', 'aliasc'] }, { Name: "User01" }],
+ [{ Name: "User10" }, { Name: "User11" }],
+ [{ Name: "User20" }, { Name: "User21" }]
+ ]
+ },
+ "SomeParameter5": "five" // double binder
+ });
+
+ result.innerText = 'some alias: ' + result1.SomeParameter4.Users[0][0].Alias[1];
+
+ result2 = await netproxyasync("./api/DemoProposal2/two?SomeParameter3=three&SomeParameter6=six",
+ {
+ "SomeParameter4": // Now the beast has a name
+ {
+ Name: "four",
+ "Users":
+ [
+ [{ Name: "User00", Alias: ['aliasa', 'aliasb', 'aliasc'] }, { Name: "User01" }],
+ [{ Name: "User10" }, { Name: "User11" }],
+ [{ Name: "User20" }, { Name: "User21" }]
+ ]
+ },
+ "SomeParameter5": "five" // double binder
+ });
+
+ result.innerText += ' other alias: ' + result2.SomeParameter4.Users[0][0].Alias[2];
+
+}
+
+async function PostIt()
+{
+ var formData = new FormData();
+
+ formData.append("Name", "Alphons");
+ formData.append("Age", "25")
+
+ var r = netproxyasync("/api/PostIt", formData);
+
+}
\ No newline at end of file
diff --git a/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Index.html b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Index.html
index a8cc217..9c9cd12 100644
--- a/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Index.html
+++ b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Index.html
@@ -1,10 +1,42 @@
-<!DOCTYPE html>
-<html>
+<!doctype html>
+<html lang="en">
<head>
- <meta charset="utf-8" />
- <title></title>
+ <title>VanDerHeijden.JsonBodyProvider Testing</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <meta charset="utf-8" />
+
+ <link href="css/netproxyspinner.css" rel="stylesheet" />
+ <script src="scripts/macros.js?1" defer></script>
+ <script src="scripts/netproxy.js?1" defer></script>
+
+ <link href="Default.css" rel="stylesheet" />
+ <link href="Ui.css" rel="stylesheet" />
+ <script src="Default.js?1" defer></script>
+
</head>
<body>
- <p>Examples in JsonBodyProviderTest.http file</p>
+ <img src="images/netproxyspinner.gif" id="netproxyspinner" />
+
+ <div><span id="UnitTest" class="ui-btn">Unit Test</span></div>
+
+ <div id="Result"></div>
+
+ <div id="UploadExample">
+ <label class="UploadImage ui-btn" for="UploadImage">Upload a File</label>
+ <input type="file" id="UploadImage" />
+ </div>
+
+ <div><span id="MultiBinderTest" class="ui-btn">MultiBinderTest</span></div>
+
+ <div><span id="PostIt" class="ui-btn">PostIt</span></div>
+
+ <div><a href="speedtest.html" class="ui-btn">SpeedTest</a></div>
+
+ <form action="/api/upload" method="post" enctype="multipart/form-data">
+ <label for="files">Select multiple files:</label>
+ <input type="file" name="files" id="files" multiple>
+ <button type="submit">Upload</button>
+ </form>
+
</body>
-</html>
\ No newline at end of file
+</html>
diff --git a/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/ResetAll.css b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/ResetAll.css
new file mode 100644
index 0000000..8510527
--- /dev/null
+++ b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/ResetAll.css
@@ -0,0 +1,72 @@
+/* https://w3bits.com/box-sizing-reset/ */
+html
+{
+ box-sizing: border-box;
+}
+
+*, *:before, *:after
+{
+ box-sizing: inherit;
+}
+
+/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 License: none (public domain) */
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video
+{
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section
+{
+ display: block;
+}
+
+body
+{
+ line-height: 1;
+}
+
+ol, ul
+{
+ list-style: none;
+}
+
+blockquote, q
+{
+ quotes: none;
+}
+
+ blockquote:before, blockquote:after,
+ q:before, q:after
+ {
+ content: '';
+ content: none;
+ }
+
+table
+{
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+body
+{
+ font-family: sans-serif;
+}
diff --git a/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Scripts/errorhandler.js b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Scripts/errorhandler.js
new file mode 100644
index 0000000..2157122
--- /dev/null
+++ b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Scripts/errorhandler.js
@@ -0,0 +1,44 @@
+// version 2.0.2 (last revision Mar, 2024)
+
+window.addEventListener("unhandledrejection", async function (event)
+{
+ event.preventDefault();
+ try
+ {
+ await window.netproxyasync("./api/errorlog",
+ {
+ "event": "-",
+ "errormessage": event.reason.message,
+ "errorstack": event.reason.stack,
+ "path": event.reason.path,
+ "source": event.type
+ });
+ }
+ catch (err)
+ {
+ console.error(err);
+ }
+});
+
+
+window.onerror = async function (message, source, lineno, colno, error)
+{
+ try
+ {
+ if (source.indexOf("api/errorlog") < 0)
+ await window.netproxyasync("./api/errorlog",
+ {
+ "event": message,
+ "errormessage" : error.message,
+ "errorstack": error.stack,
+ "path": source,
+ "source": "line:" + lineno + " col:" + colno
+ });
+ }
+ catch (err)
+ {
+ console.error(err);
+ }
+ return true;
+}
+
diff --git a/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Scripts/macros.js b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Scripts/macros.js
new file mode 100644
index 0000000..fd6154a
--- /dev/null
+++ b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Scripts/macros.js
@@ -0,0 +1,156 @@
+// version 1.3 2020-08-30 (C) AAB van der Heijden
+// Updated 2.4 2025-01-03
+window.$ = document.querySelector.bind(document);
+window.$$ = document.querySelectorAll.bind(document);
+window.$id = document.getElementById.bind(document);
+
+//DOM reader
+//onReady(() =>
+//{
+//});
+const onReady = (callback) => document.addEventListener("DOMContentLoaded", callback);
+//DOM and stylesheets ready
+const onLoad = (callback) => window.addEventListener("load", callback);
+
+Element.prototype.$ = function (selector)
+{
+ return this.querySelector(selector);
+};
+
+Element.prototype.$$ = function (selector)
+{
+ return this.querySelectorAll(selector);
+};
+
+Element.prototype.addClass = function (className)
+{
+ this.classList.add(className);
+ return this;
+};
+
+Element.prototype.removeClass = function (className)
+{
+ this.classList.remove(className);
+ return this;
+};
+
+Element.prototype.toggleClass = function (className)
+{
+ this.classList.toggle(className);
+ return this;
+};
+
+Element.prototype.hasClass = function (className)
+{
+ return this.classList.contains(className);
+};
+
+Element.prototype.show = function ()
+{
+ this.style.display = 'inline-block';
+ return this;
+};
+
+Element.prototype.hide = function ()
+{
+ this.style.display = 'none';
+ return this;
+};
+
+Element.prototype.toggleVisibility = function ()
+{
+ this.style.display = this.style.display === 'none' ? '' : 'none';
+ return this;
+};
+
+Document.prototype.on = function (event, selectorOrHandler, handler)
+{
+ if (typeof selectorOrHandler === 'function')
+ {
+ this.addEventListener(event, selectorOrHandler);
+ }
+ else if (typeof selectorOrHandler === 'string' && typeof handler === 'function')
+ {
+ this.addEventListener(event, function (e)
+ {
+ if (e.target.matches(selectorOrHandler))
+ {
+ handler.call(e.target, e);
+ }
+ });
+ }
+ else
+ {
+ throw new Error('Invalid arguments: Expected (event, selector, handler) or (event, handler).');
+ }
+
+ return this;
+};
+
+
+Element.prototype.on = function (event, selectorOrHandler, handler)
+{
+ if (typeof selectorOrHandler === 'function')
+ {
+ this.addEventListener(event, selectorOrHandler);
+ }
+ else
+ {
+ this.addEventListener(event, function (e)
+ {
+ if (e.target.matches(selectorOrHandler))
+ {
+ handler.call(e.target, e);
+ }
+ });
+ }
+ return this;
+};
+
+Element.prototype.off = function (event, handler)
+{
+ this.removeEventListener(event, handler);
+ return this;
+};
+
+NodeList.prototype.addClass = function (className)
+{
+ this.forEach(el => el.addClass(className));
+ return this;
+};
+
+NodeList.prototype.removeClass = function (className)
+{
+ this.forEach(el => el.removeClass(className));
+ return this;
+};
+
+NodeList.prototype.toggleClass = function (className)
+{
+ this.forEach(el => el.toggleClass(className));
+ return this;
+};
+
+NodeList.prototype.on = function (event, selectorOrHandler, handler)
+{
+ this.forEach(el => el.on(event, selectorOrHandler, handler));
+ return this;
+};
+
+NodeList.prototype.off = function (event, handler)
+{
+ this.forEach(el => el.off(event, handler));
+ return this;
+};
+
+window.formatCurrency = (value, locale = 'nl-NL', currency = 'EUR', options = {}) =>
+{
+ const formatter = new Intl.NumberFormat(locale,
+ {
+ style: 'currency',
+ currency: currency,
+ ...options
+ });
+
+ return formatter.format(value);
+};
diff --git a/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Scripts/netproxy.js b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Scripts/netproxy.js
new file mode 100644
index 0000000..f47e3f6
--- /dev/null
+++ b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Scripts/netproxy.js
@@ -0,0 +1,233 @@
+/**
+ *
+ * @name pure-dom netproxy and template api
+ *
+ * @author Alphons van der Heijden <alphons@heijden.com>
+ * @version 3.0.4 (last revision 30 aug, 2025)
+ * @copyright (c) 2019-2025 Alphons van der Heijden
+ * @alias netproxy, netproxyasync, Element.Template, TemplateHtml
+ *
+ */
+
+// please use defer in script tag
+(function ()
+{
+ 'use strict';
+
+ // Escape functie om XSS te voorkomen
+ function escapeHtml(unsafe)
+ {
+ if (Array.isArray(unsafe))
+ {
+ return unsafe.map(item => escapeHtml(item)).join(', ');
+ }
+ if (unsafe === null || unsafe === undefined)
+ {
+ return '';
+ }
+ return String(unsafe).replace(/[&<>"']/g, function (match)
+ {
+ return {
+ '&': '&amp;',
+ '<': '&lt;',
+ '>': '&gt;',
+ '"': '&quot;',
+ "'": '&#39;'
+ }[match];
+ });
+ }
+
+ // Helper functie voor spinner logica
+ function manageSpinner(spinner, show, timeoutSpinner)
+ {
+ if (spinner)
+ {
+ spinner.style.display = show ? 'block' : 'none';
+ }
+ if (timeoutSpinner && !show)
+ {
+ clearTimeout(timeoutSpinner);
+ }
+ }
+
+ // Netproxy functionaliteit
+ window.netproxy = function (url, data, onsuccess, onerror, onprogress, timeout = 30000)
+ {
+ const spinner = document.getElementById("netproxyspinner");
+ if (typeof remote !== 'undefined')
+ {
+ url = remote + url;
+ }
+
+ const timeoutSpinner = spinner ? setTimeout(() => manageSpinner(spinner, true), 1000) : null;
+ const xhr = new XMLHttpRequest();
+ xhr.open(data ? 'POST' : 'GET', url, true);
+ xhr.withCredentials = url.indexOf(window.location.host) < 0 && url[0] !== '/';
+
+ if (!(data instanceof FormData))
+ {
+ xhr.timeout = timeout;
+ xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
+ }
+
+ xhr.onloadend = function ()
+ {
+ manageSpinner(spinner, false, timeoutSpinner);
+
+ if (xhr.status === 204)
+ {
+ if (typeof onsuccess === 'function')
+ {
+ onsuccess.call(null, null);
+ }
+ return;
+ }
+
+ if (xhr.status >= 200 && xhr.status < 300)
+ {
+ const contentDisposition = xhr.getResponseHeader('Content-Disposition');
+ if (contentDisposition && contentDisposition.includes('attachment'))
+ {
+ const blob = new Blob([xhr.response], { type: xhr.getResponseHeader('Content-Type') });
+ const filename = contentDisposition.split('filename="')[1]?.split('"')[0] || 'download';
+ const a = document.createElement('a');
+ a.href = URL.createObjectURL(blob);
+ a.download = filename;
+ a.rel = 'noopener';
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ setTimeout(() => URL.revokeObjectURL(a.href), 40000);
+ return;
+ }
+
+ let response = xhr.response;
+ try
+ {
+ response = JSON.parse(xhr.responseText);
+ }
+ catch (e)
+ {
+ // Blijf bij tekst als JSON-parsen faalt
+ }
+
+ try
+ {
+ if (typeof onsuccess === 'function')
+ {
+ onsuccess.call(response, response);
+ }
+ return;
+ }
+ catch (error)
+ {
+ if (typeof onerror === 'function')
+ {
+ onerror.call(xhr, error);
+ }
+ return;
+ }
+ }
+
+ if (xhr.status >= 400)
+ {
+ const error = new Error(`Http:${xhr.status}`);
+ error.path = url;
+ if (typeof onerror === 'function')
+ {
+ onerror.call(xhr, error);
+ return;
+ }
+ throw error;
+ }
+
+ if (xhr.status === 0)
+ {
+ const error = xhr.timedout
+ ? new Error(`Timeout ${timeout}ms for ${url}`)
+ : new Error(`Network error or request canceled for ${url}`);
+ error.path = url;
+ if (typeof onerror === 'function')
+ {
+ onerror.call(xhr, error);
+ return;
+ }
+ throw error;
+ }
+ };
+
+ if (typeof onprogress === 'function')
+ {
+ xhr.upload.onprogress = onprogress;
+ xhr.onprogress = onprogress;
+ }
+
+ xhr.send(data instanceof FormData ? data : JSON.stringify(data));
+ };
+
+ window.netproxyasync = function (url, data, onprogress, timeout = 30000)
+ {
+ return new Promise((resolve, reject) =>
+ {
+ window.netproxy(url, data, resolve, reject, onprogress, timeout);
+ });
+ };
+
+ // Template functionaliteit
+ Element.prototype.Template = function (template, data, append)
+ {
+ var strHtml = window.TemplateHtml(template, data);
+ if (append)
+ {
+ var temp = document.createElement("span");
+ this.insertAdjacentElement('beforeend', temp);
+ temp.outerHTML = strHtml;
+ }
+ else
+ {
+ this.innerHTML = strHtml;
+ }
+ };
+
+ window.TemplateHtml = function (template, data)
+ {
+ try
+ {
+ var element = typeof template === "string" ? document.getElementById(template) : template;
+ if (!element)
+ throw new Error("Template niet gevonden.");
+ if (!element.jscache)
+ {
+ var html = element.innerHTML.replace(/[\t\r\n]/g, " ");
+ var js = "let _='';";
+ var direct, intI, intJ = 0;
+ while (intJ < html.length)
+ {
+ intI = html.indexOf("{{", intJ);
+ if (intI < 0)
+ break;
+ js += `_+='${html.substring(intJ, intI).replace(/'/g, "\\'")}';`;
+ intJ = intI + 2;
+ direct = (html[intJ] === "=");
+ if (direct)
+ intJ++;
+ intI = html.indexOf("}}", intJ);
+ if (intI < 0)
+ break;
+ js += direct ? `_+=escapeHtml(${html.substring(intJ, intI).trim()});` : html.substring(intJ, intI).trim() + ";";
+ intJ = intI + 2;
+ }
+ js += `_+='${html.substring(intJ).replace(/'/g, "\\'")}'; return _;`;
+ element.jscache = new Function('escapeHtml', 'data', js);
+ element.innerHTML = '';
+ }
+ return element.jscache.call(data, escapeHtml);
+ }
+ catch (err)
+ {
+ console.error("Template fout:", err.message);
+ return "";
+ }
+ };
+})();
+
diff --git a/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/SpeedTest.js b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/SpeedTest.js
new file mode 100644
index 0000000..8ed99ab
--- /dev/null
+++ b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/SpeedTest.js
@@ -0,0 +1,87 @@
+const output = $id('Output');
+
+const input = {
+ "SomeParameter4":
+ {
+ Name: "four",
+ "Users":
+ [
+ [{ Name: "User00", Alias: ['aliasa', 'aliasb', 'aliasc'] }, { Name: "User01" }],
+ [{ Name: "User10" }, { Name: "User11" }],
+ [{ Name: "User20" }, { Name: "User21" }]
+ ]
+ },
+ "SomeParameter5": 5.5 // double binder
+};
+
+const largeJson = { users: [], metadata: {} };
+
+for (let i = 1; i <= 1000; i++)
+{
+ largeJson.users.push({
+ id: i,
+ name: `User ${i}`,
+ email: `user${i}@example.com`,
+ address: {
+ street: `${i} Example Rd`,
+ city: "CityName",
+ zipcode: `ZIP${i}`
+ },
+ preferences: {
+ theme: i % 2 === 0 ? "dark" : "light",
+ notifications: i % 3 === 0
+ }
+ });
+}
+
+largeJson.metadata = {
+ totalCount: largeJson.users.length,
+ generatedAt: new Date().toISOString()
+};
+
+
+//console.log(JSON.stringify(largeJson));
+
+onReady(() =>
+{
+ PageEvents();
+ Init();
+});
+
+function PageEvents()
+{
+ document.on("click", async function (e)
+ {
+ if (e.target.id && typeof window[e.target.id] === "function")
+ {
+ const func = window[e.target.id].call(e, e);
+ if (func instanceof Promise)
+ await func;
+ }
+ });
+}
+
+function Init()
+{
+ netproxy("./api/HelloWorld"); // Makes session (and cookie)
+}
+
+async function SpeedTest()
+{
+ output.innerText = '';
+
+ const tasks = Array.from({ length: 4 }, () => MultiBinderTest());
+ await Promise.all(tasks);
+ console.log("All tasks completed");
+}
+
+async function MultiBinderTest()
+{
+ const result = await netproxyasync("./api/Speedtest/two?SomeParameter3=three&SomeParameter6=6", input);
+}
+
+async function LargeJson()
+{
+ await netproxyasync("./api/LargeJson", { largeJson: largeJson });
+}
+
diff --git a/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Ui.css b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Ui.css
new file mode 100644
index 0000000..4c5c3e5
--- /dev/null
+++ b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/Ui.css
@@ -0,0 +1,321 @@
+@import "ResetAll.css";
+
+body
+{
+ overflow-x: hidden;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+ #NetProxySpinner img
+ {
+ width:50px;
+ height:50px;
+ }
+
+ .ui-btn
+ {
+ position: relative;
+ background-color: #f6f6f6;
+ border: solid 1px #ddd;
+ border-radius: 0.3215em;
+ display: inline-block;
+ text-align: center;
+ cursor: pointer;
+ user-select: none;
+ text-shadow: 1px 1px #fff;
+ text-decoration: none !important;
+ color: #333;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ font-family: sans-serif;
+ font-weight: 700;
+ padding: 0.7em 1.0em;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.15);
+ min-width:150px;
+ }
+
+ .ui-btn:hover
+ {
+ background-color: #eaeaea;
+ }
+
+.ui-select .ui-btn
+{
+ width:100%;
+}
+
+ .ui-select .ui-btn select
+ {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ min-height: 1.5em;
+ min-height: 100%;
+ height: 3em;
+ max-height: 100%;
+ outline: 0;
+ -webkit-border-radius: inherit;
+ border-radius: inherit;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ cursor: pointer;
+ filter: Alpha(Opacity=0);
+ opacity: 0;
+ z-index: 2;
+ }
+
+.ui-btn-wide
+{
+ width: 100%;
+}
+
+
+input, select
+{
+ font-size: 1em;
+ line-height: 1.3;
+ font-family: sans-serif;
+}
+
+.ui-btn-icon:after
+{
+ font-size: 1.5em;
+ display: inline-block;
+ content: "";
+ position: absolute;
+ top: calc(50% - 0.5em);
+ height: 1em;
+ width: 1em;
+ background-color: rgba(0,0,0,.3);
+ background-position: center center;
+ background-repeat: no-repeat;
+ border-radius: 1em;
+ border: solid 1px #fff;
+}
+
+.ui-btn.ui-btn-notext
+{
+ height: 0.1em;
+ width: 2px !important;
+ border-radius: 1.2em;
+ padding:1.1em;
+ vertical-align:middle;
+}
+
+
+input[type="file"]::-webkit-file-upload-button
+{
+ width:0;
+ padding-left: 0px;
+ padding-right: 0px;
+ border: none;
+}
+
+input.ui-btn
+{
+ width: 100%;
+}
+
+.ui-btn-icon-left
+{
+ padding-left: 2.0em;
+}
+
+.ui-btn-icon-right
+{
+ padding-left: 0.5em;
+ padding-right: 2.0em;
+}
+
+.ui-btn-icon-left:after
+{
+ left: 0.25em;
+}
+
+.ui-btn-icon-right:after
+{
+ right: 0.25em;
+}
+
+button.ui-btn-icon-left, input.ui-btn-icon-left
+{
+ padding-left: 2.0em;
+}
+
+button.ui-btn-icon-right, input.ui-btn-icon-right
+{
+ padding-right: 2.0em;
+}
+
+.ui-grid-a
+{
+ display: grid;
+ grid-template-columns: auto auto;
+ grid-gap: 5px;
+}
+
+.ui-block
+{
+ grid-column-end: span 2;
+}
+
+.ui-block-a
+{
+ grid-column: 1;
+}
+
+.ui-block-b
+{
+ grid-column: 2;
+}
+
+
+/* PAGES */
+
+:root
+{
+ --menu-width: 100%;
+}
+
+.ui-page-container
+{
+ position: relative;
+ height: 100%;
+ display: grid;
+ grid-template-columns: var(--menu-width) 100% 100% 100% 100% 100% 100% 100%;
+ transition: transform 0.4s;
+ transform: translateX(calc(0px - var(--menu-width)));
+}
+
+.ui-page
+{
+ display: none;
+ vertical-align: baseline;
+ line-height: 1.5em;
+ text-shadow: 0 1px 0 #f3f3f3;
+}
+
+ .ui-page .ui-content
+ {
+ padding:10px;
+ }
+
+td
+{
+ box-sizing: content-box;
+ vertical-align:middle;
+}
+
+.ui-header
+{
+ background-color: #e0e0e0;
+}
+ .ui-header .ui-btn
+ {
+ position: absolute;
+ top: 0.25em;
+ }
+
+ .ui-header .ui-btn.ui-btn-icon-right.ui-btn-notext
+ {
+ position: absolute;
+ top: 0.5em !important;
+ right: 0.5em;
+ }
+
+ .ui-header .ui-btn.ui-btn-icon-left.ui-btn-notext
+ {
+ position: absolute;
+ top: 0.5em !important;
+ left: 0.5em;
+ }
+
+ .ui-header h1
+ {
+ text-align: center;
+ display:block;
+ font-size: 1.5em;
+ font-weight: bold;
+ margin: 0 20%;
+ padding: 0.75em 0;
+ overflow:hidden;
+ text-overflow:ellipsis;
+ white-space: nowrap;
+ }
+
+.ui-field-contain
+{
+ border-bottom-color: #828282;
+ border-bottom-color: rgba(0,0,0,.15);
+ border-bottom-width: 1px;
+ border-bottom-style: solid;
+ display: block;
+ position: relative;
+ overflow: visible;
+ clear: both;
+ padding: .8em 1em;
+}
+
+input:focus, button:focus
+{
+ box-shadow: 0 0 12px #38c;
+}
+
+.ui-input-text
+{
+ margin: .5em 0;
+ border-width: 1px;
+ border-style: solid;
+
+ background-color: #fff;
+ border-color: #ddd;
+ color: #333;
+ text-shadow: 0 1px 0 #f3f3f3;
+}
+
+.ui-input-text input
+{
+ width:100%;
+ margin: 0;
+ min-height: 2.2em;
+ text-align: left;
+ border: 0;
+ background: transparent none;
+ border-radius: inherit;
+
+ padding: .4em;
+ line-height: 1.4em;
+ display: block;
+ box-sizing: border-box;
+ outline: 0;
+}
+
+.ui-shadow-inset
+{
+ box-shadow: inset 0 1px 3px rgba(0,0,0,.2);
+}
+
+.ui-popup
+{
+ display:none;
+ position: fixed;
+ width: 92.5%;
+ max-width: 500px;
+ min-height: 200px;
+ border-radius: .3125em;
+ box-shadow: 0 0 12px rgba(0,0,0,.6);
+ background-color: white;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+
+ .ui-popup .ui-content
+ {
+ padding: 5px;
+ }
+
+
diff --git a/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/css/netproxyspinner.css b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/css/netproxyspinner.css
new file mode 100644
index 0000000..5e9f374
--- /dev/null
+++ b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/css/netproxyspinner.css
@@ -0,0 +1,16 @@
+#netproxyspinner
+{
+ display: none;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ margin-top: -25px;
+ margin-left: -25px;
+ z-index: 1;
+}
+
+ #netproxyspinner img
+ {
+ width: 50px;
+ height: 50px;
+ }
diff --git a/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/favicon.ico b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/favicon.ico
new file mode 100644
index 0000000..22ad614
Binary files /dev/null and b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/favicon.ico differ
diff --git a/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/images/netproxyspinner.gif b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/images/netproxyspinner.gif
new file mode 100644
index 0000000..cceac65
Binary files /dev/null and b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/images/netproxyspinner.gif differ
diff --git a/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/speedtest.html b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/speedtest.html
new file mode 100644
index 0000000..1718b30
--- /dev/null
+++ b/tests/VanDerHeijden.JsonBodyProviderTest/wwwroot/speedtest.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <title>Just Testing</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <meta charset="utf-8" />
+
+ <!-- nugetpackage netproxy -->
+ <link href="css/netproxyspinner.css" rel="stylesheet" />
+ <script src="scripts/macros.js?1" defer></script>
+ <script src="scripts/netproxy.js?1" defer></script>
+
+ <link href="Default.css" rel="stylesheet" />
+ <link href="Ui.css" rel="stylesheet" />
+ <script src="SpeedTest.js" defer></script>
+
+</head>
+<body>
+ <img src="images/netproxyspinner.gif" id="netproxyspinner" />
+
+ <div><span id="SpeedTest" class="ui-btn">Start Speed Test</span></div>
+
+ <div><span id="LargeJson" class="ui-btn">Upload LargeJson</span></div>
+
+ <div id="Output"></div>
+
+</body>
+</html>