From 9110240ec988d3cf13f813d3e1069421fa9a8be3 Mon Sep 17 00:00:00 2001 From: jaredcnance Date: Tue, 14 Nov 2017 21:53:30 -0600 Subject: [PATCH 01/17] feat(*): add benchmarks project and dependencies.props --- .editorconfig | 14 ++ JsonApiDotnetCore.sln | 25 +- benchmarks/Benchmarks/.gitignore | 236 ++++++++++++++++++ ...piDeserializer_Benchmarks-report-github.md | 13 + ....JsonApiDeserializer_Benchmarks-report.csv | 2 + ...JsonApiDeserializer_Benchmarks-report.html | 30 +++ benchmarks/Benchmarks/Benchmarks.csproj | 15 ++ benchmarks/Benchmarks/Program.cs | 10 + .../JsonApiDeserializer_Benchmarks.cs | 59 +++++ build/dependencies.props | 11 + .../JsonApiDotNetCoreExample.csproj | 2 +- .../NoEntityFrameworkExample.csproj | 2 +- .../ReportsExample/ReportsExample.csproj | 48 ++-- .../JsonApiDotNetCore.csproj | 2 + .../JsonApiDotNetCoreExampleTests.csproj | 2 +- .../NoEntityFrameworkTests.csproj | 2 +- .../Serialization/JsonApiDeSerializerTests.cs | 161 +++++------- test/UnitTests/UnitTests.csproj | 3 +- 18 files changed, 512 insertions(+), 125 deletions(-) create mode 100644 .editorconfig create mode 100644 benchmarks/Benchmarks/.gitignore create mode 100755 benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md create mode 100755 benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv create mode 100755 benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html create mode 100644 benchmarks/Benchmarks/Benchmarks.csproj create mode 100644 benchmarks/Benchmarks/Program.cs create mode 100644 benchmarks/Benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs create mode 100644 build/dependencies.props diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..134066baff --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +charset = utf-8 + +[*.{csproj,props}] +indent_size = 2 diff --git a/JsonApiDotnetCore.sln b/JsonApiDotnetCore.sln index a144223671..ef4c03dc49 100644 --- a/JsonApiDotnetCore.sln +++ b/JsonApiDotnetCore.sln @@ -28,6 +28,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{02 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReportsExample", "src\Examples\ReportsExample\ReportsExample.csproj", "{FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{076E1AE4-FD25-4684-B826-CAAE37FEA0AA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "benchmarks\Benchmarks\Benchmarks.csproj", "{1F604666-BB0F-413E-922D-9D37C6073285}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -106,10 +110,22 @@ Global {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Debug|x86.Build.0 = Debug|Any CPU {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|Any CPU.ActiveCfg = Release|Any CPU {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|Any CPU.Build.0 = Release|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|x64.ActiveCfg = Release|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|x64.Build.0 = Release|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|x86.ActiveCfg = Release|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|x86.Build.0 = Release|Any CPU + {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|x64.ActiveCfg = Release|x64 + {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|x64.Build.0 = Release|x64 + {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|x86.ActiveCfg = Release|x86 + {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|x86.Build.0 = Release|x86 + {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|x64.ActiveCfg = Debug|x64 + {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|x64.Build.0 = Debug|x64 + {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|x86.ActiveCfg = Debug|x86 + {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|x86.Build.0 = Debug|x86 + {1F604666-BB0F-413E-922D-9D37C6073285}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F604666-BB0F-413E-922D-9D37C6073285}.Release|Any CPU.Build.0 = Release|Any CPU + {1F604666-BB0F-413E-922D-9D37C6073285}.Release|x64.ActiveCfg = Release|x64 + {1F604666-BB0F-413E-922D-9D37C6073285}.Release|x64.Build.0 = Release|x64 + {1F604666-BB0F-413E-922D-9D37C6073285}.Release|x86.ActiveCfg = Release|x86 + {1F604666-BB0F-413E-922D-9D37C6073285}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -123,6 +139,7 @@ Global {6D4BD85A-A262-44C6-8572-FE3A30410BF3} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} {026FBC6C-AF76-4568-9B87-EC73457899FD} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF} {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D} = {026FBC6C-AF76-4568-9B87-EC73457899FD} + {1F604666-BB0F-413E-922D-9D37C6073285} = {076E1AE4-FD25-4684-B826-CAAE37FEA0AA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A2421882-8F0A-4905-928F-B550B192F9A4} diff --git a/benchmarks/Benchmarks/.gitignore b/benchmarks/Benchmarks/.gitignore new file mode 100644 index 0000000000..0f552f400b --- /dev/null +++ b/benchmarks/Benchmarks/.gitignore @@ -0,0 +1,236 @@ +_data/ + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Microsoft Azure ApplicationInsights config file +ApplicationInsights.config + +# Windows Store app package directory +AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ diff --git a/benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md b/benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md new file mode 100755 index 0000000000..1eacea495f --- /dev/null +++ b/benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md @@ -0,0 +1,13 @@ +``` ini + +BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12 +Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4 +.NET Core SDK=2.0.0 + [Host] : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT + DefaultJob : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT + + +``` +| Method | Mean | Error | StdDev | +|------------------------ |---------:|----------:|----------:| +| DeserializeSimpleObject | 27.29 us | 0.5275 us | 0.5863 us | diff --git a/benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv b/benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv new file mode 100755 index 0000000000..1a8fbe9734 --- /dev/null +++ b/benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv @@ -0,0 +1,2 @@ +Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,RemoveOutliers,Affinity,Jit,Platform,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,EnvironmentVariables,Toolchain,InvocationCount,IterationTime,LaunchCount,RunStrategy,TargetCount,UnrollFactor,WarmupCount,Mean,Error,StdDev +DeserializeSimpleObject,Default,False,Default,Default,Default,Default,Default,Default,0,RyuJit,X64,Core,False,True,False,True,False,False,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,16,Default,27.29 us,0.5275 us,0.5863 us diff --git a/benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html b/benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html new file mode 100755 index 0000000000..c7e5ed02a4 --- /dev/null +++ b/benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html @@ -0,0 +1,30 @@ + + + + +JsonApiDeserializer_Benchmarks + + + + +

+BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12
+Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4
+.NET Core SDK=2.0.0
+  [Host]     : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT
+  DefaultJob : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT
+
+
+ + + + + +
MethodMeanErrorStdDev
DeserializeSimpleObject27.29 us0.5275 us0.5863 us
+ + diff --git a/benchmarks/Benchmarks/Benchmarks.csproj b/benchmarks/Benchmarks/Benchmarks.csproj new file mode 100644 index 0000000000..a7a1593823 --- /dev/null +++ b/benchmarks/Benchmarks/Benchmarks.csproj @@ -0,0 +1,15 @@ + + + + Exe + $(NetCoreAppVersion) + + + + + + + + + + diff --git a/benchmarks/Benchmarks/Program.cs b/benchmarks/Benchmarks/Program.cs new file mode 100644 index 0000000000..be5115f62c --- /dev/null +++ b/benchmarks/Benchmarks/Program.cs @@ -0,0 +1,10 @@ +using BenchmarkDotNet.Running; +using Benchmarks.Serialization; + +namespace Benchmarks { + class Program { + static void Main(string[] args) { + var summary = BenchmarkRunner.Run(); + } + } +} diff --git a/benchmarks/Benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs b/benchmarks/Benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs new file mode 100644 index 0000000000..9303b4d4c1 --- /dev/null +++ b/benchmarks/Benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using BenchmarkDotNet.Attributes; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Internal.Generics; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Services; +using Moq; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Benchmarks.Serialization { + public class JsonApiDeserializer_Benchmarks { + private const string TYPE_NAME = "simple-types"; + private static readonly string Content = JsonConvert.SerializeObject(new Document { + Data = new DocumentData { + Type = TYPE_NAME, + Id = "1", + Attributes = new Dictionary { + { + "name", + Guid.NewGuid().ToString() + } + } + } + }); + + private readonly JsonApiDeSerializer _jsonApiDeSerializer; + + public JsonApiDeserializer_Benchmarks() { + var contextGraphBuilder = new ContextGraphBuilder(); + contextGraphBuilder.AddResource(TYPE_NAME); + var contextGraph = contextGraphBuilder.Build(); + + var jsonApiContextMock = new Mock(); + jsonApiContextMock.SetupAllProperties(); + jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); + jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); + + var jsonApiOptions = new JsonApiOptions(); + jsonApiOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); + + var genericProcessorFactoryMock = new Mock(); + + _jsonApiDeSerializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); + } + + [Benchmark] + public object DeserializeSimpleObject() => _jsonApiDeSerializer.Deserialize(Content); + + private class SimpleType : Identifiable { + [Attr("name")] + public string Name { get; set; } + } + } +} diff --git a/build/dependencies.props b/build/dependencies.props new file mode 100644 index 0000000000..d4686bba2e --- /dev/null +++ b/build/dependencies.props @@ -0,0 +1,11 @@ + + + netcoreapp1.1 + netstandard1.6 + + + 4.7.10 + 2.2.0 + 8.0.1-beta-1 + + diff --git a/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj b/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj index dcbf2a2841..a2981cffd1 100755 --- a/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj +++ b/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj @@ -1,5 +1,5 @@ - + $(NetCoreAppVersion) true diff --git a/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj b/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj index afaf0e7cff..32506808fe 100755 --- a/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj +++ b/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj @@ -1,5 +1,5 @@  - + $(NetCoreAppVersion) diff --git a/src/Examples/ReportsExample/ReportsExample.csproj b/src/Examples/ReportsExample/ReportsExample.csproj index 2131c545a4..f8f83e454c 100644 --- a/src/Examples/ReportsExample/ReportsExample.csproj +++ b/src/Examples/ReportsExample/ReportsExample.csproj @@ -1,24 +1,24 @@ - - - - $(NetCoreAppVersion) - - - - - - - - - - - - - - - - - - - - + + + + $(NetCoreAppVersion) + + + + + + + + + + + + + + + + + + + + diff --git a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj index 9ebddb4512..53c81b2ec1 100755 --- a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj +++ b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj @@ -1,4 +1,5 @@  + 2.2.0 $(NetStandardVersion) @@ -20,4 +21,5 @@ + diff --git a/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj b/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj index b22953dc23..1b40e2dd73 100755 --- a/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj +++ b/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj @@ -1,5 +1,5 @@  - + $(NetCoreAppVersion) true diff --git a/test/NoEntityFrameworkTests/NoEntityFrameworkTests.csproj b/test/NoEntityFrameworkTests/NoEntityFrameworkTests.csproj index 8daa7dc9af..5553a7c1eb 100644 --- a/test/NoEntityFrameworkTests/NoEntityFrameworkTests.csproj +++ b/test/NoEntityFrameworkTests/NoEntityFrameworkTests.csproj @@ -1,5 +1,5 @@  - + $(NetCoreAppVersion) true diff --git a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs index eeefa4d857..5096cbac31 100644 --- a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs +++ b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs @@ -11,13 +11,10 @@ using Newtonsoft.Json.Serialization; using Xunit; -namespace UnitTests.Serialization -{ - public class JsonApiDeSerializerTests - { +namespace UnitTests.Serialization { + public class JsonApiDeSerializerTests { [Fact] - public void Can_Deserialize_Complex_Types() - { + public void Can_Deserialize_Complex_Types() { // arrange var contextGraphBuilder = new ContextGraphBuilder(); contextGraphBuilder.AddResource("test-resource"); @@ -36,17 +33,16 @@ public void Can_Deserialize_Complex_Types() var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); - var content = new Document - { - Data = new DocumentData + var content = new Document { + Data = new DocumentData { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary { { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary { - { - "complex-member", new { compoundName = "testName" } - } - } + "complex-member", + new { compoundName = "testName" } + } + } } }; @@ -59,8 +55,7 @@ public void Can_Deserialize_Complex_Types() } [Fact] - public void Can_Deserialize_Complex_List_Types() - { + public void Can_Deserialize_Complex_List_Types() { // arrange var contextGraphBuilder = new ContextGraphBuilder(); contextGraphBuilder.AddResource("test-resource"); @@ -78,19 +73,18 @@ public void Can_Deserialize_Complex_List_Types() var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); - var content = new Document - { - Data = new DocumentData + var content = new Document { + Data = new DocumentData { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary { { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary { - { - "complex-members", new [] { - new { compoundName = "testName" } - } - } - } + "complex-members", + new [] { + new { compoundName = "testName" } + } + } + } } }; @@ -104,8 +98,7 @@ public void Can_Deserialize_Complex_List_Types() } [Fact] - public void Can_Deserialize_Complex_Types_With_Dasherized_Attrs() - { + public void Can_Deserialize_Complex_Types_With_Dasherized_Attrs() { // arrange var contextGraphBuilder = new ContextGraphBuilder(); contextGraphBuilder.AddResource("test-resource"); @@ -124,17 +117,16 @@ public void Can_Deserialize_Complex_Types_With_Dasherized_Attrs() var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); - var content = new Document - { - Data = new DocumentData + var content = new Document { + Data = new DocumentData { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary { { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary { - { - "complex-member", new Dictionary { { "compound-name", "testName" } } - } - } + "complex-member", + new Dictionary { { "compound-name", "testName" } } + } + } } }; @@ -147,8 +139,7 @@ public void Can_Deserialize_Complex_Types_With_Dasherized_Attrs() } [Fact] - public void Immutable_Attrs_Are_Not_Included_In_AttributesToUpdate() - { + public void Immutable_Attrs_Are_Not_Included_In_AttributesToUpdate() { // arrange var contextGraphBuilder = new ContextGraphBuilder(); contextGraphBuilder.AddResource("test-resource"); @@ -169,18 +160,18 @@ public void Immutable_Attrs_Are_Not_Included_In_AttributesToUpdate() var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); - var content = new Document - { - Data = new DocumentData + var content = new Document { + Data = new DocumentData { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary { { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary { - { "complex-member", new Dictionary { - { "compound-name", "testName" } } - }, - { "immutable", "value"} - } + "complex-member", + new Dictionary { { "compound-name", "testName" } + } + }, + { "immutable", "value" } + } } }; @@ -198,8 +189,7 @@ public void Immutable_Attrs_Are_Not_Included_In_AttributesToUpdate() } [Fact] - public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship() - { + public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship() { // arrange var contextGraphBuilder = new ContextGraphBuilder(); contextGraphBuilder.AddResource("independents"); @@ -219,15 +209,12 @@ public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship() var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); var property = Guid.NewGuid().ToString(); - var content = new Document - { - Data = new DocumentData - { - Type = "independents", - Id = "1", - Attributes = new Dictionary { - { "property", property } - } + var content = new Document { + Data = new DocumentData { + Type = "independents", + Id = "1", + Attributes = new Dictionary { { "property", property } + } } }; @@ -242,8 +229,7 @@ public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship() } [Fact] - public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship_With_Relationship_Body() - { + public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship_With_Relationship_Body() { // arrange var contextGraphBuilder = new ContextGraphBuilder(); contextGraphBuilder.AddResource("independents"); @@ -263,19 +249,15 @@ public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship_With_Rel var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); var property = Guid.NewGuid().ToString(); - var content = new Document - { - Data = new DocumentData - { - Type = "independents", - Id = "1", - Attributes = new Dictionary { - { "property", property } - }, - // a common case for this is deserialization in unit tests - Relationships = new Dictionary { - { "dependent", new RelationshipData { } } - } + var content = new Document { + Data = new DocumentData { + Type = "independents", + Id = "1", + Attributes = new Dictionary { { "property", property } + }, + // a common case for this is deserialization in unit tests + Relationships = new Dictionary { { "dependent", new RelationshipData { } } + } } }; @@ -289,34 +271,29 @@ public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship_With_Rel Assert.Equal(property, result.Property); } - private class TestResource : Identifiable - { + private class TestResource : Identifiable { [Attr("complex-member")] public ComplexType ComplexMember { get; set; } - [Attr("immutable", isImmutable: true)] + [Attr("immutable", isImmutable : true)] public string Immutable { get; set; } } - private class TestResourceWithList : Identifiable - { + private class TestResourceWithList : Identifiable { [Attr("complex-members")] public List ComplexMembers { get; set; } } - private class ComplexType - { + private class ComplexType { public string CompoundName { get; set; } } - private class Independent : Identifiable - { + private class Independent : Identifiable { [Attr("property")] public string Property { get; set; } [HasOne("dependent")] public Dependent Dependent { get; set; } } - private class Dependent : Identifiable - { + private class Dependent : Identifiable { [HasOne("independent")] public Independent Independent { get; set; } public int IndependentId { get; set; } } diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj index dcd659d2df..4848455eac 100644 --- a/test/UnitTests/UnitTests.csproj +++ b/test/UnitTests/UnitTests.csproj @@ -1,4 +1,5 @@ + $(NetCoreAppVersion) false @@ -13,4 +14,4 @@ - \ No newline at end of file + From 5a29b2605e300762c237974bcdfa3c2f9374c06e Mon Sep 17 00:00:00 2001 From: jaredcnance Date: Tue, 14 Nov 2017 21:58:07 -0600 Subject: [PATCH 02/17] chore(benchmarks): remove parent dir --- JsonApiDotnetCore.sln | 2 +- benchmarks/{Benchmarks => }/.gitignore | 0 ...ialization.JsonApiDeserializer_Benchmarks-report-github.md | 0 ...ks.Serialization.JsonApiDeserializer_Benchmarks-report.csv | 0 ...s.Serialization.JsonApiDeserializer_Benchmarks-report.html | 0 benchmarks/{Benchmarks => }/Benchmarks.csproj | 4 ++-- benchmarks/{Benchmarks => }/Program.cs | 0 .../Serialization/JsonApiDeserializer_Benchmarks.cs | 0 8 files changed, 3 insertions(+), 3 deletions(-) rename benchmarks/{Benchmarks => }/.gitignore (100%) rename benchmarks/{Benchmarks => }/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md (100%) rename benchmarks/{Benchmarks => }/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv (100%) rename benchmarks/{Benchmarks => }/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html (100%) rename benchmarks/{Benchmarks => }/Benchmarks.csproj (75%) rename benchmarks/{Benchmarks => }/Program.cs (100%) rename benchmarks/{Benchmarks => }/Serialization/JsonApiDeserializer_Benchmarks.cs (100%) diff --git a/JsonApiDotnetCore.sln b/JsonApiDotnetCore.sln index ef4c03dc49..385fa4d6ad 100644 --- a/JsonApiDotnetCore.sln +++ b/JsonApiDotnetCore.sln @@ -30,7 +30,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReportsExample", "src\Examp EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{076E1AE4-FD25-4684-B826-CAAE37FEA0AA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "benchmarks\Benchmarks\Benchmarks.csproj", "{1F604666-BB0F-413E-922D-9D37C6073285}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "benchmarks\Benchmarks.csproj", "{1F604666-BB0F-413E-922D-9D37C6073285}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/benchmarks/Benchmarks/.gitignore b/benchmarks/.gitignore similarity index 100% rename from benchmarks/Benchmarks/.gitignore rename to benchmarks/.gitignore diff --git a/benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md similarity index 100% rename from benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md rename to benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md diff --git a/benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv similarity index 100% rename from benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv rename to benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv diff --git a/benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html similarity index 100% rename from benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html rename to benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html diff --git a/benchmarks/Benchmarks/Benchmarks.csproj b/benchmarks/Benchmarks.csproj similarity index 75% rename from benchmarks/Benchmarks/Benchmarks.csproj rename to benchmarks/Benchmarks.csproj index a7a1593823..107fd008c2 100644 --- a/benchmarks/Benchmarks/Benchmarks.csproj +++ b/benchmarks/Benchmarks.csproj @@ -1,5 +1,5 @@ - + Exe $(NetCoreAppVersion) @@ -10,6 +10,6 @@ - + diff --git a/benchmarks/Benchmarks/Program.cs b/benchmarks/Program.cs similarity index 100% rename from benchmarks/Benchmarks/Program.cs rename to benchmarks/Program.cs diff --git a/benchmarks/Benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs b/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs similarity index 100% rename from benchmarks/Benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs rename to benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs From e0d17859f9c71ccbfc415583f8494cb5c4dd4d37 Mon Sep 17 00:00:00 2001 From: jaredcnance Date: Tue, 14 Nov 2017 22:07:25 -0600 Subject: [PATCH 03/17] bench(serializer): test simple object serialization --- ...piDeserializer_Benchmarks-report-github.md | 2 +- ....JsonApiDeserializer_Benchmarks-report.csv | 2 +- ...JsonApiDeserializer_Benchmarks-report.html | 2 +- ...nApiSerializer_Benchmarks-report-github.md | 13 +++++ ...on.JsonApiSerializer_Benchmarks-report.csv | 2 + ...n.JsonApiSerializer_Benchmarks-report.html | 30 ++++++++++++ benchmarks/Program.cs | 3 +- .../JsonApiSerializer_Benchmarks.cs | 49 +++++++++++++++++++ 8 files changed, 99 insertions(+), 4 deletions(-) create mode 100755 benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-github.md create mode 100755 benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.csv create mode 100755 benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.html create mode 100644 benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md index 1eacea495f..2004bb0243 100755 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md @@ -10,4 +10,4 @@ Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4 ``` | Method | Mean | Error | StdDev | |------------------------ |---------:|----------:|----------:| -| DeserializeSimpleObject | 27.29 us | 0.5275 us | 0.5863 us | +| DeserializeSimpleObject | 27.34 us | 0.5458 us | 0.7472 us | diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv index 1a8fbe9734..230be6418f 100755 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv @@ -1,2 +1,2 @@ Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,RemoveOutliers,Affinity,Jit,Platform,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,EnvironmentVariables,Toolchain,InvocationCount,IterationTime,LaunchCount,RunStrategy,TargetCount,UnrollFactor,WarmupCount,Mean,Error,StdDev -DeserializeSimpleObject,Default,False,Default,Default,Default,Default,Default,Default,0,RyuJit,X64,Core,False,True,False,True,False,False,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,16,Default,27.29 us,0.5275 us,0.5863 us +DeserializeSimpleObject,Default,False,Default,Default,Default,Default,Default,Default,0,RyuJit,X64,Core,False,True,False,True,False,False,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,16,Default,27.34 us,0.5458 us,0.7472 us diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html index c7e5ed02a4..e1906ca4e0 100755 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html @@ -24,7 +24,7 @@ - +
MethodMeanErrorStdDev
DeserializeSimpleObject27.29 us0.5275 us0.5863 us
DeserializeSimpleObject27.34 us0.5458 us0.7472 us
diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-github.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-github.md new file mode 100755 index 0000000000..66104fb0a2 --- /dev/null +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-github.md @@ -0,0 +1,13 @@ +``` ini + +BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12 +Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4 +.NET Core SDK=2.0.0 + [Host] : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT + DefaultJob : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT + + +``` +| Method | Mean | Error | StdDev | +|------------------------ |---------:|----------:|----------:| +| DeserializeSimpleObject | 6.940 us | 0.1364 us | 0.1675 us | diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.csv b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.csv new file mode 100755 index 0000000000..d49d1e3e51 --- /dev/null +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.csv @@ -0,0 +1,2 @@ +Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,RemoveOutliers,Affinity,Jit,Platform,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,EnvironmentVariables,Toolchain,InvocationCount,IterationTime,LaunchCount,RunStrategy,TargetCount,UnrollFactor,WarmupCount,Mean,Error,StdDev +DeserializeSimpleObject,Default,False,Default,Default,Default,Default,Default,Default,0,RyuJit,X64,Core,False,True,False,True,False,False,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,16,Default,6.940 us,0.1364 us,0.1675 us diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.html b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.html new file mode 100755 index 0000000000..0f84d09546 --- /dev/null +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.html @@ -0,0 +1,30 @@ + + + + +JsonApiSerializer_Benchmarks + + + + +

+BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12
+Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4
+.NET Core SDK=2.0.0
+  [Host]     : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT
+  DefaultJob : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT
+
+
+ + + + + +
MethodMeanErrorStdDev
DeserializeSimpleObject6.940 us0.1364 us0.1675 us
+ + diff --git a/benchmarks/Program.cs b/benchmarks/Program.cs index be5115f62c..2e552496c9 100644 --- a/benchmarks/Program.cs +++ b/benchmarks/Program.cs @@ -4,7 +4,8 @@ namespace Benchmarks { class Program { static void Main(string[] args) { - var summary = BenchmarkRunner.Run(); + BenchmarkRunner.Run(); + BenchmarkRunner.Run(); } } } diff --git a/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs b/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs new file mode 100644 index 0000000000..0c87a3edfb --- /dev/null +++ b/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using BenchmarkDotNet.Attributes; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Internal.Generics; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Services; +using Moq; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Benchmarks.Serialization { + public class JsonApiSerializer_Benchmarks { + private const string TYPE_NAME = "simple-types"; + private static readonly SimpleType Content = new SimpleType(); + + private readonly JsonApiSerializer _jsonApiSerializer; + + public JsonApiSerializer_Benchmarks() { + var contextGraphBuilder = new ContextGraphBuilder(); + contextGraphBuilder.AddResource(TYPE_NAME); + var contextGraph = contextGraphBuilder.Build(); + + var jsonApiContextMock = new Mock(); + jsonApiContextMock.SetupAllProperties(); + jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); + jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); + + var jsonApiOptions = new JsonApiOptions(); + jsonApiOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); + + var genericProcessorFactoryMock = new Mock(); + + var documentBuilder = new DocumentBuilder(jsonApiContextMock.Object); + _jsonApiSerializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); + } + + [Benchmark] + public object DeserializeSimpleObject() => _jsonApiSerializer.Serialize(Content); + + private class SimpleType : Identifiable { + [Attr("name")] + public string Name { get; set; } + } + } +} From 2bda569d99c738d4fa9b836add14e2288d95bb66 Mon Sep 17 00:00:00 2001 From: jaredcnance Date: Tue, 14 Nov 2017 22:16:50 -0600 Subject: [PATCH 04/17] perf(*): only keep markdown benchmarks --- ...n.JsonApiDeserializer_Benchmarks-report-default.md | 11 +++++++++++ ...on.JsonApiDeserializer_Benchmarks-report-github.md | 2 +- ...lization.JsonApiDeserializer_Benchmarks-report.csv | 2 +- ...ization.JsonApiDeserializer_Benchmarks-report.html | 2 +- ...ion.JsonApiSerializer_Benchmarks-report-default.md | 11 +++++++++++ ...tion.JsonApiSerializer_Benchmarks-report-github.md | 2 +- ...ialization.JsonApiSerializer_Benchmarks-report.csv | 2 +- ...alization.JsonApiSerializer_Benchmarks-report.html | 2 +- .../Serialization/JsonApiDeserializer_Benchmarks.cs | 2 ++ .../Serialization/JsonApiSerializer_Benchmarks.cs | 4 ++-- 10 files changed, 32 insertions(+), 8 deletions(-) create mode 100755 benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-default.md create mode 100755 benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-default.md diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-default.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-default.md new file mode 100755 index 0000000000..489c6588bf --- /dev/null +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-default.md @@ -0,0 +1,11 @@ + +BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12 +Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4 +.NET Core SDK=2.0.0 + [Host] : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT + DefaultJob : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT + + + Method | Mean | Error | StdDev | +------------------------ |---------:|----------:|----------:| + DeserializeSimpleObject | 27.79 us | 0.5299 us | 0.4956 us | diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md index 2004bb0243..e87cadc3a7 100755 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md @@ -10,4 +10,4 @@ Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4 ``` | Method | Mean | Error | StdDev | |------------------------ |---------:|----------:|----------:| -| DeserializeSimpleObject | 27.34 us | 0.5458 us | 0.7472 us | +| DeserializeSimpleObject | 27.79 us | 0.5299 us | 0.4956 us | diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv index 230be6418f..80fffa3d41 100755 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv @@ -1,2 +1,2 @@ Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,RemoveOutliers,Affinity,Jit,Platform,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,EnvironmentVariables,Toolchain,InvocationCount,IterationTime,LaunchCount,RunStrategy,TargetCount,UnrollFactor,WarmupCount,Mean,Error,StdDev -DeserializeSimpleObject,Default,False,Default,Default,Default,Default,Default,Default,0,RyuJit,X64,Core,False,True,False,True,False,False,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,16,Default,27.34 us,0.5458 us,0.7472 us +DeserializeSimpleObject,Default,False,Default,Default,Default,Default,Default,Default,0,RyuJit,X64,Core,False,True,False,True,False,False,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,16,Default,27.79 us,0.5299 us,0.4956 us diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html index e1906ca4e0..deb436dab9 100755 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html @@ -24,7 +24,7 @@ - +
MethodMeanErrorStdDev
DeserializeSimpleObject27.34 us0.5458 us0.7472 us
DeserializeSimpleObject27.79 us0.5299 us0.4956 us
diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-default.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-default.md new file mode 100755 index 0000000000..6e486a91d3 --- /dev/null +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-default.md @@ -0,0 +1,11 @@ + +BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12 +Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4 +.NET Core SDK=2.0.0 + [Host] : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT + DefaultJob : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT + + + Method | Mean | Error | StdDev | +------------------------ |---------:|----------:|----------:| + DeserializeSimpleObject | 7.032 us | 0.1101 us | 0.1030 us | diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-github.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-github.md index 66104fb0a2..e63c564dca 100755 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-github.md +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-github.md @@ -10,4 +10,4 @@ Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4 ``` | Method | Mean | Error | StdDev | |------------------------ |---------:|----------:|----------:| -| DeserializeSimpleObject | 6.940 us | 0.1364 us | 0.1675 us | +| DeserializeSimpleObject | 7.032 us | 0.1101 us | 0.1030 us | diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.csv b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.csv index d49d1e3e51..3279dabd04 100755 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.csv +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.csv @@ -1,2 +1,2 @@ Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,RemoveOutliers,Affinity,Jit,Platform,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,EnvironmentVariables,Toolchain,InvocationCount,IterationTime,LaunchCount,RunStrategy,TargetCount,UnrollFactor,WarmupCount,Mean,Error,StdDev -DeserializeSimpleObject,Default,False,Default,Default,Default,Default,Default,Default,0,RyuJit,X64,Core,False,True,False,True,False,False,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,16,Default,6.940 us,0.1364 us,0.1675 us +DeserializeSimpleObject,Default,False,Default,Default,Default,Default,Default,Default,0,RyuJit,X64,Core,False,True,False,True,False,False,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,16,Default,7.032 us,0.1101 us,0.1030 us diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.html b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.html index 0f84d09546..642ee73032 100755 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.html +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.html @@ -24,7 +24,7 @@ - +
MethodMeanErrorStdDev
DeserializeSimpleObject6.940 us0.1364 us0.1675 us
DeserializeSimpleObject7.032 us0.1101 us0.1030 us
diff --git a/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs b/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs index 9303b4d4c1..c490bee362 100644 --- a/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs +++ b/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes.Exporters; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal.Generics; @@ -12,6 +13,7 @@ using Newtonsoft.Json.Serialization; namespace Benchmarks.Serialization { + [MarkdownExporter] public class JsonApiDeserializer_Benchmarks { private const string TYPE_NAME = "simple-types"; private static readonly string Content = JsonConvert.SerializeObject(new Document { diff --git a/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs b/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs index 0c87a3edfb..e5de3cae79 100644 --- a/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs +++ b/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs @@ -1,6 +1,6 @@ -using System; using System.Collections.Generic; using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes.Exporters; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal.Generics; @@ -8,10 +8,10 @@ using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Services; using Moq; -using Newtonsoft.Json; using Newtonsoft.Json.Serialization; namespace Benchmarks.Serialization { + [MarkdownExporter] public class JsonApiSerializer_Benchmarks { private const string TYPE_NAME = "simple-types"; private static readonly SimpleType Content = new SimpleType(); From 9be408b90bc93321f390c5de8941cb4dd1946572 Mon Sep 17 00:00:00 2001 From: jaredcnance Date: Sun, 26 Nov 2017 11:13:32 -0600 Subject: [PATCH 05/17] bench(QueryParser): add sort benchmarks --- benchmarks/.gitignore | 3 ++ ...ry.QueryParser_Benchmarks-report-github.md | 15 ++++++ benchmarks/Benchmarks.csproj | 1 + benchmarks/Program.cs | 2 + benchmarks/Query/QueryParser_Benchmarks.cs | 48 +++++++++++++++++++ 5 files changed, 69 insertions(+) create mode 100755 benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Query.QueryParser_Benchmarks-report-github.md create mode 100644 benchmarks/Query/QueryParser_Benchmarks.cs diff --git a/benchmarks/.gitignore b/benchmarks/.gitignore index 0f552f400b..5a3c72cbbb 100644 --- a/benchmarks/.gitignore +++ b/benchmarks/.gitignore @@ -1,4 +1,7 @@ _data/ +*-report-default.md +*-report.csv +*-report.html ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Query.QueryParser_Benchmarks-report-github.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Query.QueryParser_Benchmarks-report-github.md new file mode 100755 index 0000000000..97fea375d6 --- /dev/null +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Query.QueryParser_Benchmarks-report-github.md @@ -0,0 +1,15 @@ +``` ini + +BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12 +Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4 +.NET Core SDK=2.0.0 + [Host] : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT + Job-HURVUO : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT + +LaunchCount=3 TargetCount=20 WarmupCount=10 + +``` +| Method | Mean | Error | StdDev | +|--------------- |---------:|----------:|----------:| +| AscendingSort | 3.146 us | 0.0326 us | 0.0709 us | +| DescendingSort | 3.372 us | 0.1228 us | 0.2618 us | diff --git a/benchmarks/Benchmarks.csproj b/benchmarks/Benchmarks.csproj index 107fd008c2..b5ff121826 100644 --- a/benchmarks/Benchmarks.csproj +++ b/benchmarks/Benchmarks.csproj @@ -3,6 +3,7 @@ Exe $(NetCoreAppVersion) + Benchmarks diff --git a/benchmarks/Program.cs b/benchmarks/Program.cs index 2e552496c9..da4eaa2b50 100644 --- a/benchmarks/Program.cs +++ b/benchmarks/Program.cs @@ -1,4 +1,5 @@ using BenchmarkDotNet.Running; +using Benchmarks.Query; using Benchmarks.Serialization; namespace Benchmarks { @@ -6,6 +7,7 @@ class Program { static void Main(string[] args) { BenchmarkRunner.Run(); BenchmarkRunner.Run(); + BenchmarkRunner.Run(); } } } diff --git a/benchmarks/Query/QueryParser_Benchmarks.cs b/benchmarks/Query/QueryParser_Benchmarks.cs new file mode 100644 index 0000000000..0edaee6cb5 --- /dev/null +++ b/benchmarks/Query/QueryParser_Benchmarks.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes.Exporters; +using BenchmarkDotNet.Attributes.Jobs; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Services; +using Moq; + +namespace Benchmarks.Query { + [MarkdownExporter, SimpleJob(launchCount : 3, warmupCount : 10, targetCount : 20)] + public class QueryParser_Benchmarks { + private readonly BenchmarkFacade _queryParser; + + private const string ATTRIBUTE = "Attribute"; + private const string ASCENDING_SORT = ATTRIBUTE; + private const string DESCENDING_SORT = "-" + ATTRIBUTE; + + public QueryParser_Benchmarks() { + var controllerContextMock = new Mock(); + controllerContextMock.Setup(m => m.RequestEntity).Returns(new ContextEntity { + Attributes = new List { + new AttrAttribute(ATTRIBUTE) { + InternalAttributeName = ATTRIBUTE + } + } + }); + var options = new JsonApiOptions(); + _queryParser = new BenchmarkFacade(controllerContextMock.Object, options); + } + + [Benchmark] + public void AscendingSort() => _queryParser._ParseSortParameters(ASCENDING_SORT); + + [Benchmark] + public void DescendingSort() => _queryParser._ParseSortParameters(DESCENDING_SORT); + + // this facade allows us to expose and micro-benchmark protected methods + private class BenchmarkFacade : QueryParser { + public BenchmarkFacade( + IControllerContext controllerContext, + JsonApiOptions options) : base(controllerContext, options) { } + + public void _ParseSortParameters(string value) => base.ParseSortParameters(value); + } + } +} From 44bc57d9d0755ca5da16c7be05ecfdb9259099b6 Mon Sep 17 00:00:00 2001 From: jaredcnance Date: Sun, 26 Nov 2017 11:16:57 -0600 Subject: [PATCH 06/17] perf(QueryParser): improve sort parsing perf AscendingSort: 30.9% improvement DescendingSort: 20% improvement BEFORE Method | Mean | Error | StdDev| --------------- |---------:|----------:|----------:| AscendingSort | 4.558 us | 0.2451 us | 0.6832 us | DescendingSort | 4.218 us | 0.1780 us | 0.4990 us | AFTER Method | Mean | Error | StdDev | --------------- |---------:|----------:|----------:| AscendingSort | 3.146 us | 0.0326 us | 0.0709 us | DescendingSort | 3.372 us | 0.1228 us | 0.2618 us | --- src/JsonApiDotNetCore/Services/QueryParser.cs | 102 ++++++++---------- 1 file changed, 44 insertions(+), 58 deletions(-) diff --git a/src/JsonApiDotNetCore/Services/QueryParser.cs b/src/JsonApiDotNetCore/Services/QueryParser.cs index 297eb246a0..46f7a4e68a 100644 --- a/src/JsonApiDotNetCore/Services/QueryParser.cs +++ b/src/JsonApiDotNetCore/Services/QueryParser.cs @@ -9,63 +9,52 @@ using JsonApiDotNetCore.Models; using Microsoft.AspNetCore.Http; -namespace JsonApiDotNetCore.Services -{ - public interface IQueryParser - { +namespace JsonApiDotNetCore.Services { + public interface IQueryParser { QuerySet Parse(IQueryCollection query); } - public class QueryParser : IQueryParser - { + public class QueryParser : IQueryParser { private readonly IControllerContext _controllerContext; private readonly JsonApiOptions _options; public QueryParser( IControllerContext controllerContext, - JsonApiOptions options) - { + JsonApiOptions options) { _controllerContext = controllerContext; _options = options; } - public virtual QuerySet Parse(IQueryCollection query) - { + public virtual QuerySet Parse(IQueryCollection query) { var querySet = new QuerySet(); - var disabledQueries = _controllerContext.GetControllerAttribute()?.QueryParams ?? QueryParams.None; + var disabledQueries = _controllerContext.GetControllerAttribute() ? .QueryParams ?? QueryParams.None; - foreach (var pair in query) - { - if (pair.Key.StartsWith("filter")) - { + foreach (var pair in query) { + if (pair.Key.StartsWith("filter")) { if (disabledQueries.HasFlag(QueryParams.Filter) == false) querySet.Filters.AddRange(ParseFilterQuery(pair.Key, pair.Value)); continue; } - if (pair.Key.StartsWith("sort")) - { + if (pair.Key.StartsWith("sort")) { if (disabledQueries.HasFlag(QueryParams.Sort) == false) querySet.SortParameters = ParseSortParameters(pair.Value); continue; } - if (pair.Key.StartsWith("include")) - { + if (pair.Key.StartsWith("include")) { if (disabledQueries.HasFlag(QueryParams.Include) == false) querySet.IncludedRelationships = ParseIncludedRelationships(pair.Value); continue; } - if (pair.Key.StartsWith("page")) - { + if (pair.Key.StartsWith("page")) { if (disabledQueries.HasFlag(QueryParams.Page) == false) querySet.PageQuery = ParsePageQuery(querySet.PageQuery, pair.Key, pair.Value); continue; } - if (pair.Key.StartsWith("fields")) - { + if (pair.Key.StartsWith("fields")) { if (disabledQueries.HasFlag(QueryParams.Fields) == false) querySet.Fields = ParseFieldsQuery(pair.Key, pair.Value); continue; @@ -78,26 +67,24 @@ public virtual QuerySet Parse(IQueryCollection query) return querySet; } - protected virtual List ParseFilterQuery(string key, string value) - { + protected virtual List ParseFilterQuery(string key, string value) { // expected input = filter[id]=1 // expected input = filter[id]=eq:1 var queries = new List(); - var propertyName = key.Split('[', ']')[1].ToProperCase(); + var propertyName = key.Split('[', ']') [1].ToProperCase(); var values = value.Split(','); - foreach (var val in values) - { - (var operation, var filterValue) = ParseFilterOperation(val); + foreach (var val in values) { + (var operation, + var filterValue) = ParseFilterOperation(val); queries.Add(new FilterQuery(propertyName, filterValue, operation)); } return queries; } - protected virtual (string operation, string value) ParseFilterOperation(string value) - { + protected virtual(string operation, string value) ParseFilterOperation(string value) { if (value.Length < 3) return (string.Empty, value); @@ -116,13 +103,12 @@ protected virtual (string operation, string value) ParseFilterOperation(string v return (prefix, value); } - protected virtual PageQuery ParsePageQuery(PageQuery pageQuery, string key, string value) - { + protected virtual PageQuery ParsePageQuery(PageQuery pageQuery, string key, string value) { // expected input = page[size]=10 // page[number]=1 pageQuery = pageQuery ?? new PageQuery(); - var propertyName = key.Split('[', ']')[1]; + var propertyName = key.Split('[', ']') [1]; if (propertyName == "size") pageQuery.PageSize = Convert.ToInt32(value); @@ -134,28 +120,31 @@ protected virtual PageQuery ParsePageQuery(PageQuery pageQuery, string key, stri // sort=id,name // sort=-id - protected virtual List ParseSortParameters(string value) - { + protected virtual List ParseSortParameters(string value) { + const char SORT_DELIMITER = ','; + const char DESCENDING_SORT_OPERATOR = '-'; + var sortParameters = new List(); - value.Split(',').ToList().ForEach(p => - { + var sortSegments = value.Split(SORT_DELIMITER); + foreach (var sortSegment in sortSegments) { + + var propertyName = sortSegment; var direction = SortDirection.Ascending; - if (p[0] == '-') - { + + if (sortSegment[0] == DESCENDING_SORT_OPERATOR) { direction = SortDirection.Descending; - p = p.Substring(1); + propertyName = propertyName.Substring(1); } - var attribute = GetAttribute(p.ToProperCase()); + var attribute = GetAttribute(propertyName); sortParameters.Add(new SortQuery(direction, attribute)); - }); + }; return sortParameters; } - protected virtual List ParseIncludedRelationships(string value) - { + protected virtual List ParseIncludedRelationships(string value) { if (value.Contains(".")) throw new JsonApiException(400, "Deeply nested relationships are not supported"); @@ -164,10 +153,9 @@ protected virtual List ParseIncludedRelationships(string value) .ToList(); } - protected virtual List ParseFieldsQuery(string key, string value) - { + protected virtual List ParseFieldsQuery(string key, string value) { // expected: fields[TYPE]=prop1,prop2 - var typeName = key.Split('[', ']')[1]; + var typeName = key.Split('[', ']') [1]; var includedFields = new List { "Id" }; @@ -175,8 +163,7 @@ protected virtual List ParseFieldsQuery(string key, string value) return includedFields; var fields = value.Split(','); - foreach (var field in fields) - { + foreach (var field in fields) { var internalAttrName = _controllerContext.RequestEntity .Attributes .SingleOrDefault(attr => attr.PublicAttributeName == field) @@ -187,12 +174,11 @@ protected virtual List ParseFieldsQuery(string key, string value) return includedFields; } - protected virtual AttrAttribute GetAttribute(string propertyName) - => _controllerContext - .RequestEntity - .Attributes - .FirstOrDefault(attr => - string.Equals(attr.InternalAttributeName, propertyName, StringComparison.OrdinalIgnoreCase) - ); + protected virtual AttrAttribute GetAttribute(string propertyName) => _controllerContext + .RequestEntity + .Attributes + .FirstOrDefault(attr => + string.Equals(attr.PublicAttributeName, propertyName, StringComparison.OrdinalIgnoreCase) + ); } -} \ No newline at end of file +} From d17ea131b9c9c4ad78f408c39050ede5f7c6f66a Mon Sep 17 00:00:00 2001 From: jaredcnance Date: Sun, 26 Nov 2017 11:30:47 -0600 Subject: [PATCH 07/17] chore(benchmark-results): cleanup results --- ...ry.QueryParser_Benchmarks-report-github.md | 6 ++-- ...iDeserializer_Benchmarks-report-default.md | 11 ------- ...piDeserializer_Benchmarks-report-github.md | 2 +- ....JsonApiDeserializer_Benchmarks-report.csv | 2 -- ...JsonApiDeserializer_Benchmarks-report.html | 30 ------------------- ...ApiSerializer_Benchmarks-report-default.md | 11 ------- ...nApiSerializer_Benchmarks-report-github.md | 2 +- ...on.JsonApiSerializer_Benchmarks-report.csv | 2 -- ...n.JsonApiSerializer_Benchmarks-report.html | 30 ------------------- .../JsonApiSerializer_Benchmarks.cs | 2 +- 10 files changed, 6 insertions(+), 92 deletions(-) delete mode 100755 benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-default.md delete mode 100755 benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv delete mode 100755 benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html delete mode 100755 benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-default.md delete mode 100755 benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.csv delete mode 100755 benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.html diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Query.QueryParser_Benchmarks-report-github.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Query.QueryParser_Benchmarks-report-github.md index 97fea375d6..324281fa40 100755 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Query.QueryParser_Benchmarks-report-github.md +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Query.QueryParser_Benchmarks-report-github.md @@ -4,12 +4,12 @@ BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12 Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4 .NET Core SDK=2.0.0 [Host] : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT - Job-HURVUO : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT + Job-ROPOBW : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT LaunchCount=3 TargetCount=20 WarmupCount=10 ``` | Method | Mean | Error | StdDev | |--------------- |---------:|----------:|----------:| -| AscendingSort | 3.146 us | 0.0326 us | 0.0709 us | -| DescendingSort | 3.372 us | 0.1228 us | 0.2618 us | +| AscendingSort | 3.101 us | 0.0298 us | 0.0636 us | +| DescendingSort | 3.195 us | 0.0297 us | 0.0632 us | diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-default.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-default.md deleted file mode 100755 index 489c6588bf..0000000000 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-default.md +++ /dev/null @@ -1,11 +0,0 @@ - -BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12 -Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4 -.NET Core SDK=2.0.0 - [Host] : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT - DefaultJob : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT - - - Method | Mean | Error | StdDev | ------------------------- |---------:|----------:|----------:| - DeserializeSimpleObject | 27.79 us | 0.5299 us | 0.4956 us | diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md index e87cadc3a7..d8571f9644 100755 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md @@ -10,4 +10,4 @@ Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4 ``` | Method | Mean | Error | StdDev | |------------------------ |---------:|----------:|----------:| -| DeserializeSimpleObject | 27.79 us | 0.5299 us | 0.4956 us | +| DeserializeSimpleObject | 26.37 us | 0.4458 us | 0.4170 us | diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv deleted file mode 100755 index 80fffa3d41..0000000000 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.csv +++ /dev/null @@ -1,2 +0,0 @@ -Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,RemoveOutliers,Affinity,Jit,Platform,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,EnvironmentVariables,Toolchain,InvocationCount,IterationTime,LaunchCount,RunStrategy,TargetCount,UnrollFactor,WarmupCount,Mean,Error,StdDev -DeserializeSimpleObject,Default,False,Default,Default,Default,Default,Default,Default,0,RyuJit,X64,Core,False,True,False,True,False,False,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,16,Default,27.79 us,0.5299 us,0.4956 us diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html deleted file mode 100755 index deb436dab9..0000000000 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - -JsonApiDeserializer_Benchmarks - - - - -

-BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12
-Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4
-.NET Core SDK=2.0.0
-  [Host]     : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT
-  DefaultJob : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT
-
-
- - - - - -
MethodMeanErrorStdDev
DeserializeSimpleObject27.79 us0.5299 us0.4956 us
- - diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-default.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-default.md deleted file mode 100755 index 6e486a91d3..0000000000 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-default.md +++ /dev/null @@ -1,11 +0,0 @@ - -BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12 -Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4 -.NET Core SDK=2.0.0 - [Host] : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT - DefaultJob : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT - - - Method | Mean | Error | StdDev | ------------------------- |---------:|----------:|----------:| - DeserializeSimpleObject | 7.032 us | 0.1101 us | 0.1030 us | diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-github.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-github.md index e63c564dca..4671d6b3af 100755 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-github.md +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-github.md @@ -10,4 +10,4 @@ Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4 ``` | Method | Mean | Error | StdDev | |------------------------ |---------:|----------:|----------:| -| DeserializeSimpleObject | 7.032 us | 0.1101 us | 0.1030 us | +| DeserializeSimpleObject | 7.135 us | 0.1401 us | 0.1439 us | diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.csv b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.csv deleted file mode 100755 index 3279dabd04..0000000000 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.csv +++ /dev/null @@ -1,2 +0,0 @@ -Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,RemoveOutliers,Affinity,Jit,Platform,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,EnvironmentVariables,Toolchain,InvocationCount,IterationTime,LaunchCount,RunStrategy,TargetCount,UnrollFactor,WarmupCount,Mean,Error,StdDev -DeserializeSimpleObject,Default,False,Default,Default,Default,Default,Default,Default,0,RyuJit,X64,Core,False,True,False,True,False,False,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,16,Default,7.032 us,0.1101 us,0.1030 us diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.html b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.html deleted file mode 100755 index 642ee73032..0000000000 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - -JsonApiSerializer_Benchmarks - - - - -

-BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12
-Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4
-.NET Core SDK=2.0.0
-  [Host]     : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT
-  DefaultJob : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT
-
-
- - - - - -
MethodMeanErrorStdDev
DeserializeSimpleObject7.032 us0.1101 us0.1030 us
- - diff --git a/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs b/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs index e5de3cae79..3d5ef7c001 100644 --- a/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs +++ b/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs @@ -39,7 +39,7 @@ public JsonApiSerializer_Benchmarks() { } [Benchmark] - public object DeserializeSimpleObject() => _jsonApiSerializer.Serialize(Content); + public object SerializeSimpleObject() => _jsonApiSerializer.Serialize(Content); private class SimpleType : Identifiable { [Attr("name")] From ce4a0e1208c21047120dbdb740d4a5ce4d540871 Mon Sep 17 00:00:00 2001 From: jaredcnance Date: Sun, 26 Nov 2017 15:40:04 -0600 Subject: [PATCH 08/17] feat(FilterQuery / QueryAccessor): use public attribute names for keys Closes #197 --- .../Internal/Query/AttrFilterQuery.cs | 20 ++++++++-------- .../Internal/Query/FilterQuery.cs | 14 +++++++---- .../Services/QueryAccessor.cs | 23 +++++++++++++++---- src/JsonApiDotNetCore/Services/QueryParser.cs | 4 +++- 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/JsonApiDotNetCore/Internal/Query/AttrFilterQuery.cs b/src/JsonApiDotNetCore/Internal/Query/AttrFilterQuery.cs index 8af2fe95e1..74db2b342e 100644 --- a/src/JsonApiDotNetCore/Internal/Query/AttrFilterQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/AttrFilterQuery.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; @@ -9,14 +10,14 @@ public class AttrFilterQuery : BaseFilterQuery private readonly IJsonApiContext _jsonApiContext; public AttrFilterQuery( - IJsonApiContext jsonApiCopntext, + IJsonApiContext jsonApiContext, FilterQuery filterQuery) { - _jsonApiContext = jsonApiCopntext; + _jsonApiContext = jsonApiContext; - var attribute = GetAttribute(filterQuery.Key); + var attribute = GetAttribute(filterQuery.Attribute); - FilteredAttribute = attribute ?? throw new JsonApiException(400, $"{filterQuery.Key} is not a valid property."); + FilteredAttribute = attribute ?? throw new JsonApiException(400, $"'{filterQuery.Attribute}' is not a valid attribute."); PropertyValue = filterQuery.Value; FilterOperation = GetFilterOperation(filterQuery.Operation); } @@ -25,12 +26,9 @@ public AttrFilterQuery( public string PropertyValue { get; set; } public FilterOperations FilterOperation { get; set; } - private AttrAttribute GetAttribute(string propertyName) - { - return _jsonApiContext.RequestEntity.Attributes - .FirstOrDefault(attr => - attr.InternalAttributeName.ToLower() == propertyName.ToLower() + private AttrAttribute GetAttribute(string attribute) => + _jsonApiContext.RequestEntity.Attributes.FirstOrDefault( + attr => string.Equals(attr.PublicAttributeName, attribute, StringComparison.OrdinalIgnoreCase) ); - } } -} \ No newline at end of file +} diff --git a/src/JsonApiDotNetCore/Internal/Query/FilterQuery.cs b/src/JsonApiDotNetCore/Internal/Query/FilterQuery.cs index 7f4f1a40c6..dd72e827ff 100644 --- a/src/JsonApiDotNetCore/Internal/Query/FilterQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/FilterQuery.cs @@ -1,17 +1,23 @@ +using System; +using JsonApiDotNetCore.Extensions; + namespace JsonApiDotNetCore.Internal.Query { public class FilterQuery { - public FilterQuery(string key, string value, string operation) + public FilterQuery(string attribute, string value, string operation) { - Key = key; + Attribute = attribute; + Key = attribute.ToProperCase(); Value = value; Operation = operation; } + [Obsolete("Key has been replaced by '" + nameof(Attribute) + "'. Members should be located by their public name, not by coercing the provided value to the internal name.")] public string Key { get; set; } + public string Attribute { get; } public string Value { get; set; } public string Operation { get; set; } - public bool IsAttributeOfRelationship => Key.Contains("."); + public bool IsAttributeOfRelationship => Attribute.Contains("."); } -} \ No newline at end of file +} diff --git a/src/JsonApiDotNetCore/Services/QueryAccessor.cs b/src/JsonApiDotNetCore/Services/QueryAccessor.cs index 09942d4031..63b91ed239 100644 --- a/src/JsonApiDotNetCore/Services/QueryAccessor.cs +++ b/src/JsonApiDotNetCore/Services/QueryAccessor.cs @@ -61,9 +61,22 @@ public bool TryGetValue(string key, out T value) } } - private string GetFilterValue(string key) => _jsonApiContext.QuerySet - .Filters - .FirstOrDefault(f => string.Equals(f.Key, key, StringComparison.OrdinalIgnoreCase)) - ?.Value; + private string GetFilterValue(string key) { + var publicValue = _jsonApiContext.QuerySet.Filters + .FirstOrDefault(f => string.Equals(f.Attribute, key, StringComparison.OrdinalIgnoreCase))?.Value; + + if(publicValue != null) + return publicValue; + + var internalValue = _jsonApiContext.QuerySet.Filters + .FirstOrDefault(f => string.Equals(f.Key, key, StringComparison.OrdinalIgnoreCase))?.Value; + + if(internalValue != null) { + _logger.LogWarning("Locating filters by the internal propterty name is deprecated. You should use the public attribute name instead."); + return publicValue; + } + + return null; + } } -} \ No newline at end of file +} diff --git a/src/JsonApiDotNetCore/Services/QueryParser.cs b/src/JsonApiDotNetCore/Services/QueryParser.cs index 46f7a4e68a..1e956d64f4 100644 --- a/src/JsonApiDotNetCore/Services/QueryParser.cs +++ b/src/JsonApiDotNetCore/Services/QueryParser.cs @@ -18,6 +18,8 @@ public class QueryParser : IQueryParser { private readonly IControllerContext _controllerContext; private readonly JsonApiOptions _options; + private const char OPEN_BRACKET = '['; + private const char CLOSE_BRACKET = ']'; public QueryParser( IControllerContext controllerContext, JsonApiOptions options) { @@ -72,7 +74,7 @@ protected virtual List ParseFilterQuery(string key, string value) { // expected input = filter[id]=eq:1 var queries = new List(); - var propertyName = key.Split('[', ']') [1].ToProperCase(); + var propertyName = key.Split(OPEN_BRACKET, CLOSE_BRACKET) [1]; var values = value.Split(','); foreach (var val in values) { From 197c435851ef3e6c7522a87c8244f7fd09fddec4 Mon Sep 17 00:00:00 2001 From: jaredcnance Date: Sun, 26 Nov 2017 15:56:23 -0600 Subject: [PATCH 09/17] perf(QueryParser): constants instead of inline strings --- src/JsonApiDotNetCore/Services/QueryParser.cs | 74 ++++++++++++------- 1 file changed, 46 insertions(+), 28 deletions(-) diff --git a/src/JsonApiDotNetCore/Services/QueryParser.cs b/src/JsonApiDotNetCore/Services/QueryParser.cs index 1e956d64f4..8f721aef62 100644 --- a/src/JsonApiDotNetCore/Services/QueryParser.cs +++ b/src/JsonApiDotNetCore/Services/QueryParser.cs @@ -3,7 +3,6 @@ using System.Linq; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Models; @@ -18,8 +17,17 @@ public class QueryParser : IQueryParser { private readonly IControllerContext _controllerContext; private readonly JsonApiOptions _options; + private const string FILTER = "filter"; + private const string SORT = "sort"; + private const string INCLUDE = "include"; + private const string PAGE = "page"; + private const string FIELDS = "fields"; private const char OPEN_BRACKET = '['; private const char CLOSE_BRACKET = ']'; + private const char COMMA = ','; + private const char COLON = ':'; + private const string COLON_STR = ":"; + public QueryParser( IControllerContext controllerContext, JsonApiOptions options) { @@ -32,31 +40,31 @@ public virtual QuerySet Parse(IQueryCollection query) { var disabledQueries = _controllerContext.GetControllerAttribute() ? .QueryParams ?? QueryParams.None; foreach (var pair in query) { - if (pair.Key.StartsWith("filter")) { + if (pair.Key.StartsWith(FILTER)) { if (disabledQueries.HasFlag(QueryParams.Filter) == false) querySet.Filters.AddRange(ParseFilterQuery(pair.Key, pair.Value)); continue; } - if (pair.Key.StartsWith("sort")) { + if (pair.Key.StartsWith(SORT)) { if (disabledQueries.HasFlag(QueryParams.Sort) == false) querySet.SortParameters = ParseSortParameters(pair.Value); continue; } - if (pair.Key.StartsWith("include")) { + if (pair.Key.StartsWith(INCLUDE)) { if (disabledQueries.HasFlag(QueryParams.Include) == false) querySet.IncludedRelationships = ParseIncludedRelationships(pair.Value); continue; } - if (pair.Key.StartsWith("page")) { + if (pair.Key.StartsWith(PAGE)) { if (disabledQueries.HasFlag(QueryParams.Page) == false) querySet.PageQuery = ParsePageQuery(querySet.PageQuery, pair.Key, pair.Value); continue; } - if (pair.Key.StartsWith("fields")) { + if (pair.Key.StartsWith(FIELDS)) { if (disabledQueries.HasFlag(QueryParams.Fields) == false) querySet.Fields = ParseFieldsQuery(pair.Key, pair.Value); continue; @@ -76,10 +84,9 @@ protected virtual List ParseFilterQuery(string key, string value) { var propertyName = key.Split(OPEN_BRACKET, CLOSE_BRACKET) [1]; - var values = value.Split(','); + var values = value.Split(COMMA); foreach (var val in values) { - (var operation, - var filterValue) = ParseFilterOperation(val); + (var operation, var filterValue) = ParseFilterOperation(val); queries.Add(new FilterQuery(propertyName, filterValue, operation)); } @@ -90,7 +97,7 @@ protected virtual(string operation, string value) ParseFilterOperation(string va if (value.Length < 3) return (string.Empty, value); - var operation = value.Split(':'); + var operation = value.Split(COLON); if (operation.Length == 1) return (string.Empty, value); @@ -100,7 +107,7 @@ protected virtual(string operation, string value) ParseFilterOperation(string va return (string.Empty, value); var prefix = operation[0]; - value = string.Join(":", operation.Skip(1)); + value = string.Join(COLON_STR, operation.Skip(1)); return (prefix, value); } @@ -110,11 +117,14 @@ protected virtual PageQuery ParsePageQuery(PageQuery pageQuery, string key, stri // page[number]=1 pageQuery = pageQuery ?? new PageQuery(); - var propertyName = key.Split('[', ']') [1]; + var propertyName = key.Split(OPEN_BRACKET, CLOSE_BRACKET) [1]; + + const string SIZE = "size"; + const string NUMBER = "number"; - if (propertyName == "size") + if (propertyName == SIZE) pageQuery.PageSize = Convert.ToInt32(value); - else if (propertyName == "number") + else if (propertyName == NUMBER) pageQuery.PageOffset = Convert.ToInt32(value); return pageQuery; @@ -123,11 +133,11 @@ protected virtual PageQuery ParsePageQuery(PageQuery pageQuery, string key, stri // sort=id,name // sort=-id protected virtual List ParseSortParameters(string value) { - const char SORT_DELIMITER = ','; + var sortParameters = new List(); + const char DESCENDING_SORT_OPERATOR = '-'; + var sortSegments = value.Split(COMMA); - var sortParameters = new List(); - var sortSegments = value.Split(SORT_DELIMITER); foreach (var sortSegment in sortSegments) { var propertyName = sortSegment; @@ -147,24 +157,26 @@ protected virtual List ParseSortParameters(string value) { } protected virtual List ParseIncludedRelationships(string value) { - if (value.Contains(".")) + const string NESTED_DELIMITER = "."; + if (value.Contains(NESTED_DELIMITER)) throw new JsonApiException(400, "Deeply nested relationships are not supported"); return value - .Split(',') + .Split(COMMA) .ToList(); } protected virtual List ParseFieldsQuery(string key, string value) { // expected: fields[TYPE]=prop1,prop2 - var typeName = key.Split('[', ']') [1]; + var typeName = key.Split(OPEN_BRACKET, CLOSE_BRACKET) [1]; - var includedFields = new List { "Id" }; + const string ID = "Id"; + var includedFields = new List { ID }; if (typeName != _controllerContext.RequestEntity.EntityName) return includedFields; - var fields = value.Split(','); + var fields = value.Split(COMMA); foreach (var field in fields) { var internalAttrName = _controllerContext.RequestEntity .Attributes @@ -176,11 +188,17 @@ protected virtual List ParseFieldsQuery(string key, string value) { return includedFields; } - protected virtual AttrAttribute GetAttribute(string propertyName) => _controllerContext - .RequestEntity - .Attributes - .FirstOrDefault(attr => - string.Equals(attr.PublicAttributeName, propertyName, StringComparison.OrdinalIgnoreCase) - ); + protected virtual AttrAttribute GetAttribute(string propertyName) { + try { + return _controllerContext + .RequestEntity + .Attributes + .Single(attr => + string.Equals(attr.PublicAttributeName, propertyName, StringComparison.OrdinalIgnoreCase) + ); + } catch (InvalidOperationException e) { + throw new JsonApiException(400, $"Attribute '{propertyName}' does not exist on resource '{_controllerContext.RequestEntity.EntityName}'"); + } + } } } From 47cc72165dbb266232c12ef3b0f6116fa3280c8e Mon Sep 17 00:00:00 2001 From: jaredcnance Date: Sun, 26 Nov 2017 15:56:34 -0600 Subject: [PATCH 10/17] bench(*): run new benchmarks --- ...ry.QueryParser_Benchmarks-report-github.md | 11 +++++----- ...piDeserializer_Benchmarks-report-github.md | 2 +- ...nApiSerializer_Benchmarks-report-github.md | 6 +++--- benchmarks/Query/QueryParser_Benchmarks.cs | 21 +++++++++++++++++++ 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Query.QueryParser_Benchmarks-report-github.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Query.QueryParser_Benchmarks-report-github.md index 324281fa40..4678b3141e 100755 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Query.QueryParser_Benchmarks-report-github.md +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Query.QueryParser_Benchmarks-report-github.md @@ -4,12 +4,13 @@ BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12 Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4 .NET Core SDK=2.0.0 [Host] : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT - Job-ROPOBW : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT + Job-OWXJBF : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT LaunchCount=3 TargetCount=20 WarmupCount=10 ``` -| Method | Mean | Error | StdDev | -|--------------- |---------:|----------:|----------:| -| AscendingSort | 3.101 us | 0.0298 us | 0.0636 us | -| DescendingSort | 3.195 us | 0.0297 us | 0.0632 us | +| Method | Mean | Error | StdDev | Median | +|--------------- |-------------:|-----------:|------------:|-------------:| +| AscendingSort | 4.451 us | 1.5230 us | 3.2457 us | 3.305 us | +| DescendingSort | 3.287 us | 0.0307 us | 0.0673 us | 3.263 us | +| ComplexQuery | 1,973.029 us | 67.5600 us | 143.9759 us | 1,952.663 us | diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md index d8571f9644..6c8c3f2905 100755 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md @@ -10,4 +10,4 @@ Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4 ``` | Method | Mean | Error | StdDev | |------------------------ |---------:|----------:|----------:| -| DeserializeSimpleObject | 26.37 us | 0.4458 us | 0.4170 us | +| DeserializeSimpleObject | 27.05 us | 0.5353 us | 0.5950 us | diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-github.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-github.md index 4671d6b3af..f86bf0faa9 100755 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-github.md +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-github.md @@ -8,6 +8,6 @@ Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4 ``` -| Method | Mean | Error | StdDev | -|------------------------ |---------:|----------:|----------:| -| DeserializeSimpleObject | 7.135 us | 0.1401 us | 0.1439 us | +| Method | Mean | Error | StdDev | +|---------------------- |---------:|----------:|----------:| +| SerializeSimpleObject | 7.195 us | 0.1436 us | 0.1816 us | diff --git a/benchmarks/Query/QueryParser_Benchmarks.cs b/benchmarks/Query/QueryParser_Benchmarks.cs index 0edaee6cb5..8a077de2bf 100644 --- a/benchmarks/Query/QueryParser_Benchmarks.cs +++ b/benchmarks/Query/QueryParser_Benchmarks.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes.Exporters; @@ -6,6 +7,8 @@ using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; +using Microsoft.AspNetCore.Http.Internal; +using Microsoft.Extensions.Primitives; using Moq; namespace Benchmarks.Query { @@ -36,6 +39,24 @@ public QueryParser_Benchmarks() { [Benchmark] public void DescendingSort() => _queryParser._ParseSortParameters(DESCENDING_SORT); + [Benchmark] + public void ComplexQuery() => Run(100, () => _queryParser.Parse( + new QueryCollection( + new Dictionary { + { $"filter[{ATTRIBUTE}]", new StringValues(new [] { "abc", "eq:abc" }) }, + { $"sort", $"-{ATTRIBUTE}" }, + { $"include", "relationship" }, + { $"page[size]", "1" }, + { $"fields[resource]", ATTRIBUTE }, + } + ) + )); + + private void Run(int iterations, Action action) { + for (int i = 0; i < iterations; i++) + action(); + } + // this facade allows us to expose and micro-benchmark protected methods private class BenchmarkFacade : QueryParser { public BenchmarkFacade( From 8b56f03c378f51d7fc2f402afa0e292c31cf1877 Mon Sep 17 00:00:00 2001 From: jaredcnance Date: Sun, 3 Dec 2017 15:14:23 -0600 Subject: [PATCH 11/17] feat(*): add constants for Content-Type --- ...uery.QueryParser_Benchmarks-report-github.md | 12 ++++++------ benchmarks/Program.cs | 9 ++++++--- benchmarks/Query/QueryParser_Benchmarks.cs | 2 +- .../Formatters/JsonApiInputFormatter.cs | 3 ++- .../Formatters/JsonApiOutputFormatter.cs | 3 ++- .../Formatters/JsonApiWriter.cs | 7 ++++--- src/JsonApiDotNetCore/Internal/Constants.cs | 8 ++++++++ .../Internal/JsonApiExceptionFactory.cs | 7 +++++-- .../Internal/Query/BaseFilterQuery.cs | 4 ++-- .../Internal/Query/RelatedAttrFilterQuery.cs | 9 +++++---- .../Middleware/RequestMiddleware.cs | 5 +++-- src/JsonApiDotNetCore/Models/DocumentBase.cs | 17 +++-------------- 12 files changed, 47 insertions(+), 39 deletions(-) create mode 100644 src/JsonApiDotNetCore/Internal/Constants.cs diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Query.QueryParser_Benchmarks-report-github.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Query.QueryParser_Benchmarks-report-github.md index 4678b3141e..b3ebd8a29c 100755 --- a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Query.QueryParser_Benchmarks-report-github.md +++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Query.QueryParser_Benchmarks-report-github.md @@ -4,13 +4,13 @@ BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12 Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4 .NET Core SDK=2.0.0 [Host] : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT - Job-OWXJBF : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT + Job-WKDOLS : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT LaunchCount=3 TargetCount=20 WarmupCount=10 ``` -| Method | Mean | Error | StdDev | Median | -|--------------- |-------------:|-----------:|------------:|-------------:| -| AscendingSort | 4.451 us | 1.5230 us | 3.2457 us | 3.305 us | -| DescendingSort | 3.287 us | 0.0307 us | 0.0673 us | 3.263 us | -| ComplexQuery | 1,973.029 us | 67.5600 us | 143.9759 us | 1,952.663 us | +| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Allocated | +|--------------- |-------------:|-----------:|-----------:|---------:|--------:|----------:| +| AscendingSort | 4.316 us | 1.3773 us | 3.0232 us | 0.5066 | 0.1303 | 1.08 KB | +| DescendingSort | 3.300 us | 0.0314 us | 0.0682 us | 0.5123 | 0.1318 | 1.13 KB | +| ComplexQuery | 2,041.642 us | 41.5631 us | 92.1010 us | 312.5000 | 80.2734 | 648.99 KB | diff --git a/benchmarks/Program.cs b/benchmarks/Program.cs index da4eaa2b50..7665d5fb97 100644 --- a/benchmarks/Program.cs +++ b/benchmarks/Program.cs @@ -5,9 +5,12 @@ namespace Benchmarks { class Program { static void Main(string[] args) { - BenchmarkRunner.Run(); - BenchmarkRunner.Run(); - BenchmarkRunner.Run(); + var switcher = new BenchmarkSwitcher(new[] { + typeof(JsonApiDeserializer_Benchmarks), + typeof(JsonApiSerializer_Benchmarks), + typeof(QueryParser_Benchmarks) + }); + switcher.Run(args); } } } diff --git a/benchmarks/Query/QueryParser_Benchmarks.cs b/benchmarks/Query/QueryParser_Benchmarks.cs index 8a077de2bf..61fe3b1bc2 100644 --- a/benchmarks/Query/QueryParser_Benchmarks.cs +++ b/benchmarks/Query/QueryParser_Benchmarks.cs @@ -12,7 +12,7 @@ using Moq; namespace Benchmarks.Query { - [MarkdownExporter, SimpleJob(launchCount : 3, warmupCount : 10, targetCount : 20)] + [MarkdownExporter, SimpleJob(launchCount : 3, warmupCount : 10, targetCount : 20), MemoryDiagnoser] public class QueryParser_Benchmarks { private readonly BenchmarkFacade _queryParser; diff --git a/src/JsonApiDotNetCore/Formatters/JsonApiInputFormatter.cs b/src/JsonApiDotNetCore/Formatters/JsonApiInputFormatter.cs index 12e57deadf..f556b7433d 100644 --- a/src/JsonApiDotNetCore/Formatters/JsonApiInputFormatter.cs +++ b/src/JsonApiDotNetCore/Formatters/JsonApiInputFormatter.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using JsonApiDotNetCore.Internal; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Extensions.DependencyInjection; @@ -14,7 +15,7 @@ public bool CanRead(InputFormatterContext context) var contentTypeString = context.HttpContext.Request.ContentType; - return contentTypeString == "application/vnd.api+json"; + return contentTypeString == Constants.ContentType; } public async Task ReadAsync(InputFormatterContext context) diff --git a/src/JsonApiDotNetCore/Formatters/JsonApiOutputFormatter.cs b/src/JsonApiDotNetCore/Formatters/JsonApiOutputFormatter.cs index 2431055d1d..b456932fc5 100644 --- a/src/JsonApiDotNetCore/Formatters/JsonApiOutputFormatter.cs +++ b/src/JsonApiDotNetCore/Formatters/JsonApiOutputFormatter.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using JsonApiDotNetCore.Internal; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Extensions.DependencyInjection; @@ -14,7 +15,7 @@ public bool CanWriteResult(OutputFormatterCanWriteContext context) var contentTypeString = context.HttpContext.Request.ContentType; - return string.IsNullOrEmpty(contentTypeString) || contentTypeString == "application/vnd.api+json"; + return string.IsNullOrEmpty(contentTypeString) || contentTypeString == Constants.ContentType; } public async Task WriteAsync(OutputFormatterWriteContext context) diff --git a/src/JsonApiDotNetCore/Formatters/JsonApiWriter.cs b/src/JsonApiDotNetCore/Formatters/JsonApiWriter.cs index 730a88f13e..d644def66d 100644 --- a/src/JsonApiDotNetCore/Formatters/JsonApiWriter.cs +++ b/src/JsonApiDotNetCore/Formatters/JsonApiWriter.cs @@ -13,7 +13,8 @@ public class JsonApiWriter : IJsonApiWriter private readonly ILogger _logger; private readonly IJsonApiSerializer _serializer; - public JsonApiWriter(IJsonApiSerializer serializer, + public JsonApiWriter( + IJsonApiSerializer serializer, ILoggerFactory loggerFactory) { _serializer = serializer; @@ -30,7 +31,7 @@ public async Task WriteAsync(OutputFormatterWriteContext context) var response = context.HttpContext.Response; using (var writer = context.WriterFactory(response.Body, Encoding.UTF8)) { - response.ContentType = "application/vnd.api+json"; + response.ContentType = Constants.ContentType; string responseContent; try { @@ -55,4 +56,4 @@ private string GetResponseBody(object responseObject) return _serializer.Serialize(responseObject); } } -} \ No newline at end of file +} diff --git a/src/JsonApiDotNetCore/Internal/Constants.cs b/src/JsonApiDotNetCore/Internal/Constants.cs new file mode 100644 index 0000000000..750d94ba07 --- /dev/null +++ b/src/JsonApiDotNetCore/Internal/Constants.cs @@ -0,0 +1,8 @@ +namespace JsonApiDotNetCore.Internal +{ + public static class Constants + { + public const string AcceptHeader = "Accept"; + public const string ContentType = "application/vnd.api+json"; + } +} diff --git a/src/JsonApiDotNetCore/Internal/JsonApiExceptionFactory.cs b/src/JsonApiDotNetCore/Internal/JsonApiExceptionFactory.cs index 42f3037f89..36b4969b1d 100644 --- a/src/JsonApiDotNetCore/Internal/JsonApiExceptionFactory.cs +++ b/src/JsonApiDotNetCore/Internal/JsonApiExceptionFactory.cs @@ -5,14 +5,17 @@ namespace JsonApiDotNetCore.Internal { public static class JsonApiExceptionFactory { + private const string JsonApiException = nameof(JsonApiException); + private const string InvalidCastException = nameof(InvalidCastException); + public static JsonApiException GetException(Exception exception) { var exceptionType = exception.GetType().ToString().Split('.').Last(); switch(exceptionType) { - case "JsonApiException": + case JsonApiException: return (JsonApiException)exception; - case "InvalidCastException": + case InvalidCastException: return new JsonApiException(409, exception.Message); default: return new JsonApiException(500, exception.Message, GetExceptionDetail(exception.InnerException)); diff --git a/src/JsonApiDotNetCore/Internal/Query/BaseFilterQuery.cs b/src/JsonApiDotNetCore/Internal/Query/BaseFilterQuery.cs index 527b842ca8..1c43d84254 100644 --- a/src/JsonApiDotNetCore/Internal/Query/BaseFilterQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/BaseFilterQuery.cs @@ -8,10 +8,10 @@ protected FilterOperations GetFilterOperation(string prefix) { if (prefix.Length == 0) return FilterOperations.eq; - if (!Enum.TryParse(prefix, out FilterOperations opertion)) + if (Enum.TryParse(prefix, out FilterOperations opertion) == false) throw new JsonApiException(400, $"Invalid filter prefix '{prefix}'"); return opertion; } } -} \ No newline at end of file +} diff --git a/src/JsonApiDotNetCore/Internal/Query/RelatedAttrFilterQuery.cs b/src/JsonApiDotNetCore/Internal/Query/RelatedAttrFilterQuery.cs index dc633c5302..3b249c176c 100644 --- a/src/JsonApiDotNetCore/Internal/Query/RelatedAttrFilterQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/RelatedAttrFilterQuery.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; @@ -14,7 +15,7 @@ public RelatedAttrFilterQuery( { _jsonApiContext = jsonApiCopntext; - var relationshipArray = filterQuery.Key.Split('.'); + var relationshipArray = filterQuery.Attribute.Split('.'); var relationship = GetRelationship(relationshipArray[0]); if (relationship == null) @@ -36,14 +37,14 @@ public RelatedAttrFilterQuery( private RelationshipAttribute GetRelationship(string propertyName) { return _jsonApiContext.RequestEntity.Relationships - .FirstOrDefault(r => r.InternalRelationshipName.ToLower() == propertyName.ToLower()); + .FirstOrDefault(r => string.Equals(r.PublicRelationshipName, propertyName, StringComparison.OrdinalIgnoreCase)); } private AttrAttribute GetAttribute(RelationshipAttribute relationship, string attribute) { var relatedContextExntity = _jsonApiContext.ContextGraph.GetContextEntity(relationship.Type); return relatedContextExntity.Attributes - .FirstOrDefault(a => a.InternalAttributeName.ToLower() == attribute.ToLower()); + .FirstOrDefault(a => string.Equals(a.PublicAttributeName, attribute, StringComparison.OrdinalIgnoreCase)); } } -} \ No newline at end of file +} diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index 670def21d6..6e2612c9a6 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using JsonApiDotNetCore.Internal; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; @@ -37,7 +38,7 @@ private static bool IsValidContentTypeHeader(HttpContext context) private static bool IsValidAcceptHeader(HttpContext context) { - if (context.Request.Headers.TryGetValue("Accept", out StringValues acceptHeaders) == false) + if (context.Request.Headers.TryGetValue(Constants.AcceptHeader, out StringValues acceptHeaders) == false) return true; foreach (var acceptHeader in acceptHeaders) @@ -54,7 +55,7 @@ private static bool IsValidAcceptHeader(HttpContext context) private static bool ContainsMediaTypeParameters(string mediaType) { var mediaTypeArr = mediaType.Split(';'); - return (mediaTypeArr[0] == "application/vnd.api+json" && mediaTypeArr.Length == 2); + return (mediaTypeArr[0] == Constants.ContentType && mediaTypeArr.Length == 2); } private static void FlushResponse(HttpContext context, int statusCode) diff --git a/src/JsonApiDotNetCore/Models/DocumentBase.cs b/src/JsonApiDotNetCore/Models/DocumentBase.cs index 1cb31595ec..df51301c20 100644 --- a/src/JsonApiDotNetCore/Models/DocumentBase.cs +++ b/src/JsonApiDotNetCore/Models/DocumentBase.cs @@ -15,19 +15,8 @@ public class DocumentBase public Dictionary Meta { get; set; } // http://www.newtonsoft.com/json/help/html/ConditionalProperties.htm - public bool ShouldSerializeIncluded() - { - return (Included != null); - } - - public bool ShouldSerializeMeta() - { - return (Meta != null); - } - - public bool ShouldSerializeLinks() - { - return (Links != null); - } + public bool ShouldSerializeIncluded() => (Included != null); + public bool ShouldSerializeMeta() => (Meta != null); + public bool ShouldSerializeLinks() => (Links != null); } } From 0c8a0e90c9efedb0d5cef380cf43e84e9b2fdb7c Mon Sep 17 00:00:00 2001 From: jaredcnance Date: Sun, 3 Dec 2017 15:18:10 -0600 Subject: [PATCH 12/17] fix(queryParser): remove access to obsolete key --- src/JsonApiDotNetCore/Services/QueryAccessor.cs | 2 +- src/JsonApiDotNetCore/Services/QueryParser.cs | 2 +- test/UnitTests/Services/QueryParser_Tests.cs | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/JsonApiDotNetCore/Services/QueryAccessor.cs b/src/JsonApiDotNetCore/Services/QueryAccessor.cs index 63b91ed239..41bc64151b 100644 --- a/src/JsonApiDotNetCore/Services/QueryAccessor.cs +++ b/src/JsonApiDotNetCore/Services/QueryAccessor.cs @@ -69,7 +69,7 @@ private string GetFilterValue(string key) { return publicValue; var internalValue = _jsonApiContext.QuerySet.Filters - .FirstOrDefault(f => string.Equals(f.Key, key, StringComparison.OrdinalIgnoreCase))?.Value; + .FirstOrDefault(f => string.Equals(f.Attribute, key, StringComparison.OrdinalIgnoreCase))?.Value; if(internalValue != null) { _logger.LogWarning("Locating filters by the internal propterty name is deprecated. You should use the public attribute name instead."); diff --git a/src/JsonApiDotNetCore/Services/QueryParser.cs b/src/JsonApiDotNetCore/Services/QueryParser.cs index 8f721aef62..47f9efa631 100644 --- a/src/JsonApiDotNetCore/Services/QueryParser.cs +++ b/src/JsonApiDotNetCore/Services/QueryParser.cs @@ -197,7 +197,7 @@ protected virtual AttrAttribute GetAttribute(string propertyName) { string.Equals(attr.PublicAttributeName, propertyName, StringComparison.OrdinalIgnoreCase) ); } catch (InvalidOperationException e) { - throw new JsonApiException(400, $"Attribute '{propertyName}' does not exist on resource '{_controllerContext.RequestEntity.EntityName}'"); + throw new JsonApiException(400, $"Attribute '{propertyName}' does not exist on resource '{_controllerContext.RequestEntity.EntityName}'", e); } } } diff --git a/test/UnitTests/Services/QueryParser_Tests.cs b/test/UnitTests/Services/QueryParser_Tests.cs index a64c5b3692..abe5d75568 100644 --- a/test/UnitTests/Services/QueryParser_Tests.cs +++ b/test/UnitTests/Services/QueryParser_Tests.cs @@ -43,7 +43,7 @@ public void Can_Build_Filters() var querySet = queryParser.Parse(_queryCollectionMock.Object); // assert - Assert.Equal("value", querySet.Filters.Single(f => f.Key == "Key").Value); + Assert.Equal("value", querySet.Filters.Single(f => f.Attribute == "key").Value); } [Fact] @@ -69,8 +69,8 @@ public void Filters_Properly_Parses_DateTime_With_Operation() var querySet = queryParser.Parse(_queryCollectionMock.Object); // assert - Assert.Equal(dt, querySet.Filters.Single(f => f.Key == "Key").Value); - Assert.Equal("le", querySet.Filters.Single(f => f.Key == "Key").Operation); + Assert.Equal(dt, querySet.Filters.Single(f => f.Attribute == "key").Value); + Assert.Equal("le", querySet.Filters.Single(f => f.Attribute == "key").Operation); } [Fact] @@ -96,8 +96,8 @@ public void Filters_Properly_Parses_DateTime_Without_Operation() var querySet = queryParser.Parse(_queryCollectionMock.Object); // assert - Assert.Equal(dt, querySet.Filters.Single(f => f.Key == "Key").Value); - Assert.Equal(string.Empty, querySet.Filters.Single(f => f.Key == "Key").Operation); + Assert.Equal(dt, querySet.Filters.Single(f => f.Attribute == "key").Value); + Assert.Equal(string.Empty, querySet.Filters.Single(f => f.Attribute == "key").Operation); } [Fact] From 5abad2ff86874695f5c7f667e11b1f5db224f734 Mon Sep 17 00:00:00 2001 From: jaredcnance Date: Sat, 16 Dec 2017 07:31:24 -0600 Subject: [PATCH 13/17] fix(JsonApiDeSerializer): throw on possible null exception --- src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs index 25021c441e..1c0c5014f7 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs @@ -169,9 +169,12 @@ private object SetHasOneRelationship(object entity, if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData)) { var relationshipAttr = _jsonApiContext.RequestEntity.Relationships - .SingleOrDefault(r => r.PublicRelationshipName == relationshipName); + .SingleOrDefault(r => r.PublicRelationshipName == relationshipName); - var data = (Dictionary)relationshipData.ExposedData; + if (relationshipAttr == null) + throw new JsonApiException(400, $"{_jsonApiContext.RequestEntity.EntityName} does not contain a relationship '{relationshipName}'"); + + var data = (Dictionary) relationshipData.ExposedData; if (data == null) return entity; From 54b750f2976c6365735a54a06e8661f8659a212f Mon Sep 17 00:00:00 2001 From: jaredcnance Date: Sat, 16 Dec 2017 07:35:48 -0600 Subject: [PATCH 14/17] test(queryParser): add test coverage and handle exception --- src/JsonApiDotNetCore/Services/QueryParser.cs | 53 ++++--- test/UnitTests/Services/QueryParser_Tests.cs | 130 +++++++++++++++++- 2 files changed, 165 insertions(+), 18 deletions(-) diff --git a/src/JsonApiDotNetCore/Services/QueryParser.cs b/src/JsonApiDotNetCore/Services/QueryParser.cs index 47f9efa631..2a97723347 100644 --- a/src/JsonApiDotNetCore/Services/QueryParser.cs +++ b/src/JsonApiDotNetCore/Services/QueryParser.cs @@ -112,38 +112,47 @@ protected virtual(string operation, string value) ParseFilterOperation(string va return (prefix, value); } - protected virtual PageQuery ParsePageQuery(PageQuery pageQuery, string key, string value) { + protected virtual PageQuery ParsePageQuery(PageQuery pageQuery, string key, string value) + { // expected input = page[size]=10 // page[number]=1 pageQuery = pageQuery ?? new PageQuery(); - var propertyName = key.Split(OPEN_BRACKET, CLOSE_BRACKET) [1]; + var propertyName = key.Split(OPEN_BRACKET, CLOSE_BRACKET)[1]; const string SIZE = "size"; const string NUMBER = "number"; if (propertyName == SIZE) - pageQuery.PageSize = Convert.ToInt32(value); + pageQuery.PageSize = int.TryParse(value, out var pageSize) ? + pageSize : + throw new JsonApiException(400, $"Invalid page size '{value}'"); + else if (propertyName == NUMBER) - pageQuery.PageOffset = Convert.ToInt32(value); + pageQuery.PageOffset = int.TryParse(value, out var pageOffset) ? + pageOffset : + throw new JsonApiException(400, $"Invalid page size '{value}'"); return pageQuery; } // sort=id,name // sort=-id - protected virtual List ParseSortParameters(string value) { + protected virtual List ParseSortParameters(string value) + { var sortParameters = new List(); const char DESCENDING_SORT_OPERATOR = '-'; var sortSegments = value.Split(COMMA); - foreach (var sortSegment in sortSegments) { + foreach (var sortSegment in sortSegments) + { var propertyName = sortSegment; var direction = SortDirection.Ascending; - if (sortSegment[0] == DESCENDING_SORT_OPERATOR) { + if (sortSegment[0] == DESCENDING_SORT_OPERATOR) + { direction = SortDirection.Descending; propertyName = propertyName.Substring(1); } @@ -166,37 +175,47 @@ protected virtual List ParseIncludedRelationships(string value) { .ToList(); } - protected virtual List ParseFieldsQuery(string key, string value) { + protected virtual List ParseFieldsQuery(string key, string value) + { // expected: fields[TYPE]=prop1,prop2 - var typeName = key.Split(OPEN_BRACKET, CLOSE_BRACKET) [1]; + var typeName = key.Split(OPEN_BRACKET, CLOSE_BRACKET)[1]; const string ID = "Id"; var includedFields = new List { ID }; - if (typeName != _controllerContext.RequestEntity.EntityName) + // this will not support nested inclusions, it requires that the typeName is the current request type + if (string.Equals(typeName, _controllerContext.RequestEntity.EntityName, StringComparison.OrdinalIgnoreCase) == false) return includedFields; var fields = value.Split(COMMA); - foreach (var field in fields) { - var internalAttrName = _controllerContext.RequestEntity + foreach (var field in fields) + { + var attr = _controllerContext.RequestEntity .Attributes - .SingleOrDefault(attr => attr.PublicAttributeName == field) - .InternalAttributeName; + .SingleOrDefault(a => string.Equals(a.PublicAttributeName, field, StringComparison.OrdinalIgnoreCase)); + + if (attr == null) throw new JsonApiException(400, $"'{_controllerContext.RequestEntity.EntityName}' does not contain '{field}'."); + + var internalAttrName = attr.InternalAttributeName; includedFields.Add(internalAttrName); } return includedFields; } - protected virtual AttrAttribute GetAttribute(string propertyName) { - try { + protected virtual AttrAttribute GetAttribute(string propertyName) + { + try + { return _controllerContext .RequestEntity .Attributes .Single(attr => string.Equals(attr.PublicAttributeName, propertyName, StringComparison.OrdinalIgnoreCase) ); - } catch (InvalidOperationException e) { + } + catch (InvalidOperationException e) + { throw new JsonApiException(400, $"Attribute '{propertyName}' does not exist on resource '{_controllerContext.RequestEntity.EntityName}'", e); } } diff --git a/test/UnitTests/Services/QueryParser_Tests.cs b/test/UnitTests/Services/QueryParser_Tests.cs index abe5d75568..ed7808d1f4 100644 --- a/test/UnitTests/Services/QueryParser_Tests.cs +++ b/test/UnitTests/Services/QueryParser_Tests.cs @@ -224,5 +224,133 @@ public void Can_Disable_Fields() // assert Assert.Empty(querySet.Fields); } + + [Fact] + public void Can_Parse_Fields_Query() + { + // arrange + const string type = "articles"; + const string attrName = "some-field"; + const string internalAttrName = "SomeField"; + + var query = new Dictionary { { $"fields[{type}]", new StringValues(attrName) } }; + + _queryCollectionMock + .Setup(m => m.GetEnumerator()) + .Returns(query.GetEnumerator()); + + _controllerContextMock + .Setup(m => m.RequestEntity) + .Returns(new ContextEntity + { + EntityName = type, + Attributes = new List + { + new AttrAttribute(attrName) + { + InternalAttributeName = internalAttrName + } + } + }); + + var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + + // act + var querySet = queryParser.Parse(_queryCollectionMock.Object); + + // assert + Assert.NotEmpty(querySet.Fields); + Assert.Equal(2, querySet.Fields.Count); + Assert.Equal("Id", querySet.Fields[0]); + Assert.Equal(internalAttrName, querySet.Fields[1]); + } + + [Fact] + public void Throws_JsonApiException_If_Field_DoesNotExist() + { + // arrange + const string type = "articles"; + const string attrName = "dne"; + + var query = new Dictionary { { $"fields[{type}]", new StringValues(attrName) } }; + + _queryCollectionMock + .Setup(m => m.GetEnumerator()) + .Returns(query.GetEnumerator()); + + _controllerContextMock + .Setup(m => m.RequestEntity) + .Returns(new ContextEntity + { + EntityName = type, + Attributes = new List() + }); + + var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + + // act , assert + var ex = Assert.Throws(() => queryParser.Parse(_queryCollectionMock.Object)); + Assert.Equal(400, ex.GetStatusCode()); + } + + [Theory] + [InlineData("1", 1, false)] + [InlineData("abcde", 0, true)] + [InlineData("", 0, true)] + public void Can_Parse_Page_Size_Query(string value, int expectedValue, bool shouldThrow) + { + // arrange + var query = new Dictionary + { { "page[size]", new StringValues(value) } + }; + + _queryCollectionMock + .Setup(m => m.GetEnumerator()) + .Returns(query.GetEnumerator()); + + var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + + // act + if (shouldThrow) + { + var ex = Assert.Throws(() => queryParser.Parse(_queryCollectionMock.Object)); + Assert.Equal(400, ex.GetStatusCode()); + } + else + { + var querySet = queryParser.Parse(_queryCollectionMock.Object); + Assert.Equal(expectedValue, querySet.PageQuery.PageSize); + } + } + + [Theory] + [InlineData("1", 1, false)] + [InlineData("abcde", 0, true)] + [InlineData("", 0, true)] + public void Can_Parse_Page_Number_Query(string value, int expectedValue, bool shouldThrow) + { + // arrange + var query = new Dictionary + { { "page[number]", new StringValues(value) } + }; + + _queryCollectionMock + .Setup(m => m.GetEnumerator()) + .Returns(query.GetEnumerator()); + + var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + + // act + if (shouldThrow) + { + var ex = Assert.Throws(() => queryParser.Parse(_queryCollectionMock.Object)); + Assert.Equal(400, ex.GetStatusCode()); + } + else + { + var querySet = queryParser.Parse(_queryCollectionMock.Object); + Assert.Equal(expectedValue, querySet.PageQuery.PageOffset); + } + } } -} +} \ No newline at end of file From 1553ce1675424cf71c1f35ee2e214470d0b6fec1 Mon Sep 17 00:00:00 2001 From: jaredcnance Date: Sat, 16 Dec 2017 07:37:08 -0600 Subject: [PATCH 15/17] fix(ContextGraph): return null instead of throwing null exception --- src/JsonApiDotNetCore/Internal/ContextGraph.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/JsonApiDotNetCore/Internal/ContextGraph.cs b/src/JsonApiDotNetCore/Internal/ContextGraph.cs index fd29794194..163196341b 100644 --- a/src/JsonApiDotNetCore/Internal/ContextGraph.cs +++ b/src/JsonApiDotNetCore/Internal/ContextGraph.cs @@ -1,7 +1,7 @@ -using System.Reflection; +using System; using System.Collections.Generic; using System.Linq; -using System; +using System.Reflection; namespace JsonApiDotNetCore.Internal { @@ -34,9 +34,9 @@ public string GetRelationshipName(string relationshipName) { var entityType = typeof(TParent); return Entities - .SingleOrDefault(e => e.EntityType == entityType) - .Relationships - .SingleOrDefault(r => string.Equals(r.PublicRelationshipName, relationshipName, StringComparison.OrdinalIgnoreCase)) + .SingleOrDefault(e => e.EntityType == entityType) + ?.Relationships + .SingleOrDefault(r => string.Equals(r.PublicRelationshipName, relationshipName, StringComparison.OrdinalIgnoreCase)) ?.InternalRelationshipName; } } From bc594f95a925897ce0bd990d26ab0ef7783fa50a Mon Sep 17 00:00:00 2001 From: jaredcnance Date: Sat, 16 Dec 2017 07:38:28 -0600 Subject: [PATCH 16/17] fix(queryParser): add missing usings --- test/UnitTests/Services/QueryParser_Tests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/UnitTests/Services/QueryParser_Tests.cs b/test/UnitTests/Services/QueryParser_Tests.cs index ed7808d1f4..64c9830f2b 100644 --- a/test/UnitTests/Services/QueryParser_Tests.cs +++ b/test/UnitTests/Services/QueryParser_Tests.cs @@ -2,6 +2,8 @@ using System.Linq; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; From 7914d3c17be21b11b11062ba3331c19dde34c0a0 Mon Sep 17 00:00:00 2001 From: jaredcnance Date: Sat, 16 Dec 2017 07:49:48 -0600 Subject: [PATCH 17/17] fix(props): bump to netstandard2.0 fixing rebase --- build/dependencies.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index d4686bba2e..13b9ba4ecb 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -1,7 +1,7 @@ - netcoreapp1.1 - netstandard1.6 + netcoreapp2.0 + netstandard2.0 4.7.10