Needs some work
c72d83d3a6a89f371dcff13b4e80124eb640dcde
71 files changed
src/Netproxy.Web/Controllers/DefaultController.cssrc/Netproxy.Web/Converters/DetailExceptionConverter.cssrc/Netproxy.Web/DataModel/WebError.cssrc/Netproxy.Web/ErrorHandling/ErrorController.cssrc/Netproxy.Web/ErrorHandling/ErrorService.cssrc/Netproxy.Web/Extensions/ServiceRegistrationHelper.cssrc/Netproxy.Web/Netproxy.Web.csprojsrc/Netproxy.Web/Program.cssrc/Netproxy.Web/Properties/launchSettings.jsonsrc/Netproxy.Web/appsettings.Development.jsonsrc/Netproxy.Web/appsettings.jsonsrc/Netproxy.Web/wwwroot/Default.csssrc/Netproxy.Web/wwwroot/Default.htmsrc/Netproxy.Web/wwwroot/Default.jssrc/Netproxy.Web/wwwroot/Template.htmlsrc/Netproxy.Web/wwwroot/bench-template.htmlsrc/Netproxy.Web/wwwroot/css/netproxyspinner.csssrc/Netproxy.Web/wwwroot/error.cshtmlsrc/Netproxy.Web/wwwroot/favicon.icosrc/Netproxy.Web/wwwroot/images/netproxyspinner.gifsrc/Netproxy.Web/wwwroot/scripts/errorhandler.jssrc/Netproxy.Web/wwwroot/scripts/macros.jssrc/Netproxy.Web/wwwroot/scripts/netproxy.jssrc/Netproxy.Web/wwwroot/template-fp.jssrc/Netproxy.Web/wwwroot/template-test.htmlsrc/Netproxy.Web/wwwroot/template.jssrc/NetproxySolution.Web/Controllers/DefaultController.cssrc/NetproxySolution.Web/Converters/DetailExceptionConverter.cssrc/NetproxySolution.Web/DataModel/WebError.cssrc/NetproxySolution.Web/ErrorHandling/ErrorController.cssrc/NetproxySolution.Web/ErrorHandling/ErrorService.cssrc/NetproxySolution.Web/Extensions/ServiceRegistrationHelper.cssrc/NetproxySolution.Web/NetproxySolution.Web.csprojsrc/NetproxySolution.Web/Program.cssrc/NetproxySolution.Web/Properties/launchSettings.jsonsrc/NetproxySolution.Web/appsettings.Development.jsonsrc/NetproxySolution.Web/appsettings.jsonsrc/NetproxySolution.Web/wwwroot/Default.csssrc/NetproxySolution.Web/wwwroot/Default.htmsrc/NetproxySolution.Web/wwwroot/Default.jssrc/NetproxySolution.Web/wwwroot/Template.htmlsrc/NetproxySolution.Web/wwwroot/bench-template.htmlsrc/NetproxySolution.Web/wwwroot/css/netproxyspinner.csssrc/NetproxySolution.Web/wwwroot/error.cshtmlsrc/NetproxySolution.Web/wwwroot/favicon.icosrc/NetproxySolution.Web/wwwroot/images/netproxyspinner.gifsrc/NetproxySolution.Web/wwwroot/scripts/errorhandler.jssrc/NetproxySolution.Web/wwwroot/scripts/macros.jssrc/NetproxySolution.Web/wwwroot/scripts/netproxy.jssrc/NetproxySolution.Web/wwwroot/template-fp.jssrc/NetproxySolution.Web/wwwroot/template-test.htmlsrc/NetproxySolution.Web/wwwroot/template.jssrc/NetproxySolution.slntests/Netproxy.Testing/Controllers/ComplexController.cstests/Netproxy.Testing/Controllers/SpeedController.cstests/Netproxy.Testing/CustomFormValueProviderFactory.cstests/Netproxy.Testing/Netproxy.Testing.csprojtests/Netproxy.Testing/Netroxy.Testing.csprojtests/Netproxy.Testing/Program.cstests/Netproxy.Testing/Properties/launchSettings.jsontests/Netproxy.Testing/appsettings.Development.jsontests/Netproxy.Testing/appsettings.jsontests/Netproxy.Testing/web.configtests/Netproxy.Testing/wwwroot/Default.csstests/Netproxy.Testing/wwwroot/Default.jstests/Netproxy.Testing/wwwroot/ResetAll.csstests/Netproxy.Testing/wwwroot/SpeedTest.jstests/Netproxy.Testing/wwwroot/Ui.csstests/Netproxy.Testing/wwwroot/favicon.icotests/Netproxy.Testing/wwwroot/index.htmltests/Netproxy.Testing/wwwroot/speedtest.html
diff --git a/src/NetproxySolution.Web/Controllers/DefaultController.cs b/src/Netproxy.Web/Controllers/DefaultController.cs
similarity index 100%
rename from src/NetproxySolution.Web/Controllers/DefaultController.cs
rename to src/Netproxy.Web/Controllers/DefaultController.cs
diff --git a/src/NetproxySolution.Web/Converters/DetailExceptionConverter.cs b/src/Netproxy.Web/Converters/DetailExceptionConverter.cs
similarity index 100%
rename from src/NetproxySolution.Web/Converters/DetailExceptionConverter.cs
rename to src/Netproxy.Web/Converters/DetailExceptionConverter.cs
diff --git a/src/NetproxySolution.Web/DataModel/WebError.cs b/src/Netproxy.Web/DataModel/WebError.cs
similarity index 100%
rename from src/NetproxySolution.Web/DataModel/WebError.cs
rename to src/Netproxy.Web/DataModel/WebError.cs
diff --git a/src/NetproxySolution.Web/ErrorHandling/ErrorController.cs b/src/Netproxy.Web/ErrorHandling/ErrorController.cs
similarity index 100%
rename from src/NetproxySolution.Web/ErrorHandling/ErrorController.cs
rename to src/Netproxy.Web/ErrorHandling/ErrorController.cs
diff --git a/src/NetproxySolution.Web/ErrorHandling/ErrorService.cs b/src/Netproxy.Web/ErrorHandling/ErrorService.cs
similarity index 100%
rename from src/NetproxySolution.Web/ErrorHandling/ErrorService.cs
rename to src/Netproxy.Web/ErrorHandling/ErrorService.cs
diff --git a/src/NetproxySolution.Web/Extensions/ServiceRegistrationHelper.cs b/src/Netproxy.Web/Extensions/ServiceRegistrationHelper.cs
similarity index 100%
rename from src/NetproxySolution.Web/Extensions/ServiceRegistrationHelper.cs
rename to src/Netproxy.Web/Extensions/ServiceRegistrationHelper.cs
diff --git a/src/NetproxySolution.Web/NetproxySolution.Web.csproj b/src/Netproxy.Web/Netproxy.Web.csproj
similarity index 100%
rename from src/NetproxySolution.Web/NetproxySolution.Web.csproj
rename to src/Netproxy.Web/Netproxy.Web.csproj
diff --git a/src/NetproxySolution.Web/Program.cs b/src/Netproxy.Web/Program.cs
similarity index 100%
rename from src/NetproxySolution.Web/Program.cs
rename to src/Netproxy.Web/Program.cs
diff --git a/src/NetproxySolution.Web/Properties/launchSettings.json b/src/Netproxy.Web/Properties/launchSettings.json
similarity index 100%
rename from src/NetproxySolution.Web/Properties/launchSettings.json
rename to src/Netproxy.Web/Properties/launchSettings.json
diff --git a/src/NetproxySolution.Web/appsettings.Development.json b/src/Netproxy.Web/appsettings.Development.json
similarity index 100%
rename from src/NetproxySolution.Web/appsettings.Development.json
rename to src/Netproxy.Web/appsettings.Development.json
diff --git a/src/NetproxySolution.Web/appsettings.json b/src/Netproxy.Web/appsettings.json
similarity index 100%
rename from src/NetproxySolution.Web/appsettings.json
rename to src/Netproxy.Web/appsettings.json
diff --git a/src/NetproxySolution.Web/wwwroot/Default.css b/src/Netproxy.Web/wwwroot/Default.css
similarity index 100%
rename from src/NetproxySolution.Web/wwwroot/Default.css
rename to src/Netproxy.Web/wwwroot/Default.css
diff --git a/src/NetproxySolution.Web/wwwroot/Default.htm b/src/Netproxy.Web/wwwroot/Default.htm
similarity index 100%
rename from src/NetproxySolution.Web/wwwroot/Default.htm
rename to src/Netproxy.Web/wwwroot/Default.htm
diff --git a/src/NetproxySolution.Web/wwwroot/Default.js b/src/Netproxy.Web/wwwroot/Default.js
similarity index 100%
rename from src/NetproxySolution.Web/wwwroot/Default.js
rename to src/Netproxy.Web/wwwroot/Default.js
diff --git a/src/NetproxySolution.Web/wwwroot/Template.html b/src/Netproxy.Web/wwwroot/Template.html
similarity index 100%
rename from src/NetproxySolution.Web/wwwroot/Template.html
rename to src/Netproxy.Web/wwwroot/Template.html
diff --git a/src/NetproxySolution.Web/wwwroot/bench-template.html b/src/Netproxy.Web/wwwroot/bench-template.html
similarity index 100%
rename from src/NetproxySolution.Web/wwwroot/bench-template.html
rename to src/Netproxy.Web/wwwroot/bench-template.html
diff --git a/src/NetproxySolution.Web/wwwroot/css/netproxyspinner.css b/src/Netproxy.Web/wwwroot/css/netproxyspinner.css
similarity index 100%
rename from src/NetproxySolution.Web/wwwroot/css/netproxyspinner.css
rename to src/Netproxy.Web/wwwroot/css/netproxyspinner.css
diff --git a/src/NetproxySolution.Web/wwwroot/error.cshtml b/src/Netproxy.Web/wwwroot/error.cshtml
similarity index 100%
rename from src/NetproxySolution.Web/wwwroot/error.cshtml
rename to src/Netproxy.Web/wwwroot/error.cshtml
diff --git a/src/NetproxySolution.Web/wwwroot/favicon.ico b/src/Netproxy.Web/wwwroot/favicon.ico
similarity index 100%
rename from src/NetproxySolution.Web/wwwroot/favicon.ico
rename to src/Netproxy.Web/wwwroot/favicon.ico
diff --git a/src/NetproxySolution.Web/wwwroot/images/netproxyspinner.gif b/src/Netproxy.Web/wwwroot/images/netproxyspinner.gif
similarity index 100%
rename from src/NetproxySolution.Web/wwwroot/images/netproxyspinner.gif
rename to src/Netproxy.Web/wwwroot/images/netproxyspinner.gif
diff --git a/src/NetproxySolution.Web/wwwroot/scripts/errorhandler.js b/src/Netproxy.Web/wwwroot/scripts/errorhandler.js
similarity index 100%
rename from src/NetproxySolution.Web/wwwroot/scripts/errorhandler.js
rename to src/Netproxy.Web/wwwroot/scripts/errorhandler.js
diff --git a/src/NetproxySolution.Web/wwwroot/scripts/macros.js b/src/Netproxy.Web/wwwroot/scripts/macros.js
similarity index 100%
rename from src/NetproxySolution.Web/wwwroot/scripts/macros.js
rename to src/Netproxy.Web/wwwroot/scripts/macros.js
diff --git a/src/NetproxySolution.Web/wwwroot/scripts/netproxy.js b/src/Netproxy.Web/wwwroot/scripts/netproxy.js
similarity index 100%
rename from src/NetproxySolution.Web/wwwroot/scripts/netproxy.js
rename to src/Netproxy.Web/wwwroot/scripts/netproxy.js
diff --git a/src/NetproxySolution.Web/wwwroot/template-fp.js b/src/Netproxy.Web/wwwroot/template-fp.js
similarity index 100%
rename from src/NetproxySolution.Web/wwwroot/template-fp.js
rename to src/Netproxy.Web/wwwroot/template-fp.js
diff --git a/src/NetproxySolution.Web/wwwroot/template-test.html b/src/Netproxy.Web/wwwroot/template-test.html
similarity index 100%
rename from src/NetproxySolution.Web/wwwroot/template-test.html
rename to src/Netproxy.Web/wwwroot/template-test.html
diff --git a/src/NetproxySolution.Web/wwwroot/template.js b/src/Netproxy.Web/wwwroot/template.js
similarity index 100%
rename from src/NetproxySolution.Web/wwwroot/template.js
rename to src/Netproxy.Web/wwwroot/template.js
diff --git a/src/NetproxySolution.sln b/src/NetproxySolution.sln
deleted file mode 100644
index 69f11b1..0000000
--- a/src/NetproxySolution.sln
+++ /dev/null
@@ -1,31 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 18
-VisualStudioVersion = 18.1.11312.151
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetproxySolution.Web", "NetproxySolution.Web\NetproxySolution.Web.csproj", "{CB5A484C-8720-42F0-AD4F-229C23A4029F}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VanDerHeijden.JsonBodyProvider", "..\..\VanDerHeijden.JsonBodyProvider\src\VanDerHeijden.JsonBodyProvider\VanDerHeijden.JsonBodyProvider.csproj", "{E3B61230-E676-CA37-4BB4-ECE447E02513}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {CB5A484C-8720-42F0-AD4F-229C23A4029F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {CB5A484C-8720-42F0-AD4F-229C23A4029F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {CB5A484C-8720-42F0-AD4F-229C23A4029F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {CB5A484C-8720-42F0-AD4F-229C23A4029F}.Release|Any CPU.Build.0 = Release|Any CPU
- {E3B61230-E676-CA37-4BB4-ECE447E02513}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {E3B61230-E676-CA37-4BB4-ECE447E02513}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {E3B61230-E676-CA37-4BB4-ECE447E02513}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {E3B61230-E676-CA37-4BB4-ECE447E02513}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {3077B12D-FD9C-49A1-B06F-696591A75B92}
- EndGlobalSection
-EndGlobal
diff --git a/tests/Netproxy.Testing/Controllers/ComplexController.cs b/tests/Netproxy.Testing/Controllers/ComplexController.cs
new file mode 100644
index 0000000..1dbe6c5
--- /dev/null
+++ b/tests/Netproxy.Testing/Controllers/ComplexController.cs
@@ -0,0 +1,614 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.ModelBinding.MultiParameter;
+
+#nullable disable
+
+namespace Mvc.ModelBinding.MultiParameter.Testing.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(
+ [FromCooky(Name = ".AspNetCore.Session")] string SomeParameter0,
+ [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
+ {
+ SomeParameter0,
+ SomeParameter1,
+ SomeParameter2,
+ SomeParameter3,
+ SomeParameter4,
+ SomeParameter5,
+ SomeParameter6
+ });
+ }
+
+
+ [HttpPost("~/api/DemoProposal2/{SomeParameter2}")]
+ public async Task<IActionResult> DemoProposal(
+ 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(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/Netproxy.Testing/Controllers/SpeedController.cs b/tests/Netproxy.Testing/Controllers/SpeedController.cs
new file mode 100644
index 0000000..9f5e741
--- /dev/null
+++ b/tests/Netproxy.Testing/Controllers/SpeedController.cs
@@ -0,0 +1,87 @@
+using Microsoft.AspNetCore.Mvc;
+
+
+namespace Mvc.ModelBinding.MultiParameter.Testing.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/Netproxy.Testing/CustomFormValueProviderFactory.cs b/tests/Netproxy.Testing/CustomFormValueProviderFactory.cs
new file mode 100644
index 0000000..5eb60c5
--- /dev/null
+++ b/tests/Netproxy.Testing/CustomFormValueProviderFactory.cs
@@ -0,0 +1,29 @@
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using System.Globalization;
+
+namespace Mvc.ModelBinding.MultiParameter.Testing
+{
+ public class CustomFormValueProviderFactory : IValueProviderFactory
+ {
+ public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
+ {
+ ArgumentNullException.ThrowIfNull(context);
+
+ // Check if the request is of the correct content type
+ if (context.ActionContext.HttpContext.Request.ContentType == "application/x-www-form-urlencoded" ||
+ context.ActionContext.HttpContext.Request.ContentType.StartsWith("multipart/form-data"))
+ {
+ var formCollection = context.ActionContext.HttpContext.Request.Form;
+ var valueProvider = new FormValueProvider(
+ BindingSource.Form,
+ formCollection,
+ CultureInfo.CurrentCulture
+ );
+
+ context.ValueProviders.Add(valueProvider);
+ }
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/tests/Netproxy.Testing/Netproxy.Testing.csproj b/tests/Netproxy.Testing/Netproxy.Testing.csproj
new file mode 100644
index 0000000..c738477
--- /dev/null
+++ b/tests/Netproxy.Testing/Netproxy.Testing.csproj
@@ -0,0 +1,28 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+ <PropertyGroup>
+ <TargetFramework>net10.0</TargetFramework>
+ <Nullable>enable</Nullable>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <ServerGarbageCollection>true</ServerGarbageCollection>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="netproxy" Version="3.0.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Content Update="wwwroot\*">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Update="wwwroot\*\*">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ </ItemGroup>
+
+
+ <ItemGroup>
+ <ProjectReference Include="..\Mvc.ModelBinding.MultiParameter\Mvc.ModelBinding.MultiParameter.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/tests/Netproxy.Testing/Netroxy.Testing.csproj b/tests/Netproxy.Testing/Netroxy.Testing.csproj
new file mode 100644
index 0000000..c738477
--- /dev/null
+++ b/tests/Netproxy.Testing/Netroxy.Testing.csproj
@@ -0,0 +1,28 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+ <PropertyGroup>
+ <TargetFramework>net10.0</TargetFramework>
+ <Nullable>enable</Nullable>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <ServerGarbageCollection>true</ServerGarbageCollection>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="netproxy" Version="3.0.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Content Update="wwwroot\*">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Update="wwwroot\*\*">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ </ItemGroup>
+
+
+ <ItemGroup>
+ <ProjectReference Include="..\Mvc.ModelBinding.MultiParameter\Mvc.ModelBinding.MultiParameter.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/tests/Netproxy.Testing/Program.cs b/tests/Netproxy.Testing/Program.cs
new file mode 100644
index 0000000..e49be08
--- /dev/null
+++ b/tests/Netproxy.Testing/Program.cs
@@ -0,0 +1,25 @@
+var builder = WebApplication.CreateBuilder(new WebApplicationOptions
+{
+ // the 'real' root of the application
+ ContentRootPath = AppDomain.CurrentDomain.BaseDirectory
+});
+
+builder.Services.AddMvcCore().WithMultiParameterModelBinding(SanitizeAll: 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/Netproxy.Testing/Properties/launchSettings.json b/tests/Netproxy.Testing/Properties/launchSettings.json
new file mode 100644
index 0000000..fe13918
--- /dev/null
+++ b/tests/Netproxy.Testing/Properties/launchSettings.json
@@ -0,0 +1,28 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:30035",
+ "sslPort": 44344
+ }
+ },
+ "profiles": {
+ "Mvc.ModelBinding.MultiParameter.Testing": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "https://0.0.0.0:27134;http://0.0.0.0:25134",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/tests/Netproxy.Testing/appsettings.Development.json b/tests/Netproxy.Testing/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/tests/Netproxy.Testing/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/tests/Netproxy.Testing/appsettings.json b/tests/Netproxy.Testing/appsettings.json
new file mode 100644
index 0000000..10f68b8
--- /dev/null
+++ b/tests/Netproxy.Testing/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/tests/Netproxy.Testing/web.config b/tests/Netproxy.Testing/web.config
new file mode 100644
index 0000000..4b3a74f
--- /dev/null
+++ b/tests/Netproxy.Testing/web.config
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <system.webServer>
+ <handlers>
+ <remove name="aspNetCore" />
+ <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
+ </handlers>
+ <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="InProcess" />
+ <security>
+ <requestFiltering>
+ <requestLimits maxAllowedContentLength="2500000000" />
+ </requestFiltering>
+ </security>
+ </system.webServer>
+</configuration>
\ No newline at end of file
diff --git a/tests/Netproxy.Testing/wwwroot/Default.css b/tests/Netproxy.Testing/wwwroot/Default.css
new file mode 100644
index 0000000..831ebe3
--- /dev/null
+++ b/tests/Netproxy.Testing/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/Netproxy.Testing/wwwroot/Default.js b/tests/Netproxy.Testing/wwwroot/Default.js
new file mode 100644
index 0000000..2c5f272
--- /dev/null
+++ b/tests/Netproxy.Testing/wwwroot/Default.js
@@ -0,0 +1,350 @@
+const result = $id('Result');
+var r, result1, result2;
+
+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;
+ }
+ });
+
+ $id("UploadExample").on("change", function (e)
+ {
+ StartUpload(e);
+ });
+}
+
+function Init()
+{
+ netproxy("./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", { model: { user: 'alphons' } });
+ C("SimpleString3", "r.user == 'alphons'");
+ r = await netproxyasync("./api/SimpleString4", { model: { 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", users);
+ C("ArrayOfStrings2", "r.users && r.users.length == 4");
+ r = await netproxyasync("./api/ArrayOfStrings3", { model: { "users": ["admins", "editors", null, "sisters"] } });
+ C("ArrayOfStrings3", "r.users && r.users.length == 4");
+ r = await netproxyasync("./api/ArrayOfStrings4", { model: { "users": ["admins", "editors", null, "sisters"] } });
+ C("ArrayOfStrings4", "r.users && r.users.length == 4");
+
+ var arrayofarrayofstrings = { model: { "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", { AA: { 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", { list: [{ 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");
+ 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");
+ 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;
+ });
+
+ r = await netproxyasync("./api/NotANumber");
+
+ 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/Netproxy.Testing/wwwroot/ResetAll.css b/tests/Netproxy.Testing/wwwroot/ResetAll.css
new file mode 100644
index 0000000..8510527
--- /dev/null
+++ b/tests/Netproxy.Testing/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/Netproxy.Testing/wwwroot/SpeedTest.js b/tests/Netproxy.Testing/wwwroot/SpeedTest.js
new file mode 100644
index 0000000..8ed99ab
--- /dev/null
+++ b/tests/Netproxy.Testing/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/Netproxy.Testing/wwwroot/Ui.css b/tests/Netproxy.Testing/wwwroot/Ui.css
new file mode 100644
index 0000000..4c5c3e5
--- /dev/null
+++ b/tests/Netproxy.Testing/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/Netproxy.Testing/wwwroot/favicon.ico b/tests/Netproxy.Testing/wwwroot/favicon.ico
new file mode 100644
index 0000000..22ad614
Binary files /dev/null and b/tests/Netproxy.Testing/wwwroot/favicon.ico differ
diff --git a/tests/Netproxy.Testing/wwwroot/index.html b/tests/Netproxy.Testing/wwwroot/index.html
new file mode 100644
index 0000000..26d32ae
--- /dev/null
+++ b/tests/Netproxy.Testing/wwwroot/index.html
@@ -0,0 +1,43 @@
+<!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="Default.js" defer></script>
+
+</head>
+<body>
+ <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>
diff --git a/tests/Netproxy.Testing/wwwroot/speedtest.html b/tests/Netproxy.Testing/wwwroot/speedtest.html
new file mode 100644
index 0000000..1718b30
--- /dev/null
+++ b/tests/Netproxy.Testing/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>