diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/AdvancedRestConceptsSeries.sln b/aspnetcore-webapi/AdvancedRestConceptsSeries/AdvancedRestConceptsSeries.sln
new file mode 100644
index 000000000..cb6974a97
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/AdvancedRestConceptsSeries.sln
@@ -0,0 +1,651 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StartingProject", "StartingProject", "{ABB2D577-535D-D48F-8A83-D0D3A05DDA5C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer", "StartingProject\AccountOwnerServer\AccountOwnerServer.csproj", "{818132FC-82E4-4E0C-8D9D-DB9FAA82EC17}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoggerService", "StartingProject\LoggerService\LoggerService.csproj", "{762A4F83-200A-4784-939C-30DDCCD07542}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contracts", "StartingProject\Contracts\Contracts.csproj", "{F7C739C1-1C1A-45FC-A5CF-19C85F88C492}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Entities", "StartingProject\Entities\Entities.csproj", "{2A46F144-074D-406E-8891-218DF31A92FA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repository", "StartingProject\Repository\Repository.csproj", "{5C5CC11C-846A-469B-A1C7-1CE3A9E53918}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Paging", "Paging", "{EC6922CA-170E-0A2A-CC7E-F73B34E70907}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer", "Paging\AccountOwnerServer\AccountOwnerServer.csproj", "{49DD4823-A00F-48CD-A374-088B6C8E8D7E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoggerService", "Paging\LoggerService\LoggerService.csproj", "{431FAA2C-9E7B-452B-B39F-82B08746E065}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contracts", "Paging\Contracts\Contracts.csproj", "{3C452E09-F2E5-4B2A-BFB0-CCF0D61D5C33}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Entities", "Paging\Entities\Entities.csproj", "{F0FF9E38-F849-4807-81B7-8723A0D9935E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repository", "Paging\Repository\Repository.csproj", "{10FA361F-FD9B-4BF2-BF17-3A1FCC203B9B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer.Tests", "Paging\AccountOwnerServer.Tests\AccountOwnerServer.Tests.csproj", "{5197E122-7973-4DFC-816D-5AB8F9F20D62}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Filtering", "Filtering", "{37512E4E-BED2-30A1-7091-F8823DB56BB0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer", "Filtering\AccountOwnerServer\AccountOwnerServer.csproj", "{066DBED8-9500-4EDD-AE69-9735F1A34DB5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoggerService", "Filtering\LoggerService\LoggerService.csproj", "{0023B98F-FAA0-40DE-8047-37A57B72287B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contracts", "Filtering\Contracts\Contracts.csproj", "{9F334D4D-D520-4E36-A3A0-FCF17D5027FE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Entities", "Filtering\Entities\Entities.csproj", "{618D8E73-942A-4C6A-B6DF-C3E24377D1A6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repository", "Filtering\Repository\Repository.csproj", "{0695060C-08C2-4AD7-9906-3D7F17AF85F8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer.Tests", "Filtering\AccountOwnerServer.Tests\AccountOwnerServer.Tests.csproj", "{3188FDDE-FBF2-43FD-A841-40766012C8A2}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Searching", "Searching", "{E2866E30-6F85-1E9C-4A7D-9D94AC184FFF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer", "Searching\AccountOwnerServer\AccountOwnerServer.csproj", "{8534D3A8-7CBE-42A0-BAAB-68F9D0BFFA0C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoggerService", "Searching\LoggerService\LoggerService.csproj", "{BFFF4AD6-8508-465F-B087-4CEE919D5909}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contracts", "Searching\Contracts\Contracts.csproj", "{927D22CA-7788-4035-A400-8B3F2CAEA650}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Entities", "Searching\Entities\Entities.csproj", "{D40EBE29-0DF7-494A-9B64-84CA9FC4D87B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repository", "Searching\Repository\Repository.csproj", "{17F76803-025A-41A1-B91A-9E2B70CCF70B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer.Tests", "Searching\AccountOwnerServer.Tests\AccountOwnerServer.Tests.csproj", "{0F702691-51BD-4F55-9818-AA5E7616B507}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sorting", "Sorting", "{05210415-73DC-ACC3-ABDB-6AAB38F7838E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer", "Sorting\AccountOwnerServer\AccountOwnerServer.csproj", "{A12941F8-AA71-477F-AC5F-37FD66253807}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoggerService", "Sorting\LoggerService\LoggerService.csproj", "{FEB14BF3-536E-467C-930E-3F253B90A145}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contracts", "Sorting\Contracts\Contracts.csproj", "{9E6C8A4F-F911-4F9E-8E2F-2583C792A800}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Entities", "Sorting\Entities\Entities.csproj", "{445C10D4-F39E-4048-BA13-28FBFB9DCD63}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repository", "Sorting\Repository\Repository.csproj", "{7E02B4A7-C232-4B63-B56D-38683D659FDE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer.Tests", "Sorting\AccountOwnerServer.Tests\AccountOwnerServer.Tests.csproj", "{3B2C28CD-FB49-44D9-9FC4-76A71F815FC4}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataShaping", "DataShaping", "{06DC3261-A065-1F52-480A-5354B826AC99}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer", "DataShaping\AccountOwnerServer\AccountOwnerServer.csproj", "{F787C38F-16C2-4EDE-9D28-E0EDE13215B3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoggerService", "DataShaping\LoggerService\LoggerService.csproj", "{1EDCE8B0-14B1-4C65-9619-92A3D88FC587}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contracts", "DataShaping\Contracts\Contracts.csproj", "{E2EB8A87-973D-444F-81E4-2BA34B3CD32D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Entities", "DataShaping\Entities\Entities.csproj", "{0901A59F-AE0F-4B55-98B0-05FF23387FF7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repository", "DataShaping\Repository\Repository.csproj", "{0AE788C5-4205-4A07-A1AA-6A0A32AF72F3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer.Tests", "DataShaping\AccountOwnerServer.Tests\AccountOwnerServer.Tests.csproj", "{F808372B-E577-4632-A733-A6BBEFAE3FE7}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hateoas", "Hateoas", "{442FB6B7-7A89-545F-4529-C1729FE94F9C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer", "Hateoas\AccountOwnerServer\AccountOwnerServer.csproj", "{1A6DE1F4-AB77-4AA6-8EE7-080F918F1F60}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoggerService", "Hateoas\LoggerService\LoggerService.csproj", "{1124B5DC-D042-4396-88E2-C1DB42ED198C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contracts", "Hateoas\Contracts\Contracts.csproj", "{069467F4-AD60-4098-82CE-C09304303F4E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Entities", "Hateoas\Entities\Entities.csproj", "{90BD1FE2-AD58-41F3-A351-612E167708FF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repository", "Hateoas\Repository\Repository.csproj", "{EE529C09-917B-4FC0-AF19-0092B954F300}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer.Tests", "Hateoas\AccountOwnerServer.Tests\AccountOwnerServer.Tests.csproj", "{1B039528-384B-414A-B2C0-61738DC8CAA8}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {818132FC-82E4-4E0C-8D9D-DB9FAA82EC17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {818132FC-82E4-4E0C-8D9D-DB9FAA82EC17}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {818132FC-82E4-4E0C-8D9D-DB9FAA82EC17}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {818132FC-82E4-4E0C-8D9D-DB9FAA82EC17}.Debug|x64.Build.0 = Debug|Any CPU
+ {818132FC-82E4-4E0C-8D9D-DB9FAA82EC17}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {818132FC-82E4-4E0C-8D9D-DB9FAA82EC17}.Debug|x86.Build.0 = Debug|Any CPU
+ {818132FC-82E4-4E0C-8D9D-DB9FAA82EC17}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {818132FC-82E4-4E0C-8D9D-DB9FAA82EC17}.Release|Any CPU.Build.0 = Release|Any CPU
+ {818132FC-82E4-4E0C-8D9D-DB9FAA82EC17}.Release|x64.ActiveCfg = Release|Any CPU
+ {818132FC-82E4-4E0C-8D9D-DB9FAA82EC17}.Release|x64.Build.0 = Release|Any CPU
+ {818132FC-82E4-4E0C-8D9D-DB9FAA82EC17}.Release|x86.ActiveCfg = Release|Any CPU
+ {818132FC-82E4-4E0C-8D9D-DB9FAA82EC17}.Release|x86.Build.0 = Release|Any CPU
+ {762A4F83-200A-4784-939C-30DDCCD07542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {762A4F83-200A-4784-939C-30DDCCD07542}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {762A4F83-200A-4784-939C-30DDCCD07542}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {762A4F83-200A-4784-939C-30DDCCD07542}.Debug|x64.Build.0 = Debug|Any CPU
+ {762A4F83-200A-4784-939C-30DDCCD07542}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {762A4F83-200A-4784-939C-30DDCCD07542}.Debug|x86.Build.0 = Debug|Any CPU
+ {762A4F83-200A-4784-939C-30DDCCD07542}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {762A4F83-200A-4784-939C-30DDCCD07542}.Release|Any CPU.Build.0 = Release|Any CPU
+ {762A4F83-200A-4784-939C-30DDCCD07542}.Release|x64.ActiveCfg = Release|Any CPU
+ {762A4F83-200A-4784-939C-30DDCCD07542}.Release|x64.Build.0 = Release|Any CPU
+ {762A4F83-200A-4784-939C-30DDCCD07542}.Release|x86.ActiveCfg = Release|Any CPU
+ {762A4F83-200A-4784-939C-30DDCCD07542}.Release|x86.Build.0 = Release|Any CPU
+ {F7C739C1-1C1A-45FC-A5CF-19C85F88C492}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F7C739C1-1C1A-45FC-A5CF-19C85F88C492}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F7C739C1-1C1A-45FC-A5CF-19C85F88C492}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F7C739C1-1C1A-45FC-A5CF-19C85F88C492}.Debug|x64.Build.0 = Debug|Any CPU
+ {F7C739C1-1C1A-45FC-A5CF-19C85F88C492}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F7C739C1-1C1A-45FC-A5CF-19C85F88C492}.Debug|x86.Build.0 = Debug|Any CPU
+ {F7C739C1-1C1A-45FC-A5CF-19C85F88C492}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F7C739C1-1C1A-45FC-A5CF-19C85F88C492}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F7C739C1-1C1A-45FC-A5CF-19C85F88C492}.Release|x64.ActiveCfg = Release|Any CPU
+ {F7C739C1-1C1A-45FC-A5CF-19C85F88C492}.Release|x64.Build.0 = Release|Any CPU
+ {F7C739C1-1C1A-45FC-A5CF-19C85F88C492}.Release|x86.ActiveCfg = Release|Any CPU
+ {F7C739C1-1C1A-45FC-A5CF-19C85F88C492}.Release|x86.Build.0 = Release|Any CPU
+ {2A46F144-074D-406E-8891-218DF31A92FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2A46F144-074D-406E-8891-218DF31A92FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2A46F144-074D-406E-8891-218DF31A92FA}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2A46F144-074D-406E-8891-218DF31A92FA}.Debug|x64.Build.0 = Debug|Any CPU
+ {2A46F144-074D-406E-8891-218DF31A92FA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {2A46F144-074D-406E-8891-218DF31A92FA}.Debug|x86.Build.0 = Debug|Any CPU
+ {2A46F144-074D-406E-8891-218DF31A92FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2A46F144-074D-406E-8891-218DF31A92FA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2A46F144-074D-406E-8891-218DF31A92FA}.Release|x64.ActiveCfg = Release|Any CPU
+ {2A46F144-074D-406E-8891-218DF31A92FA}.Release|x64.Build.0 = Release|Any CPU
+ {2A46F144-074D-406E-8891-218DF31A92FA}.Release|x86.ActiveCfg = Release|Any CPU
+ {2A46F144-074D-406E-8891-218DF31A92FA}.Release|x86.Build.0 = Release|Any CPU
+ {5C5CC11C-846A-469B-A1C7-1CE3A9E53918}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5C5CC11C-846A-469B-A1C7-1CE3A9E53918}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5C5CC11C-846A-469B-A1C7-1CE3A9E53918}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5C5CC11C-846A-469B-A1C7-1CE3A9E53918}.Debug|x64.Build.0 = Debug|Any CPU
+ {5C5CC11C-846A-469B-A1C7-1CE3A9E53918}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5C5CC11C-846A-469B-A1C7-1CE3A9E53918}.Debug|x86.Build.0 = Debug|Any CPU
+ {5C5CC11C-846A-469B-A1C7-1CE3A9E53918}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5C5CC11C-846A-469B-A1C7-1CE3A9E53918}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5C5CC11C-846A-469B-A1C7-1CE3A9E53918}.Release|x64.ActiveCfg = Release|Any CPU
+ {5C5CC11C-846A-469B-A1C7-1CE3A9E53918}.Release|x64.Build.0 = Release|Any CPU
+ {5C5CC11C-846A-469B-A1C7-1CE3A9E53918}.Release|x86.ActiveCfg = Release|Any CPU
+ {5C5CC11C-846A-469B-A1C7-1CE3A9E53918}.Release|x86.Build.0 = Release|Any CPU
+ {49DD4823-A00F-48CD-A374-088B6C8E8D7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {49DD4823-A00F-48CD-A374-088B6C8E8D7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {49DD4823-A00F-48CD-A374-088B6C8E8D7E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {49DD4823-A00F-48CD-A374-088B6C8E8D7E}.Debug|x64.Build.0 = Debug|Any CPU
+ {49DD4823-A00F-48CD-A374-088B6C8E8D7E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {49DD4823-A00F-48CD-A374-088B6C8E8D7E}.Debug|x86.Build.0 = Debug|Any CPU
+ {49DD4823-A00F-48CD-A374-088B6C8E8D7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {49DD4823-A00F-48CD-A374-088B6C8E8D7E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {49DD4823-A00F-48CD-A374-088B6C8E8D7E}.Release|x64.ActiveCfg = Release|Any CPU
+ {49DD4823-A00F-48CD-A374-088B6C8E8D7E}.Release|x64.Build.0 = Release|Any CPU
+ {49DD4823-A00F-48CD-A374-088B6C8E8D7E}.Release|x86.ActiveCfg = Release|Any CPU
+ {49DD4823-A00F-48CD-A374-088B6C8E8D7E}.Release|x86.Build.0 = Release|Any CPU
+ {431FAA2C-9E7B-452B-B39F-82B08746E065}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {431FAA2C-9E7B-452B-B39F-82B08746E065}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {431FAA2C-9E7B-452B-B39F-82B08746E065}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {431FAA2C-9E7B-452B-B39F-82B08746E065}.Debug|x64.Build.0 = Debug|Any CPU
+ {431FAA2C-9E7B-452B-B39F-82B08746E065}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {431FAA2C-9E7B-452B-B39F-82B08746E065}.Debug|x86.Build.0 = Debug|Any CPU
+ {431FAA2C-9E7B-452B-B39F-82B08746E065}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {431FAA2C-9E7B-452B-B39F-82B08746E065}.Release|Any CPU.Build.0 = Release|Any CPU
+ {431FAA2C-9E7B-452B-B39F-82B08746E065}.Release|x64.ActiveCfg = Release|Any CPU
+ {431FAA2C-9E7B-452B-B39F-82B08746E065}.Release|x64.Build.0 = Release|Any CPU
+ {431FAA2C-9E7B-452B-B39F-82B08746E065}.Release|x86.ActiveCfg = Release|Any CPU
+ {431FAA2C-9E7B-452B-B39F-82B08746E065}.Release|x86.Build.0 = Release|Any CPU
+ {3C452E09-F2E5-4B2A-BFB0-CCF0D61D5C33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3C452E09-F2E5-4B2A-BFB0-CCF0D61D5C33}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3C452E09-F2E5-4B2A-BFB0-CCF0D61D5C33}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3C452E09-F2E5-4B2A-BFB0-CCF0D61D5C33}.Debug|x64.Build.0 = Debug|Any CPU
+ {3C452E09-F2E5-4B2A-BFB0-CCF0D61D5C33}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3C452E09-F2E5-4B2A-BFB0-CCF0D61D5C33}.Debug|x86.Build.0 = Debug|Any CPU
+ {3C452E09-F2E5-4B2A-BFB0-CCF0D61D5C33}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3C452E09-F2E5-4B2A-BFB0-CCF0D61D5C33}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3C452E09-F2E5-4B2A-BFB0-CCF0D61D5C33}.Release|x64.ActiveCfg = Release|Any CPU
+ {3C452E09-F2E5-4B2A-BFB0-CCF0D61D5C33}.Release|x64.Build.0 = Release|Any CPU
+ {3C452E09-F2E5-4B2A-BFB0-CCF0D61D5C33}.Release|x86.ActiveCfg = Release|Any CPU
+ {3C452E09-F2E5-4B2A-BFB0-CCF0D61D5C33}.Release|x86.Build.0 = Release|Any CPU
+ {F0FF9E38-F849-4807-81B7-8723A0D9935E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F0FF9E38-F849-4807-81B7-8723A0D9935E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F0FF9E38-F849-4807-81B7-8723A0D9935E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F0FF9E38-F849-4807-81B7-8723A0D9935E}.Debug|x64.Build.0 = Debug|Any CPU
+ {F0FF9E38-F849-4807-81B7-8723A0D9935E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F0FF9E38-F849-4807-81B7-8723A0D9935E}.Debug|x86.Build.0 = Debug|Any CPU
+ {F0FF9E38-F849-4807-81B7-8723A0D9935E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F0FF9E38-F849-4807-81B7-8723A0D9935E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F0FF9E38-F849-4807-81B7-8723A0D9935E}.Release|x64.ActiveCfg = Release|Any CPU
+ {F0FF9E38-F849-4807-81B7-8723A0D9935E}.Release|x64.Build.0 = Release|Any CPU
+ {F0FF9E38-F849-4807-81B7-8723A0D9935E}.Release|x86.ActiveCfg = Release|Any CPU
+ {F0FF9E38-F849-4807-81B7-8723A0D9935E}.Release|x86.Build.0 = Release|Any CPU
+ {10FA361F-FD9B-4BF2-BF17-3A1FCC203B9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {10FA361F-FD9B-4BF2-BF17-3A1FCC203B9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {10FA361F-FD9B-4BF2-BF17-3A1FCC203B9B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {10FA361F-FD9B-4BF2-BF17-3A1FCC203B9B}.Debug|x64.Build.0 = Debug|Any CPU
+ {10FA361F-FD9B-4BF2-BF17-3A1FCC203B9B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {10FA361F-FD9B-4BF2-BF17-3A1FCC203B9B}.Debug|x86.Build.0 = Debug|Any CPU
+ {10FA361F-FD9B-4BF2-BF17-3A1FCC203B9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {10FA361F-FD9B-4BF2-BF17-3A1FCC203B9B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {10FA361F-FD9B-4BF2-BF17-3A1FCC203B9B}.Release|x64.ActiveCfg = Release|Any CPU
+ {10FA361F-FD9B-4BF2-BF17-3A1FCC203B9B}.Release|x64.Build.0 = Release|Any CPU
+ {10FA361F-FD9B-4BF2-BF17-3A1FCC203B9B}.Release|x86.ActiveCfg = Release|Any CPU
+ {10FA361F-FD9B-4BF2-BF17-3A1FCC203B9B}.Release|x86.Build.0 = Release|Any CPU
+ {5197E122-7973-4DFC-816D-5AB8F9F20D62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5197E122-7973-4DFC-816D-5AB8F9F20D62}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5197E122-7973-4DFC-816D-5AB8F9F20D62}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5197E122-7973-4DFC-816D-5AB8F9F20D62}.Debug|x64.Build.0 = Debug|Any CPU
+ {5197E122-7973-4DFC-816D-5AB8F9F20D62}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5197E122-7973-4DFC-816D-5AB8F9F20D62}.Debug|x86.Build.0 = Debug|Any CPU
+ {5197E122-7973-4DFC-816D-5AB8F9F20D62}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5197E122-7973-4DFC-816D-5AB8F9F20D62}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5197E122-7973-4DFC-816D-5AB8F9F20D62}.Release|x64.ActiveCfg = Release|Any CPU
+ {5197E122-7973-4DFC-816D-5AB8F9F20D62}.Release|x64.Build.0 = Release|Any CPU
+ {5197E122-7973-4DFC-816D-5AB8F9F20D62}.Release|x86.ActiveCfg = Release|Any CPU
+ {5197E122-7973-4DFC-816D-5AB8F9F20D62}.Release|x86.Build.0 = Release|Any CPU
+ {066DBED8-9500-4EDD-AE69-9735F1A34DB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {066DBED8-9500-4EDD-AE69-9735F1A34DB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {066DBED8-9500-4EDD-AE69-9735F1A34DB5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {066DBED8-9500-4EDD-AE69-9735F1A34DB5}.Debug|x64.Build.0 = Debug|Any CPU
+ {066DBED8-9500-4EDD-AE69-9735F1A34DB5}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {066DBED8-9500-4EDD-AE69-9735F1A34DB5}.Debug|x86.Build.0 = Debug|Any CPU
+ {066DBED8-9500-4EDD-AE69-9735F1A34DB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {066DBED8-9500-4EDD-AE69-9735F1A34DB5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {066DBED8-9500-4EDD-AE69-9735F1A34DB5}.Release|x64.ActiveCfg = Release|Any CPU
+ {066DBED8-9500-4EDD-AE69-9735F1A34DB5}.Release|x64.Build.0 = Release|Any CPU
+ {066DBED8-9500-4EDD-AE69-9735F1A34DB5}.Release|x86.ActiveCfg = Release|Any CPU
+ {066DBED8-9500-4EDD-AE69-9735F1A34DB5}.Release|x86.Build.0 = Release|Any CPU
+ {0023B98F-FAA0-40DE-8047-37A57B72287B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0023B98F-FAA0-40DE-8047-37A57B72287B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0023B98F-FAA0-40DE-8047-37A57B72287B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0023B98F-FAA0-40DE-8047-37A57B72287B}.Debug|x64.Build.0 = Debug|Any CPU
+ {0023B98F-FAA0-40DE-8047-37A57B72287B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0023B98F-FAA0-40DE-8047-37A57B72287B}.Debug|x86.Build.0 = Debug|Any CPU
+ {0023B98F-FAA0-40DE-8047-37A57B72287B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0023B98F-FAA0-40DE-8047-37A57B72287B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0023B98F-FAA0-40DE-8047-37A57B72287B}.Release|x64.ActiveCfg = Release|Any CPU
+ {0023B98F-FAA0-40DE-8047-37A57B72287B}.Release|x64.Build.0 = Release|Any CPU
+ {0023B98F-FAA0-40DE-8047-37A57B72287B}.Release|x86.ActiveCfg = Release|Any CPU
+ {0023B98F-FAA0-40DE-8047-37A57B72287B}.Release|x86.Build.0 = Release|Any CPU
+ {9F334D4D-D520-4E36-A3A0-FCF17D5027FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9F334D4D-D520-4E36-A3A0-FCF17D5027FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9F334D4D-D520-4E36-A3A0-FCF17D5027FE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9F334D4D-D520-4E36-A3A0-FCF17D5027FE}.Debug|x64.Build.0 = Debug|Any CPU
+ {9F334D4D-D520-4E36-A3A0-FCF17D5027FE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9F334D4D-D520-4E36-A3A0-FCF17D5027FE}.Debug|x86.Build.0 = Debug|Any CPU
+ {9F334D4D-D520-4E36-A3A0-FCF17D5027FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9F334D4D-D520-4E36-A3A0-FCF17D5027FE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9F334D4D-D520-4E36-A3A0-FCF17D5027FE}.Release|x64.ActiveCfg = Release|Any CPU
+ {9F334D4D-D520-4E36-A3A0-FCF17D5027FE}.Release|x64.Build.0 = Release|Any CPU
+ {9F334D4D-D520-4E36-A3A0-FCF17D5027FE}.Release|x86.ActiveCfg = Release|Any CPU
+ {9F334D4D-D520-4E36-A3A0-FCF17D5027FE}.Release|x86.Build.0 = Release|Any CPU
+ {618D8E73-942A-4C6A-B6DF-C3E24377D1A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {618D8E73-942A-4C6A-B6DF-C3E24377D1A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {618D8E73-942A-4C6A-B6DF-C3E24377D1A6}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {618D8E73-942A-4C6A-B6DF-C3E24377D1A6}.Debug|x64.Build.0 = Debug|Any CPU
+ {618D8E73-942A-4C6A-B6DF-C3E24377D1A6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {618D8E73-942A-4C6A-B6DF-C3E24377D1A6}.Debug|x86.Build.0 = Debug|Any CPU
+ {618D8E73-942A-4C6A-B6DF-C3E24377D1A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {618D8E73-942A-4C6A-B6DF-C3E24377D1A6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {618D8E73-942A-4C6A-B6DF-C3E24377D1A6}.Release|x64.ActiveCfg = Release|Any CPU
+ {618D8E73-942A-4C6A-B6DF-C3E24377D1A6}.Release|x64.Build.0 = Release|Any CPU
+ {618D8E73-942A-4C6A-B6DF-C3E24377D1A6}.Release|x86.ActiveCfg = Release|Any CPU
+ {618D8E73-942A-4C6A-B6DF-C3E24377D1A6}.Release|x86.Build.0 = Release|Any CPU
+ {0695060C-08C2-4AD7-9906-3D7F17AF85F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0695060C-08C2-4AD7-9906-3D7F17AF85F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0695060C-08C2-4AD7-9906-3D7F17AF85F8}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0695060C-08C2-4AD7-9906-3D7F17AF85F8}.Debug|x64.Build.0 = Debug|Any CPU
+ {0695060C-08C2-4AD7-9906-3D7F17AF85F8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0695060C-08C2-4AD7-9906-3D7F17AF85F8}.Debug|x86.Build.0 = Debug|Any CPU
+ {0695060C-08C2-4AD7-9906-3D7F17AF85F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0695060C-08C2-4AD7-9906-3D7F17AF85F8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0695060C-08C2-4AD7-9906-3D7F17AF85F8}.Release|x64.ActiveCfg = Release|Any CPU
+ {0695060C-08C2-4AD7-9906-3D7F17AF85F8}.Release|x64.Build.0 = Release|Any CPU
+ {0695060C-08C2-4AD7-9906-3D7F17AF85F8}.Release|x86.ActiveCfg = Release|Any CPU
+ {0695060C-08C2-4AD7-9906-3D7F17AF85F8}.Release|x86.Build.0 = Release|Any CPU
+ {3188FDDE-FBF2-43FD-A841-40766012C8A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3188FDDE-FBF2-43FD-A841-40766012C8A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3188FDDE-FBF2-43FD-A841-40766012C8A2}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3188FDDE-FBF2-43FD-A841-40766012C8A2}.Debug|x64.Build.0 = Debug|Any CPU
+ {3188FDDE-FBF2-43FD-A841-40766012C8A2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3188FDDE-FBF2-43FD-A841-40766012C8A2}.Debug|x86.Build.0 = Debug|Any CPU
+ {3188FDDE-FBF2-43FD-A841-40766012C8A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3188FDDE-FBF2-43FD-A841-40766012C8A2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3188FDDE-FBF2-43FD-A841-40766012C8A2}.Release|x64.ActiveCfg = Release|Any CPU
+ {3188FDDE-FBF2-43FD-A841-40766012C8A2}.Release|x64.Build.0 = Release|Any CPU
+ {3188FDDE-FBF2-43FD-A841-40766012C8A2}.Release|x86.ActiveCfg = Release|Any CPU
+ {3188FDDE-FBF2-43FD-A841-40766012C8A2}.Release|x86.Build.0 = Release|Any CPU
+ {8534D3A8-7CBE-42A0-BAAB-68F9D0BFFA0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8534D3A8-7CBE-42A0-BAAB-68F9D0BFFA0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8534D3A8-7CBE-42A0-BAAB-68F9D0BFFA0C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8534D3A8-7CBE-42A0-BAAB-68F9D0BFFA0C}.Debug|x64.Build.0 = Debug|Any CPU
+ {8534D3A8-7CBE-42A0-BAAB-68F9D0BFFA0C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8534D3A8-7CBE-42A0-BAAB-68F9D0BFFA0C}.Debug|x86.Build.0 = Debug|Any CPU
+ {8534D3A8-7CBE-42A0-BAAB-68F9D0BFFA0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8534D3A8-7CBE-42A0-BAAB-68F9D0BFFA0C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8534D3A8-7CBE-42A0-BAAB-68F9D0BFFA0C}.Release|x64.ActiveCfg = Release|Any CPU
+ {8534D3A8-7CBE-42A0-BAAB-68F9D0BFFA0C}.Release|x64.Build.0 = Release|Any CPU
+ {8534D3A8-7CBE-42A0-BAAB-68F9D0BFFA0C}.Release|x86.ActiveCfg = Release|Any CPU
+ {8534D3A8-7CBE-42A0-BAAB-68F9D0BFFA0C}.Release|x86.Build.0 = Release|Any CPU
+ {BFFF4AD6-8508-465F-B087-4CEE919D5909}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BFFF4AD6-8508-465F-B087-4CEE919D5909}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BFFF4AD6-8508-465F-B087-4CEE919D5909}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {BFFF4AD6-8508-465F-B087-4CEE919D5909}.Debug|x64.Build.0 = Debug|Any CPU
+ {BFFF4AD6-8508-465F-B087-4CEE919D5909}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {BFFF4AD6-8508-465F-B087-4CEE919D5909}.Debug|x86.Build.0 = Debug|Any CPU
+ {BFFF4AD6-8508-465F-B087-4CEE919D5909}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BFFF4AD6-8508-465F-B087-4CEE919D5909}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BFFF4AD6-8508-465F-B087-4CEE919D5909}.Release|x64.ActiveCfg = Release|Any CPU
+ {BFFF4AD6-8508-465F-B087-4CEE919D5909}.Release|x64.Build.0 = Release|Any CPU
+ {BFFF4AD6-8508-465F-B087-4CEE919D5909}.Release|x86.ActiveCfg = Release|Any CPU
+ {BFFF4AD6-8508-465F-B087-4CEE919D5909}.Release|x86.Build.0 = Release|Any CPU
+ {927D22CA-7788-4035-A400-8B3F2CAEA650}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {927D22CA-7788-4035-A400-8B3F2CAEA650}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {927D22CA-7788-4035-A400-8B3F2CAEA650}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {927D22CA-7788-4035-A400-8B3F2CAEA650}.Debug|x64.Build.0 = Debug|Any CPU
+ {927D22CA-7788-4035-A400-8B3F2CAEA650}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {927D22CA-7788-4035-A400-8B3F2CAEA650}.Debug|x86.Build.0 = Debug|Any CPU
+ {927D22CA-7788-4035-A400-8B3F2CAEA650}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {927D22CA-7788-4035-A400-8B3F2CAEA650}.Release|Any CPU.Build.0 = Release|Any CPU
+ {927D22CA-7788-4035-A400-8B3F2CAEA650}.Release|x64.ActiveCfg = Release|Any CPU
+ {927D22CA-7788-4035-A400-8B3F2CAEA650}.Release|x64.Build.0 = Release|Any CPU
+ {927D22CA-7788-4035-A400-8B3F2CAEA650}.Release|x86.ActiveCfg = Release|Any CPU
+ {927D22CA-7788-4035-A400-8B3F2CAEA650}.Release|x86.Build.0 = Release|Any CPU
+ {D40EBE29-0DF7-494A-9B64-84CA9FC4D87B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D40EBE29-0DF7-494A-9B64-84CA9FC4D87B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D40EBE29-0DF7-494A-9B64-84CA9FC4D87B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D40EBE29-0DF7-494A-9B64-84CA9FC4D87B}.Debug|x64.Build.0 = Debug|Any CPU
+ {D40EBE29-0DF7-494A-9B64-84CA9FC4D87B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D40EBE29-0DF7-494A-9B64-84CA9FC4D87B}.Debug|x86.Build.0 = Debug|Any CPU
+ {D40EBE29-0DF7-494A-9B64-84CA9FC4D87B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D40EBE29-0DF7-494A-9B64-84CA9FC4D87B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D40EBE29-0DF7-494A-9B64-84CA9FC4D87B}.Release|x64.ActiveCfg = Release|Any CPU
+ {D40EBE29-0DF7-494A-9B64-84CA9FC4D87B}.Release|x64.Build.0 = Release|Any CPU
+ {D40EBE29-0DF7-494A-9B64-84CA9FC4D87B}.Release|x86.ActiveCfg = Release|Any CPU
+ {D40EBE29-0DF7-494A-9B64-84CA9FC4D87B}.Release|x86.Build.0 = Release|Any CPU
+ {17F76803-025A-41A1-B91A-9E2B70CCF70B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {17F76803-025A-41A1-B91A-9E2B70CCF70B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {17F76803-025A-41A1-B91A-9E2B70CCF70B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {17F76803-025A-41A1-B91A-9E2B70CCF70B}.Debug|x64.Build.0 = Debug|Any CPU
+ {17F76803-025A-41A1-B91A-9E2B70CCF70B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {17F76803-025A-41A1-B91A-9E2B70CCF70B}.Debug|x86.Build.0 = Debug|Any CPU
+ {17F76803-025A-41A1-B91A-9E2B70CCF70B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {17F76803-025A-41A1-B91A-9E2B70CCF70B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {17F76803-025A-41A1-B91A-9E2B70CCF70B}.Release|x64.ActiveCfg = Release|Any CPU
+ {17F76803-025A-41A1-B91A-9E2B70CCF70B}.Release|x64.Build.0 = Release|Any CPU
+ {17F76803-025A-41A1-B91A-9E2B70CCF70B}.Release|x86.ActiveCfg = Release|Any CPU
+ {17F76803-025A-41A1-B91A-9E2B70CCF70B}.Release|x86.Build.0 = Release|Any CPU
+ {0F702691-51BD-4F55-9818-AA5E7616B507}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0F702691-51BD-4F55-9818-AA5E7616B507}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0F702691-51BD-4F55-9818-AA5E7616B507}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0F702691-51BD-4F55-9818-AA5E7616B507}.Debug|x64.Build.0 = Debug|Any CPU
+ {0F702691-51BD-4F55-9818-AA5E7616B507}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0F702691-51BD-4F55-9818-AA5E7616B507}.Debug|x86.Build.0 = Debug|Any CPU
+ {0F702691-51BD-4F55-9818-AA5E7616B507}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0F702691-51BD-4F55-9818-AA5E7616B507}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0F702691-51BD-4F55-9818-AA5E7616B507}.Release|x64.ActiveCfg = Release|Any CPU
+ {0F702691-51BD-4F55-9818-AA5E7616B507}.Release|x64.Build.0 = Release|Any CPU
+ {0F702691-51BD-4F55-9818-AA5E7616B507}.Release|x86.ActiveCfg = Release|Any CPU
+ {0F702691-51BD-4F55-9818-AA5E7616B507}.Release|x86.Build.0 = Release|Any CPU
+ {A12941F8-AA71-477F-AC5F-37FD66253807}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A12941F8-AA71-477F-AC5F-37FD66253807}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A12941F8-AA71-477F-AC5F-37FD66253807}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A12941F8-AA71-477F-AC5F-37FD66253807}.Debug|x64.Build.0 = Debug|Any CPU
+ {A12941F8-AA71-477F-AC5F-37FD66253807}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A12941F8-AA71-477F-AC5F-37FD66253807}.Debug|x86.Build.0 = Debug|Any CPU
+ {A12941F8-AA71-477F-AC5F-37FD66253807}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A12941F8-AA71-477F-AC5F-37FD66253807}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A12941F8-AA71-477F-AC5F-37FD66253807}.Release|x64.ActiveCfg = Release|Any CPU
+ {A12941F8-AA71-477F-AC5F-37FD66253807}.Release|x64.Build.0 = Release|Any CPU
+ {A12941F8-AA71-477F-AC5F-37FD66253807}.Release|x86.ActiveCfg = Release|Any CPU
+ {A12941F8-AA71-477F-AC5F-37FD66253807}.Release|x86.Build.0 = Release|Any CPU
+ {FEB14BF3-536E-467C-930E-3F253B90A145}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FEB14BF3-536E-467C-930E-3F253B90A145}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FEB14BF3-536E-467C-930E-3F253B90A145}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {FEB14BF3-536E-467C-930E-3F253B90A145}.Debug|x64.Build.0 = Debug|Any CPU
+ {FEB14BF3-536E-467C-930E-3F253B90A145}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {FEB14BF3-536E-467C-930E-3F253B90A145}.Debug|x86.Build.0 = Debug|Any CPU
+ {FEB14BF3-536E-467C-930E-3F253B90A145}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FEB14BF3-536E-467C-930E-3F253B90A145}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FEB14BF3-536E-467C-930E-3F253B90A145}.Release|x64.ActiveCfg = Release|Any CPU
+ {FEB14BF3-536E-467C-930E-3F253B90A145}.Release|x64.Build.0 = Release|Any CPU
+ {FEB14BF3-536E-467C-930E-3F253B90A145}.Release|x86.ActiveCfg = Release|Any CPU
+ {FEB14BF3-536E-467C-930E-3F253B90A145}.Release|x86.Build.0 = Release|Any CPU
+ {9E6C8A4F-F911-4F9E-8E2F-2583C792A800}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9E6C8A4F-F911-4F9E-8E2F-2583C792A800}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9E6C8A4F-F911-4F9E-8E2F-2583C792A800}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9E6C8A4F-F911-4F9E-8E2F-2583C792A800}.Debug|x64.Build.0 = Debug|Any CPU
+ {9E6C8A4F-F911-4F9E-8E2F-2583C792A800}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9E6C8A4F-F911-4F9E-8E2F-2583C792A800}.Debug|x86.Build.0 = Debug|Any CPU
+ {9E6C8A4F-F911-4F9E-8E2F-2583C792A800}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9E6C8A4F-F911-4F9E-8E2F-2583C792A800}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9E6C8A4F-F911-4F9E-8E2F-2583C792A800}.Release|x64.ActiveCfg = Release|Any CPU
+ {9E6C8A4F-F911-4F9E-8E2F-2583C792A800}.Release|x64.Build.0 = Release|Any CPU
+ {9E6C8A4F-F911-4F9E-8E2F-2583C792A800}.Release|x86.ActiveCfg = Release|Any CPU
+ {9E6C8A4F-F911-4F9E-8E2F-2583C792A800}.Release|x86.Build.0 = Release|Any CPU
+ {445C10D4-F39E-4048-BA13-28FBFB9DCD63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {445C10D4-F39E-4048-BA13-28FBFB9DCD63}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {445C10D4-F39E-4048-BA13-28FBFB9DCD63}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {445C10D4-F39E-4048-BA13-28FBFB9DCD63}.Debug|x64.Build.0 = Debug|Any CPU
+ {445C10D4-F39E-4048-BA13-28FBFB9DCD63}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {445C10D4-F39E-4048-BA13-28FBFB9DCD63}.Debug|x86.Build.0 = Debug|Any CPU
+ {445C10D4-F39E-4048-BA13-28FBFB9DCD63}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {445C10D4-F39E-4048-BA13-28FBFB9DCD63}.Release|Any CPU.Build.0 = Release|Any CPU
+ {445C10D4-F39E-4048-BA13-28FBFB9DCD63}.Release|x64.ActiveCfg = Release|Any CPU
+ {445C10D4-F39E-4048-BA13-28FBFB9DCD63}.Release|x64.Build.0 = Release|Any CPU
+ {445C10D4-F39E-4048-BA13-28FBFB9DCD63}.Release|x86.ActiveCfg = Release|Any CPU
+ {445C10D4-F39E-4048-BA13-28FBFB9DCD63}.Release|x86.Build.0 = Release|Any CPU
+ {7E02B4A7-C232-4B63-B56D-38683D659FDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7E02B4A7-C232-4B63-B56D-38683D659FDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7E02B4A7-C232-4B63-B56D-38683D659FDE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7E02B4A7-C232-4B63-B56D-38683D659FDE}.Debug|x64.Build.0 = Debug|Any CPU
+ {7E02B4A7-C232-4B63-B56D-38683D659FDE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7E02B4A7-C232-4B63-B56D-38683D659FDE}.Debug|x86.Build.0 = Debug|Any CPU
+ {7E02B4A7-C232-4B63-B56D-38683D659FDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7E02B4A7-C232-4B63-B56D-38683D659FDE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7E02B4A7-C232-4B63-B56D-38683D659FDE}.Release|x64.ActiveCfg = Release|Any CPU
+ {7E02B4A7-C232-4B63-B56D-38683D659FDE}.Release|x64.Build.0 = Release|Any CPU
+ {7E02B4A7-C232-4B63-B56D-38683D659FDE}.Release|x86.ActiveCfg = Release|Any CPU
+ {7E02B4A7-C232-4B63-B56D-38683D659FDE}.Release|x86.Build.0 = Release|Any CPU
+ {3B2C28CD-FB49-44D9-9FC4-76A71F815FC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3B2C28CD-FB49-44D9-9FC4-76A71F815FC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3B2C28CD-FB49-44D9-9FC4-76A71F815FC4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3B2C28CD-FB49-44D9-9FC4-76A71F815FC4}.Debug|x64.Build.0 = Debug|Any CPU
+ {3B2C28CD-FB49-44D9-9FC4-76A71F815FC4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3B2C28CD-FB49-44D9-9FC4-76A71F815FC4}.Debug|x86.Build.0 = Debug|Any CPU
+ {3B2C28CD-FB49-44D9-9FC4-76A71F815FC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3B2C28CD-FB49-44D9-9FC4-76A71F815FC4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3B2C28CD-FB49-44D9-9FC4-76A71F815FC4}.Release|x64.ActiveCfg = Release|Any CPU
+ {3B2C28CD-FB49-44D9-9FC4-76A71F815FC4}.Release|x64.Build.0 = Release|Any CPU
+ {3B2C28CD-FB49-44D9-9FC4-76A71F815FC4}.Release|x86.ActiveCfg = Release|Any CPU
+ {3B2C28CD-FB49-44D9-9FC4-76A71F815FC4}.Release|x86.Build.0 = Release|Any CPU
+ {F787C38F-16C2-4EDE-9D28-E0EDE13215B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F787C38F-16C2-4EDE-9D28-E0EDE13215B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F787C38F-16C2-4EDE-9D28-E0EDE13215B3}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F787C38F-16C2-4EDE-9D28-E0EDE13215B3}.Debug|x64.Build.0 = Debug|Any CPU
+ {F787C38F-16C2-4EDE-9D28-E0EDE13215B3}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F787C38F-16C2-4EDE-9D28-E0EDE13215B3}.Debug|x86.Build.0 = Debug|Any CPU
+ {F787C38F-16C2-4EDE-9D28-E0EDE13215B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F787C38F-16C2-4EDE-9D28-E0EDE13215B3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F787C38F-16C2-4EDE-9D28-E0EDE13215B3}.Release|x64.ActiveCfg = Release|Any CPU
+ {F787C38F-16C2-4EDE-9D28-E0EDE13215B3}.Release|x64.Build.0 = Release|Any CPU
+ {F787C38F-16C2-4EDE-9D28-E0EDE13215B3}.Release|x86.ActiveCfg = Release|Any CPU
+ {F787C38F-16C2-4EDE-9D28-E0EDE13215B3}.Release|x86.Build.0 = Release|Any CPU
+ {1EDCE8B0-14B1-4C65-9619-92A3D88FC587}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1EDCE8B0-14B1-4C65-9619-92A3D88FC587}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1EDCE8B0-14B1-4C65-9619-92A3D88FC587}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1EDCE8B0-14B1-4C65-9619-92A3D88FC587}.Debug|x64.Build.0 = Debug|Any CPU
+ {1EDCE8B0-14B1-4C65-9619-92A3D88FC587}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1EDCE8B0-14B1-4C65-9619-92A3D88FC587}.Debug|x86.Build.0 = Debug|Any CPU
+ {1EDCE8B0-14B1-4C65-9619-92A3D88FC587}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1EDCE8B0-14B1-4C65-9619-92A3D88FC587}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1EDCE8B0-14B1-4C65-9619-92A3D88FC587}.Release|x64.ActiveCfg = Release|Any CPU
+ {1EDCE8B0-14B1-4C65-9619-92A3D88FC587}.Release|x64.Build.0 = Release|Any CPU
+ {1EDCE8B0-14B1-4C65-9619-92A3D88FC587}.Release|x86.ActiveCfg = Release|Any CPU
+ {1EDCE8B0-14B1-4C65-9619-92A3D88FC587}.Release|x86.Build.0 = Release|Any CPU
+ {E2EB8A87-973D-444F-81E4-2BA34B3CD32D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E2EB8A87-973D-444F-81E4-2BA34B3CD32D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E2EB8A87-973D-444F-81E4-2BA34B3CD32D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E2EB8A87-973D-444F-81E4-2BA34B3CD32D}.Debug|x64.Build.0 = Debug|Any CPU
+ {E2EB8A87-973D-444F-81E4-2BA34B3CD32D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E2EB8A87-973D-444F-81E4-2BA34B3CD32D}.Debug|x86.Build.0 = Debug|Any CPU
+ {E2EB8A87-973D-444F-81E4-2BA34B3CD32D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E2EB8A87-973D-444F-81E4-2BA34B3CD32D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E2EB8A87-973D-444F-81E4-2BA34B3CD32D}.Release|x64.ActiveCfg = Release|Any CPU
+ {E2EB8A87-973D-444F-81E4-2BA34B3CD32D}.Release|x64.Build.0 = Release|Any CPU
+ {E2EB8A87-973D-444F-81E4-2BA34B3CD32D}.Release|x86.ActiveCfg = Release|Any CPU
+ {E2EB8A87-973D-444F-81E4-2BA34B3CD32D}.Release|x86.Build.0 = Release|Any CPU
+ {0901A59F-AE0F-4B55-98B0-05FF23387FF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0901A59F-AE0F-4B55-98B0-05FF23387FF7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0901A59F-AE0F-4B55-98B0-05FF23387FF7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0901A59F-AE0F-4B55-98B0-05FF23387FF7}.Debug|x64.Build.0 = Debug|Any CPU
+ {0901A59F-AE0F-4B55-98B0-05FF23387FF7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0901A59F-AE0F-4B55-98B0-05FF23387FF7}.Debug|x86.Build.0 = Debug|Any CPU
+ {0901A59F-AE0F-4B55-98B0-05FF23387FF7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0901A59F-AE0F-4B55-98B0-05FF23387FF7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0901A59F-AE0F-4B55-98B0-05FF23387FF7}.Release|x64.ActiveCfg = Release|Any CPU
+ {0901A59F-AE0F-4B55-98B0-05FF23387FF7}.Release|x64.Build.0 = Release|Any CPU
+ {0901A59F-AE0F-4B55-98B0-05FF23387FF7}.Release|x86.ActiveCfg = Release|Any CPU
+ {0901A59F-AE0F-4B55-98B0-05FF23387FF7}.Release|x86.Build.0 = Release|Any CPU
+ {0AE788C5-4205-4A07-A1AA-6A0A32AF72F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0AE788C5-4205-4A07-A1AA-6A0A32AF72F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0AE788C5-4205-4A07-A1AA-6A0A32AF72F3}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0AE788C5-4205-4A07-A1AA-6A0A32AF72F3}.Debug|x64.Build.0 = Debug|Any CPU
+ {0AE788C5-4205-4A07-A1AA-6A0A32AF72F3}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0AE788C5-4205-4A07-A1AA-6A0A32AF72F3}.Debug|x86.Build.0 = Debug|Any CPU
+ {0AE788C5-4205-4A07-A1AA-6A0A32AF72F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0AE788C5-4205-4A07-A1AA-6A0A32AF72F3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0AE788C5-4205-4A07-A1AA-6A0A32AF72F3}.Release|x64.ActiveCfg = Release|Any CPU
+ {0AE788C5-4205-4A07-A1AA-6A0A32AF72F3}.Release|x64.Build.0 = Release|Any CPU
+ {0AE788C5-4205-4A07-A1AA-6A0A32AF72F3}.Release|x86.ActiveCfg = Release|Any CPU
+ {0AE788C5-4205-4A07-A1AA-6A0A32AF72F3}.Release|x86.Build.0 = Release|Any CPU
+ {F808372B-E577-4632-A733-A6BBEFAE3FE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F808372B-E577-4632-A733-A6BBEFAE3FE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F808372B-E577-4632-A733-A6BBEFAE3FE7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F808372B-E577-4632-A733-A6BBEFAE3FE7}.Debug|x64.Build.0 = Debug|Any CPU
+ {F808372B-E577-4632-A733-A6BBEFAE3FE7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F808372B-E577-4632-A733-A6BBEFAE3FE7}.Debug|x86.Build.0 = Debug|Any CPU
+ {F808372B-E577-4632-A733-A6BBEFAE3FE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F808372B-E577-4632-A733-A6BBEFAE3FE7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F808372B-E577-4632-A733-A6BBEFAE3FE7}.Release|x64.ActiveCfg = Release|Any CPU
+ {F808372B-E577-4632-A733-A6BBEFAE3FE7}.Release|x64.Build.0 = Release|Any CPU
+ {F808372B-E577-4632-A733-A6BBEFAE3FE7}.Release|x86.ActiveCfg = Release|Any CPU
+ {F808372B-E577-4632-A733-A6BBEFAE3FE7}.Release|x86.Build.0 = Release|Any CPU
+ {1A6DE1F4-AB77-4AA6-8EE7-080F918F1F60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1A6DE1F4-AB77-4AA6-8EE7-080F918F1F60}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1A6DE1F4-AB77-4AA6-8EE7-080F918F1F60}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1A6DE1F4-AB77-4AA6-8EE7-080F918F1F60}.Debug|x64.Build.0 = Debug|Any CPU
+ {1A6DE1F4-AB77-4AA6-8EE7-080F918F1F60}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1A6DE1F4-AB77-4AA6-8EE7-080F918F1F60}.Debug|x86.Build.0 = Debug|Any CPU
+ {1A6DE1F4-AB77-4AA6-8EE7-080F918F1F60}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1A6DE1F4-AB77-4AA6-8EE7-080F918F1F60}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1A6DE1F4-AB77-4AA6-8EE7-080F918F1F60}.Release|x64.ActiveCfg = Release|Any CPU
+ {1A6DE1F4-AB77-4AA6-8EE7-080F918F1F60}.Release|x64.Build.0 = Release|Any CPU
+ {1A6DE1F4-AB77-4AA6-8EE7-080F918F1F60}.Release|x86.ActiveCfg = Release|Any CPU
+ {1A6DE1F4-AB77-4AA6-8EE7-080F918F1F60}.Release|x86.Build.0 = Release|Any CPU
+ {1124B5DC-D042-4396-88E2-C1DB42ED198C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1124B5DC-D042-4396-88E2-C1DB42ED198C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1124B5DC-D042-4396-88E2-C1DB42ED198C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1124B5DC-D042-4396-88E2-C1DB42ED198C}.Debug|x64.Build.0 = Debug|Any CPU
+ {1124B5DC-D042-4396-88E2-C1DB42ED198C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1124B5DC-D042-4396-88E2-C1DB42ED198C}.Debug|x86.Build.0 = Debug|Any CPU
+ {1124B5DC-D042-4396-88E2-C1DB42ED198C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1124B5DC-D042-4396-88E2-C1DB42ED198C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1124B5DC-D042-4396-88E2-C1DB42ED198C}.Release|x64.ActiveCfg = Release|Any CPU
+ {1124B5DC-D042-4396-88E2-C1DB42ED198C}.Release|x64.Build.0 = Release|Any CPU
+ {1124B5DC-D042-4396-88E2-C1DB42ED198C}.Release|x86.ActiveCfg = Release|Any CPU
+ {1124B5DC-D042-4396-88E2-C1DB42ED198C}.Release|x86.Build.0 = Release|Any CPU
+ {069467F4-AD60-4098-82CE-C09304303F4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {069467F4-AD60-4098-82CE-C09304303F4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {069467F4-AD60-4098-82CE-C09304303F4E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {069467F4-AD60-4098-82CE-C09304303F4E}.Debug|x64.Build.0 = Debug|Any CPU
+ {069467F4-AD60-4098-82CE-C09304303F4E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {069467F4-AD60-4098-82CE-C09304303F4E}.Debug|x86.Build.0 = Debug|Any CPU
+ {069467F4-AD60-4098-82CE-C09304303F4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {069467F4-AD60-4098-82CE-C09304303F4E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {069467F4-AD60-4098-82CE-C09304303F4E}.Release|x64.ActiveCfg = Release|Any CPU
+ {069467F4-AD60-4098-82CE-C09304303F4E}.Release|x64.Build.0 = Release|Any CPU
+ {069467F4-AD60-4098-82CE-C09304303F4E}.Release|x86.ActiveCfg = Release|Any CPU
+ {069467F4-AD60-4098-82CE-C09304303F4E}.Release|x86.Build.0 = Release|Any CPU
+ {90BD1FE2-AD58-41F3-A351-612E167708FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {90BD1FE2-AD58-41F3-A351-612E167708FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {90BD1FE2-AD58-41F3-A351-612E167708FF}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {90BD1FE2-AD58-41F3-A351-612E167708FF}.Debug|x64.Build.0 = Debug|Any CPU
+ {90BD1FE2-AD58-41F3-A351-612E167708FF}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {90BD1FE2-AD58-41F3-A351-612E167708FF}.Debug|x86.Build.0 = Debug|Any CPU
+ {90BD1FE2-AD58-41F3-A351-612E167708FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {90BD1FE2-AD58-41F3-A351-612E167708FF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {90BD1FE2-AD58-41F3-A351-612E167708FF}.Release|x64.ActiveCfg = Release|Any CPU
+ {90BD1FE2-AD58-41F3-A351-612E167708FF}.Release|x64.Build.0 = Release|Any CPU
+ {90BD1FE2-AD58-41F3-A351-612E167708FF}.Release|x86.ActiveCfg = Release|Any CPU
+ {90BD1FE2-AD58-41F3-A351-612E167708FF}.Release|x86.Build.0 = Release|Any CPU
+ {EE529C09-917B-4FC0-AF19-0092B954F300}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EE529C09-917B-4FC0-AF19-0092B954F300}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EE529C09-917B-4FC0-AF19-0092B954F300}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EE529C09-917B-4FC0-AF19-0092B954F300}.Debug|x64.Build.0 = Debug|Any CPU
+ {EE529C09-917B-4FC0-AF19-0092B954F300}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EE529C09-917B-4FC0-AF19-0092B954F300}.Debug|x86.Build.0 = Debug|Any CPU
+ {EE529C09-917B-4FC0-AF19-0092B954F300}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EE529C09-917B-4FC0-AF19-0092B954F300}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EE529C09-917B-4FC0-AF19-0092B954F300}.Release|x64.ActiveCfg = Release|Any CPU
+ {EE529C09-917B-4FC0-AF19-0092B954F300}.Release|x64.Build.0 = Release|Any CPU
+ {EE529C09-917B-4FC0-AF19-0092B954F300}.Release|x86.ActiveCfg = Release|Any CPU
+ {EE529C09-917B-4FC0-AF19-0092B954F300}.Release|x86.Build.0 = Release|Any CPU
+ {1B039528-384B-414A-B2C0-61738DC8CAA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1B039528-384B-414A-B2C0-61738DC8CAA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1B039528-384B-414A-B2C0-61738DC8CAA8}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1B039528-384B-414A-B2C0-61738DC8CAA8}.Debug|x64.Build.0 = Debug|Any CPU
+ {1B039528-384B-414A-B2C0-61738DC8CAA8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1B039528-384B-414A-B2C0-61738DC8CAA8}.Debug|x86.Build.0 = Debug|Any CPU
+ {1B039528-384B-414A-B2C0-61738DC8CAA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1B039528-384B-414A-B2C0-61738DC8CAA8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1B039528-384B-414A-B2C0-61738DC8CAA8}.Release|x64.ActiveCfg = Release|Any CPU
+ {1B039528-384B-414A-B2C0-61738DC8CAA8}.Release|x64.Build.0 = Release|Any CPU
+ {1B039528-384B-414A-B2C0-61738DC8CAA8}.Release|x86.ActiveCfg = Release|Any CPU
+ {1B039528-384B-414A-B2C0-61738DC8CAA8}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {818132FC-82E4-4E0C-8D9D-DB9FAA82EC17} = {ABB2D577-535D-D48F-8A83-D0D3A05DDA5C}
+ {762A4F83-200A-4784-939C-30DDCCD07542} = {ABB2D577-535D-D48F-8A83-D0D3A05DDA5C}
+ {F7C739C1-1C1A-45FC-A5CF-19C85F88C492} = {ABB2D577-535D-D48F-8A83-D0D3A05DDA5C}
+ {2A46F144-074D-406E-8891-218DF31A92FA} = {ABB2D577-535D-D48F-8A83-D0D3A05DDA5C}
+ {5C5CC11C-846A-469B-A1C7-1CE3A9E53918} = {ABB2D577-535D-D48F-8A83-D0D3A05DDA5C}
+ {49DD4823-A00F-48CD-A374-088B6C8E8D7E} = {EC6922CA-170E-0A2A-CC7E-F73B34E70907}
+ {431FAA2C-9E7B-452B-B39F-82B08746E065} = {EC6922CA-170E-0A2A-CC7E-F73B34E70907}
+ {3C452E09-F2E5-4B2A-BFB0-CCF0D61D5C33} = {EC6922CA-170E-0A2A-CC7E-F73B34E70907}
+ {F0FF9E38-F849-4807-81B7-8723A0D9935E} = {EC6922CA-170E-0A2A-CC7E-F73B34E70907}
+ {10FA361F-FD9B-4BF2-BF17-3A1FCC203B9B} = {EC6922CA-170E-0A2A-CC7E-F73B34E70907}
+ {5197E122-7973-4DFC-816D-5AB8F9F20D62} = {EC6922CA-170E-0A2A-CC7E-F73B34E70907}
+ {066DBED8-9500-4EDD-AE69-9735F1A34DB5} = {37512E4E-BED2-30A1-7091-F8823DB56BB0}
+ {0023B98F-FAA0-40DE-8047-37A57B72287B} = {37512E4E-BED2-30A1-7091-F8823DB56BB0}
+ {9F334D4D-D520-4E36-A3A0-FCF17D5027FE} = {37512E4E-BED2-30A1-7091-F8823DB56BB0}
+ {618D8E73-942A-4C6A-B6DF-C3E24377D1A6} = {37512E4E-BED2-30A1-7091-F8823DB56BB0}
+ {0695060C-08C2-4AD7-9906-3D7F17AF85F8} = {37512E4E-BED2-30A1-7091-F8823DB56BB0}
+ {3188FDDE-FBF2-43FD-A841-40766012C8A2} = {37512E4E-BED2-30A1-7091-F8823DB56BB0}
+ {8534D3A8-7CBE-42A0-BAAB-68F9D0BFFA0C} = {E2866E30-6F85-1E9C-4A7D-9D94AC184FFF}
+ {BFFF4AD6-8508-465F-B087-4CEE919D5909} = {E2866E30-6F85-1E9C-4A7D-9D94AC184FFF}
+ {927D22CA-7788-4035-A400-8B3F2CAEA650} = {E2866E30-6F85-1E9C-4A7D-9D94AC184FFF}
+ {D40EBE29-0DF7-494A-9B64-84CA9FC4D87B} = {E2866E30-6F85-1E9C-4A7D-9D94AC184FFF}
+ {17F76803-025A-41A1-B91A-9E2B70CCF70B} = {E2866E30-6F85-1E9C-4A7D-9D94AC184FFF}
+ {0F702691-51BD-4F55-9818-AA5E7616B507} = {E2866E30-6F85-1E9C-4A7D-9D94AC184FFF}
+ {A12941F8-AA71-477F-AC5F-37FD66253807} = {05210415-73DC-ACC3-ABDB-6AAB38F7838E}
+ {FEB14BF3-536E-467C-930E-3F253B90A145} = {05210415-73DC-ACC3-ABDB-6AAB38F7838E}
+ {9E6C8A4F-F911-4F9E-8E2F-2583C792A800} = {05210415-73DC-ACC3-ABDB-6AAB38F7838E}
+ {445C10D4-F39E-4048-BA13-28FBFB9DCD63} = {05210415-73DC-ACC3-ABDB-6AAB38F7838E}
+ {7E02B4A7-C232-4B63-B56D-38683D659FDE} = {05210415-73DC-ACC3-ABDB-6AAB38F7838E}
+ {3B2C28CD-FB49-44D9-9FC4-76A71F815FC4} = {05210415-73DC-ACC3-ABDB-6AAB38F7838E}
+ {F787C38F-16C2-4EDE-9D28-E0EDE13215B3} = {06DC3261-A065-1F52-480A-5354B826AC99}
+ {1EDCE8B0-14B1-4C65-9619-92A3D88FC587} = {06DC3261-A065-1F52-480A-5354B826AC99}
+ {E2EB8A87-973D-444F-81E4-2BA34B3CD32D} = {06DC3261-A065-1F52-480A-5354B826AC99}
+ {0901A59F-AE0F-4B55-98B0-05FF23387FF7} = {06DC3261-A065-1F52-480A-5354B826AC99}
+ {0AE788C5-4205-4A07-A1AA-6A0A32AF72F3} = {06DC3261-A065-1F52-480A-5354B826AC99}
+ {F808372B-E577-4632-A733-A6BBEFAE3FE7} = {06DC3261-A065-1F52-480A-5354B826AC99}
+ {1A6DE1F4-AB77-4AA6-8EE7-080F918F1F60} = {442FB6B7-7A89-545F-4529-C1729FE94F9C}
+ {1124B5DC-D042-4396-88E2-C1DB42ED198C} = {442FB6B7-7A89-545F-4529-C1729FE94F9C}
+ {069467F4-AD60-4098-82CE-C09304303F4E} = {442FB6B7-7A89-545F-4529-C1729FE94F9C}
+ {90BD1FE2-AD58-41F3-A351-612E167708FF} = {442FB6B7-7A89-545F-4529-C1729FE94F9C}
+ {EE529C09-917B-4FC0-AF19-0092B954F300} = {442FB6B7-7A89-545F-4529-C1729FE94F9C}
+ {1B039528-384B-414A-B2C0-61738DC8CAA8} = {442FB6B7-7A89-545F-4529-C1729FE94F9C}
+ EndGlobalSection
+EndGlobal
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/AccountOwnerServer.Tests.csproj b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/AccountOwnerServer.Tests.csproj
new file mode 100644
index 000000000..338bac1ce
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/AccountOwnerServer.Tests.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net10.0
+ enable
+ false
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/DataShaperTests.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/DataShaperTests.cs
new file mode 100644
index 000000000..0ac05946f
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/DataShaperTests.cs
@@ -0,0 +1,56 @@
+using Entities.Helpers;
+using Entities.Models;
+using System.Linq;
+using Xunit;
+
+namespace AccountOwnerServer.Tests;
+
+public class DataShaperTests
+{
+ private static readonly DataShaper Shaper = new();
+
+ [Fact]
+ public void GivenNoFields_WhenWeShape_ThenEveryPropertyIsKept()
+ {
+ var shaped = Shaper.ShapeData(SqlServerSample.Owners().First(), null);
+
+ Assert.Equal(4, shaped.Count);
+ Assert.True(shaped.ContainsKey(nameof(Owner.Address)));
+ }
+
+ [Fact]
+ public void GivenASubsetOfFields_WhenWeShape_ThenOnlyThoseComeBack()
+ {
+ var shaped = Shaper.ShapeData(SqlServerSample.Owners().First(), "name,dateOfBirth");
+
+ Assert.Equal(2, shaped.Count);
+ Assert.True(shaped.ContainsKey(nameof(Owner.Name)));
+ Assert.False(shaped.ContainsKey(nameof(Owner.Address)));
+ }
+
+ [Fact]
+ public void GivenAFieldInAnyCasing_WhenWeShape_ThenItStillMatchesThePropertyName()
+ {
+ var shaped = Shaper.ShapeData(SqlServerSample.Owners().First(), "NAME");
+
+ Assert.True(shaped.ContainsKey(nameof(Owner.Name)));
+ }
+
+ [Fact]
+ public void GivenAnUnknownField_WhenWeShape_ThenItIsIgnored()
+ {
+ var shaped = Shaper.ShapeData(SqlServerSample.Owners().First(), "name,age");
+
+ Assert.Single(shaped);
+ Assert.True(shaped.ContainsKey(nameof(Owner.Name)));
+ }
+
+ [Fact]
+ public void GivenACollection_WhenWeShape_ThenEveryEntityIsShaped()
+ {
+ var shaped = Shaper.ShapeData(SqlServerSample.Owners(), "name").ToList();
+
+ Assert.Equal(5, shaped.Count);
+ Assert.All(shaped, s => Assert.Single(s));
+ }
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/OwnerParametersTests.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/OwnerParametersTests.cs
new file mode 100644
index 000000000..4ccdf0adb
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/OwnerParametersTests.cs
@@ -0,0 +1,41 @@
+using Entities.Models;
+using Xunit;
+
+namespace AccountOwnerServer.Tests;
+
+public class OwnerParametersTests
+{
+ [Fact]
+ public void GivenTheSameMinAndMaxYear_WhenTheRangeIsValidated_ThenItIsAccepted()
+ {
+ var parameters = new OwnerParameters { MinYearOfBirth = 1990, MaxYearOfBirth = 1990 };
+
+ Assert.True(parameters.ValidYearRange);
+ }
+
+ [Fact]
+ public void GivenAMaxYearBelowTheMinYear_WhenTheRangeIsValidated_ThenItIsRejected()
+ {
+ var parameters = new OwnerParameters { MinYearOfBirth = 1975, MaxYearOfBirth = 1974 };
+
+ Assert.False(parameters.ValidYearRange);
+ }
+
+ [Fact]
+ public void GivenNoYearsAtAll_WhenTheRangeIsValidated_ThenItIsAccepted()
+ {
+ var parameters = new OwnerParameters();
+
+ Assert.Null(parameters.MinYearOfBirth);
+ Assert.Null(parameters.MaxYearOfBirth);
+ Assert.True(parameters.ValidYearRange);
+ }
+
+ [Fact]
+ public void GivenOnlyAMinYear_WhenTheRangeIsValidated_ThenItIsAccepted()
+ {
+ var parameters = new OwnerParameters { MinYearOfBirth = 1975 };
+
+ Assert.True(parameters.ValidYearRange);
+ }
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/OwnerRepositorySqlServerTests.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/OwnerRepositorySqlServerTests.cs
new file mode 100644
index 000000000..ab07cf2ae
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/OwnerRepositorySqlServerTests.cs
@@ -0,0 +1,128 @@
+using Entities.Helpers;
+using Entities.Models;
+using Repository;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace AccountOwnerServer.Tests;
+
+///
+/// These run against a real SQL Server started by Testcontainers. They are skipped unless
+/// CODEMAZE_SQLSERVER_TESTS=1 is set, because the SQL Server image is not guaranteed to be
+/// available on every machine or CI runner.
+///
+public class OwnerRepositorySqlServerTests
+{
+ [SqlServerFact]
+ public async Task GivenAPageSizeOfTwo_WhenWeAskForTheSecondPage_ThenWeGetTwoOwnersAndTheRightMetadata()
+ {
+ await using var sample = await SqlServerSample.StartAsync();
+ await using var context = sample.CreateContext();
+
+ var repository = new OwnerRepository(context, new SortHelper(), new DataShaper());
+
+ var owners = await repository.GetOwners(new OwnerParameters { PageNumber = 2, PageSize = 2 });
+
+ Assert.Equal(2, owners.Count);
+ Assert.Equal(5, owners.TotalCount);
+ Assert.Equal(3, owners.TotalPages);
+ Assert.True(owners.HasPrevious);
+ Assert.True(owners.HasNext);
+ }
+
+ [SqlServerFact]
+ public async Task GivenTheSameMinAndMaxYear_WhenWeFilter_ThenTheOwnerBornInThatYearComesBack()
+ {
+ await using var sample = await SqlServerSample.StartAsync();
+ await using var context = sample.CreateContext();
+
+ var repository = new OwnerRepository(context, new SortHelper(), new DataShaper());
+
+ var owners = await repository.GetOwners(new OwnerParameters
+ {
+ MinYearOfBirth = 1990,
+ MaxYearOfBirth = 1990
+ });
+
+ Assert.Equal(1, owners.TotalCount);
+ Assert.Equal("Sam Query", NameOf(owners.Single()));
+ }
+
+ [SqlServerFact]
+ public async Task GivenOnlyAMinimumYear_WhenWeFilter_ThenTheUpperBoundIsOpen()
+ {
+ await using var sample = await SqlServerSample.StartAsync();
+ await using var context = sample.CreateContext();
+
+ var repository = new OwnerRepository(context, new SortHelper(), new DataShaper());
+
+ var owners = await repository.GetOwners(new OwnerParameters { MinYearOfBirth = 1990 });
+
+ Assert.Equal(2, owners.TotalCount);
+ }
+
+ [SqlServerFact]
+ public async Task GivenALowerCaseSearchTerm_WhenWeSearch_ThenTheDefaultCollationStillMatches()
+ {
+ await using var sample = await SqlServerSample.StartAsync();
+ await using var context = sample.CreateContext();
+
+ var repository = new OwnerRepository(context, new SortHelper(), new DataShaper());
+
+ var owners = await repository.GetOwners(new OwnerParameters { SearchTerm = "anna" });
+
+ Assert.Equal(1, owners.TotalCount);
+ Assert.Equal("Anna Bosh", NameOf(owners.Single()));
+ }
+
+ [SqlServerFact]
+ public async Task GivenAnUnknownOrderByField_WhenWeQuery_ThenThePageIsStillOrderedRatherThanFailing()
+ {
+ await using var sample = await SqlServerSample.StartAsync();
+ await using var context = sample.CreateContext();
+
+ var repository = new OwnerRepository(context, new SortHelper(), new DataShaper());
+
+ var owners = await repository.GetOwners(new OwnerParameters { OrderBy = "age" });
+
+ Assert.Equal(5, owners.TotalCount);
+ Assert.Equal("Anna Bosh", NameOf(owners.First()));
+ }
+
+ [SqlServerFact]
+ public async Task GivenAnUpperCaseDescToken_WhenWeQuery_ThenTheOrderIsDescending()
+ {
+ await using var sample = await SqlServerSample.StartAsync();
+ await using var context = sample.CreateContext();
+
+ var repository = new OwnerRepository(context, new SortHelper(), new DataShaper());
+
+ var owners = await repository.GetOwners(new OwnerParameters { OrderBy = "name DESC" });
+
+ Assert.Equal("Sam Query", NameOf(owners.First()));
+ }
+
+ [SqlServerFact]
+ public async Task GivenFieldsAndAPageSize_WhenWeQuery_ThenOnlyThePageIsShapedAndTheMetadataComesFromTheQuery()
+ {
+ await using var sample = await SqlServerSample.StartAsync();
+ await using var context = sample.CreateContext();
+
+ var repository = new OwnerRepository(context, new SortHelper(), new DataShaper());
+
+ var owners = await repository.GetOwners(new OwnerParameters
+ {
+ Fields = "name",
+ PageSize = 2,
+ PageNumber = 1
+ });
+
+ Assert.Equal(2, owners.Count);
+ Assert.Equal(5, owners.TotalCount);
+ Assert.Equal(3, owners.TotalPages);
+ }
+
+ private static string NameOf(Entity owner) => owner[nameof(Owner.Name)].ToString()!;
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/QueryStringParametersTests.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/QueryStringParametersTests.cs
new file mode 100644
index 000000000..0b3791e6f
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/QueryStringParametersTests.cs
@@ -0,0 +1,39 @@
+using Entities.Models;
+using Xunit;
+
+namespace AccountOwnerServer.Tests;
+
+public class QueryStringParametersTests
+{
+ [Theory]
+ [InlineData(0, 1)]
+ [InlineData(-3, 1)]
+ [InlineData(2, 2)]
+ public void GivenAPageNumber_WhenItIsSet_ThenItNeverDropsBelowOne(int requested, int expected)
+ {
+ var parameters = new OwnerParameters { PageNumber = requested };
+
+ Assert.Equal(expected, parameters.PageNumber);
+ }
+
+ [Theory]
+ [InlineData(0, 1)]
+ [InlineData(-10, 1)]
+ [InlineData(7, 7)]
+ [InlineData(500, 50)]
+ public void GivenAPageSize_WhenItIsSet_ThenItIsClampedBetweenOneAndFifty(int requested, int expected)
+ {
+ var parameters = new OwnerParameters { PageSize = requested };
+
+ Assert.Equal(expected, parameters.PageSize);
+ }
+
+ [Fact]
+ public void GivenNoQueryString_WhenParametersAreCreated_ThenTheDefaultsAreOneAndTen()
+ {
+ var parameters = new OwnerParameters();
+
+ Assert.Equal(1, parameters.PageNumber);
+ Assert.Equal(10, parameters.PageSize);
+ }
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/SearchExtensionTests.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/SearchExtensionTests.cs
new file mode 100644
index 000000000..e83fe53cc
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/SearchExtensionTests.cs
@@ -0,0 +1,48 @@
+using Entities.Models;
+using Repository.Extensions;
+using System.Linq;
+using Xunit;
+
+namespace AccountOwnerServer.Tests;
+
+public class SearchExtensionTests
+{
+ private static IQueryable Owners() => SqlServerSample.Owners().AsQueryable();
+
+ [Fact]
+ public void GivenABlankSearchTerm_WhenWeSearch_ThenTheQueryIsReturnedUntouched()
+ {
+ var owners = Owners();
+
+ Assert.Same(owners, owners.Search(null));
+ Assert.Same(owners, owners.Search(string.Empty));
+ Assert.Same(owners, owners.Search(" "));
+ }
+
+ [Fact]
+ public void GivenANameFragment_WhenWeSearch_ThenTheMatchingOwnerComesBack()
+ {
+ var result = Owners().Search("Anna").ToList();
+
+ Assert.Single(result);
+ Assert.Equal("Anna Bosh", result[0].Name);
+ }
+
+ [Fact]
+ public void GivenAnAddressFragment_WhenWeSearch_ThenTheColumnIsSearchedToo()
+ {
+ var result = Owners().Search("Wellfield").ToList();
+
+ Assert.Single(result);
+ Assert.Equal("John Keen", result[0].Name);
+ }
+
+ [Fact]
+ public void GivenAPaddedSearchTerm_WhenWeSearch_ThenItIsTrimmedBeforeMatching()
+ {
+ var result = Owners().Search(" Somion ").ToList();
+
+ Assert.Single(result);
+ Assert.Equal("Nick Somion", result[0].Name);
+ }
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/SortHelperTests.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/SortHelperTests.cs
new file mode 100644
index 000000000..685ec28ad
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/SortHelperTests.cs
@@ -0,0 +1,61 @@
+using Entities.Helpers;
+using Entities.Models;
+using System.Linq;
+using Xunit;
+
+namespace AccountOwnerServer.Tests;
+
+public class SortHelperTests
+{
+ private static IQueryable Owners() => SqlServerSample.Owners().AsQueryable();
+
+ private static readonly SortHelper Sorter = new();
+
+ [Fact]
+ public void GivenABlankOrderBy_WhenWeSort_ThenTheQueryIsReturnedUntouched()
+ {
+ var owners = Owners();
+
+ Assert.Same(owners, Sorter.ApplySort(owners, null));
+ Assert.Same(owners, Sorter.ApplySort(owners, " "));
+ }
+
+ [Fact]
+ public void GivenOnlyUnknownFields_WhenWeSort_ThenTheQueryIsReturnedUntouchedRatherThanThrowing()
+ {
+ var owners = Owners();
+
+ var sorted = Sorter.ApplySort(owners, "age");
+
+ Assert.Same(owners, sorted);
+ }
+
+ [Fact]
+ public void GivenAKnownField_WhenWeSort_ThenTheRowsComeBackInThatOrder()
+ {
+ var sorted = Sorter.ApplySort(Owners(), "dateOfBirth").ToList();
+
+ Assert.Equal("Anna Bosh", sorted.First().Name);
+ Assert.Equal("Nick Somion", sorted.Last().Name);
+ }
+
+ [Theory]
+ [InlineData("name desc")]
+ [InlineData("name DESC")]
+ [InlineData("name Desc")]
+ public void GivenDescInAnyCasing_WhenWeSort_ThenTheOrderIsDescending(string orderBy)
+ {
+ var sorted = Sorter.ApplySort(Owners(), orderBy).ToList();
+
+ Assert.Equal("Sam Query", sorted.First().Name);
+ Assert.Equal("Anna Bosh", sorted.Last().Name);
+ }
+
+ [Fact]
+ public void GivenAKnownAndAnUnknownField_WhenWeSort_ThenTheKnownOneStillApplies()
+ {
+ var sorted = Sorter.ApplySort(Owners(), "age,name desc").ToList();
+
+ Assert.Equal("Sam Query", sorted.First().Name);
+ }
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/SqlServerFactAttribute.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/SqlServerFactAttribute.cs
new file mode 100644
index 000000000..6ae7fafae
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/SqlServerFactAttribute.cs
@@ -0,0 +1,20 @@
+using System;
+using Xunit;
+
+namespace AccountOwnerServer.Tests;
+
+///
+/// A fact that runs only when SQL Server integration tests are switched on with
+/// CODEMAZE_SQLSERVER_TESTS=1. The tests need a working Docker daemon: Testcontainers
+/// starts mcr.microsoft.com/mssql/server for them.
+///
+public sealed class SqlServerFactAttribute : FactAttribute
+{
+ public SqlServerFactAttribute()
+ {
+ if (Environment.GetEnvironmentVariable("CODEMAZE_SQLSERVER_TESTS") != "1")
+ {
+ Skip = "Set CODEMAZE_SQLSERVER_TESTS=1 (and have Docker running) to run the SQL Server tests.";
+ }
+ }
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/SqlServerSample.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/SqlServerSample.cs
new file mode 100644
index 000000000..e557d6730
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.Tests/SqlServerSample.cs
@@ -0,0 +1,73 @@
+using Entities;
+using Entities.Models;
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Testcontainers.MsSql;
+
+namespace AccountOwnerServer.Tests;
+
+///
+/// A throwaway SQL Server container seeded with the five owners and eight accounts the
+/// articles use. Each test owns its own container, so nothing is created when the tests
+/// are skipped.
+///
+public sealed class SqlServerSample : IAsyncDisposable
+{
+ private readonly MsSqlContainer _container;
+
+ private SqlServerSample(MsSqlContainer container)
+ {
+ _container = container;
+ }
+
+ public static async Task StartAsync()
+ {
+ var container = new MsSqlBuilder("mcr.microsoft.com/mssql/server:2022-latest").Build();
+ await container.StartAsync();
+
+ var sample = new SqlServerSample(container);
+
+ await using var context = sample.CreateContext();
+ await context.Database.EnsureCreatedAsync();
+
+ context.Owners.AddRange(Owners());
+ context.Accounts.AddRange(Accounts());
+ await context.SaveChangesAsync();
+
+ return sample;
+ }
+
+ public RepositoryContext CreateContext()
+ {
+ var options = new DbContextOptionsBuilder()
+ .UseSqlServer(_container.GetConnectionString())
+ .Options;
+
+ return new RepositoryContext(options);
+ }
+
+ public async ValueTask DisposeAsync() => await _container.DisposeAsync();
+
+ public static List Owners() =>
+ [
+ new() { Id = Guid.Parse("24fd81f8-d58a-4bcc-9f35-dc6cd5641906"), Name = "John Keen", DateOfBirth = new DateTime(1980, 12, 5), Address = "61 Wellfield Road" },
+ new() { Id = Guid.Parse("261e1685-cf26-494c-b17c-3546e65f5620"), Name = "Anna Bosh", DateOfBirth = new DateTime(1974, 11, 14), Address = "27 Colored Row" },
+ new() { Id = Guid.Parse("66774006-2371-4d5b-8518-2177bcf3f73e"), Name = "Nick Somion", DateOfBirth = new DateTime(1998, 12, 15), Address = "North sunny address 102" },
+ new() { Id = Guid.Parse("a3c1880c-674c-4d18-8f91-5d3608a2c937"), Name = "Sam Query", DateOfBirth = new DateTime(1990, 4, 22), Address = "91 Western Roads" },
+ new() { Id = Guid.Parse("f98e4d74-0f68-4aac-89fd-047f1aaca6b6"), Name = "Martin Miller", DateOfBirth = new DateTime(1983, 5, 21), Address = "3 Edgar Buildings" }
+ ];
+
+ public static List Accounts() =>
+ [
+ new() { Id = Guid.Parse("03e91478-5608-4132-a753-d494dafce00b"), DateCreated = new DateTime(2003, 12, 15), AccountType = "Domestic", OwnerId = Guid.Parse("f98e4d74-0f68-4aac-89fd-047f1aaca6b6") },
+ new() { Id = Guid.Parse("356a5a9b-64bf-4de0-bc84-5395a1fdc9c4"), DateCreated = new DateTime(1996, 2, 15), AccountType = "Domestic", OwnerId = Guid.Parse("261e1685-cf26-494c-b17c-3546e65f5620") },
+ new() { Id = Guid.Parse("371b93f2-f8c5-4a32-894a-fc672741aa5b"), DateCreated = new DateTime(1999, 5, 4), AccountType = "Domestic", OwnerId = Guid.Parse("24fd81f8-d58a-4bcc-9f35-dc6cd5641906") },
+ new() { Id = Guid.Parse("670775db-ecc0-4b90-a9ab-37cd0d8e2801"), DateCreated = new DateTime(1999, 12, 21), AccountType = "Savings", OwnerId = Guid.Parse("24fd81f8-d58a-4bcc-9f35-dc6cd5641906") },
+ new() { Id = Guid.Parse("a3fbad0b-7f48-4feb-8ac0-6d3bbc997bfc"), DateCreated = new DateTime(2010, 5, 28), AccountType = "Domestic", OwnerId = Guid.Parse("a3c1880c-674c-4d18-8f91-5d3608a2c937") },
+ new() { Id = Guid.Parse("aa15f658-04bb-4f73-82af-82db49d0fbef"), DateCreated = new DateTime(1999, 5, 12), AccountType = "Foreign", OwnerId = Guid.Parse("24fd81f8-d58a-4bcc-9f35-dc6cd5641906") },
+ new() { Id = Guid.Parse("c6066eb0-53ca-43e1-97aa-3c2169eec659"), DateCreated = new DateTime(1996, 2, 16), AccountType = "Foreign", OwnerId = Guid.Parse("261e1685-cf26-494c-b17c-3546e65f5620") },
+ new() { Id = Guid.Parse("eccadf79-85fe-402f-893c-32d3f03ed9b1"), DateCreated = new DateTime(2010, 6, 20), AccountType = "Foreign", OwnerId = Guid.Parse("a3c1880c-674c-4d18-8f91-5d3608a2c937") }
+ ];
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.sln b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.sln
new file mode 100644
index 000000000..3bd7f01da
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer.sln
@@ -0,0 +1,104 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer", "AccountOwnerServer\AccountOwnerServer.csproj", "{65C2DEA3-71F9-4F60-9B2B-7E4899FD25D8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoggerService", "LoggerService\LoggerService.csproj", "{D347A258-1B00-447F-8A01-D6DB6215DBF9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contracts", "Contracts\Contracts.csproj", "{9E985092-73A6-4A7B-B71E-DA22D96456AE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Entities", "Entities\Entities.csproj", "{DA32AC0E-3E93-48C7-9F99-5822EA0AE692}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repository", "Repository\Repository.csproj", "{BF0DE019-6796-440E-B0F0-C644E3631544}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer.Tests", "AccountOwnerServer.Tests\AccountOwnerServer.Tests.csproj", "{0F167183-CB75-49F2-B781-027542075B7D}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {65C2DEA3-71F9-4F60-9B2B-7E4899FD25D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {65C2DEA3-71F9-4F60-9B2B-7E4899FD25D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {65C2DEA3-71F9-4F60-9B2B-7E4899FD25D8}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {65C2DEA3-71F9-4F60-9B2B-7E4899FD25D8}.Debug|x64.Build.0 = Debug|Any CPU
+ {65C2DEA3-71F9-4F60-9B2B-7E4899FD25D8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {65C2DEA3-71F9-4F60-9B2B-7E4899FD25D8}.Debug|x86.Build.0 = Debug|Any CPU
+ {65C2DEA3-71F9-4F60-9B2B-7E4899FD25D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {65C2DEA3-71F9-4F60-9B2B-7E4899FD25D8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {65C2DEA3-71F9-4F60-9B2B-7E4899FD25D8}.Release|x64.ActiveCfg = Release|Any CPU
+ {65C2DEA3-71F9-4F60-9B2B-7E4899FD25D8}.Release|x64.Build.0 = Release|Any CPU
+ {65C2DEA3-71F9-4F60-9B2B-7E4899FD25D8}.Release|x86.ActiveCfg = Release|Any CPU
+ {65C2DEA3-71F9-4F60-9B2B-7E4899FD25D8}.Release|x86.Build.0 = Release|Any CPU
+ {D347A258-1B00-447F-8A01-D6DB6215DBF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D347A258-1B00-447F-8A01-D6DB6215DBF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D347A258-1B00-447F-8A01-D6DB6215DBF9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D347A258-1B00-447F-8A01-D6DB6215DBF9}.Debug|x64.Build.0 = Debug|Any CPU
+ {D347A258-1B00-447F-8A01-D6DB6215DBF9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D347A258-1B00-447F-8A01-D6DB6215DBF9}.Debug|x86.Build.0 = Debug|Any CPU
+ {D347A258-1B00-447F-8A01-D6DB6215DBF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D347A258-1B00-447F-8A01-D6DB6215DBF9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D347A258-1B00-447F-8A01-D6DB6215DBF9}.Release|x64.ActiveCfg = Release|Any CPU
+ {D347A258-1B00-447F-8A01-D6DB6215DBF9}.Release|x64.Build.0 = Release|Any CPU
+ {D347A258-1B00-447F-8A01-D6DB6215DBF9}.Release|x86.ActiveCfg = Release|Any CPU
+ {D347A258-1B00-447F-8A01-D6DB6215DBF9}.Release|x86.Build.0 = Release|Any CPU
+ {9E985092-73A6-4A7B-B71E-DA22D96456AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9E985092-73A6-4A7B-B71E-DA22D96456AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9E985092-73A6-4A7B-B71E-DA22D96456AE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9E985092-73A6-4A7B-B71E-DA22D96456AE}.Debug|x64.Build.0 = Debug|Any CPU
+ {9E985092-73A6-4A7B-B71E-DA22D96456AE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9E985092-73A6-4A7B-B71E-DA22D96456AE}.Debug|x86.Build.0 = Debug|Any CPU
+ {9E985092-73A6-4A7B-B71E-DA22D96456AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9E985092-73A6-4A7B-B71E-DA22D96456AE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9E985092-73A6-4A7B-B71E-DA22D96456AE}.Release|x64.ActiveCfg = Release|Any CPU
+ {9E985092-73A6-4A7B-B71E-DA22D96456AE}.Release|x64.Build.0 = Release|Any CPU
+ {9E985092-73A6-4A7B-B71E-DA22D96456AE}.Release|x86.ActiveCfg = Release|Any CPU
+ {9E985092-73A6-4A7B-B71E-DA22D96456AE}.Release|x86.Build.0 = Release|Any CPU
+ {DA32AC0E-3E93-48C7-9F99-5822EA0AE692}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DA32AC0E-3E93-48C7-9F99-5822EA0AE692}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DA32AC0E-3E93-48C7-9F99-5822EA0AE692}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {DA32AC0E-3E93-48C7-9F99-5822EA0AE692}.Debug|x64.Build.0 = Debug|Any CPU
+ {DA32AC0E-3E93-48C7-9F99-5822EA0AE692}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {DA32AC0E-3E93-48C7-9F99-5822EA0AE692}.Debug|x86.Build.0 = Debug|Any CPU
+ {DA32AC0E-3E93-48C7-9F99-5822EA0AE692}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DA32AC0E-3E93-48C7-9F99-5822EA0AE692}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DA32AC0E-3E93-48C7-9F99-5822EA0AE692}.Release|x64.ActiveCfg = Release|Any CPU
+ {DA32AC0E-3E93-48C7-9F99-5822EA0AE692}.Release|x64.Build.0 = Release|Any CPU
+ {DA32AC0E-3E93-48C7-9F99-5822EA0AE692}.Release|x86.ActiveCfg = Release|Any CPU
+ {DA32AC0E-3E93-48C7-9F99-5822EA0AE692}.Release|x86.Build.0 = Release|Any CPU
+ {BF0DE019-6796-440E-B0F0-C644E3631544}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BF0DE019-6796-440E-B0F0-C644E3631544}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BF0DE019-6796-440E-B0F0-C644E3631544}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {BF0DE019-6796-440E-B0F0-C644E3631544}.Debug|x64.Build.0 = Debug|Any CPU
+ {BF0DE019-6796-440E-B0F0-C644E3631544}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {BF0DE019-6796-440E-B0F0-C644E3631544}.Debug|x86.Build.0 = Debug|Any CPU
+ {BF0DE019-6796-440E-B0F0-C644E3631544}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BF0DE019-6796-440E-B0F0-C644E3631544}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BF0DE019-6796-440E-B0F0-C644E3631544}.Release|x64.ActiveCfg = Release|Any CPU
+ {BF0DE019-6796-440E-B0F0-C644E3631544}.Release|x64.Build.0 = Release|Any CPU
+ {BF0DE019-6796-440E-B0F0-C644E3631544}.Release|x86.ActiveCfg = Release|Any CPU
+ {BF0DE019-6796-440E-B0F0-C644E3631544}.Release|x86.Build.0 = Release|Any CPU
+ {0F167183-CB75-49F2-B781-027542075B7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0F167183-CB75-49F2-B781-027542075B7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0F167183-CB75-49F2-B781-027542075B7D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0F167183-CB75-49F2-B781-027542075B7D}.Debug|x64.Build.0 = Debug|Any CPU
+ {0F167183-CB75-49F2-B781-027542075B7D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0F167183-CB75-49F2-B781-027542075B7D}.Debug|x86.Build.0 = Debug|Any CPU
+ {0F167183-CB75-49F2-B781-027542075B7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0F167183-CB75-49F2-B781-027542075B7D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0F167183-CB75-49F2-B781-027542075B7D}.Release|x64.ActiveCfg = Release|Any CPU
+ {0F167183-CB75-49F2-B781-027542075B7D}.Release|x64.Build.0 = Release|Any CPU
+ {0F167183-CB75-49F2-B781-027542075B7D}.Release|x86.ActiveCfg = Release|Any CPU
+ {0F167183-CB75-49F2-B781-027542075B7D}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/AccountOwnerServer.csproj b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/AccountOwnerServer.csproj
new file mode 100644
index 000000000..8d9f2e4a6
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/AccountOwnerServer.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net10.0
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/Controllers/AccountController.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/Controllers/AccountController.cs
new file mode 100644
index 000000000..ca0df4724
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/Controllers/AccountController.cs
@@ -0,0 +1,62 @@
+using Contracts;
+using Entities.Extensions;
+using Entities.Models;
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
+using System.Threading.Tasks;
+using System;
+
+namespace AccountOwnerServer.Controllers;
+
+[Route("api/owners/{ownerId}/accounts")]
+[ApiController]
+public class AccountController : ControllerBase
+{
+ private readonly ILoggerManager _logger;
+ private readonly IRepositoryWrapper _repository;
+
+ public AccountController(ILoggerManager logger,
+ IRepositoryWrapper repository)
+ {
+ _logger = logger;
+ _repository = repository;
+ }
+
+ [HttpGet]
+ public async Task>> GetAccountsForOwner(Guid ownerId, [FromQuery] AccountParameters parameters)
+ {
+ var accounts = await _repository.Account.GetAccountsByOwner(ownerId, parameters);
+
+ var metadata = new
+ {
+ accounts.TotalCount,
+ accounts.PageSize,
+ accounts.CurrentPage,
+ accounts.TotalPages,
+ accounts.HasNext,
+ accounts.HasPrevious
+ };
+
+ Response.Headers["X-Pagination"] = JsonSerializer.Serialize(metadata);
+
+ _logger.LogInfo($"Returned {accounts.TotalCount} accounts from database.");
+
+ return Ok(accounts);
+ }
+
+ [HttpGet("{id}")]
+ public IActionResult GetAccountForOwner(Guid ownerId, Guid id, [FromQuery] string? fields)
+ {
+ var account = _repository.Account.GetAccountByOwner(ownerId, id);
+
+ if (account.IsObjectNull())
+ {
+ _logger.LogError($"Account with id: {id}, hasn't been found in db.");
+ return NotFound();
+ }
+
+ return Ok(_repository.Account.GetAccountByOwner(ownerId, id, fields));
+ }
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/Controllers/OwnerController.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/Controllers/OwnerController.cs
new file mode 100644
index 000000000..09bdd09b6
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/Controllers/OwnerController.cs
@@ -0,0 +1,137 @@
+using Contracts;
+using Entities.Extensions;
+using Entities.Models;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
+using System.Threading.Tasks;
+using System;
+
+namespace AccountOwnerServer.Controllers;
+
+[Route("api/owners")]
+[ApiController]
+public class OwnerController : ControllerBase
+{
+ private readonly ILoggerManager _logger;
+ private readonly IRepositoryWrapper _repository;
+
+ public OwnerController(ILoggerManager logger,
+ IRepositoryWrapper repository)
+ {
+ _logger = logger;
+ _repository = repository;
+ }
+
+ [HttpGet]
+ public async Task>> GetOwners([FromQuery] OwnerParameters ownerParameters)
+ {
+ if (!ownerParameters.ValidYearRange)
+ {
+ return Problem(
+ detail: "maxYearOfBirth must be greater than or equal to minYearOfBirth.",
+ statusCode: StatusCodes.Status400BadRequest);
+ }
+
+ var owners = await _repository.Owner.GetOwners(ownerParameters);
+
+ var metadata = new
+ {
+ owners.TotalCount,
+ owners.PageSize,
+ owners.CurrentPage,
+ owners.TotalPages,
+ owners.HasNext,
+ owners.HasPrevious
+ };
+
+ Response.Headers["X-Pagination"] = JsonSerializer.Serialize(metadata);
+
+ _logger.LogInfo($"Returned {owners.TotalCount} owners from database.");
+
+ return Ok(owners);
+ }
+
+ [HttpGet("{id}", Name = "OwnerById")]
+ public IActionResult GetOwnerById(Guid id, [FromQuery] string? fields)
+ {
+ var dbOwner = _repository.Owner.GetOwnerById(id);
+
+ if (dbOwner.IsEmptyObject())
+ {
+ _logger.LogError($"Owner with id: {id}, hasn't been found in db.");
+ return NotFound();
+ }
+
+ return Ok(_repository.Owner.GetOwnerById(id, fields));
+ }
+
+ [HttpPost]
+ public IActionResult CreateOwner([FromBody] Owner owner)
+ {
+ if (owner.IsObjectNull())
+ {
+ _logger.LogError("Owner object sent from client is null.");
+ return BadRequest("Owner object is null");
+ }
+
+ if (!ModelState.IsValid)
+ {
+ _logger.LogError("Invalid owner object sent from client.");
+ return BadRequest("Invalid model object");
+ }
+
+ _repository.Owner.CreateOwner(owner);
+ _repository.Save();
+
+ return CreatedAtRoute("OwnerById", new { id = owner.Id }, owner);
+ }
+
+ [HttpPut("{id}")]
+ public IActionResult UpdateOwner(Guid id, [FromBody] Owner owner)
+ {
+ if (owner.IsObjectNull())
+ {
+ _logger.LogError("Owner object sent from client is null.");
+ return BadRequest("Owner object is null");
+ }
+
+ if (!ModelState.IsValid)
+ {
+ _logger.LogError("Invalid owner object sent from client.");
+ return BadRequest("Invalid model object");
+ }
+
+ var dbOwner = _repository.Owner.GetOwnerById(id);
+
+ if (dbOwner.IsEmptyObject())
+ {
+ _logger.LogError($"Owner with id: {id}, hasn't been found in db.");
+ return NotFound();
+ }
+
+ _repository.Owner.UpdateOwner(dbOwner, owner);
+ _repository.Save();
+
+ return NoContent();
+ }
+
+ [HttpDelete("{id}")]
+ public IActionResult DeleteOwner(Guid id)
+ {
+ var owner = _repository.Owner.GetOwnerById(id);
+
+ if (owner.IsEmptyObject())
+ {
+ _logger.LogError($"Owner with id: {id}, hasn't been found in db.");
+ return NotFound();
+ }
+
+ _repository.Owner.DeleteOwner(owner);
+ _repository.Save();
+
+ return NoContent();
+ }
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/Extensions/ServiceExtensions.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/Extensions/ServiceExtensions.cs
new file mode 100644
index 000000000..62a069134
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/Extensions/ServiceExtensions.cs
@@ -0,0 +1,42 @@
+using Contracts;
+using Entities.Helpers;
+using Entities.Models;
+using Entities;
+using LoggerService;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Repository;
+
+namespace AccountOwnerServer.Extensions;
+
+public static class ServiceExtensions
+{
+ public static void ConfigureCors(this IServiceCollection services) =>
+ services.AddCors(options =>
+ options.AddPolicy("CorsPolicy",
+ builder => builder.WithOrigins("http://localhost:5000", "https://localhost:5001")
+ .AllowAnyMethod()
+ .AllowAnyHeader()
+ .AllowCredentials()
+ .WithExposedHeaders("X-Pagination")));
+
+ public static void ConfigureLoggerService(this IServiceCollection services) =>
+ services.AddSingleton();
+
+ public static void ConfigureSqlContext(this IServiceCollection services, IConfiguration config)
+ {
+ var connectionString = config["sqlconnection:connectionString"];
+ services.AddDbContext(o => o.UseSqlServer(connectionString));
+ }
+
+ public static void ConfigureRepositoryWrapper(this IServiceCollection services)
+ {
+ services.AddScoped, SortHelper>();
+ services.AddScoped, SortHelper>();
+
+ services.AddSingleton, DataShaper>();
+ services.AddSingleton, DataShaper>();
+ services.AddScoped();
+ }
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/Program.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/Program.cs
new file mode 100644
index 000000000..0739f0183
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/Program.cs
@@ -0,0 +1,64 @@
+using AccountOwnerServer.Extensions;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Diagnostics;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.HttpOverrides;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using NLog;
+using System.Net;
+
+LogManager.Setup().LoadConfigurationFromFile("nlog.config");
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.ConfigureCors();
+builder.Services.ConfigureLoggerService();
+builder.Services.ConfigureSqlContext(builder.Configuration);
+builder.Services.ConfigureRepositoryWrapper();
+
+builder.Services.AddControllers(config =>
+{
+ config.RespectBrowserAcceptHeader = true;
+ config.ReturnHttpNotAcceptable = true;
+}).AddXmlDataContractSerializerFormatters();
+
+var app = builder.Build();
+
+if (app.Environment.IsDevelopment())
+ app.UseDeveloperExceptionPage();
+else
+ app.UseHsts();
+
+app.UseExceptionHandler(appError =>
+{
+ appError.Run(async context =>
+ {
+ context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+ context.Response.ContentType = "application/json";
+
+ var contextFeature = context.Features.Get();
+
+ if (contextFeature is not null)
+ {
+ await context.Response.WriteAsJsonAsync(new
+ {
+ context.Response.StatusCode,
+ Message = "Internal Server Error."
+ });
+ }
+ });
+});
+
+app.UseHttpsRedirection();
+
+app.UseCors("CorsPolicy");
+
+app.UseForwardedHeaders(new ForwardedHeadersOptions
+{
+ ForwardedHeaders = ForwardedHeaders.All
+});
+
+app.MapControllers();
+
+app.Run();
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/Properties/launchSettings.json b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/Properties/launchSettings.json
new file mode 100644
index 000000000..a82b9223a
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/Properties/launchSettings.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "AccountOwnerServer": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "api/owners",
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/appsettings.Development.json b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/appsettings.Development.json
new file mode 100644
index 000000000..e203e9407
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug",
+ "System": "Information",
+ "Microsoft": "Information"
+ }
+ }
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/appsettings.json b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/appsettings.json
new file mode 100644
index 000000000..0096cb3d4
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/appsettings.json
@@ -0,0 +1,11 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Warning"
+ }
+ },
+ "sqlconnection": {
+ "connectionString": "Server=(localdb)\\mssqllocaldb;Database=AccountOwner;Trusted_Connection=True;TrustServerCertificate=True;"
+ },
+ "AllowedHosts": "*"
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/nlog.config b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/nlog.config
new file mode 100644
index 000000000..52f675aa6
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/AccountOwnerServer/nlog.config
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Contracts/Contracts.csproj b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Contracts/Contracts.csproj
new file mode 100644
index 000000000..b201bbf27
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Contracts/Contracts.csproj
@@ -0,0 +1,12 @@
+
+
+
+ net10.0
+ enable
+
+
+
+
+
+
+
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Contracts/IAccountRepository.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Contracts/IAccountRepository.cs
new file mode 100644
index 000000000..5c8d00177
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Contracts/IAccountRepository.cs
@@ -0,0 +1,13 @@
+using Entities.Helpers;
+using Entities.Models;
+using System;
+using System.Threading.Tasks;
+
+namespace Contracts;
+
+public interface IAccountRepository : IRepositoryBase
+{
+ Task> GetAccountsByOwner(Guid ownerId, AccountParameters parameters);
+ Entity GetAccountByOwner(Guid ownerId, Guid id, string? fields);
+ Account? GetAccountByOwner(Guid ownerId, Guid id);
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Contracts/ILoggerManager.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Contracts/ILoggerManager.cs
new file mode 100644
index 000000000..42d30745d
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Contracts/ILoggerManager.cs
@@ -0,0 +1,9 @@
+namespace Contracts;
+
+public interface ILoggerManager
+{
+ void LogInfo(string message);
+ void LogWarn(string message);
+ void LogDebug(string message);
+ void LogError(string message);
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Contracts/IOwnerRepository.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Contracts/IOwnerRepository.cs
new file mode 100644
index 000000000..8b29e5331
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Contracts/IOwnerRepository.cs
@@ -0,0 +1,16 @@
+using Entities.Helpers;
+using Entities.Models;
+using System;
+using System.Threading.Tasks;
+
+namespace Contracts;
+
+public interface IOwnerRepository : IRepositoryBase
+{
+ Task> GetOwners(OwnerParameters ownerParameters);
+ Entity GetOwnerById(Guid ownerId, string? fields);
+ Owner GetOwnerById(Guid ownerId);
+ void CreateOwner(Owner owner);
+ void UpdateOwner(Owner dbOwner, Owner owner);
+ void DeleteOwner(Owner owner);
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Contracts/IRepositoryBase.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Contracts/IRepositoryBase.cs
new file mode 100644
index 000000000..51283872d
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Contracts/IRepositoryBase.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace Contracts;
+
+public interface IRepositoryBase
+{
+ IQueryable FindAll();
+ IQueryable FindByCondition(Expression> expression);
+ void Create(T entity);
+ void Update(T entity);
+ void Delete(T entity);
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Contracts/IRepositoryWrapper.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Contracts/IRepositoryWrapper.cs
new file mode 100644
index 000000000..b7dd600fe
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Contracts/IRepositoryWrapper.cs
@@ -0,0 +1,8 @@
+namespace Contracts;
+
+public interface IRepositoryWrapper
+{
+ IOwnerRepository Owner { get; }
+ IAccountRepository Account { get; }
+ void Save();
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Entities.csproj b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Entities.csproj
new file mode 100644
index 000000000..4aa0cf347
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Entities.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net10.0
+ enable
+
+
+
+
+
+
+
+
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Enumerations/AccountType.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Enumerations/AccountType.cs
new file mode 100644
index 000000000..b84e0ad48
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Enumerations/AccountType.cs
@@ -0,0 +1,8 @@
+namespace Entities.Enumerations;
+
+public enum AccountType
+{
+ Domestic,
+ Savings,
+ Foreign
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/ExtendedModels/OwnerExtended.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/ExtendedModels/OwnerExtended.cs
new file mode 100644
index 000000000..b42caa4c4
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/ExtendedModels/OwnerExtended.cs
@@ -0,0 +1,27 @@
+using Entities.Models;
+using System;
+using System.Collections.Generic;
+
+namespace Entities.ExtendedModels;
+
+public class OwnerExtended : IEntity
+{
+ public Guid Id { get; set; }
+ public string Name { get; set; } = string.Empty;
+ public DateTime DateOfBirth { get; set; }
+ public string Address { get; set; } = string.Empty;
+
+ public IEnumerable Accounts { get; set; } = [];
+
+ public OwnerExtended()
+ {
+ }
+
+ public OwnerExtended(Owner owner)
+ {
+ Id = owner.Id;
+ Name = owner.Name;
+ DateOfBirth = owner.DateOfBirth;
+ Address = owner.Address;
+ }
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Extensions/IEntityExtensions.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Extensions/IEntityExtensions.cs
new file mode 100644
index 000000000..243eafc70
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Extensions/IEntityExtensions.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Entities.Extensions;
+
+public static class IEntityExtensions
+{
+ public static bool IsObjectNull(this IEntity? entity) => entity is null;
+
+ public static bool IsEmptyObject(this IEntity entity) => entity.Id.Equals(Guid.Empty);
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Extensions/OwnerExtensions.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Extensions/OwnerExtensions.cs
new file mode 100644
index 000000000..ba2768266
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Extensions/OwnerExtensions.cs
@@ -0,0 +1,13 @@
+using Entities.Models;
+
+namespace Entities.Extensions;
+
+public static class OwnerExtensions
+{
+ public static void Map(this Owner dbOwner, Owner owner)
+ {
+ dbOwner.Name = owner.Name;
+ dbOwner.Address = owner.Address;
+ dbOwner.DateOfBirth = owner.DateOfBirth;
+ }
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Helpers/DataShaper.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Helpers/DataShaper.cs
new file mode 100644
index 000000000..9236f85c3
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Helpers/DataShaper.cs
@@ -0,0 +1,63 @@
+using Entities.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace Entities.Helpers;
+
+public class DataShaper : IDataShaper
+{
+ private readonly PropertyInfo[] _properties;
+
+ public DataShaper()
+ {
+ _properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
+ }
+
+ public IEnumerable ShapeData(IEnumerable entities, string? fieldsString)
+ {
+ var requiredProperties = GetRequiredProperties(fieldsString);
+
+ return entities.Select(entity => FetchDataForEntity(entity, requiredProperties)).ToList();
+ }
+
+ public Entity ShapeData(T entity, string? fieldsString)
+ {
+ var requiredProperties = GetRequiredProperties(fieldsString);
+
+ return FetchDataForEntity(entity, requiredProperties);
+ }
+
+ private IEnumerable GetRequiredProperties(string? fieldsString)
+ {
+ if (string.IsNullOrWhiteSpace(fieldsString))
+ return _properties;
+
+ var requiredProperties = new List();
+ var fields = fieldsString.Split(',', StringSplitOptions.RemoveEmptyEntries);
+
+ foreach (var field in fields)
+ {
+ var property = _properties.FirstOrDefault(pi =>
+ pi.Name.Equals(field.Trim(), StringComparison.OrdinalIgnoreCase));
+
+ if (property is null)
+ continue;
+
+ requiredProperties.Add(property);
+ }
+
+ return requiredProperties;
+ }
+
+ private static Entity FetchDataForEntity(T entity, IEnumerable requiredProperties)
+ {
+ var shapedObject = new Entity();
+
+ foreach (var property in requiredProperties)
+ shapedObject.TryAdd(property.Name, property.GetValue(entity)!);
+
+ return shapedObject;
+ }
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Helpers/IDataShaper.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Helpers/IDataShaper.cs
new file mode 100644
index 000000000..0fe3ce53e
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Helpers/IDataShaper.cs
@@ -0,0 +1,10 @@
+using Entities.Models;
+using System.Collections.Generic;
+
+namespace Entities.Helpers;
+
+public interface IDataShaper
+{
+ IEnumerable ShapeData(IEnumerable entities, string? fieldsString);
+ Entity ShapeData(T entity, string? fieldsString);
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Helpers/ISortHelper.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Helpers/ISortHelper.cs
new file mode 100644
index 000000000..3d867c508
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Helpers/ISortHelper.cs
@@ -0,0 +1,8 @@
+using System.Linq;
+
+namespace Entities.Helpers;
+
+public interface ISortHelper
+{
+ IQueryable ApplySort(IQueryable entities, string? orderByQueryString);
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Helpers/PagedList.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Helpers/PagedList.cs
new file mode 100644
index 000000000..a81287b98
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Helpers/PagedList.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+
+namespace Entities.Helpers;
+
+public class PagedList : List
+{
+ public int CurrentPage { get; private set; }
+ public int TotalPages { get; private set; }
+ public int PageSize { get; private set; }
+ public int TotalCount { get; private set; }
+
+ public bool HasPrevious => CurrentPage > 1;
+ public bool HasNext => CurrentPage < TotalPages;
+
+ public PagedList()
+ {
+ }
+
+ public PagedList(List items, int count, int pageNumber, int pageSize)
+ {
+ TotalCount = count;
+ PageSize = pageSize;
+ CurrentPage = pageNumber;
+ TotalPages = (int)Math.Ceiling(count / (double)pageSize);
+
+ AddRange(items);
+ }
+
+ public static async Task> ToPagedListAsync(IQueryable source, int pageNumber, int pageSize)
+ {
+ var count = await source.CountAsync();
+ var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync();
+
+ return new PagedList(items, count, pageNumber, pageSize);
+ }
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Helpers/SortHelper.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Helpers/SortHelper.cs
new file mode 100644
index 000000000..705154cbe
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Helpers/SortHelper.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Linq;
+using System.Linq.Dynamic.Core;
+using System.Reflection;
+using System.Text;
+
+namespace Entities.Helpers;
+
+public class SortHelper : ISortHelper
+{
+ public IQueryable ApplySort(IQueryable entities, string? orderByQueryString)
+ {
+ if (string.IsNullOrWhiteSpace(orderByQueryString))
+ return entities;
+
+ var orderParams = orderByQueryString.Trim().Split(',');
+ var propertyInfos = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
+ var orderQueryBuilder = new StringBuilder();
+
+ foreach (var param in orderParams)
+ {
+ if (string.IsNullOrWhiteSpace(param))
+ continue;
+
+ var propertyFromQueryName = param.Split(' ')[0];
+ var objectProperty = propertyInfos.FirstOrDefault(pi =>
+ pi.Name.Equals(propertyFromQueryName, StringComparison.InvariantCultureIgnoreCase));
+
+ if (objectProperty is null)
+ continue;
+
+ var sortingOrder = param.EndsWith(" desc", StringComparison.OrdinalIgnoreCase)
+ ? "descending"
+ : "ascending";
+
+ orderQueryBuilder.Append($"{objectProperty.Name} {sortingOrder}, ");
+ }
+
+ var orderQuery = orderQueryBuilder.ToString().TrimEnd(',', ' ');
+
+ if (string.IsNullOrWhiteSpace(orderQuery))
+ return entities;
+
+ return entities.OrderBy(orderQuery);
+ }
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/IEntity.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/IEntity.cs
new file mode 100644
index 000000000..825a8525b
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/IEntity.cs
@@ -0,0 +1,8 @@
+using System;
+
+namespace Entities;
+
+public interface IEntity
+{
+ Guid Id { get; set; }
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Models/Account.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Models/Account.cs
new file mode 100644
index 000000000..7a5590d9f
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Models/Account.cs
@@ -0,0 +1,22 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Entities.Models;
+
+[Table("account")]
+public class Account : IEntity
+{
+ [Key]
+ [Column("AccountId")]
+ public Guid Id { get; set; }
+
+ [Required(ErrorMessage = "Date created is required")]
+ public DateTime DateCreated { get; set; }
+
+ [Required(ErrorMessage = "Account type is required")]
+ public string AccountType { get; set; } = string.Empty;
+
+ [Required(ErrorMessage = "Owner Id is required")]
+ public Guid OwnerId { get; set; }
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Models/AccountParameters.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Models/AccountParameters.cs
new file mode 100644
index 000000000..448b50178
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Models/AccountParameters.cs
@@ -0,0 +1,9 @@
+namespace Entities.Models;
+
+public class AccountParameters : QueryStringParameters
+{
+ public AccountParameters()
+ {
+ OrderBy = "DateCreated";
+ }
+}
diff --git a/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Models/Entity.cs b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Models/Entity.cs
new file mode 100644
index 000000000..a6d222acc
--- /dev/null
+++ b/aspnetcore-webapi/AdvancedRestConceptsSeries/DataShaping/Entities/Models/Entity.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Xml;
+using System.Xml.Schema;
+using System.Xml.Serialization;
+
+namespace Entities.Models;
+
+public class Entity : DynamicObject, IXmlSerializable, IDictionary
+{
+ private readonly string _root = "Entity";
+ private readonly IDictionary _expando;
+
+ public Entity()
+ {
+ _expando = new ExpandoObject()!;
+ }
+
+ public override bool TryGetMember(GetMemberBinder binder, out object? result)
+ {
+ if (_expando.TryGetValue(binder.Name, out var value))
+ {
+ result = value;
+ return true;
+ }
+
+ return base.TryGetMember(binder, out result);
+ }
+
+ public override bool TrySetMember(SetMemberBinder binder, object? value)
+ {
+ _expando[binder.Name] = value!;
+
+ return true;
+ }
+
+ public XmlSchema? GetSchema() => null;
+
+ public void ReadXml(XmlReader reader)
+ {
+ reader.ReadStartElement(_root);
+
+ while (!reader.Name.Equals(_root))
+ {
+ var name = reader.Name;
+
+ reader.MoveToAttribute("type");
+ var typeContent = reader.ReadContentAsString();
+ var underlyingType = Type.GetType(typeContent);
+ reader.MoveToContent();
+ _expando[name] = reader.ReadElementContentAs(underlyingType!, null!)!;
+ }
+ }
+
+ public void WriteXml(XmlWriter writer)
+ {
+ foreach (var key in _expando.Keys)
+ WriteXmlElement(key, _expando[key], writer);
+ }
+
+ private void WriteXmlElement(string key, object value, XmlWriter writer)
+ {
+ writer.WriteStartElement(key);
+ writer.WriteString(value.ToString());
+ writer.WriteEndElement();
+ }
+
+ public void Add(string key, object value) => _expando.Add(key, value);
+
+ public bool ContainsKey(string key) => _expando.ContainsKey(key);
+
+ public ICollection Keys => _expando.Keys;
+
+ public bool Remove(string key) => _expando.Remove(key);
+
+ public bool TryGetValue(string key, out object value) => _expando.TryGetValue(key, out value!);
+
+ public ICollection