diff --git a/.azure/pipelines/fast-pr-validation.yml b/.azure/pipelines/fast-pr-validation.yml
index fe8f93b205..d0045c031e 100644
--- a/.azure/pipelines/fast-pr-validation.yml
+++ b/.azure/pipelines/fast-pr-validation.yml
@@ -1,3 +1,4 @@
+
trigger:
- master
- release/*
@@ -5,31 +6,18 @@ trigger:
jobs:
- template: jobs/default-build.yml
parameters:
+ jobName: PR_FastCheck
+ jobDisplayName: Fast Check
agentOs: Windows
- jobName: Windows_FastCheck
- jobDisplayName: Windows - Fast Check
buildArgs: "/t:FastCheck"
- artifacts:
- publish: false
-- job: RepoBuilds
- pool:
- vmImage: vs2017-win2016
- strategy:
- maxParallel: 3
- matrix:
- DataProtection:
- _FolderName: DataProtection
- WebSockets:
- _FolderName: WebSockets
- steps:
- - script: src/$(_FolderName)/build.cmd -ci
- displayName: Run src/$(_FolderName)/build.cmd
- - task: PublishTestResults@2
- displayName: Publish test results
- condition: always()
- inputs:
- testRunner: vstest
- testResultsFiles: 'src/$(_FolderName)/artifacts/logs/**/*.trx'
+- template: jobs/default-build.yml
+ parameters:
+ jobName: Windows_Build
+ jobDisplayName: "Build: Windows"
+ agentOs: Windows
+ beforeBuild:
+ - powershell: "& ./src/IISIntegration/tools/UpdateIISExpressCertificate.ps1"
+ displayName: Setup IISExpress test certificates
- template: jobs/iisintegration-job.yml
parameters:
TestGroupName: IIS
diff --git a/.azure/pipelines/jobs/default-build.yml b/.azure/pipelines/jobs/default-build.yml
index df7f08f57d..87873f95d8 100644
--- a/.azure/pipelines/jobs/default-build.yml
+++ b/.azure/pipelines/jobs/default-build.yml
@@ -63,6 +63,7 @@ jobs:
- job: ${{ coalesce(parameters.jobName, parameters.agentOs) }}
displayName: ${{ coalesce(parameters.jobDisplayName, parameters.agentOs) }}
dependsOn: ${{ parameters.dependsOn }}
+ timeoutInMinutes: 90
workspace:
clean: all
strategy:
diff --git a/.gitmodules b/.gitmodules
index 50e0b0d516..2e4ed7971c 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -26,18 +26,10 @@
path = modules/Diagnostics
url = https://github.com/aspnet/Diagnostics.git
branch = master
-[submodule "modules/DotNetTools"]
- path = modules/DotNetTools
- url = https://github.com/aspnet/DotNetTools.git
- branch = master
[submodule "modules/Hosting"]
path = modules/Hosting
url = https://github.com/aspnet/Hosting.git
branch = master
-[submodule "modules/HtmlAbstractions"]
- path = modules/HtmlAbstractions
- url = https://github.com/aspnet/HtmlAbstractions.git
- branch = master
[submodule "modules/HttpAbstractions"]
path = modules/HttpAbstractions
url = https://github.com/aspnet/HttpAbstractions.git
@@ -58,10 +50,6 @@
path = modules/JavaScriptServices
url = https://github.com/aspnet/JavaScriptServices.git
branch = master
-[submodule "modules/JsonPatch"]
- path = modules/JsonPatch
- url = https://github.com/aspnet/JsonPatch.git
- branch = master
[submodule "modules/KestrelHttpServer"]
path = modules/KestrelHttpServer
url = https://github.com/aspnet/KestrelHttpServer.git
diff --git a/Directory.Build.props b/Directory.Build.props
index 7968a8b8ad..b121675096 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -17,6 +17,8 @@
replace PackageLicenseUrl with PackageLicenseExpression.
-->
$(NoWarn);NU5125
+
+ $(NoWarn);NU5105
nugetaspnet@microsoft.com
@@ -49,6 +51,16 @@
false
+
+
+ Microsoft400
+ 3PartySHA2
+ Microsoft400
+ NuGet
+ VsixSHA2
+ MicrosoftJAR
+
+
@@ -75,8 +87,16 @@
true
+
+ netcoreapp2.2;net461
+
+
+
+
+
+
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 1013afefeb..a7ad514960 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -1,9 +1,46 @@
+
+ $(AssemblyName)
+ false
+
+
+
+
+
+
+ true
+ $(PackagesInPatch.Contains(' $(PackageId);'))
+
+
+
+
+ true
+
+ false
+
+
+
+
+ $(BaselinePackageVersion).0
+
+ $(BaselinePackageVersion)
+ $(BaselinePackageVersion)
+
+
true
+
+ false
+
$(IsImplementationProject)
true
false
@@ -16,6 +53,7 @@
+
diff --git a/build/CodeSign.props b/build/CodeSign.props
index d7d412b073..8b3a235030 100644
--- a/build/CodeSign.props
+++ b/build/CodeSign.props
@@ -43,6 +43,7 @@
+
diff --git a/build/CodeSign.targets b/build/CodeSign.targets
index 032fbd1c84..13d89fc64f 100644
--- a/build/CodeSign.targets
+++ b/build/CodeSign.targets
@@ -4,7 +4,7 @@
$(CodeSignDependsOn);CollectFileSignInfo
-
+
<_RepositoryProject Remove="@(_RepositoryProject)" />
@@ -12,6 +12,10 @@
RepositoryRoot=%(Repository.RootPath)
%(Repository.Build)
+ <_ShippedRepositoryProject Include="$(MSBuildProjectFullPath)" Condition="'%(ShippedRepository.Identity)' != ''">
+ RepositoryRoot=%(ShippedRepository.RootPath)
+ false
+
@@ -28,21 +32,37 @@
+
+
+
<_FilesToSign Include="@(_RepoFileSignInfo)" Condition="'%(_RepoFileSignInfo.IsFileToSign)' == 'true' AND ('$(_ReposWereBuilt)' == 'true' OR '%(_RepoFileSignInfo.Container)' != '' ) " />
+ <_FilesToSign Include="@(_ShippedRepoFileSignInfo)" Condition="'%(_ShippedRepoFileSignInfo.IsFileToSign)' == 'true' AND '%(_ShippedRepoFileSignInfo.Container)' != '' " />
+
+ <_Temp Remove="@(_Temp)" />
+ <_Temp Include="@(FilesToExcludeFromSigning)" />
+
+
+ <_Temp Remove="@(_Temp)" />
+
+
-
diff --git a/build/PackageArchive.targets b/build/PackageArchive.targets
index 5ba1cadcf8..0eea5b6c15 100644
--- a/build/PackageArchive.targets
+++ b/build/PackageArchive.targets
@@ -8,9 +8,7 @@
-
-
-
+
DotNetRestoreSourcePropsPath=$(GeneratedRestoreSourcesPropsPath);
diff --git a/build/SharedFx.targets b/build/SharedFx.targets
index 0ccf8b7a4e..f172eb9350 100644
--- a/build/SharedFx.targets
+++ b/build/SharedFx.targets
@@ -2,21 +2,22 @@
$(RepositoryRoot)src\Framework\Framework.UnitTests\Framework.UnitTests.csproj
$([MSBuild]::NormalizePath($(UnitTestFxProject)))
- $(CodeSignDependsOn);GetSharedFxFilesToSign
+ $(CodeSignDependsOn);GetSharedFxFilesToSign
_BuildSharedFxProjects;TestSharedFx
$(BuildSharedFxDependsOn);CodeSign
$(IntermediateDir)ar\$(SharedFxRid)\
+ $(GetArtifactInfo);GetFxProjectArtifactInfo
true
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -43,7 +44,7 @@
- <_RestoreGraphProjectInput>@(ProjectToBuild)
+ <_RestoreGraphProjectInput>@(FxProjectToBuild)
$(BuildProperties);
SharedFxRid=$(SharedFxRid);
@@ -58,11 +59,11 @@
Targets="Restore"
Properties="$(SharedFxBuildProperties);RestoreGraphProjectInput=$(_RestoreGraphProjectInput);_DummyTarget=Restore" />
-
-
+
+
+ <_InspectionTargetsFile>$(MSBuildProjectDirectory)\Project.Inspection.targets
+
+
+
+ <_Temp Remove="@(_Temp)" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/artifacts.props b/build/artifacts.props
index 8700fdf2ca..fbea00052b 100644
--- a/build/artifacts.props
+++ b/build/artifacts.props
@@ -175,7 +175,6 @@
-
diff --git a/build/buildorder.props b/build/buildorder.props
index 3ee7ccff69..5a2e14559f 100644
--- a/build/buildorder.props
+++ b/build/buildorder.props
@@ -7,16 +7,12 @@
-
-
-
-
@@ -28,7 +24,6 @@
-
diff --git a/build/dependencies.folderbuilds.props b/build/dependencies.folderbuilds.props
index b4940f76b6..ef8e16b829 100644
--- a/build/dependencies.folderbuilds.props
+++ b/build/dependencies.folderbuilds.props
@@ -1,7 +1,7 @@
- 3.0.0-alpha1-20181108.8
+ 3.0.0-build-20181114.5
3.0.0-alpha1-10717
3.0.0-alpha1-10717
3.0.0-alpha1-10717
diff --git a/build/dependencies.props b/build/dependencies.props
index 985728b25c..73f5d2a0c7 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -31,67 +31,68 @@
4.6.0-preview1-26907-04
4.6.0-preview1-26829-04
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
- 3.0.0-preview-181108-06
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
3.0.0-preview-181109-02
3.0.0-preview-181109-02
@@ -116,10 +117,12 @@
$(KoreBuildVersion)
$(KoreBuildVersion)
+ 2.2.1-build-20181114.3
+
0.9.9
0.10.13
@@ -212,6 +215,7 @@
6.0.1
0.20.0
3.12.1
+ 2.43.0
17.17134.0
3.12.1
1.4.0
diff --git a/build/external-dependencies.props b/build/external-dependencies.props
index 223187a8cc..bc4408f024 100644
--- a/build/external-dependencies.props
+++ b/build/external-dependencies.props
@@ -71,6 +71,7 @@
+
diff --git a/build/repo.props b/build/repo.props
index bdf4c80e1a..b3ebfc33d0 100644
--- a/build/repo.props
+++ b/build/repo.props
@@ -4,6 +4,8 @@
true
+ true
+ false
false
true
@@ -13,8 +15,6 @@
$(RepositoryRoot).deps\build\
$(RepositoryRoot).deps\Signed\Packages\
-
- true
$(RepositoryRoot)eng\signcheck.exclusions.txt
true
@@ -49,6 +49,22 @@
+
+
+
+
+
+
diff --git a/build/repo.targets b/build/repo.targets
index cff950d2cd..2384a606ac 100644
--- a/build/repo.targets
+++ b/build/repo.targets
@@ -15,20 +15,60 @@
$(IntermediateDir)sources.g.props
$(IntermediateDir)branding.g.props
- SetTeamCityBuildNumberToVersion;$(PrepareDependsOn);VerifyPackageArtifactConfig;VerifyExternalDependencyConfig;PrepareOutputPaths
+ SetTeamCityBuildNumberToVersion;$(PrepareDependsOn)
+ $(PrepareDependsOn);VerifyPackageArtifactConfig;VerifyExternalDependencyConfig;PrepareOutputPaths
$(CleanDependsOn);CleanArtifacts;CleanRepoArtifacts
- $(RestoreDependsOn);InstallDotNet
- $(CompileDependsOn);BuildRepositories;BuildSharedFx
- $(PackageDependsOn);CheckExpectedPackagesExist;CodeSign
- $(TestDependsOn);_TestRepositories
- $(GetArtifactInfoDependsOn);ResolveRepoInfo
+ $(RestoreDependsOn);InstallDotNet;RestoreProjects
+ $(CompileDependsOn);BuildProjects
+ $(CompileDependsOn);PackProjects;BuildRepositories;BuildSharedFx
+ $(PackageDependsOn);PackProjects
+ $(PackageDependsOn);CheckExpectedPackagesExist
+ $(PackageDependsOn);CodeSign
+ $(TestDependsOn);TestProjects
+ $(TestDependsOn);_TestRepositories
+ $(GetArtifactInfoDependsOn);GetProjectArtifactInfo
+ $(GetArtifactInfoDependsOn);ResolveRepoInfo
-
+
+
+
+
+
+
+
+ $(MSBuildThisFileDirectory)..\eng\ProjectReferences.props
+
+
+
+
+ @(_ProjectReferenceProvider->'', '%0A ')
+
+
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
$(BuildProperties);MicrosoftNETCoreAppPackageVersion=$(MicrosoftNETCoreAppPackageVersion);
@@ -238,14 +278,15 @@
-
+
-
+
<_UndeclaredPackageArtifact Include="%(ArtifactInfo.PackageId)" Condition="'%(ArtifactInfo.ArtifactType)' == 'NuGetPackage'" />
<_UndeclaredPackageArtifact Remove="@(PackageArtifact)" />
+
+
+
+
+ $(RestoreSources);
+ https://dotnetfeed.blob.core.windows.net/orchestrated-release-2-2/20181110-02/final/index.json;
+
+
$(RestoreSources);
https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json;
diff --git a/build/submodules.props b/build/submodules.props
index 74d00d31ab..45250b9d8c 100644
--- a/build/submodules.props
+++ b/build/submodules.props
@@ -41,18 +41,14 @@
-
-
-
-
@@ -65,7 +61,6 @@
-
diff --git a/build/tasks/RepoTasks.csproj b/build/tasks/RepoTasks.csproj
index 6be1f375d2..20cbf6cce8 100644
--- a/build/tasks/RepoTasks.csproj
+++ b/build/tasks/RepoTasks.csproj
@@ -6,6 +6,7 @@
+
diff --git a/docs/PreparingPatchUpdates.md b/docs/PreparingPatchUpdates.md
new file mode 100644
index 0000000000..34813e8162
--- /dev/null
+++ b/docs/PreparingPatchUpdates.md
@@ -0,0 +1,41 @@
+Preparing new servicing updates
+===============================
+
+In order to prepare this repo to build a new servicing update, the following changes need to be made.
+
+* Increment the patch version in the [version.props](/version.props) file in the repository root.
+
+ ```diff
+ - 7
+ + 8
+ ```
+
+* Update the package archive baselines. This is used to make sure each build of the package archives we give to Azure only contains new files and does
+ not require overwriting existing files. See [src/PackageArchive/ZipManifestGenerator/](/src/PackageArchive/ZipManifestGenerator/README.md) for instructions on how to run this tool.
+
+* Update the package baselines. This is used to ensure packages keep a consistent set of dependencies between releases.
+ See [eng/tools/BaselineGenerator/](/eng/tools/BaselineGenerator/README.md) for instructions on how to run this tool.
+
+* **For packages with source code in this repo (not a submodule):** Update the list of packages in [eng/PatchConfig.props](/eng/PatchConfig.props) to list which packages should be patching in this release.
+
+* **For packages still building from submodules:** Update the list of repositories which will contain changes in [build/submodules.props](/build/submodules.props).
+
+ * `` items represent repos which were released in a previous patch, and will not contain servicing updates in the next patch.
+ * `` items represent repos which will produce new packages in this patch.
+ * It is usually best to move everything to `` and then iteratively add them back to `` as new repos receive approval to patch.
+ * Don't change the `PatchPolicy` attribute. The build system uses this to ensure patching rules are obeyed.
+
+* For each repository still listed as a ``, update the version.props file in that submodule. For example, https://github.com/aspnet/Templating/pull/824
+
+ * The version prefix in repos should match the version of ASP.NET Core.
+ * Exception: SignalR, which is "1.1", not "2.1".
+ * This leaves holes in versioning, which is okay. This may mean you increment the patch value by more than one. Example:
+ * EF Core ships patches in 2.1.4 as "2.1.4"
+ * EF Core does not ship patches in 2.1.5 or 2.1.6
+ * EF Core ships in 2.1.7, therefore, EFCore's version.props file should jump from 2.1.4 to 2.1.7.
+
+ ```diff
+
+ - 2.1.4
+ + 2.1.7
+ ```
diff --git a/eng/Baseline.props b/eng/Baseline.props
new file mode 100644
index 0000000000..b8d8c3cb60
--- /dev/null
+++ b/eng/Baseline.props
@@ -0,0 +1,116 @@
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ 2.2.0
+
+
+
+ 2.2.0
+
+
+
+
+ 2.2.0
+
+
+
+
+
+
+
+
+
+ 2.2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2.2.0
+
+
+
+
+ 2.2.0
+
+
+
+
+
+
+
+
+ 2.2.0
+
+
+
+
+
+
+
+ 2.2.0
+
+
+
+
+
+
+
+ 2.2.0
+
+
+
+
+
+
+
+ 2.2.0
+
+
+
+
+
+
+
+ 2.2.0
+
+
+
+
+
+
+
+ 2.2.0
+
+
+
+
+
+
+ 2.2.0
+
+
+
+
+
+
+
+ 2.2.0
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eng/Dependencies.props b/eng/Dependencies.props
new file mode 100644
index 0000000000..ffa3071b04
--- /dev/null
+++ b/eng/Dependencies.props
@@ -0,0 +1,72 @@
+
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/eng/NuGetPackageVerifier.json b/eng/NuGetPackageVerifier.json
new file mode 100644
index 0000000000..8c6f0eedfa
--- /dev/null
+++ b/eng/NuGetPackageVerifier.json
@@ -0,0 +1,41 @@
+{
+ "adx": {
+ "rules": [
+ "AdxVerificationCompositeRule"
+ ],
+ "packages": {
+ "dotnet-watch": {
+ "packageTypes": [
+ "DotnetTool"
+ ]
+ },
+ "dotnet-sql-cache": {
+ "packageTypes": [
+ "DotnetTool"
+ ]
+ },
+ "dotnet-user-secrets": {
+ "packageTypes": [
+ "DotnetTool"
+ ]
+ },
+ "dotnet-dev-certs": {
+ "packageTypes": [
+ "DotnetTool"
+ ]
+ },
+ "Microsoft.AspNetCore.DeveloperCertificates.XPlat": {
+ "Exclusions": {
+ "DOC_MISSING": {
+ "lib/netcoreapp3.0/Microsoft.AspNetCore.DeveloperCertificates.XPlat.dll": "Docs not required to shipoob package"
+ }
+ }
+ }
+ }
+ },
+ "Default": {
+ "rules": [
+ "DefaultCompositeRule"
+ ]
+ }
+}
diff --git a/eng/PatchConfig.props b/eng/PatchConfig.props
new file mode 100644
index 0000000000..c05b2376c6
--- /dev/null
+++ b/eng/PatchConfig.props
@@ -0,0 +1,14 @@
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+
+
+
+
+ Microsoft.AspNetCore.Server.IIS;
+ Microsoft.AspNetCore.Server.IISIntegration;
+ Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
+
+
+
+
diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props
new file mode 100644
index 0000000000..55d8dc6094
--- /dev/null
+++ b/eng/ProjectReferences.props
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/eng/dependencies.temp.props b/eng/dependencies.temp.props
new file mode 100644
index 0000000000..38aeea4dbe
--- /dev/null
+++ b/eng/dependencies.temp.props
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/eng/targets/CSharp.Common.props b/eng/targets/CSharp.Common.props
new file mode 100644
index 0000000000..b3fc97e2d9
--- /dev/null
+++ b/eng/targets/CSharp.Common.props
@@ -0,0 +1,14 @@
+
+
+
+ 7.2
+
+
+ SHA256
+
+
+
+
+
+
+
diff --git a/eng/targets/CSharp.Common.targets b/eng/targets/CSharp.Common.targets
new file mode 100644
index 0000000000..a7f7b610b6
--- /dev/null
+++ b/eng/targets/CSharp.Common.targets
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/eng/targets/Packaging.targets b/eng/targets/Packaging.targets
new file mode 100644
index 0000000000..2a1427c2de
--- /dev/null
+++ b/eng/targets/Packaging.targets
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+ $(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg
+
+
+
+
+ NuGetPackage
+ $(PackageId)
+ $(PackageVersion)
+ $(RepositoryRoot)
+ true
+
+
+
+
diff --git a/eng/targets/ResolveReferences.targets b/eng/targets/ResolveReferences.targets
new file mode 100644
index 0000000000..caf44207ee
--- /dev/null
+++ b/eng/targets/ResolveReferences.targets
@@ -0,0 +1,135 @@
+
+
+
+
+ ResolveCustomReferences;
+ $(ResolveReferencesDependsOn);
+
+
+
+
+
+ true
+ true
+ true
+ false
+
+
+ true
+ true
+ false
+
+
+
+ <_ImplicitPackageReference Include="@(PackageReference->WithMetadataValue('IsImplicitlyDefined', 'true'))" />
+ <_ExplicitPackageReference Include="@(PackageReference)" Exclude="@(_ImplicitPackageReference)" />
+ <_ExplicitPackageReference Remove="Internal.AspNetCore.Sdk" />
+
+
+
+
+ <_ProjectReferenceByAssemblyName Condition="'$(UseProjectReferences)' == 'true'"
+ Include="@(ProjectReferenceProvider)"
+ Exclude="@(UnusedProjectReferenceProvider)" />
+
+
+
+
+
+
+
+
+
+
+
+ <_LatestPackageReferenceWithVersion Include="@(Reference)" Condition=" '$(UseLatestPackageReferences)' == 'true' ">
+ %(LatestPackageReference.Identity)
+ %(LatestPackageReference.Version)
+
+ <_LatestPackageReferenceWithVersion Remove="@(_LatestPackageReferenceWithVersion)" Condition="'%(Id)' != '%(Identity)' " />
+
+
+
+
+
+
+ <_BaselinePackageReferenceWithVersion Include="@(Reference)" Condition=" '$(IsServicingBuild)' == 'true' OR '$(UseLatestPackageReferences)' != 'true' ">
+ %(BaselinePackageReference.Identity)
+ %(BaselinePackageReference.Version)
+
+
+ <_BaselinePackageReferenceWithVersion Remove="@(_BaselinePackageReferenceWithVersion)" Condition="'%(Id)' != '%(Identity)' " />
+
+
+
+
+
+
+ <_PrivatePackageReferenceWithVersion Include="@(Reference->WithMetadataValue('PrivateAssets', 'All'))">
+ %(LatestPackageReference.Identity)
+ %(LatestPackageReference.Version)
+
+
+ <_PrivatePackageReferenceWithVersion Remove="@(_PrivatePackageReferenceWithVersion)" Condition="'%(Id)' != '%(Identity)' " />
+
+
+
+
+
+
+ <_LatestPackageReferenceWithVersion Remove="@(_LatestPackageReferenceWithVersion)" />
+ <_BaselinePackageReferenceWithVersion Remove="@(_BaselinePackageReferenceWithVersion)" />
+ <_PrivatePackageReferenceWithVersion Remove="@(_PrivatePackageReferenceWithVersion)" />
+ <_ImplicitPackageReference Remove="@(_ImplicitPackageReference)" />
+
+
+
+
+ <_ExplicitPackageReference Remove="@(_ExplicitPackageReference)" />
+
+
+
+
+
+
+
+
+
+ <_TargetFramework Remove="@(_TargetFramework)" />
+ <_TargetFramework Include="$(TargetFramework)" Condition="'$(TargetFramework)' != '' "/>
+ <_TargetFramework Include="$(TargetFrameworks)" Condition="'$(TargetFramework)' == '' "/>
+
+
+
+
+
+
+
+
+
+
+ $([MSBuild]::MakeRelative($(RepositoryRoot), $(MSBuildProjectFullPath)))
+
+
+
+
diff --git a/eng/targets/SharedFx.Common.targets b/eng/targets/SharedFx.Common.targets
index 4f83af1176..61b41d9eee 100644
--- a/eng/targets/SharedFx.Common.targets
+++ b/eng/targets/SharedFx.Common.targets
@@ -332,4 +332,32 @@ This targets file should only be imported by .shfxproj files.
+
+
+
+ $(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg
+ $(PackageOutputPath)$(PackageId).$(PackageVersion).symbols.nupkg
+
+
+
+
+ NuGetPackage
+ $(PackageId)
+ $(PackageVersion)
+ $(RepositoryRoot)
+ $(PackageSigningCertName)
+ true
+ true
+
+
+ NuGetSymbolsPackage
+ $(PackageId)
+ $(PackageVersion)
+ $(RepositoryRoot)
+ $(PackageSigningCertName)
+ true
+ true
+
+
+
diff --git a/eng/tools/BaselineGenerator/BaselineGenerator.csproj b/eng/tools/BaselineGenerator/BaselineGenerator.csproj
new file mode 100644
index 0000000000..625777dbd2
--- /dev/null
+++ b/eng/tools/BaselineGenerator/BaselineGenerator.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ netcoreapp2.1
+ -o "$(MSBuildThisFileDirectory)../../Baseline.props"
+ $(MSBuildProjectDirectory)
+
+
+
+
+
+
+
+
diff --git a/eng/tools/BaselineGenerator/Program.cs b/eng/tools/BaselineGenerator/Program.cs
new file mode 100644
index 0000000000..689653e199
--- /dev/null
+++ b/eng/tools/BaselineGenerator/Program.cs
@@ -0,0 +1,138 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Linq;
+using Microsoft.Extensions.CommandLineUtils;
+using NuGet.Packaging;
+using NuGet.Packaging.Core;
+
+namespace PackageBaselineGenerator
+{
+ ///
+ /// This generates Baseline.props with information about the last RTM release.
+ ///
+ class Program : CommandLineApplication
+ {
+ static void Main(string[] args)
+ {
+ new Program().Execute(args);
+ }
+
+ private readonly CommandOption _source;
+ private readonly CommandOption _output;
+ private readonly CommandOption _feedv3;
+
+ public Program()
+ {
+ _source = Option("-s|--source ", "The NuGet v2 source of the package to fetch", CommandOptionType.SingleValue);
+ _output = Option("-o|--output ", "The generated file output path", CommandOptionType.SingleValue);
+ _feedv3 = Option("--v3", "Sources is nuget v3", CommandOptionType.NoValue);
+
+ Invoke = () => Run().GetAwaiter().GetResult();
+ }
+
+ private async Task Run()
+ {
+ var source = _source.HasValue()
+ ? _source.Value()
+ : "https://www.nuget.org/api/v2/package";
+
+ var packageCache = Environment.GetEnvironmentVariable("NUGET_PACKAGES") != null
+ ? Environment.GetEnvironmentVariable("NUGET_PACKAGES")
+ : Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages");
+
+ var tempDir = Path.Combine(Directory.GetCurrentDirectory(), "obj", "tmp");
+ Directory.CreateDirectory(tempDir);
+
+ var input = XDocument.Load(Path.Combine(Directory.GetCurrentDirectory(), "baseline.xml"));
+
+ var output = _output.HasValue()
+ ? _output.Value()
+ : Path.Combine(Directory.GetCurrentDirectory(), "Baseline.props");
+
+ var baselineVersion = input.Root.Attribute("Version").Value;
+
+ var doc = new XDocument(
+ new XComment(" Auto generated. Do not edit manually, use eng/tools/BaselineGenerator/ to recreate. "),
+ new XElement("Project",
+ new XElement("PropertyGroup",
+ new XElement("MSBuildAllProjects", "$(MSBuildAllProjects);$(MSBuildThisFileFullPath)"),
+ new XElement("AspNetCoreBaselineVersion", baselineVersion))));
+
+ var client = new HttpClient();
+
+ foreach (var pkg in input.Root.Descendants("Package"))
+ {
+ var id = pkg.Attribute("Id").Value;
+ var version = pkg.Attribute("Version").Value;
+ var packageFileName = $"{id}.{version}.nupkg";
+ var nupkgPath = Path.Combine(packageCache, id.ToLowerInvariant(), version, packageFileName);
+ if (!File.Exists(nupkgPath))
+ {
+ nupkgPath = Path.Combine(tempDir, packageFileName);
+ }
+
+ if (!File.Exists(nupkgPath))
+ {
+ var url = _feedv3.HasValue()
+ ? $"{source}/{id.ToLowerInvariant()}/{version}/{id.ToLowerInvariant()}.{version}.nupkg"
+ : $"{source}/{id}/{version}";
+ Console.WriteLine($"Downloading {url}");
+
+ var response = await client.GetStreamAsync(url);
+
+ using (var file = File.Create(nupkgPath))
+ {
+ await response.CopyToAsync(file);
+ }
+ }
+
+
+ using (var reader = new PackageArchiveReader(nupkgPath))
+ {
+ var first = true;
+ foreach (var group in reader.NuspecReader.GetDependencyGroups())
+ {
+ if (first)
+ {
+ first = false;
+ doc.Root.Add(new XComment($" Package: {id}"));
+
+ var propertyGroup = new XElement("PropertyGroup",
+ new XAttribute("Condition", $" '$(PackageId)' == '{id}' "),
+ new XElement("BaselinePackageVersion", version));
+ doc.Root.Add(propertyGroup);
+ }
+
+ var itemGroup = new XElement("ItemGroup", new XAttribute("Condition", $" '$(PackageId)' == '{id}' AND '$(TargetFramework)' == '{group.TargetFramework.GetShortFolderName()}' "));
+ doc.Root.Add(itemGroup);
+
+ foreach (var dependency in group.Packages)
+ {
+ itemGroup.Add(new XElement("BaselinePackageReference", new XAttribute("Include", dependency.Id), new XAttribute("Version", dependency.VersionRange.ToString())));
+ }
+ }
+ }
+ }
+
+ var settings = new XmlWriterSettings
+ {
+ OmitXmlDeclaration = true,
+ Encoding = Encoding.UTF8,
+ Indent = true,
+ };
+ using (var writer = XmlWriter.Create(output, settings))
+ {
+ doc.Save(writer);
+ }
+
+ return 0;
+ }
+ }
+}
diff --git a/eng/tools/BaselineGenerator/README.md b/eng/tools/BaselineGenerator/README.md
new file mode 100644
index 0000000000..1afd97d1b5
--- /dev/null
+++ b/eng/tools/BaselineGenerator/README.md
@@ -0,0 +1,10 @@
+BaselineGenerator
+=================
+
+This tool is used to generate an MSBuild file which sets the "baseline" against which servicing updates are built.
+
+## Usage
+
+1. Add to the [baseline.xml](./baseline.xml) a list of package ID's and their latest released versions. The source of this information can typically
+ be found in the build.xml file generated during ProdCon builds. See https://github.com/dotnet/versions/blob/master/build-info/dotnet/product/cli/release/2.1.6/build.xml for example.
+2. Run `dotnet run` on this project.
diff --git a/eng/tools/BaselineGenerator/baseline.xml b/eng/tools/BaselineGenerator/baseline.xml
new file mode 100644
index 0000000000..f6ec31acb2
--- /dev/null
+++ b/eng/tools/BaselineGenerator/baseline.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/eng/tools/Directory.Build.props b/eng/tools/Directory.Build.props
new file mode 100644
index 0000000000..fda3ea0cbe
--- /dev/null
+++ b/eng/tools/Directory.Build.props
@@ -0,0 +1,3 @@
+
+
+
diff --git a/eng/tools/Directory.Build.targets b/eng/tools/Directory.Build.targets
new file mode 100644
index 0000000000..f75adf7e4d
--- /dev/null
+++ b/eng/tools/Directory.Build.targets
@@ -0,0 +1,2 @@
+
+
diff --git a/eng/tools/tools.sln b/eng/tools/tools.sln
new file mode 100644
index 0000000000..527e47cbc3
--- /dev/null
+++ b/eng/tools/tools.sln
@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26124.0
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaselineGenerator", "BaselineGenerator\BaselineGenerator.csproj", "{CF76A947-3A72-4824-87E6-BF029D84218B}"
+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(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Debug|x64.Build.0 = Debug|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Debug|x86.Build.0 = Debug|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Release|x64.ActiveCfg = Release|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Release|x64.Build.0 = Release|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Release|x86.ActiveCfg = Release|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/global.json b/global.json
index 9c74e979dc..6df6220662 100644
--- a/global.json
+++ b/global.json
@@ -3,6 +3,6 @@
"version": "3.0.100-preview-009750"
},
"msbuild-sdks": {
- "Internal.AspNetCore.Sdk": "3.0.0-alpha1-20181114.4"
+ "Internal.AspNetCore.Sdk": "3.0.0-build-20181114.5"
}
}
diff --git a/korebuild-lock.txt b/korebuild-lock.txt
index 87afe665b0..73613543d0 100644
--- a/korebuild-lock.txt
+++ b/korebuild-lock.txt
@@ -1,2 +1,2 @@
-version:3.0.0-alpha1-20181114.4
-commithash:576bd9858150e9aec1c3527dc68ebe9f69d55b5a
+version:3.0.0-build-20181114.5
+commithash:880e9a204d4ee4a18dfd83c9fb05a192a28bca60
diff --git a/modules/DotNetTools b/modules/DotNetTools
deleted file mode 160000
index a24b4ee459..0000000000
--- a/modules/DotNetTools
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit a24b4ee4598b425d9e8b2f041be508c32eb41cf5
diff --git a/modules/HtmlAbstractions b/modules/HtmlAbstractions
deleted file mode 160000
index 45b7e2ddb7..0000000000
--- a/modules/HtmlAbstractions
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 45b7e2ddb796f0cb909d8f04bc4b5a5cf13b5e46
diff --git a/modules/JsonPatch b/modules/JsonPatch
deleted file mode 160000
index bf9fd0d106..0000000000
--- a/modules/JsonPatch
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit bf9fd0d10667e605d1df45f89e198be7f3bd1138
diff --git a/scripts/UpdateDependencies.ps1 b/scripts/UpdateDependencies.ps1
index 7aeb8fd3f8..6adaea833e 100755
--- a/scripts/UpdateDependencies.ps1
+++ b/scripts/UpdateDependencies.ps1
@@ -56,7 +56,6 @@ foreach ($package in $remoteDeps.SelectNodes('//Package')) {
if (-not $NoCommit) {
$currentBranch = Invoke-Block { & git rev-parse --abbrev-ref HEAD }
-
$destinationBranch = "dotnetbot/UpdateDeps"
Invoke-Block { & git checkout -tb $destinationBranch "origin/$GithubUpstreamBranch" }
}
diff --git a/src/DataProtection/Abstractions/src/Microsoft.AspNetCore.DataProtection.Abstractions.csproj b/src/DataProtection/Abstractions/src/Microsoft.AspNetCore.DataProtection.Abstractions.csproj
index 94dc1366dd..021b3fde2c 100644
--- a/src/DataProtection/Abstractions/src/Microsoft.AspNetCore.DataProtection.Abstractions.csproj
+++ b/src/DataProtection/Abstractions/src/Microsoft.AspNetCore.DataProtection.Abstractions.csproj
@@ -15,7 +15,7 @@ Microsoft.AspNetCore.DataProtection.IDataProtector
-
+
diff --git a/src/DataProtection/Abstractions/test/Microsoft.AspNetCore.DataProtection.Abstractions.Tests.csproj b/src/DataProtection/Abstractions/test/Microsoft.AspNetCore.DataProtection.Abstractions.Tests.csproj
index b01ec90350..781387c466 100644
--- a/src/DataProtection/Abstractions/test/Microsoft.AspNetCore.DataProtection.Abstractions.Tests.csproj
+++ b/src/DataProtection/Abstractions/test/Microsoft.AspNetCore.DataProtection.Abstractions.Tests.csproj
@@ -5,8 +5,8 @@
-
-
+
+
diff --git a/src/DataProtection/AzureKeyVault/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj b/src/DataProtection/AzureKeyVault/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj
index 86b9195c60..42aa2edb13 100644
--- a/src/DataProtection/AzureKeyVault/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj
+++ b/src/DataProtection/AzureKeyVault/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj
@@ -9,12 +9,9 @@
-
-
-
-
-
-
+
+
+
diff --git a/src/DataProtection/AzureKeyVault/test/Microsoft.AspNetCore.DataProtection.AzureKeyVault.Tests.csproj b/src/DataProtection/AzureKeyVault/test/Microsoft.AspNetCore.DataProtection.AzureKeyVault.Tests.csproj
index 06993140cc..879b9042a1 100644
--- a/src/DataProtection/AzureKeyVault/test/Microsoft.AspNetCore.DataProtection.AzureKeyVault.Tests.csproj
+++ b/src/DataProtection/AzureKeyVault/test/Microsoft.AspNetCore.DataProtection.AzureKeyVault.Tests.csproj
@@ -6,12 +6,9 @@
-
-
-
-
-
-
+
+
+
diff --git a/src/DataProtection/AzureStorage/src/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj b/src/DataProtection/AzureStorage/src/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj
index 368e6a9934..d65f55a425 100644
--- a/src/DataProtection/AzureStorage/src/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj
+++ b/src/DataProtection/AzureStorage/src/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj
@@ -9,11 +9,8 @@
-
-
-
-
-
+
+
diff --git a/src/DataProtection/AzureStorage/test/Microsoft.AspNetCore.DataProtection.AzureStorage.Tests.csproj b/src/DataProtection/AzureStorage/test/Microsoft.AspNetCore.DataProtection.AzureStorage.Tests.csproj
index 9dd2382f0a..f84e84974e 100644
--- a/src/DataProtection/AzureStorage/test/Microsoft.AspNetCore.DataProtection.AzureStorage.Tests.csproj
+++ b/src/DataProtection/AzureStorage/test/Microsoft.AspNetCore.DataProtection.AzureStorage.Tests.csproj
@@ -6,12 +6,9 @@
-
-
-
-
-
-
+
+
+
diff --git a/src/DataProtection/Cryptography.Internal/test/Microsoft.AspNetCore.Cryptography.Internal.Tests.csproj b/src/DataProtection/Cryptography.Internal/test/Microsoft.AspNetCore.Cryptography.Internal.Tests.csproj
index f0ed7c4f52..29e8a7c294 100644
--- a/src/DataProtection/Cryptography.Internal/test/Microsoft.AspNetCore.Cryptography.Internal.Tests.csproj
+++ b/src/DataProtection/Cryptography.Internal/test/Microsoft.AspNetCore.Cryptography.Internal.Tests.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/src/DataProtection/Cryptography.KeyDerivation/src/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj b/src/DataProtection/Cryptography.KeyDerivation/src/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj
index b4122a7546..3b869383e6 100644
--- a/src/DataProtection/Cryptography.KeyDerivation/src/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj
+++ b/src/DataProtection/Cryptography.KeyDerivation/src/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/src/DataProtection/Cryptography.KeyDerivation/test/Microsoft.AspNetCore.Cryptography.KeyDerivation.Tests.csproj b/src/DataProtection/Cryptography.KeyDerivation/test/Microsoft.AspNetCore.Cryptography.KeyDerivation.Tests.csproj
index caed9fddfa..e7a1be7ece 100644
--- a/src/DataProtection/Cryptography.KeyDerivation/test/Microsoft.AspNetCore.Cryptography.KeyDerivation.Tests.csproj
+++ b/src/DataProtection/Cryptography.KeyDerivation/test/Microsoft.AspNetCore.Cryptography.KeyDerivation.Tests.csproj
@@ -10,8 +10,8 @@
-
-
+
+
diff --git a/src/DataProtection/DataProtection/src/Microsoft.AspNetCore.DataProtection.csproj b/src/DataProtection/DataProtection/src/Microsoft.AspNetCore.DataProtection.csproj
index 1428564533..18aa5ddc40 100644
--- a/src/DataProtection/DataProtection/src/Microsoft.AspNetCore.DataProtection.csproj
+++ b/src/DataProtection/DataProtection/src/Microsoft.AspNetCore.DataProtection.csproj
@@ -14,18 +14,15 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests.csproj b/src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests.csproj
index c371a1aade..3886642178 100644
--- a/src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests.csproj
+++ b/src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests.csproj
@@ -11,12 +11,12 @@
-
-
+
+
-
-
-
+
+
+
diff --git a/src/DataProtection/Directory.Build.props b/src/DataProtection/Directory.Build.props
deleted file mode 100644
index 4a6e505662..0000000000
--- a/src/DataProtection/Directory.Build.props
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/DataProtection/EntityFrameworkCore/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj b/src/DataProtection/EntityFrameworkCore/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj
index 0903521d3f..85f5e47c55 100644
--- a/src/DataProtection/EntityFrameworkCore/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj
+++ b/src/DataProtection/EntityFrameworkCore/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj
@@ -9,15 +9,8 @@
-
-
-
-
-
-
-
-
-
+
+
diff --git a/src/DataProtection/EntityFrameworkCore/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test.csproj b/src/DataProtection/EntityFrameworkCore/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test.csproj
index 928e1a5471..f08eb96c9b 100644
--- a/src/DataProtection/EntityFrameworkCore/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test.csproj
+++ b/src/DataProtection/EntityFrameworkCore/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test.csproj
@@ -5,11 +5,8 @@
-
-
-
-
-
+
+
diff --git a/src/DataProtection/Extensions/src/Microsoft.AspNetCore.DataProtection.Extensions.csproj b/src/DataProtection/Extensions/src/Microsoft.AspNetCore.DataProtection.Extensions.csproj
index e8335310e4..c106a52961 100644
--- a/src/DataProtection/Extensions/src/Microsoft.AspNetCore.DataProtection.Extensions.csproj
+++ b/src/DataProtection/Extensions/src/Microsoft.AspNetCore.DataProtection.Extensions.csproj
@@ -12,11 +12,8 @@
-
-
-
-
-
+
+
diff --git a/src/DataProtection/Extensions/test/Microsoft.AspNetCore.DataProtection.Extensions.Tests.csproj b/src/DataProtection/Extensions/test/Microsoft.AspNetCore.DataProtection.Extensions.Tests.csproj
index 4460b6da12..19284dddfd 100644
--- a/src/DataProtection/Extensions/test/Microsoft.AspNetCore.DataProtection.Extensions.Tests.csproj
+++ b/src/DataProtection/Extensions/test/Microsoft.AspNetCore.DataProtection.Extensions.Tests.csproj
@@ -10,8 +10,9 @@
-
-
+
+
+
diff --git a/src/DataProtection/StackExchangeRedis/src/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.csproj b/src/DataProtection/StackExchangeRedis/src/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.csproj
index 09852c77d0..6e2143d602 100644
--- a/src/DataProtection/StackExchangeRedis/src/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.csproj
+++ b/src/DataProtection/StackExchangeRedis/src/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.csproj
@@ -9,11 +9,8 @@
-
-
-
-
-
+
+
diff --git a/src/DataProtection/StackExchangeRedis/test/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.Tests.csproj b/src/DataProtection/StackExchangeRedis/test/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.Tests.csproj
index 0b028a9ea9..964abdb650 100644
--- a/src/DataProtection/StackExchangeRedis/test/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.Tests.csproj
+++ b/src/DataProtection/StackExchangeRedis/test/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.Tests.csproj
@@ -11,14 +11,11 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/src/DataProtection/SystemWeb/src/Microsoft.AspNetCore.DataProtection.SystemWeb.csproj b/src/DataProtection/SystemWeb/src/Microsoft.AspNetCore.DataProtection.SystemWeb.csproj
index 03629c1de3..603fe68cc8 100644
--- a/src/DataProtection/SystemWeb/src/Microsoft.AspNetCore.DataProtection.SystemWeb.csproj
+++ b/src/DataProtection/SystemWeb/src/Microsoft.AspNetCore.DataProtection.SystemWeb.csproj
@@ -13,11 +13,8 @@
-
-
-
-
-
+
+
diff --git a/src/DataProtection/build/repo.props b/src/DataProtection/build/repo.props
deleted file mode 100644
index b8dd411686..0000000000
--- a/src/DataProtection/build/repo.props
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
- true
- false
- false
- $([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)..\'))
- $(DataProtectionRoot)**\*.csproj
-
-
diff --git a/src/DataProtection/samples/AzureBlob/AzureBlob.csproj b/src/DataProtection/samples/AzureBlob/AzureBlob.csproj
index fa85d4c68a..55e41b6b18 100644
--- a/src/DataProtection/samples/AzureBlob/AzureBlob.csproj
+++ b/src/DataProtection/samples/AzureBlob/AzureBlob.csproj
@@ -6,14 +6,11 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/src/DataProtection/samples/AzureKeyVault/AzureKeyVault.csproj b/src/DataProtection/samples/AzureKeyVault/AzureKeyVault.csproj
index d39e9fd4d7..b577b27124 100644
--- a/src/DataProtection/samples/AzureKeyVault/AzureKeyVault.csproj
+++ b/src/DataProtection/samples/AzureKeyVault/AzureKeyVault.csproj
@@ -6,15 +6,12 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/src/DataProtection/samples/CustomEncryptorSample/CustomEncryptorSample.csproj b/src/DataProtection/samples/CustomEncryptorSample/CustomEncryptorSample.csproj
index 09c632a2fb..de4c5e9e38 100644
--- a/src/DataProtection/samples/CustomEncryptorSample/CustomEncryptorSample.csproj
+++ b/src/DataProtection/samples/CustomEncryptorSample/CustomEncryptorSample.csproj
@@ -6,13 +6,10 @@
-
-
-
-
-
-
-
+
+
+
+
diff --git a/src/DataProtection/samples/KeyManagementSample/KeyManagementSample.csproj b/src/DataProtection/samples/KeyManagementSample/KeyManagementSample.csproj
index 9ee112ee7c..5ec8e7b074 100644
--- a/src/DataProtection/samples/KeyManagementSample/KeyManagementSample.csproj
+++ b/src/DataProtection/samples/KeyManagementSample/KeyManagementSample.csproj
@@ -6,8 +6,8 @@
-
-
+
+
diff --git a/src/DataProtection/samples/NonDISample/NonDISample.csproj b/src/DataProtection/samples/NonDISample/NonDISample.csproj
index 560c00c1e5..04956556af 100644
--- a/src/DataProtection/samples/NonDISample/NonDISample.csproj
+++ b/src/DataProtection/samples/NonDISample/NonDISample.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/src/DataProtection/samples/Redis/Redis.csproj b/src/DataProtection/samples/Redis/Redis.csproj
index 306d4917e8..fe41c02daf 100644
--- a/src/DataProtection/samples/Redis/Redis.csproj
+++ b/src/DataProtection/samples/Redis/Redis.csproj
@@ -6,13 +6,10 @@
-
-
-
-
-
-
-
+
+
+
+
diff --git a/src/DataProtection/version.props b/src/DataProtection/version.props
deleted file mode 100644
index 1f8b85dec9..0000000000
--- a/src/DataProtection/version.props
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
- 3.0.0
- alpha1
- $(VersionPrefix)
- $(VersionPrefix)-$(VersionSuffix)-final
- t000
- $(VersionSuffix)-$(BuildNumber)
-
-
diff --git a/src/Features/JsonPatch/src/Adapters/AdapterFactory.cs b/src/Features/JsonPatch/src/Adapters/AdapterFactory.cs
new file mode 100644
index 0000000000..82aaa56a70
--- /dev/null
+++ b/src/Features/JsonPatch/src/Adapters/AdapterFactory.cs
@@ -0,0 +1,49 @@
+using Microsoft.AspNetCore.JsonPatch.Internal;
+using Newtonsoft.Json.Serialization;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.AspNetCore.JsonPatch.Adapters
+{
+ ///
+ /// The default AdapterFactory to be used for resolving .
+ ///
+ public class AdapterFactory : IAdapterFactory
+ {
+ ///
+ public virtual IAdapter Create(object target, IContractResolver contractResolver)
+ {
+ if (target == null)
+ {
+ throw new ArgumentNullException(nameof(target));
+ }
+
+ if (contractResolver == null)
+ {
+ throw new ArgumentNullException(nameof(contractResolver));
+ }
+
+ var jsonContract = contractResolver.ResolveContract(target.GetType());
+
+ if (target is IList)
+ {
+ return new ListAdapter();
+ }
+ else if (jsonContract is JsonDictionaryContract jsonDictionaryContract)
+ {
+ var type = typeof(DictionaryAdapter<,>).MakeGenericType(jsonDictionaryContract.DictionaryKeyType, jsonDictionaryContract.DictionaryValueType);
+ return (IAdapter)Activator.CreateInstance(type);
+ }
+ else if (jsonContract is JsonDynamicContract)
+ {
+ return new DynamicObjectAdapter();
+ }
+ else
+ {
+ return new PocoAdapter();
+ }
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Adapters/IAdapterFactory.cs b/src/Features/JsonPatch/src/Adapters/IAdapterFactory.cs
new file mode 100644
index 0000000000..43ca1e65b6
--- /dev/null
+++ b/src/Features/JsonPatch/src/Adapters/IAdapterFactory.cs
@@ -0,0 +1,22 @@
+using Microsoft.AspNetCore.JsonPatch.Internal;
+using Newtonsoft.Json.Serialization;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.AspNetCore.JsonPatch.Adapters
+{
+ ///
+ /// Defines the operations used for loading an based on the current object and ContractResolver.
+ ///
+ public interface IAdapterFactory
+ {
+ ///
+ /// Creates an for the current object
+ ///
+ /// The target object
+ /// The current contract resolver
+ /// The needed
+ IAdapter Create(object target, IContractResolver contractResolver);
+ }
+}
diff --git a/src/Features/JsonPatch/src/Adapters/IObjectAdapter.cs b/src/Features/JsonPatch/src/Adapters/IObjectAdapter.cs
new file mode 100644
index 0000000000..e5206bfa0d
--- /dev/null
+++ b/src/Features/JsonPatch/src/Adapters/IObjectAdapter.cs
@@ -0,0 +1,112 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.JsonPatch.Operations;
+
+namespace Microsoft.AspNetCore.JsonPatch.Adapters
+{
+ ///
+ /// Defines the operations that can be performed on a JSON patch document.
+ ///
+ public interface IObjectAdapter
+ {
+ ///
+ /// Using the "add" operation a new value is inserted into the root of the target
+ /// document, into the target array at the specified valid index, or to a target object at
+ /// the specified location.
+ ///
+ /// When adding to arrays, the specified index MUST NOT be greater than the number of elements in the array.
+ /// To append the value to the array, the index of "-" character is used (see [RFC6901]).
+ ///
+ /// When adding to an object, if an object member does not already exist, a new member is added to the object at the
+ /// specified location or if an object member does exist, that member's value is replaced.
+ ///
+ /// The operation object MUST contain a "value" member whose content
+ /// specifies the value to be added.
+ ///
+ /// For example:
+ ///
+ /// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
+ ///
+ /// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-4
+ ///
+ /// The add operation.
+ /// Object to apply the operation to.
+ void Add(Operation operation, object objectToApplyTo);
+
+ ///
+ /// Using the "copy" operation, a value is copied from a specified location to the
+ /// target location.
+ ///
+ /// The operation object MUST contain a "from" member, which references the location in the
+ /// target document to copy the value from.
+ ///
+ /// The "from" location MUST exist for the operation to be successful.
+ ///
+ /// For example:
+ ///
+ /// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
+ ///
+ /// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-7
+ ///
+ /// The copy operation.
+ /// Object to apply the operation to.
+ void Copy(Operation operation, object objectToApplyTo);
+
+ ///
+ /// Using the "move" operation the value at a specified location is removed and
+ /// added to the target location.
+ ///
+ /// The operation object MUST contain a "from" member, which references the location in the
+ /// target document to move the value from.
+ ///
+ /// The "from" location MUST exist for the operation to be successful.
+ ///
+ /// For example:
+ ///
+ /// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
+ ///
+ /// A location cannot be moved into one of its children.
+ ///
+ /// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-6
+ ///
+ /// The move operation.
+ /// Object to apply the operation to.
+ void Move(Operation operation, object objectToApplyTo);
+
+ ///
+ /// Using the "remove" operation the value at the target location is removed.
+ ///
+ /// The target location MUST exist for the operation to be successful.
+ ///
+ /// For example:
+ ///
+ /// { "op": "remove", "path": "/a/b/c" }
+ ///
+ /// If removing an element from an array, any elements above the
+ /// specified index are shifted one position to the left.
+ ///
+ /// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-6
+ ///
+ /// The remove operation.
+ /// Object to apply the operation to.
+ void Remove(Operation operation, object objectToApplyTo);
+
+ ///
+ /// Using the "replace" operation he value at the target location is replaced
+ /// with a new value. The operation object MUST contain a "value" member
+ /// which specifies the replacement value.
+ ///
+ /// The target location MUST exist for the operation to be successful.
+ ///
+ /// For example:
+ ///
+ /// { "op": "replace", "path": "/a/b/c", "value": 42 }
+ ///
+ /// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-6
+ ///
+ /// The replace operation.
+ /// Object to apply the operation to.
+ void Replace(Operation operation, object objectToApplyTo);
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Adapters/IObjectAdapterWithTest.cs b/src/Features/JsonPatch/src/Adapters/IObjectAdapterWithTest.cs
new file mode 100644
index 0000000000..e1b4ce7950
--- /dev/null
+++ b/src/Features/JsonPatch/src/Adapters/IObjectAdapterWithTest.cs
@@ -0,0 +1,32 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.JsonPatch.Operations;
+
+namespace Microsoft.AspNetCore.JsonPatch.Adapters
+{
+ ///
+ /// Defines the operations that can be performed on a JSON patch document, including "test".
+ ///
+ public interface IObjectAdapterWithTest : IObjectAdapter
+ {
+ ///
+ /// Using the "test" operation a value at the target location is compared for
+ /// equality to a specified value.
+ ///
+ /// The operation object MUST contain a "value" member that specifies
+ /// value to be compared to the target location's value.
+ ///
+ /// The target location MUST be equal to the "value" value for the
+ /// operation to be considered successful.
+ ///
+ /// For example:
+ /// { "op": "test", "path": "/a/b/c", "value": "foo" }
+ ///
+ /// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-7
+ ///
+ /// The test operation.
+ /// Object to apply the operation to.
+ void Test(Operation operation, object objectToApplyTo);
+ }
+}
diff --git a/src/Features/JsonPatch/src/Adapters/ObjectAdapter.cs b/src/Features/JsonPatch/src/Adapters/ObjectAdapter.cs
new file mode 100644
index 0000000000..75cc513312
--- /dev/null
+++ b/src/Features/JsonPatch/src/Adapters/ObjectAdapter.cs
@@ -0,0 +1,348 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.JsonPatch.Internal;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Adapters
+{
+ ///
+ public class ObjectAdapter : IObjectAdapterWithTest
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The .
+ /// The for logging .
+ public ObjectAdapter(
+ IContractResolver contractResolver,
+ Action logErrorAction):
+ this(contractResolver, logErrorAction, new AdapterFactory())
+ {
+ }
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The .
+ /// The for logging .
+ /// The to use when creating adaptors.
+ public ObjectAdapter(
+ IContractResolver contractResolver,
+ Action logErrorAction,
+ IAdapterFactory adapterFactory)
+ {
+ ContractResolver = contractResolver ?? throw new ArgumentNullException(nameof(contractResolver));
+ LogErrorAction = logErrorAction;
+ AdapterFactory = adapterFactory ?? throw new ArgumentNullException(nameof(adapterFactory));
+ }
+
+ ///
+ /// Gets or sets the .
+ ///
+ public IContractResolver ContractResolver { get; }
+
+ ///
+ /// Gets or sets the
+ ///
+ public IAdapterFactory AdapterFactory { get; }
+
+ ///
+ /// Action for logging .
+ ///
+ public Action LogErrorAction { get; }
+
+ public void Add(Operation operation, object objectToApplyTo)
+ {
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ Add(operation.path, operation.value, objectToApplyTo, operation);
+ }
+
+ ///
+ /// Add is used by various operations (eg: add, copy, ...), yet through different operations;
+ /// This method allows code reuse yet reporting the correct operation on error
+ ///
+ private void Add(
+ string path,
+ object value,
+ object objectToApplyTo,
+ Operation operation)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ var parsedPath = new ParsedPath(path);
+ var visitor = new ObjectVisitor(parsedPath, ContractResolver, AdapterFactory);
+
+ var target = objectToApplyTo;
+ if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
+ {
+ var error = CreatePathNotFoundError(objectToApplyTo, path, operation, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+
+ if (!adapter.TryAdd(target, parsedPath.LastSegment, ContractResolver, value, out errorMessage))
+ {
+ var error = CreateOperationFailedError(objectToApplyTo, path, operation, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+ }
+
+ public void Move(Operation operation, object objectToApplyTo)
+ {
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ // Get value at 'from' location and add that value to the 'path' location
+ if (TryGetValue(operation.from, objectToApplyTo, operation, out var propertyValue))
+ {
+ // remove that value
+ Remove(operation.from, objectToApplyTo, operation);
+
+ // add that value to the path location
+ Add(operation.path,
+ propertyValue,
+ objectToApplyTo,
+ operation);
+ }
+ }
+
+ public void Remove(Operation operation, object objectToApplyTo)
+ {
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ Remove(operation.path, objectToApplyTo, operation);
+ }
+
+ ///
+ /// Remove is used by various operations (eg: remove, move, ...), yet through different operations;
+ /// This method allows code reuse yet reporting the correct operation on error. The return value
+ /// contains the type of the item that has been removed (and a bool possibly signifying an error)
+ /// This can be used by other methods, like replace, to ensure that we can pass in the correctly
+ /// typed value to whatever method follows.
+ ///
+ private void Remove(string path, object objectToApplyTo, Operation operationToReport)
+ {
+ var parsedPath = new ParsedPath(path);
+ var visitor = new ObjectVisitor(parsedPath, ContractResolver, AdapterFactory);
+
+ var target = objectToApplyTo;
+ if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
+ {
+ var error = CreatePathNotFoundError(objectToApplyTo, path, operationToReport, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+
+ if (!adapter.TryRemove(target, parsedPath.LastSegment, ContractResolver, out errorMessage))
+ {
+ var error = CreateOperationFailedError(objectToApplyTo, path, operationToReport, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+ }
+
+ public void Replace(Operation operation, object objectToApplyTo)
+ {
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ var parsedPath = new ParsedPath(operation.path);
+ var visitor = new ObjectVisitor(parsedPath, ContractResolver, AdapterFactory);
+
+ var target = objectToApplyTo;
+ if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
+ {
+ var error = CreatePathNotFoundError(objectToApplyTo, operation.path, operation, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+
+ if (!adapter.TryReplace(target, parsedPath.LastSegment, ContractResolver, operation.value, out errorMessage))
+ {
+ var error = CreateOperationFailedError(objectToApplyTo, operation.path, operation, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+ }
+
+ public void Copy(Operation operation, object objectToApplyTo)
+ {
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ // Get value at 'from' location and add that value to the 'path' location
+ if (TryGetValue(operation.from, objectToApplyTo, operation, out var propertyValue))
+ {
+ // Create deep copy
+ var copyResult = ConversionResultProvider.CopyTo(propertyValue, propertyValue?.GetType());
+ if (copyResult.CanBeConverted)
+ {
+ Add(operation.path,
+ copyResult.ConvertedInstance,
+ objectToApplyTo,
+ operation);
+ }
+ else
+ {
+ var error = CreateOperationFailedError(objectToApplyTo, operation.path, operation, Resources.FormatCannotCopyProperty(operation.from));
+ ErrorReporter(error);
+ return;
+ }
+ }
+ }
+
+ public void Test(Operation operation, object objectToApplyTo)
+ {
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ var parsedPath = new ParsedPath(operation.path);
+ var visitor = new ObjectVisitor(parsedPath, ContractResolver, AdapterFactory);
+
+ var target = objectToApplyTo;
+ if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
+ {
+ var error = CreatePathNotFoundError(objectToApplyTo, operation.path, operation, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+
+ if (!adapter.TryTest(target, parsedPath.LastSegment, ContractResolver, operation.value, out errorMessage))
+ {
+ var error = CreateOperationFailedError(objectToApplyTo, operation.path, operation, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+ }
+
+ private bool TryGetValue(
+ string fromLocation,
+ object objectToGetValueFrom,
+ Operation operation,
+ out object propertyValue)
+ {
+ if (fromLocation == null)
+ {
+ throw new ArgumentNullException(nameof(fromLocation));
+ }
+
+ if (objectToGetValueFrom == null)
+ {
+ throw new ArgumentNullException(nameof(objectToGetValueFrom));
+ }
+
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ propertyValue = null;
+
+ var parsedPath = new ParsedPath(fromLocation);
+ var visitor = new ObjectVisitor(parsedPath, ContractResolver, AdapterFactory);
+
+ var target = objectToGetValueFrom;
+ if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
+ {
+ var error = CreatePathNotFoundError(objectToGetValueFrom, fromLocation, operation, errorMessage);
+ ErrorReporter(error);
+ return false;
+ }
+
+ if (!adapter.TryGet(target, parsedPath.LastSegment, ContractResolver, out propertyValue, out errorMessage))
+ {
+ var error = CreateOperationFailedError(objectToGetValueFrom, fromLocation, operation, errorMessage);
+ ErrorReporter(error);
+ return false;
+ }
+
+ return true;
+ }
+
+ private Action ErrorReporter
+ {
+ get
+ {
+ return LogErrorAction ?? Internal.ErrorReporter.Default;
+ }
+ }
+
+ private JsonPatchError CreateOperationFailedError(object target, string path, Operation operation, string errorMessage)
+ {
+ return new JsonPatchError(
+ target,
+ operation,
+ errorMessage ?? Resources.FormatCannotPerformOperation(operation.op, path));
+ }
+
+ private JsonPatchError CreatePathNotFoundError(object target, string path, Operation operation, string errorMessage)
+ {
+ return new JsonPatchError(
+ target,
+ operation,
+ errorMessage ?? Resources.FormatTargetLocationNotFound(operation.op, path));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Converters/JsonPatchDocumentConverter.cs b/src/Features/JsonPatch/src/Converters/JsonPatchDocumentConverter.cs
new file mode 100644
index 0000000000..aed9c48474
--- /dev/null
+++ b/src/Features/JsonPatch/src/Converters/JsonPatchDocumentConverter.cs
@@ -0,0 +1,76 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Converters
+{
+ public class JsonPatchDocumentConverter : JsonConverter
+ {
+ public override bool CanConvert(Type objectType)
+ {
+ return true;
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
+ JsonSerializer serializer)
+ {
+ if (objectType != typeof(JsonPatchDocument))
+ {
+ throw new ArgumentException(Resources.FormatParameterMustMatchType("objectType", "JsonPatchDocument"), "objectType");
+ }
+
+ try
+ {
+ if (reader.TokenType == JsonToken.Null)
+ {
+ return null;
+ }
+
+ // load jObject
+ var jObject = JArray.Load(reader);
+
+ // Create target object for Json => list of operations
+ var targetOperations = new List();
+
+ // Create a new reader for this jObject, and set all properties
+ // to match the original reader.
+ var jObjectReader = jObject.CreateReader();
+ jObjectReader.Culture = reader.Culture;
+ jObjectReader.DateParseHandling = reader.DateParseHandling;
+ jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
+ jObjectReader.FloatParseHandling = reader.FloatParseHandling;
+
+ // Populate the object properties
+ serializer.Populate(jObjectReader, targetOperations);
+
+ // container target: the JsonPatchDocument.
+ var container = new JsonPatchDocument(targetOperations, new DefaultContractResolver());
+
+ return container;
+ }
+ catch (Exception ex)
+ {
+ throw new JsonSerializationException(Resources.InvalidJsonPatchDocument, ex);
+ }
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ if (value is IJsonPatchDocument)
+ {
+ var jsonPatchDoc = (IJsonPatchDocument)value;
+ var lst = jsonPatchDoc.GetOperations();
+
+ // write out the operations, no envelope
+ serializer.Serialize(writer, lst);
+ }
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Converters/TypedJsonPatchDocumentConverter.cs b/src/Features/JsonPatch/src/Converters/TypedJsonPatchDocumentConverter.cs
new file mode 100644
index 0000000000..fd779ba4ee
--- /dev/null
+++ b/src/Features/JsonPatch/src/Converters/TypedJsonPatchDocumentConverter.cs
@@ -0,0 +1,64 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Converters
+{
+ public class TypedJsonPatchDocumentConverter : JsonPatchDocumentConverter
+ {
+ public override object ReadJson(
+ JsonReader reader,
+ Type objectType,
+ object existingValue,
+ JsonSerializer serializer)
+ {
+ try
+ {
+ if (reader.TokenType == JsonToken.Null)
+ {
+ return null;
+ }
+
+ var genericType = objectType.GetTypeInfo().GenericTypeArguments[0];
+
+ // load jObject
+ var jObject = JArray.Load(reader);
+
+ // Create target object for Json => list of operations, typed to genericType
+ var genericOperation = typeof(Operation<>);
+ var concreteOperationType = genericOperation.MakeGenericType(genericType);
+
+ var genericList = typeof(List<>);
+ var concreteList = genericList.MakeGenericType(concreteOperationType);
+
+ var targetOperations = Activator.CreateInstance(concreteList);
+
+ //Create a new reader for this jObject, and set all properties to match the original reader.
+ var jObjectReader = jObject.CreateReader();
+ jObjectReader.Culture = reader.Culture;
+ jObjectReader.DateParseHandling = reader.DateParseHandling;
+ jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
+ jObjectReader.FloatParseHandling = reader.FloatParseHandling;
+
+ // Populate the object properties
+ serializer.Populate(jObjectReader, targetOperations);
+
+ // container target: the typed JsonPatchDocument.
+ var container = Activator.CreateInstance(objectType, targetOperations, new DefaultContractResolver());
+
+ return container;
+ }
+ catch (Exception ex)
+ {
+ throw new JsonSerializationException(Resources.InvalidJsonPatchDocument, ex);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Exceptions/JsonPatchException.cs b/src/Features/JsonPatch/src/Exceptions/JsonPatchException.cs
new file mode 100644
index 0000000000..90e080575a
--- /dev/null
+++ b/src/Features/JsonPatch/src/Exceptions/JsonPatchException.cs
@@ -0,0 +1,38 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+
+namespace Microsoft.AspNetCore.JsonPatch.Exceptions
+{
+ public class JsonPatchException : Exception
+ {
+ public Operation FailedOperation { get; private set; }
+ public object AffectedObject { get; private set; }
+
+
+ public JsonPatchException()
+ {
+
+ }
+
+ public JsonPatchException(JsonPatchError jsonPatchError, Exception innerException)
+ : base(jsonPatchError.ErrorMessage, innerException)
+ {
+ FailedOperation = jsonPatchError.Operation;
+ AffectedObject = jsonPatchError.AffectedObject;
+ }
+
+ public JsonPatchException(JsonPatchError jsonPatchError)
+ : this(jsonPatchError, null)
+ {
+ }
+
+ public JsonPatchException(string message, Exception innerException)
+ : base (message, innerException)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Helpers/GetValueResult.cs b/src/Features/JsonPatch/src/Helpers/GetValueResult.cs
new file mode 100644
index 0000000000..e2e739a027
--- /dev/null
+++ b/src/Features/JsonPatch/src/Helpers/GetValueResult.cs
@@ -0,0 +1,30 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.JsonPatch.Helpers
+{
+ ///
+ /// Return value for the helper method used by Copy/Move. Needed to ensure we can make a different
+ /// decision in the calling method when the value is null because it cannot be fetched (HasError = true)
+ /// versus when it actually is null (much like why RemovedPropertyTypeResult is used for returning
+ /// type in the Remove operation).
+ ///
+ public class GetValueResult
+ {
+ public GetValueResult(object propertyValue, bool hasError)
+ {
+ PropertyValue = propertyValue;
+ HasError = hasError;
+ }
+
+ ///
+ /// The value of the property we're trying to get
+ ///
+ public object PropertyValue { get; private set; }
+
+ ///
+ /// HasError: true when an error occurred, the operation didn't complete succesfully
+ ///
+ public bool HasError { get; private set; }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Helpers/JsonPatchProperty.cs b/src/Features/JsonPatch/src/Helpers/JsonPatchProperty.cs
new file mode 100644
index 0000000000..041b0104ac
--- /dev/null
+++ b/src/Features/JsonPatch/src/Helpers/JsonPatchProperty.cs
@@ -0,0 +1,43 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ ///
+ /// Metadata for JsonProperty.
+ ///
+ public class JsonPatchProperty
+ {
+ ///
+ /// Initializes a new instance.
+ ///
+ public JsonPatchProperty(JsonProperty property, object parent)
+ {
+ if (property == null)
+ {
+ throw new ArgumentNullException(nameof(property));
+ }
+
+ if (parent == null)
+ {
+ throw new ArgumentNullException(nameof(parent));
+ }
+
+ Property = property;
+ Parent = parent;
+ }
+
+ ///
+ /// Gets or sets JsonProperty.
+ ///
+ public JsonProperty Property { get; set; }
+
+ ///
+ /// Gets or sets Parent.
+ ///
+ public object Parent { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/IJsonPatchDocument.cs b/src/Features/JsonPatch/src/IJsonPatchDocument.cs
new file mode 100644
index 0000000000..fc5f5bd4d1
--- /dev/null
+++ b/src/Features/JsonPatch/src/IJsonPatchDocument.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.JsonPatch.Operations;
+using System.Collections.Generic;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ public interface IJsonPatchDocument
+ {
+ IContractResolver ContractResolver { get; set; }
+
+ IList GetOperations();
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Internal/ConversionResult.cs b/src/Features/JsonPatch/src/Internal/ConversionResult.cs
new file mode 100644
index 0000000000..77181eb18d
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/ConversionResult.cs
@@ -0,0 +1,17 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public class ConversionResult
+ {
+ public ConversionResult(bool canBeConverted, object convertedInstance)
+ {
+ CanBeConverted = canBeConverted;
+ ConvertedInstance = convertedInstance;
+ }
+
+ public bool CanBeConverted { get; }
+ public object ConvertedInstance { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Internal/ConversionResultProvider.cs b/src/Features/JsonPatch/src/Internal/ConversionResultProvider.cs
new file mode 100644
index 0000000000..3d0b6cc979
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/ConversionResultProvider.cs
@@ -0,0 +1,75 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Reflection;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public static class ConversionResultProvider
+ {
+ public static ConversionResult ConvertTo(object value, Type typeToConvertTo)
+ {
+ if (value == null)
+ {
+ return new ConversionResult(IsNullableType(typeToConvertTo), null);
+ }
+ else if (typeToConvertTo.IsAssignableFrom(value.GetType()))
+ {
+ // No need to convert
+ return new ConversionResult(true, value);
+ }
+ else
+ {
+ try
+ {
+ var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), typeToConvertTo);
+ return new ConversionResult(true, deserialized);
+ }
+ catch
+ {
+ return new ConversionResult(canBeConverted: false, convertedInstance: null);
+ }
+ }
+ }
+
+ public static ConversionResult CopyTo(object value, Type typeToConvertTo)
+ {
+ var targetType = typeToConvertTo;
+ if (value == null)
+ {
+ return new ConversionResult(canBeConverted: true, convertedInstance: null);
+ }
+ else if (typeToConvertTo.IsAssignableFrom(value.GetType()))
+ {
+ // Keep original type
+ targetType = value.GetType();
+ }
+ try
+ {
+ var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), targetType);
+ return new ConversionResult(true, deserialized);
+ }
+ catch
+ {
+ return new ConversionResult(canBeConverted: false, convertedInstance: null);
+ }
+ }
+
+ private static bool IsNullableType(Type type)
+ {
+ var typeInfo = type.GetTypeInfo();
+ if (typeInfo.IsValueType)
+ {
+ // value types are only nullable if they are Nullable
+ return typeInfo.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
+ }
+ else
+ {
+ // reference types are always nullable
+ return true;
+ }
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/DictionaryAdapterOfTU.cs b/src/Features/JsonPatch/src/Internal/DictionaryAdapterOfTU.cs
new file mode 100644
index 0000000000..be2bbffd86
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/DictionaryAdapterOfTU.cs
@@ -0,0 +1,245 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public class DictionaryAdapter : IAdapter
+ {
+ public virtual bool TryAdd(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
+ var key = contract.DictionaryKeyResolver(segment);
+ var dictionary = (IDictionary)target;
+
+ // As per JsonPatch spec, if a key already exists, adding should replace the existing value
+ if (!TryConvertKey(key, out var convertedKey, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryConvertValue(value, out var convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ dictionary[convertedKey] = convertedValue;
+ errorMessage = null;
+ return true;
+ }
+
+ public virtual bool TryGet(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage)
+ {
+ var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
+ var key = contract.DictionaryKeyResolver(segment);
+ var dictionary = (IDictionary)target;
+
+ if (!TryConvertKey(key, out var convertedKey, out errorMessage))
+ {
+ value = null;
+ return false;
+ }
+
+ if (!dictionary.ContainsKey(convertedKey))
+ {
+ value = null;
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ value = dictionary[convertedKey];
+ errorMessage = null;
+ return true;
+ }
+
+ public virtual bool TryRemove(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out string errorMessage)
+ {
+ var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
+ var key = contract.DictionaryKeyResolver(segment);
+ var dictionary = (IDictionary)target;
+
+ if (!TryConvertKey(key, out var convertedKey, out errorMessage))
+ {
+ return false;
+ }
+
+ // As per JsonPatch spec, the target location must exist for remove to be successful
+ if (!dictionary.ContainsKey(convertedKey))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ dictionary.Remove(convertedKey);
+
+ errorMessage = null;
+ return true;
+ }
+
+ public virtual bool TryReplace(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
+ var key = contract.DictionaryKeyResolver(segment);
+ var dictionary = (IDictionary)target;
+
+ if (!TryConvertKey(key, out var convertedKey, out errorMessage))
+ {
+ return false;
+ }
+
+ // As per JsonPatch spec, the target location must exist for remove to be successful
+ if (!dictionary.ContainsKey(convertedKey))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ if (!TryConvertValue(value, out var convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ dictionary[convertedKey] = convertedValue;
+
+ errorMessage = null;
+ return true;
+ }
+
+ public virtual bool TryTest(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
+ var key = contract.DictionaryKeyResolver(segment);
+ var dictionary = (IDictionary)target;
+
+ if (!TryConvertKey(key, out var convertedKey, out errorMessage))
+ {
+ return false;
+ }
+
+ // As per JsonPatch spec, the target location must exist for test to be successful
+ if (!dictionary.ContainsKey(convertedKey))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ if (!TryConvertValue(value, out var convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ var currentValue = dictionary[convertedKey];
+
+ // The target segment does not have an assigned value to compare the test value with
+ if (currentValue == null || string.IsNullOrEmpty(currentValue.ToString()))
+ {
+ errorMessage = Resources.FormatValueForTargetSegmentCannotBeNullOrEmpty(segment);
+ return false;
+ }
+
+ if (!JToken.DeepEquals(JsonConvert.SerializeObject(currentValue), JsonConvert.SerializeObject(convertedValue)))
+ {
+ errorMessage = Resources.FormatValueNotEqualToTestValue(currentValue, value, segment);
+ return false;
+ }
+ else
+ {
+ errorMessage = null;
+ return true;
+ }
+ }
+
+ public virtual bool TryTraverse(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object nextTarget,
+ out string errorMessage)
+ {
+ var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
+ var key = contract.DictionaryKeyResolver(segment);
+ var dictionary = (IDictionary)target;
+
+ if (!TryConvertKey(key, out var convertedKey, out errorMessage))
+ {
+ nextTarget = null;
+ return false;
+ }
+
+ if (dictionary.ContainsKey(convertedKey))
+ {
+ nextTarget = dictionary[convertedKey];
+ errorMessage = null;
+ return true;
+ }
+ else
+ {
+ nextTarget = null;
+ errorMessage = null;
+ return false;
+ }
+ }
+
+ protected virtual bool TryConvertKey(string key, out TKey convertedKey, out string errorMessage)
+ {
+ var conversionResult = ConversionResultProvider.ConvertTo(key, typeof(TKey));
+ if (conversionResult.CanBeConverted)
+ {
+ errorMessage = null;
+ convertedKey = (TKey)conversionResult.ConvertedInstance;
+ return true;
+ }
+ else
+ {
+ errorMessage = Resources.FormatInvalidPathSegment(key);
+ convertedKey = default(TKey);
+ return false;
+ }
+ }
+
+ protected virtual bool TryConvertValue(object value, out TValue convertedValue, out string errorMessage)
+ {
+ var conversionResult = ConversionResultProvider.ConvertTo(value, typeof(TValue));
+ if (conversionResult.CanBeConverted)
+ {
+ errorMessage = null;
+ convertedValue = (TValue)conversionResult.ConvertedInstance;
+ return true;
+ }
+ else
+ {
+ errorMessage = Resources.FormatInvalidValueForProperty(value);
+ convertedValue = default(TValue);
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/DynamicObjectAdapter.cs b/src/Features/JsonPatch/src/Internal/DynamicObjectAdapter.cs
new file mode 100644
index 0000000000..5b3d7d8bdd
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/DynamicObjectAdapter.cs
@@ -0,0 +1,248 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using Microsoft.CSharp.RuntimeBinder;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+using CSharpBinder = Microsoft.CSharp.RuntimeBinder;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public class DynamicObjectAdapter : IAdapter
+ {
+ public virtual bool TryAdd(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ if (!TrySetDynamicObjectProperty(target, contractResolver, segment, value, out errorMessage))
+ {
+ return false;
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public virtual bool TryGet(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage)
+ {
+ if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out value, out errorMessage))
+ {
+ value = null;
+ return false;
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public virtual bool TryRemove(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out string errorMessage)
+ {
+ if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
+ {
+ return false;
+ }
+
+ // Setting the value to "null" will use the default value in case of value types, and
+ // null in case of reference types
+ object value = null;
+ if (property.GetType().GetTypeInfo().IsValueType
+ && Nullable.GetUnderlyingType(property.GetType()) == null)
+ {
+ value = Activator.CreateInstance(property.GetType());
+ }
+
+ if (!TrySetDynamicObjectProperty(target, contractResolver, segment, value, out errorMessage))
+ {
+ return false;
+ }
+
+ errorMessage = null;
+ return true;
+
+ }
+
+ public virtual bool TryReplace(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryConvertValue(value, property.GetType(), out var convertedValue))
+ {
+ errorMessage = Resources.FormatInvalidValueForProperty(value);
+ return false;
+ }
+
+ if (!TryRemove(target, segment, contractResolver, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TrySetDynamicObjectProperty(target, contractResolver, segment, convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public virtual bool TryTest(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryConvertValue(value, property.GetType(), out var convertedValue))
+ {
+ errorMessage = Resources.FormatInvalidValueForProperty(value);
+ return false;
+ }
+
+ if (!JToken.DeepEquals(JsonConvert.SerializeObject(property), JsonConvert.SerializeObject(convertedValue)))
+ {
+ errorMessage = Resources.FormatValueNotEqualToTestValue(property, value, segment);
+ return false;
+ }
+ else
+ {
+ errorMessage = null;
+ return true;
+ }
+ }
+
+ public virtual bool TryTraverse(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object nextTarget,
+ out string errorMessage)
+ {
+ if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
+ {
+ nextTarget = null;
+ return false;
+ }
+ else
+ {
+ nextTarget = property;
+ errorMessage = null;
+ return true;
+ }
+ }
+
+ protected virtual bool TryGetDynamicObjectProperty(
+ object target,
+ IContractResolver contractResolver,
+ string segment,
+ out object value,
+ out string errorMessage)
+ {
+ var jsonDynamicContract = (JsonDynamicContract)contractResolver.ResolveContract(target.GetType());
+
+ var propertyName = jsonDynamicContract.PropertyNameResolver(segment);
+
+ var binder = CSharpBinder.Binder.GetMember(
+ CSharpBinderFlags.None,
+ propertyName,
+ target.GetType(),
+ new List
+ {
+ CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
+ });
+
+ var callsite = CallSite>.Create(binder);
+
+ try
+ {
+ value = callsite.Target(callsite, target);
+ errorMessage = null;
+ return true;
+ }
+ catch (RuntimeBinderException)
+ {
+ value = null;
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+ }
+
+ protected virtual bool TrySetDynamicObjectProperty(
+ object target,
+ IContractResolver contractResolver,
+ string segment,
+ object value,
+ out string errorMessage)
+ {
+ var jsonDynamicContract = (JsonDynamicContract)contractResolver.ResolveContract(target.GetType());
+
+ var propertyName = jsonDynamicContract.PropertyNameResolver(segment);
+
+ var binder = CSharpBinder.Binder.SetMember(
+ CSharpBinderFlags.None,
+ propertyName,
+ target.GetType(),
+ new List
+ {
+ CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
+ CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
+ });
+
+ var callsite = CallSite>.Create(binder);
+
+ try
+ {
+ callsite.Target(callsite, target, value);
+ errorMessage = null;
+ return true;
+ }
+ catch (RuntimeBinderException)
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+ }
+
+ protected virtual bool TryConvertValue(object value, Type propertyType, out object convertedValue)
+ {
+ var conversionResult = ConversionResultProvider.ConvertTo(value, propertyType);
+ if (!conversionResult.CanBeConverted)
+ {
+ convertedValue = null;
+ return false;
+ }
+
+ convertedValue = conversionResult.ConvertedInstance;
+ return true;
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/ErrorReporter.cs b/src/Features/JsonPatch/src/Internal/ErrorReporter.cs
new file mode 100644
index 0000000000..76b55a6144
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/ErrorReporter.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ internal static class ErrorReporter
+ {
+ public static readonly Action Default = (error) =>
+ {
+ throw new JsonPatchException(error);
+ };
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/IAdapter.cs b/src/Features/JsonPatch/src/Internal/IAdapter.cs
new file mode 100644
index 0000000000..ec28131f7d
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/IAdapter.cs
@@ -0,0 +1,51 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public interface IAdapter
+ {
+ bool TryTraverse(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object nextTarget,
+ out string errorMessage);
+
+ bool TryAdd(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage);
+
+ bool TryRemove(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out string errorMessage);
+
+ bool TryGet(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage);
+
+ bool TryReplace(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage);
+
+ bool TryTest(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage);
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/ListAdapter.cs b/src/Features/JsonPatch/src/Internal/ListAdapter.cs
new file mode 100644
index 0000000000..597f7b9f5f
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/ListAdapter.cs
@@ -0,0 +1,349 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Microsoft.Extensions.Internal;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public class ListAdapter : IAdapter
+ {
+ public virtual bool TryAdd(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ var list = (IList)target;
+
+ if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryGetPositionInfo(list, segment, OperationType.Add, out var positionInfo, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryConvertValue(value, typeArgument, segment, out var convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ if (positionInfo.Type == PositionType.EndOfList)
+ {
+ list.Add(convertedValue);
+ }
+ else
+ {
+ list.Insert(positionInfo.Index, convertedValue);
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public virtual bool TryGet(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage)
+ {
+ var list = (IList)target;
+
+ if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
+ {
+ value = null;
+ return false;
+ }
+
+ if (!TryGetPositionInfo(list, segment, OperationType.Get, out var positionInfo, out errorMessage))
+ {
+ value = null;
+ return false;
+ }
+
+ if (positionInfo.Type == PositionType.EndOfList)
+ {
+ value = list[list.Count - 1];
+ }
+ else
+ {
+ value = list[positionInfo.Index];
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public virtual bool TryRemove(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out string errorMessage)
+ {
+ var list = (IList)target;
+
+ if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryGetPositionInfo(list, segment, OperationType.Remove, out var positionInfo, out errorMessage))
+ {
+ return false;
+ }
+
+ if (positionInfo.Type == PositionType.EndOfList)
+ {
+ list.RemoveAt(list.Count - 1);
+ }
+ else
+ {
+ list.RemoveAt(positionInfo.Index);
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public virtual bool TryReplace(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ var list = (IList)target;
+
+ if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryGetPositionInfo(list, segment, OperationType.Replace, out var positionInfo, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryConvertValue(value, typeArgument, segment, out var convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ if (positionInfo.Type == PositionType.EndOfList)
+ {
+ list[list.Count - 1] = convertedValue;
+ }
+ else
+ {
+ list[positionInfo.Index] = convertedValue;
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public virtual bool TryTest(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ var list = (IList)target;
+
+ if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryGetPositionInfo(list, segment, OperationType.Replace, out var positionInfo, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryConvertValue(value, typeArgument, segment, out var convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ var currentValue = list[positionInfo.Index];
+ if (!JToken.DeepEquals(JsonConvert.SerializeObject(currentValue), JsonConvert.SerializeObject(convertedValue)))
+ {
+ errorMessage = Resources.FormatValueAtListPositionNotEqualToTestValue(currentValue, value, positionInfo.Index);
+ return false;
+ }
+ else
+ {
+ errorMessage = null;
+ return true;
+ }
+ }
+
+ public virtual bool TryTraverse(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage)
+ {
+ var list = target as IList;
+ if (list == null)
+ {
+ value = null;
+ errorMessage = null;
+ return false;
+ }
+
+ var index = -1;
+ if (!int.TryParse(segment, out index))
+ {
+ value = null;
+ errorMessage = Resources.FormatInvalidIndexValue(segment);
+ return false;
+ }
+
+ if (index < 0 || index >= list.Count)
+ {
+ value = null;
+ errorMessage = Resources.FormatIndexOutOfBounds(segment);
+ return false;
+ }
+
+ value = list[index];
+ errorMessage = null;
+ return true;
+ }
+
+ protected virtual bool TryConvertValue(
+ object originalValue,
+ Type listTypeArgument,
+ string segment,
+ out object convertedValue,
+ out string errorMessage)
+ {
+ var conversionResult = ConversionResultProvider.ConvertTo(originalValue, listTypeArgument);
+ if (!conversionResult.CanBeConverted)
+ {
+ convertedValue = null;
+ errorMessage = Resources.FormatInvalidValueForProperty(originalValue);
+ return false;
+ }
+
+ convertedValue = conversionResult.ConvertedInstance;
+ errorMessage = null;
+ return true;
+ }
+
+ protected virtual bool TryGetListTypeArgument(IList list, out Type listTypeArgument, out string errorMessage)
+ {
+ // Arrays are not supported as they have fixed size and operations like Add, Insert do not make sense
+ var listType = list.GetType();
+ if (listType.IsArray)
+ {
+ errorMessage = Resources.FormatPatchNotSupportedForArrays(listType.FullName);
+ listTypeArgument = null;
+ return false;
+ }
+ else
+ {
+ var genericList = ClosedGenericMatcher.ExtractGenericInterface(listType, typeof(IList<>));
+ if (genericList == null)
+ {
+ errorMessage = Resources.FormatPatchNotSupportedForNonGenericLists(listType.FullName);
+ listTypeArgument = null;
+ return false;
+ }
+ else
+ {
+ listTypeArgument = genericList.GenericTypeArguments[0];
+ errorMessage = null;
+ return true;
+ }
+ }
+ }
+
+ protected virtual bool TryGetPositionInfo(
+ IList list,
+ string segment,
+ OperationType operationType,
+ out PositionInfo positionInfo,
+ out string errorMessage)
+ {
+ if (segment == "-")
+ {
+ positionInfo = new PositionInfo(PositionType.EndOfList, -1);
+ errorMessage = null;
+ return true;
+ }
+
+ var position = -1;
+ if (int.TryParse(segment, out position))
+ {
+ if (position >= 0 && position < list.Count)
+ {
+ positionInfo = new PositionInfo(PositionType.Index, position);
+ errorMessage = null;
+ return true;
+ }
+ // As per JSON Patch spec, for Add operation the index value representing the number of elements is valid,
+ // where as for other operations like Remove, Replace, Move and Copy the target index MUST exist.
+ else if (position == list.Count && operationType == OperationType.Add)
+ {
+ positionInfo = new PositionInfo(PositionType.EndOfList, -1);
+ errorMessage = null;
+ return true;
+ }
+ else
+ {
+ positionInfo = new PositionInfo(PositionType.OutOfBounds, position);
+ errorMessage = Resources.FormatIndexOutOfBounds(segment);
+ return false;
+ }
+ }
+ else
+ {
+ positionInfo = new PositionInfo(PositionType.Invalid, -1);
+ errorMessage = Resources.FormatInvalidIndexValue(segment);
+ return false;
+ }
+ }
+
+ protected struct PositionInfo
+ {
+ public PositionInfo(PositionType type, int index)
+ {
+ Type = type;
+ Index = index;
+ }
+
+ public PositionType Type { get; }
+ public int Index { get; }
+ }
+
+ protected enum PositionType
+ {
+ Index, // valid index
+ EndOfList, // '-'
+ Invalid, // Ex: not an integer
+ OutOfBounds
+ }
+
+ protected enum OperationType
+ {
+ Add,
+ Remove,
+ Get,
+ Replace
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/ObjectVisitor.cs b/src/Features/JsonPatch/src/Internal/ObjectVisitor.cs
new file mode 100644
index 0000000000..6cb4d7450c
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/ObjectVisitor.cs
@@ -0,0 +1,79 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.JsonPatch.Adapters;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public class ObjectVisitor
+ {
+ private readonly IAdapterFactory _adapterFactory;
+ private readonly IContractResolver _contractResolver;
+ private readonly ParsedPath _path;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The path of the JsonPatch operation
+ /// The .
+ public ObjectVisitor(ParsedPath path, IContractResolver contractResolver)
+ :this(path, contractResolver, new AdapterFactory())
+ {
+ }
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The path of the JsonPatch operation
+ /// The .
+ /// The to use when creating adaptors.
+ public ObjectVisitor(ParsedPath path, IContractResolver contractResolver, IAdapterFactory adapterFactory)
+ {
+ _path = path;
+ _contractResolver = contractResolver ?? throw new ArgumentNullException(nameof(contractResolver));
+ _adapterFactory = adapterFactory ?? throw new ArgumentNullException(nameof(adapterFactory));
+ }
+
+ public bool TryVisit(ref object target, out IAdapter adapter, out string errorMessage)
+ {
+ if (target == null)
+ {
+ adapter = null;
+ errorMessage = null;
+ return false;
+ }
+
+ adapter = SelectAdapter(target);
+
+ // Traverse until the penultimate segment to get the target object and adapter
+ for (var i = 0; i < _path.Segments.Count - 1; i++)
+ {
+ if (!adapter.TryTraverse(target, _path.Segments[i], _contractResolver, out var next, out errorMessage))
+ {
+ adapter = null;
+ return false;
+ }
+
+ // If we hit a null on an interior segment then we need to stop traversing.
+ if (next == null)
+ {
+ adapter = null;
+ return false;
+ }
+
+ target = next;
+ adapter = SelectAdapter(target);
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ private IAdapter SelectAdapter(object targetObject)
+ {
+ return _adapterFactory.Create(targetObject, _contractResolver);
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/ParsedPath.cs b/src/Features/JsonPatch/src/Internal/ParsedPath.cs
new file mode 100644
index 0000000000..8d0e69aa4d
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/ParsedPath.cs
@@ -0,0 +1,92 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public struct ParsedPath
+ {
+ private static readonly string[] Empty = null;
+
+ private readonly string[] _segments;
+
+ public ParsedPath(string path)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ _segments = ParsePath(path);
+ }
+
+ public string LastSegment
+ {
+ get
+ {
+ if (_segments == null || _segments.Length == 0)
+ {
+ return null;
+ }
+
+ return _segments[_segments.Length - 1];
+ }
+ }
+
+ public IReadOnlyList Segments => _segments ?? Empty;
+
+ private static string[] ParsePath(string path)
+ {
+ var strings = new List();
+ var sb = new StringBuilder(path.Length);
+
+ for (var i = 0; i < path.Length; i++)
+ {
+ if (path[i] == '/')
+ {
+ if (sb.Length > 0)
+ {
+ strings.Add(sb.ToString());
+ sb.Length = 0;
+ }
+ }
+ else if (path[i] == '~')
+ {
+ ++i;
+ if (i >= path.Length)
+ {
+ throw new JsonPatchException(Resources.FormatInvalidValueForPath(path), null);
+ }
+
+ if (path[i] == '0')
+ {
+ sb.Append('~');
+ }
+ else if (path[i] == '1')
+ {
+ sb.Append('/');
+ }
+ else
+ {
+ throw new JsonPatchException(Resources.FormatInvalidValueForPath(path), null);
+ }
+ }
+ else
+ {
+ sb.Append(path[i]);
+ }
+ }
+
+ if (sb.Length > 0)
+ {
+ strings.Add(sb.ToString());
+ }
+
+ return strings.ToArray();
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/PathHelpers.cs b/src/Features/JsonPatch/src/Internal/PathHelpers.cs
new file mode 100644
index 0000000000..f0afedb60e
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/PathHelpers.cs
@@ -0,0 +1,32 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+using System;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ internal static class PathHelpers
+ {
+ internal static string ValidateAndNormalizePath(string path)
+ {
+ // check for most common path errors on create. This is not
+ // absolutely necessary, but it allows us to already catch mistakes
+ // on creation of the patch document rather than on execute.
+
+ if (path.Contains("//"))
+ {
+ throw new JsonPatchException(Resources.FormatInvalidValueForPath(path), null);
+ }
+
+ if (!path.StartsWith("/", StringComparison.Ordinal))
+ {
+ return "/" + path;
+ }
+ else
+ {
+ return path;
+ }
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/PocoAdapter.cs b/src/Features/JsonPatch/src/Internal/PocoAdapter.cs
new file mode 100644
index 0000000000..5ba3e5587b
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/PocoAdapter.cs
@@ -0,0 +1,236 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using System.Reflection;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public class PocoAdapter : IAdapter
+ {
+ public virtual bool TryAdd(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ if (!jsonProperty.Writable)
+ {
+ errorMessage = Resources.FormatCannotUpdateProperty(segment);
+ return false;
+ }
+
+ if (!TryConvertValue(value, jsonProperty.PropertyType, out var convertedValue))
+ {
+ errorMessage = Resources.FormatInvalidValueForProperty(value);
+ return false;
+ }
+
+ jsonProperty.ValueProvider.SetValue(target, convertedValue);
+
+ errorMessage = null;
+ return true;
+ }
+
+ public virtual bool TryGet(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage)
+ {
+ if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ value = null;
+ return false;
+ }
+
+ if (!jsonProperty.Readable)
+ {
+ errorMessage = Resources.FormatCannotReadProperty(segment);
+ value = null;
+ return false;
+ }
+
+ value = jsonProperty.ValueProvider.GetValue(target);
+ errorMessage = null;
+ return true;
+ }
+
+ public virtual bool TryRemove(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out string errorMessage)
+ {
+ if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ if (!jsonProperty.Writable)
+ {
+ errorMessage = Resources.FormatCannotUpdateProperty(segment);
+ return false;
+ }
+
+ // Setting the value to "null" will use the default value in case of value types, and
+ // null in case of reference types
+ object value = null;
+ if (jsonProperty.PropertyType.GetTypeInfo().IsValueType
+ && Nullable.GetUnderlyingType(jsonProperty.PropertyType) == null)
+ {
+ value = Activator.CreateInstance(jsonProperty.PropertyType);
+ }
+
+ jsonProperty.ValueProvider.SetValue(target, value);
+
+ errorMessage = null;
+ return true;
+ }
+
+ public virtual bool TryReplace(
+ object target,
+ string segment,
+ IContractResolver
+ contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ if (!jsonProperty.Writable)
+ {
+ errorMessage = Resources.FormatCannotUpdateProperty(segment);
+ return false;
+ }
+
+ if (!TryConvertValue(value, jsonProperty.PropertyType, out var convertedValue))
+ {
+ errorMessage = Resources.FormatInvalidValueForProperty(value);
+ return false;
+ }
+
+ jsonProperty.ValueProvider.SetValue(target, convertedValue);
+
+ errorMessage = null;
+ return true;
+ }
+
+ public virtual bool TryTest(
+ object target,
+ string segment,
+ IContractResolver
+ contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ if (!jsonProperty.Readable)
+ {
+ errorMessage = Resources.FormatCannotReadProperty(segment);
+ return false;
+ }
+
+ if (!TryConvertValue(value, jsonProperty.PropertyType, out var convertedValue))
+ {
+ errorMessage = Resources.FormatInvalidValueForProperty(value);
+ return false;
+ }
+
+ var currentValue = jsonProperty.ValueProvider.GetValue(target);
+ if (!JToken.DeepEquals(JsonConvert.SerializeObject(currentValue), JsonConvert.SerializeObject(convertedValue)))
+ {
+ errorMessage = Resources.FormatValueNotEqualToTestValue(currentValue, value, segment);
+ return false;
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public virtual bool TryTraverse(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage)
+ {
+ if (target == null)
+ {
+ value = null;
+ errorMessage = null;
+ return false;
+ }
+
+ if (TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
+ {
+ value = jsonProperty.ValueProvider.GetValue(target);
+ errorMessage = null;
+ return true;
+ }
+
+ value = null;
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ protected virtual bool TryGetJsonProperty(
+ object target,
+ IContractResolver contractResolver,
+ string segment,
+ out JsonProperty jsonProperty)
+ {
+ if (contractResolver.ResolveContract(target.GetType()) is JsonObjectContract jsonObjectContract)
+ {
+ var pocoProperty = jsonObjectContract
+ .Properties
+ .FirstOrDefault(p => string.Equals(p.PropertyName, segment, StringComparison.OrdinalIgnoreCase));
+
+ if (pocoProperty != null)
+ {
+ jsonProperty = pocoProperty;
+ return true;
+ }
+ }
+
+ jsonProperty = null;
+ return false;
+ }
+
+ protected virtual bool TryConvertValue(object value, Type propertyType, out object convertedValue)
+ {
+ var conversionResult = ConversionResultProvider.ConvertTo(value, propertyType);
+ if (!conversionResult.CanBeConverted)
+ {
+ convertedValue = null;
+ return false;
+ }
+
+ convertedValue = conversionResult.ConvertedInstance;
+ return true;
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/JsonPatchDocument.cs b/src/Features/JsonPatch/src/JsonPatchDocument.cs
new file mode 100644
index 0000000000..1888ea6b4b
--- /dev/null
+++ b/src/Features/JsonPatch/src/JsonPatchDocument.cs
@@ -0,0 +1,271 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.JsonPatch.Adapters;
+using Microsoft.AspNetCore.JsonPatch.Converters;
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+using Microsoft.AspNetCore.JsonPatch.Internal;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ // Implementation details: the purpose of this type of patch document is to allow creation of such
+ // documents for cases where there's no class/DTO to work on. Typical use case: backend not built in
+ // .NET or architecture doesn't contain a shared DTO layer.
+ [JsonConverter(typeof(JsonPatchDocumentConverter))]
+ public class JsonPatchDocument : IJsonPatchDocument
+ {
+ public List Operations { get; private set; }
+
+ [JsonIgnore]
+ public IContractResolver ContractResolver { get; set; }
+
+ public JsonPatchDocument()
+ {
+ Operations = new List();
+ ContractResolver = new DefaultContractResolver();
+ }
+
+ public JsonPatchDocument(List operations, IContractResolver contractResolver)
+ {
+ if (operations == null)
+ {
+ throw new ArgumentNullException(nameof(operations));
+ }
+
+ if (contractResolver == null)
+ {
+ throw new ArgumentNullException(nameof(contractResolver));
+ }
+
+ Operations = operations;
+ ContractResolver = contractResolver;
+ }
+
+ ///
+ /// Add operation. Will result in, for example,
+ /// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
+ ///
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Add(string path, object value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("add", PathHelpers.ValidateAndNormalizePath(path), null, value));
+ return this;
+ }
+
+ ///
+ /// Remove value at target location. Will result in, for example,
+ /// { "op": "remove", "path": "/a/b/c" }
+ ///
+ /// target location
+ ///
+ public JsonPatchDocument Remove(string path)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("remove", PathHelpers.ValidateAndNormalizePath(path), null, null));
+ return this;
+ }
+
+ ///
+ /// Replace value. Will result in, for example,
+ /// { "op": "replace", "path": "/a/b/c", "value": 42 }
+ ///
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Replace(string path, object value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("replace", PathHelpers.ValidateAndNormalizePath(path), null, value));
+ return this;
+ }
+
+ ///
+ /// Test value. Will result in, for example,
+ /// { "op": "test", "path": "/a/b/c", "value": 42 }
+ ///
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Test(string path, object value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("test", PathHelpers.ValidateAndNormalizePath(path), null, value));
+ return this;
+ }
+
+ ///
+ /// Removes value at specified location and add it to the target location. Will result in, for example:
+ /// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
+ ///
+ /// source location
+ /// target location
+ ///
+ public JsonPatchDocument Move(string from, string path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("move", PathHelpers.ValidateAndNormalizePath(path), PathHelpers.ValidateAndNormalizePath(from)));
+ return this;
+ }
+
+ ///
+ /// Copy the value at specified location to the target location. Will result in, for example:
+ /// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
+ ///
+ /// source location
+ /// target location
+ ///
+ public JsonPatchDocument Copy(string from, string path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("copy", PathHelpers.ValidateAndNormalizePath(path), PathHelpers.ValidateAndNormalizePath(from)));
+ return this;
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ public void ApplyTo(object objectToApplyTo)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, null, new AdapterFactory()));
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ /// Action to log errors
+ public void ApplyTo(object objectToApplyTo, Action logErrorAction)
+ {
+ ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, logErrorAction, new AdapterFactory()), logErrorAction);
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ /// IObjectAdapter instance to use when applying
+ /// Action to log errors
+ public void ApplyTo(object objectToApplyTo, IObjectAdapter adapter, Action logErrorAction)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ if (adapter == null)
+ {
+ throw new ArgumentNullException(nameof(adapter));
+ }
+
+ foreach (var op in Operations)
+ {
+ try
+ {
+ op.Apply(objectToApplyTo, adapter);
+ }
+ catch (JsonPatchException jsonPatchException)
+ {
+ var errorReporter = logErrorAction ?? ErrorReporter.Default;
+ errorReporter(new JsonPatchError(objectToApplyTo, op, jsonPatchException.Message));
+
+ // As per JSON Patch spec if an operation results in error, further operations should not be executed.
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ /// IObjectAdapter instance to use when applying
+ public void ApplyTo(object objectToApplyTo, IObjectAdapter adapter)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ if (adapter == null)
+ {
+ throw new ArgumentNullException(nameof(adapter));
+ }
+
+ // apply each operation in order
+ foreach (var op in Operations)
+ {
+ op.Apply(objectToApplyTo, adapter);
+ }
+ }
+
+ IList IJsonPatchDocument.GetOperations()
+ {
+ var allOps = new List();
+
+ if (Operations != null)
+ {
+ foreach (var op in Operations)
+ {
+ var untypedOp = new Operation();
+
+ untypedOp.op = op.op;
+ untypedOp.value = op.value;
+ untypedOp.path = op.path;
+ untypedOp.from = op.from;
+
+ allOps.Add(untypedOp);
+ }
+ }
+
+ return allOps;
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/JsonPatchDocumentOfT.cs b/src/Features/JsonPatch/src/JsonPatchDocumentOfT.cs
new file mode 100644
index 0000000000..e0b4fca240
--- /dev/null
+++ b/src/Features/JsonPatch/src/JsonPatchDocumentOfT.cs
@@ -0,0 +1,884 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq.Expressions;
+using Microsoft.AspNetCore.JsonPatch.Adapters;
+using Microsoft.AspNetCore.JsonPatch.Converters;
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+using Microsoft.AspNetCore.JsonPatch.Internal;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ // Implementation details: the purpose of this type of patch document is to ensure we can do type-checking
+ // when producing a JsonPatchDocument. However, we cannot send this "typed" over the wire, as that would require
+ // including type data in the JsonPatchDocument serialized as JSON (to allow for correct deserialization) - that's
+ // not according to RFC 6902, and would thus break cross-platform compatibility.
+ [JsonConverter(typeof(TypedJsonPatchDocumentConverter))]
+ public class JsonPatchDocument : IJsonPatchDocument where TModel : class
+ {
+ public List> Operations { get; private set; }
+
+ [JsonIgnore]
+ public IContractResolver ContractResolver { get; set; }
+
+ public JsonPatchDocument()
+ {
+ Operations = new List>();
+ ContractResolver = new DefaultContractResolver();
+ }
+
+ // Create from list of operations
+ public JsonPatchDocument(List> operations, IContractResolver contractResolver)
+ {
+ Operations = operations ?? throw new ArgumentNullException(nameof(operations));
+ ContractResolver = contractResolver ?? throw new ArgumentNullException(nameof(contractResolver));
+ }
+
+ ///
+ /// Add operation. Will result in, for example,
+ /// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
+ ///
+ /// value type
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Add(Expression> path, TProp value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "add",
+ GetPath(path, null),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Add value to list at given position
+ ///
+ /// value type
+ /// target location
+ /// value
+ /// position
+ ///
+ public JsonPatchDocument Add(
+ Expression>> path,
+ TProp value,
+ int position)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "add",
+ GetPath(path, position.ToString()),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Add value to the end of the list
+ ///
+ /// value type
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Add(Expression>> path, TProp value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "add",
+ GetPath(path, "-"),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Remove value at target location. Will result in, for example,
+ /// { "op": "remove", "path": "/a/b/c" }
+ ///
+ /// target location
+ ///
+ public JsonPatchDocument Remove(Expression> path)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("remove", GetPath(path, null), from: null));
+
+ return this;
+ }
+
+ ///
+ /// Remove value from list at given position
+ ///
+ /// value type
+ /// target location
+ /// position
+ ///
+ public JsonPatchDocument Remove(Expression>> path, int position)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "remove",
+ GetPath(path, position.ToString()),
+ from: null));
+
+ return this;
+ }
+
+ ///
+ /// Remove value from end of list
+ ///
+ /// value type
+ /// target location
+ ///
+ public JsonPatchDocument Remove(Expression>> path)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "remove",
+ GetPath(path, "-"),
+ from: null));
+
+ return this;
+ }
+
+ ///
+ /// Replace value. Will result in, for example,
+ /// { "op": "replace", "path": "/a/b/c", "value": 42 }
+ ///
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Replace(Expression> path, TProp value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "replace",
+ GetPath(path, null),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Replace value in a list at given position
+ ///
+ /// value type
+ /// target location
+ /// value
+ /// position
+ ///
+ public JsonPatchDocument Replace(Expression>> path,
+ TProp value, int position)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "replace",
+ GetPath(path, position.ToString()),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Replace value at end of a list
+ ///
+ /// value type
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Replace(Expression>> path, TProp value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "replace",
+ GetPath(path, "-"),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Test value. Will result in, for example,
+ /// { "op": "test", "path": "/a/b/c", "value": 42 }
+ ///
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Test(Expression> path, TProp value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "test",
+ GetPath(path, null),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Test value in a list at given position
+ ///
+ /// value type
+ /// target location
+ /// value
+ /// position
+ ///
+ public JsonPatchDocument Test(Expression>> path,
+ TProp value, int position)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "test",
+ GetPath(path, position.ToString()),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Test value at end of a list
+ ///
+ /// value type
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Test(Expression>> path, TProp value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "test",
+ GetPath(path, "-"),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Removes value at specified location and add it to the target location. Will result in, for example:
+ /// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
+ ///
+ /// source location
+ /// target location
+ ///
+ public JsonPatchDocument Move(
+ Expression> from,
+ Expression> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "move",
+ GetPath(path, null),
+ GetPath(from, null)));
+
+ return this;
+ }
+
+ ///
+ /// Move from a position in a list to a new location
+ ///
+ ///
+ /// source location
+ /// position
+ /// target location
+ ///
+ public JsonPatchDocument Move(
+ Expression>> from,
+ int positionFrom,
+ Expression> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "move",
+ GetPath(path, null),
+ GetPath(from, positionFrom.ToString())));
+
+ return this;
+ }
+
+ ///
+ /// Move from a property to a location in a list
+ ///
+ ///
+ /// source location
+ /// target location
+ /// position
+ ///
+ public JsonPatchDocument Move(
+ Expression> from,
+ Expression>> path,
+ int positionTo)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "move",
+ GetPath(path, positionTo.ToString()),
+ GetPath(from, null)));
+
+ return this;
+ }
+
+ ///
+ /// Move from a position in a list to another location in a list
+ ///
+ ///
+ /// source location
+ /// position (source)
+ /// target location
+ /// position (target)
+ ///
+ public JsonPatchDocument Move(
+ Expression>> from,
+ int positionFrom,
+ Expression>> path,
+ int positionTo)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "move",
+ GetPath(path, positionTo.ToString()),
+ GetPath(from, positionFrom.ToString())));
+
+ return this;
+ }
+
+ ///
+ /// Move from a position in a list to the end of another list
+ ///
+ ///
+ /// source location
+ /// position
+ /// target location
+ ///
+ public JsonPatchDocument Move(
+ Expression>> from,
+ int positionFrom,
+ Expression>> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "move",
+ GetPath(path, "-"),
+ GetPath(from, positionFrom.ToString())));
+
+ return this;
+ }
+
+ ///
+ /// Move to the end of a list
+ ///
+ ///
+ /// source location
+ /// target location
+ ///
+ public JsonPatchDocument Move(
+ Expression> from,
+ Expression>> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "move",
+ GetPath(path, "-"),
+ GetPath(from, null)));
+
+ return this;
+ }
+
+ ///
+ /// Copy the value at specified location to the target location. Will result in, for example:
+ /// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
+ ///
+ /// source location
+ /// target location
+ ///
+ public JsonPatchDocument Copy(
+ Expression> from,
+ Expression> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "copy",
+ GetPath(path, null),
+ GetPath(from, null)));
+
+ return this;
+ }
+
+ ///
+ /// Copy from a position in a list to a new location
+ ///
+ ///
+ /// source location
+ /// position
+ /// target location
+ ///
+ public JsonPatchDocument Copy(
+ Expression>> from,
+ int positionFrom,
+ Expression> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "copy",
+ GetPath(path, null),
+ GetPath(from, positionFrom.ToString())));
+
+ return this;
+ }
+
+ ///
+ /// Copy from a property to a location in a list
+ ///
+ ///
+ /// source location
+ /// target location
+ /// position
+ ///
+ public JsonPatchDocument Copy(
+ Expression> from,
+ Expression>> path,
+ int positionTo)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "copy",
+ GetPath(path, positionTo.ToString()),
+ GetPath(from, null)));
+
+ return this;
+ }
+
+ ///
+ /// Copy from a position in a list to a new location in a list
+ ///
+ ///
+ /// source location
+ /// position (source)
+ /// target location
+ /// position (target)
+ ///
+ public JsonPatchDocument Copy(
+ Expression>> from,
+ int positionFrom,
+ Expression>> path,
+ int positionTo)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "copy",
+ GetPath(path, positionTo.ToString()),
+ GetPath(from, positionFrom.ToString())));
+
+ return this;
+ }
+
+ ///
+ /// Copy from a position in a list to the end of another list
+ ///
+ ///
+ /// source location
+ /// position
+ /// target location
+ ///
+ public JsonPatchDocument Copy(
+ Expression>> from,
+ int positionFrom,
+ Expression>> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "copy",
+ GetPath(path, "-"),
+ GetPath(from, positionFrom.ToString())));
+
+ return this;
+ }
+
+ ///
+ /// Copy to the end of a list
+ ///
+ ///
+ /// source location
+ /// target location
+ ///
+ public JsonPatchDocument Copy(
+ Expression> from,
+ Expression>> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "copy",
+ GetPath(path, "-"),
+ GetPath(from, null)));
+
+ return this;
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ public void ApplyTo(TModel objectToApplyTo)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, null, new AdapterFactory()));
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ /// Action to log errors
+ public void ApplyTo(TModel objectToApplyTo, Action logErrorAction)
+ {
+ ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, logErrorAction, new AdapterFactory()), logErrorAction);
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ /// IObjectAdapter instance to use when applying
+ /// Action to log errors
+ public void ApplyTo(TModel objectToApplyTo, IObjectAdapter adapter, Action logErrorAction)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ if (adapter == null)
+ {
+ throw new ArgumentNullException(nameof(adapter));
+ }
+
+ foreach (var op in Operations)
+ {
+ try
+ {
+ op.Apply(objectToApplyTo, adapter);
+ }
+ catch (JsonPatchException jsonPatchException)
+ {
+ var errorReporter = logErrorAction ?? ErrorReporter.Default;
+ errorReporter(new JsonPatchError(objectToApplyTo, op, jsonPatchException.Message));
+
+ // As per JSON Patch spec if an operation results in error, further operations should not be executed.
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ /// IObjectAdapter instance to use when applying
+ public void ApplyTo(TModel objectToApplyTo, IObjectAdapter adapter)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ if (adapter == null)
+ {
+ throw new ArgumentNullException(nameof(adapter));
+ }
+
+ // apply each operation in order
+ foreach (var op in Operations)
+ {
+ op.Apply(objectToApplyTo, adapter);
+ }
+ }
+
+ IList IJsonPatchDocument.GetOperations()
+ {
+ var allOps = new List();
+
+ if (Operations != null)
+ {
+ foreach (var op in Operations)
+ {
+ var untypedOp = new Operation
+ {
+ op = op.op,
+ value = op.value,
+ path = op.path,
+ from = op.from
+ };
+
+ allOps.Add(untypedOp);
+ }
+ }
+
+ return allOps;
+ }
+
+ // Internal for testing
+ internal string GetPath(Expression> expr, string position)
+ {
+ var segments = GetPathSegments(expr.Body);
+ var path = String.Join("/", segments);
+ if (position != null)
+ {
+ path += "/" + position;
+ if (segments.Count == 0)
+ {
+ return path;
+ }
+ }
+
+ return "/" + path;
+ }
+
+ private List GetPathSegments(Expression expr)
+ {
+ var listOfSegments = new List();
+ switch (expr.NodeType)
+ {
+ case ExpressionType.ArrayIndex:
+ var binaryExpression = (BinaryExpression)expr;
+ listOfSegments.AddRange(GetPathSegments(binaryExpression.Left));
+ listOfSegments.Add(binaryExpression.Right.ToString());
+ return listOfSegments;
+
+ case ExpressionType.Call:
+ var methodCallExpression = (MethodCallExpression)expr;
+ listOfSegments.AddRange(GetPathSegments(methodCallExpression.Object));
+ listOfSegments.Add(EvaluateExpression(methodCallExpression.Arguments[0]));
+ return listOfSegments;
+
+ case ExpressionType.Convert:
+ listOfSegments.AddRange(GetPathSegments(((UnaryExpression)expr).Operand));
+ return listOfSegments;
+
+ case ExpressionType.MemberAccess:
+ var memberExpression = expr as MemberExpression;
+ listOfSegments.AddRange(GetPathSegments(memberExpression.Expression));
+ // Get property name, respecting JsonProperty attribute
+ listOfSegments.Add(GetPropertyNameFromMemberExpression(memberExpression));
+ return listOfSegments;
+
+ case ExpressionType.Parameter:
+ // Fits "x => x" (the whole document which is "" as JSON pointer)
+ return listOfSegments;
+
+ default:
+ throw new InvalidOperationException(Resources.FormatExpressionTypeNotSupported(expr));
+ }
+ }
+
+ private string GetPropertyNameFromMemberExpression(MemberExpression memberExpression)
+ {
+ var jsonObjectContract = ContractResolver.ResolveContract(memberExpression.Expression.Type) as JsonObjectContract;
+ if (jsonObjectContract != null)
+ {
+ return jsonObjectContract.Properties
+ .First(jsonProperty => jsonProperty.UnderlyingName == memberExpression.Member.Name)
+ .PropertyName;
+ }
+
+ return null;
+ }
+
+ private static bool ContinueWithSubPath(ExpressionType expressionType)
+ {
+ return (expressionType == ExpressionType.ArrayIndex
+ || expressionType == ExpressionType.Call
+ || expressionType == ExpressionType.Convert
+ || expressionType == ExpressionType.MemberAccess);
+
+ }
+
+ // Evaluates the value of the key or index which may be an int or a string,
+ // or some other expression type.
+ // The expression is converted to a delegate and the result of executing the delegate is returned as a string.
+ private static string EvaluateExpression(Expression expression)
+ {
+ var converted = Expression.Convert(expression, typeof(object));
+ var fakeParameter = Expression.Parameter(typeof(object), null);
+ var lambda = Expression.Lambda>(converted, fakeParameter);
+ var func = lambda.Compile();
+
+ return Convert.ToString(func(null), CultureInfo.InvariantCulture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/JsonPatchError.cs b/src/Features/JsonPatch/src/JsonPatchError.cs
new file mode 100644
index 0000000000..a49af7a4e2
--- /dev/null
+++ b/src/Features/JsonPatch/src/JsonPatchError.cs
@@ -0,0 +1,50 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ ///
+ /// Captures error message and the related entity and the operation that caused it.
+ ///
+ public class JsonPatchError
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The object that is affected by the error.
+ /// The that caused the error.
+ /// The error message.
+ public JsonPatchError(
+ object affectedObject,
+ Operation operation,
+ string errorMessage)
+ {
+ if (errorMessage == null)
+ {
+ throw new ArgumentNullException(nameof(errorMessage));
+ }
+
+ AffectedObject = affectedObject;
+ Operation = operation;
+ ErrorMessage = errorMessage;
+ }
+
+ ///
+ /// Gets the object that is affected by the error.
+ ///
+ public object AffectedObject { get; }
+
+ ///
+ /// Gets the that caused the error.
+ ///
+ public Operation Operation { get; }
+
+ ///
+ /// Gets the error message.
+ ///
+ public string ErrorMessage { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj b/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj
new file mode 100644
index 0000000000..3708e92927
--- /dev/null
+++ b/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj
@@ -0,0 +1,17 @@
+
+
+
+ ASP.NET Core support for JSON PATCH.
+ netstandard2.0
+ $(NoWarn);CS1591
+ true
+ aspnetcore;json;jsonpatch
+
+
+
+
+
+
+
+
+
diff --git a/src/Features/JsonPatch/src/Operations/Operation.cs b/src/Features/JsonPatch/src/Operations/Operation.cs
new file mode 100644
index 0000000000..690ade4776
--- /dev/null
+++ b/src/Features/JsonPatch/src/Operations/Operation.cs
@@ -0,0 +1,82 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.JsonPatch.Adapters;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNetCore.JsonPatch.Operations
+{
+ public class Operation : OperationBase
+ {
+ [JsonProperty("value")]
+ public object value { get; set; }
+
+ public Operation()
+ {
+
+ }
+
+ public Operation(string op, string path, string from, object value)
+ : base(op, path, from)
+ {
+ this.value = value;
+ }
+
+ public Operation(string op, string path, string from)
+ : base(op, path, from)
+ {
+ }
+
+ public void Apply(object objectToApplyTo, IObjectAdapter adapter)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ if (adapter == null)
+ {
+ throw new ArgumentNullException(nameof(adapter));
+ }
+
+ switch (OperationType)
+ {
+ case OperationType.Add:
+ adapter.Add(this, objectToApplyTo);
+ break;
+ case OperationType.Remove:
+ adapter.Remove(this, objectToApplyTo);
+ break;
+ case OperationType.Replace:
+ adapter.Replace(this, objectToApplyTo);
+ break;
+ case OperationType.Move:
+ adapter.Move(this, objectToApplyTo);
+ break;
+ case OperationType.Copy:
+ adapter.Copy(this, objectToApplyTo);
+ break;
+ case OperationType.Test:
+ if (adapter is IObjectAdapterWithTest adapterWithTest)
+ {
+ adapterWithTest.Test(this, objectToApplyTo);
+ break;
+ }
+ else
+ {
+ throw new NotSupportedException(Resources.TestOperationNotSupported);
+ }
+ default:
+ break;
+ }
+ }
+
+ public bool ShouldSerializevalue()
+ {
+ return (OperationType == OperationType.Add
+ || OperationType == OperationType.Replace
+ || OperationType == OperationType.Test);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Operations/OperationBase.cs b/src/Features/JsonPatch/src/Operations/OperationBase.cs
new file mode 100644
index 0000000000..e629e2308d
--- /dev/null
+++ b/src/Features/JsonPatch/src/Operations/OperationBase.cs
@@ -0,0 +1,76 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNetCore.JsonPatch.Operations
+{
+ public class OperationBase
+ {
+ private string _op;
+ private OperationType _operationType;
+
+ [JsonIgnore]
+ public OperationType OperationType
+ {
+ get
+ {
+ return _operationType;
+ }
+ }
+
+ [JsonProperty("path")]
+ public string path { get; set; }
+
+ [JsonProperty("op")]
+ public string op
+ {
+ get
+ {
+ return _op;
+ }
+ set
+ {
+ OperationType result;
+ if (!Enum.TryParse(value, ignoreCase: true, result: out result))
+ {
+ result = OperationType.Invalid;
+ }
+ _operationType = result;
+ _op = value;
+ }
+ }
+
+ [JsonProperty("from")]
+ public string from { get; set; }
+
+ public OperationBase()
+ {
+
+ }
+
+ public OperationBase(string op, string path, string from)
+ {
+ if (op == null)
+ {
+ throw new ArgumentNullException(nameof(op));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ this.op = op;
+ this.path = path;
+ this.from = from;
+ }
+
+ public bool ShouldSerializefrom()
+ {
+ return (OperationType == OperationType.Move
+ || OperationType == OperationType.Copy);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Operations/OperationOfT.cs b/src/Features/JsonPatch/src/Operations/OperationOfT.cs
new file mode 100644
index 0000000000..bd13528775
--- /dev/null
+++ b/src/Features/JsonPatch/src/Operations/OperationOfT.cs
@@ -0,0 +1,94 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.JsonPatch.Adapters;
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+
+namespace Microsoft.AspNetCore.JsonPatch.Operations
+{
+ public class Operation : Operation where TModel : class
+ {
+ public Operation()
+ {
+
+ }
+
+ public Operation(string op, string path, string from, object value)
+ : base(op, path, from)
+ {
+ if (op == null)
+ {
+ throw new ArgumentNullException(nameof(op));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ this.value = value;
+ }
+
+ public Operation(string op, string path, string from)
+ : base(op, path, from)
+ {
+ if (op == null)
+ {
+ throw new ArgumentNullException(nameof(op));
+ }
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ }
+
+ public void Apply(TModel objectToApplyTo, IObjectAdapter adapter)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ if (adapter == null)
+ {
+ throw new ArgumentNullException(nameof(adapter));
+ }
+
+ switch (OperationType)
+ {
+ case OperationType.Add:
+ adapter.Add(this, objectToApplyTo);
+ break;
+ case OperationType.Remove:
+ adapter.Remove(this, objectToApplyTo);
+ break;
+ case OperationType.Replace:
+ adapter.Replace(this, objectToApplyTo);
+ break;
+ case OperationType.Move:
+ adapter.Move(this, objectToApplyTo);
+ break;
+ case OperationType.Copy:
+ adapter.Copy(this, objectToApplyTo);
+ break;
+ case OperationType.Test:
+ if (adapter is IObjectAdapterWithTest adapterWithTest)
+ {
+ adapterWithTest.Test(this, objectToApplyTo);
+ break;
+ }
+ else
+ {
+ throw new JsonPatchException(new JsonPatchError(objectToApplyTo, this, Resources.TestOperationNotSupported));
+ }
+ case OperationType.Invalid:
+ throw new JsonPatchException(
+ Resources.FormatInvalidJsonPatchOperation(op), innerException: null);
+ default:
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Operations/OperationType.cs b/src/Features/JsonPatch/src/Operations/OperationType.cs
new file mode 100644
index 0000000000..725646df3a
--- /dev/null
+++ b/src/Features/JsonPatch/src/Operations/OperationType.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.JsonPatch.Operations
+{
+ public enum OperationType
+ {
+ Add,
+ Remove,
+ Replace,
+ Move,
+ Copy,
+ Test,
+ Invalid
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Properties/AssemblyInfo.cs b/src/Features/JsonPatch/src/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..11fa956b64
--- /dev/null
+++ b/src/Features/JsonPatch/src/Properties/AssemblyInfo.cs
@@ -0,0 +1,7 @@
+
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.JsonPatch.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/Features/JsonPatch/src/Properties/Resources.Designer.cs b/src/Features/JsonPatch/src/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..c314465238
--- /dev/null
+++ b/src/Features/JsonPatch/src/Properties/Resources.Designer.cs
@@ -0,0 +1,338 @@
+//
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ using System.Globalization;
+ using System.Reflection;
+ using System.Resources;
+
+ internal static class Resources
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("Microsoft.AspNetCore.JsonPatch.Resources", typeof(Resources).GetTypeInfo().Assembly);
+
+ ///
+ /// The property at '{0}' could not be copied.
+ ///
+ internal static string CannotCopyProperty
+ {
+ get => GetString("CannotCopyProperty");
+ }
+
+ ///
+ /// The property at '{0}' could not be copied.
+ ///
+ internal static string FormatCannotCopyProperty(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("CannotCopyProperty"), p0);
+
+ ///
+ /// The type of the property at path '{0}' could not be determined.
+ ///
+ internal static string CannotDeterminePropertyType
+ {
+ get => GetString("CannotDeterminePropertyType");
+ }
+
+ ///
+ /// The type of the property at path '{0}' could not be determined.
+ ///
+ internal static string FormatCannotDeterminePropertyType(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("CannotDeterminePropertyType"), p0);
+
+ ///
+ /// The '{0}' operation at path '{1}' could not be performed.
+ ///
+ internal static string CannotPerformOperation
+ {
+ get => GetString("CannotPerformOperation");
+ }
+
+ ///
+ /// The '{0}' operation at path '{1}' could not be performed.
+ ///
+ internal static string FormatCannotPerformOperation(object p0, object p1)
+ => string.Format(CultureInfo.CurrentCulture, GetString("CannotPerformOperation"), p0, p1);
+
+ ///
+ /// The property at '{0}' could not be read.
+ ///
+ internal static string CannotReadProperty
+ {
+ get => GetString("CannotReadProperty");
+ }
+
+ ///
+ /// The property at '{0}' could not be read.
+ ///
+ internal static string FormatCannotReadProperty(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("CannotReadProperty"), p0);
+
+ ///
+ /// The property at path '{0}' could not be updated.
+ ///
+ internal static string CannotUpdateProperty
+ {
+ get => GetString("CannotUpdateProperty");
+ }
+
+ ///
+ /// The property at path '{0}' could not be updated.
+ ///
+ internal static string FormatCannotUpdateProperty(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("CannotUpdateProperty"), p0);
+
+ ///
+ /// The expression '{0}' is not supported. Supported expressions include member access and indexer expressions.
+ ///
+ internal static string ExpressionTypeNotSupported
+ {
+ get => GetString("ExpressionTypeNotSupported");
+ }
+
+ ///
+ /// The expression '{0}' is not supported. Supported expressions include member access and indexer expressions.
+ ///
+ internal static string FormatExpressionTypeNotSupported(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ExpressionTypeNotSupported"), p0);
+
+ ///
+ /// The index value provided by path segment '{0}' is out of bounds of the array size.
+ ///
+ internal static string IndexOutOfBounds
+ {
+ get => GetString("IndexOutOfBounds");
+ }
+
+ ///
+ /// The index value provided by path segment '{0}' is out of bounds of the array size.
+ ///
+ internal static string FormatIndexOutOfBounds(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("IndexOutOfBounds"), p0);
+
+ ///
+ /// The path segment '{0}' is invalid for an array index.
+ ///
+ internal static string InvalidIndexValue
+ {
+ get => GetString("InvalidIndexValue");
+ }
+
+ ///
+ /// The path segment '{0}' is invalid for an array index.
+ ///
+ internal static string FormatInvalidIndexValue(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("InvalidIndexValue"), p0);
+
+ ///
+ /// The JSON patch document was malformed and could not be parsed.
+ ///
+ internal static string InvalidJsonPatchDocument
+ {
+ get => GetString("InvalidJsonPatchDocument");
+ }
+
+ ///
+ /// The JSON patch document was malformed and could not be parsed.
+ ///
+ internal static string FormatInvalidJsonPatchDocument()
+ => GetString("InvalidJsonPatchDocument");
+
+ ///
+ /// Invalid JsonPatch operation '{0}'.
+ ///
+ internal static string InvalidJsonPatchOperation
+ {
+ get => GetString("InvalidJsonPatchOperation");
+ }
+
+ ///
+ /// Invalid JsonPatch operation '{0}'.
+ ///
+ internal static string FormatInvalidJsonPatchOperation(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("InvalidJsonPatchOperation"), p0);
+
+ ///
+ /// The provided path segment '{0}' cannot be converted to the target type.
+ ///
+ internal static string InvalidPathSegment
+ {
+ get => GetString("InvalidPathSegment");
+ }
+
+ ///
+ /// The provided path segment '{0}' cannot be converted to the target type.
+ ///
+ internal static string FormatInvalidPathSegment(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("InvalidPathSegment"), p0);
+
+ ///
+ /// The provided string '{0}' is an invalid path.
+ ///
+ internal static string InvalidValueForPath
+ {
+ get => GetString("InvalidValueForPath");
+ }
+
+ ///
+ /// The provided string '{0}' is an invalid path.
+ ///
+ internal static string FormatInvalidValueForPath(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("InvalidValueForPath"), p0);
+
+ ///
+ /// The value '{0}' is invalid for target location.
+ ///
+ internal static string InvalidValueForProperty
+ {
+ get => GetString("InvalidValueForProperty");
+ }
+
+ ///
+ /// The value '{0}' is invalid for target location.
+ ///
+ internal static string FormatInvalidValueForProperty(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("InvalidValueForProperty"), p0);
+
+ ///
+ /// '{0}' must be of type '{1}'.
+ ///
+ internal static string ParameterMustMatchType
+ {
+ get => GetString("ParameterMustMatchType");
+ }
+
+ ///
+ /// '{0}' must be of type '{1}'.
+ ///
+ internal static string FormatParameterMustMatchType(object p0, object p1)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ParameterMustMatchType"), p0, p1);
+
+ ///
+ /// The type '{0}' which is an array is not supported for json patch operations as it has a fixed size.
+ ///
+ internal static string PatchNotSupportedForArrays
+ {
+ get => GetString("PatchNotSupportedForArrays");
+ }
+
+ ///
+ /// The type '{0}' which is an array is not supported for json patch operations as it has a fixed size.
+ ///
+ internal static string FormatPatchNotSupportedForArrays(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("PatchNotSupportedForArrays"), p0);
+
+ ///
+ /// The type '{0}' which is a non generic list is not supported for json patch operations. Only generic list types are supported.
+ ///
+ internal static string PatchNotSupportedForNonGenericLists
+ {
+ get => GetString("PatchNotSupportedForNonGenericLists");
+ }
+
+ ///
+ /// The type '{0}' which is a non generic list is not supported for json patch operations. Only generic list types are supported.
+ ///
+ internal static string FormatPatchNotSupportedForNonGenericLists(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("PatchNotSupportedForNonGenericLists"), p0);
+
+ ///
+ /// The target location specified by path segment '{0}' was not found.
+ ///
+ internal static string TargetLocationAtPathSegmentNotFound
+ {
+ get => GetString("TargetLocationAtPathSegmentNotFound");
+ }
+
+ ///
+ /// The target location specified by path segment '{0}' was not found.
+ ///
+ internal static string FormatTargetLocationAtPathSegmentNotFound(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("TargetLocationAtPathSegmentNotFound"), p0);
+
+ ///
+ /// For operation '{0}', the target location specified by path '{1}' was not found.
+ ///
+ internal static string TargetLocationNotFound
+ {
+ get => GetString("TargetLocationNotFound");
+ }
+
+ ///
+ /// For operation '{0}', the target location specified by path '{1}' was not found.
+ ///
+ internal static string FormatTargetLocationNotFound(object p0, object p1)
+ => string.Format(CultureInfo.CurrentCulture, GetString("TargetLocationNotFound"), p0, p1);
+
+ ///
+ /// The test operation is not supported.
+ ///
+ internal static string TestOperationNotSupported
+ {
+ get => GetString("TestOperationNotSupported");
+ }
+
+ ///
+ /// The test operation is not supported.
+ ///
+ internal static string FormatTestOperationNotSupported()
+ => GetString("TestOperationNotSupported");
+
+ ///
+ /// The current value '{0}' at position '{2}' is not equal to the test value '{1}'.
+ ///
+ internal static string ValueAtListPositionNotEqualToTestValue
+ {
+ get => GetString("ValueAtListPositionNotEqualToTestValue");
+ }
+
+ ///
+ /// The current value '{0}' at position '{2}' is not equal to the test value '{1}'.
+ ///
+ internal static string FormatValueAtListPositionNotEqualToTestValue(object p0, object p1, object p2)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ValueAtListPositionNotEqualToTestValue"), p0, p1, p2);
+
+ ///
+ /// The value at '{0}' cannot be null or empty to perform the test operation.
+ ///
+ internal static string ValueForTargetSegmentCannotBeNullOrEmpty
+ {
+ get => GetString("ValueForTargetSegmentCannotBeNullOrEmpty");
+ }
+
+ ///
+ /// The value at '{0}' cannot be null or empty to perform the test operation.
+ ///
+ internal static string FormatValueForTargetSegmentCannotBeNullOrEmpty(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ValueForTargetSegmentCannotBeNullOrEmpty"), p0);
+
+ ///
+ /// The current value '{0}' at path '{2}' is not equal to the test value '{1}'.
+ ///
+ internal static string ValueNotEqualToTestValue
+ {
+ get => GetString("ValueNotEqualToTestValue");
+ }
+
+ ///
+ /// The current value '{0}' at path '{2}' is not equal to the test value '{1}'.
+ ///
+ internal static string FormatValueNotEqualToTestValue(object p0, object p1, object p2)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ValueNotEqualToTestValue"), p0, p1, p2);
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+
+ System.Diagnostics.Debug.Assert(value != null);
+
+ if (formatterNames != null)
+ {
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Resources.resx b/src/Features/JsonPatch/src/Resources.resx
new file mode 100644
index 0000000000..87cc399c62
--- /dev/null
+++ b/src/Features/JsonPatch/src/Resources.resx
@@ -0,0 +1,186 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ The property at '{0}' could not be copied.
+
+
+ The type of the property at path '{0}' could not be determined.
+
+
+ The '{0}' operation at path '{1}' could not be performed.
+
+
+ The property at '{0}' could not be read.
+
+
+ The property at path '{0}' could not be updated.
+
+
+ The expression '{0}' is not supported. Supported expressions include member access and indexer expressions.
+
+
+ The index value provided by path segment '{0}' is out of bounds of the array size.
+
+
+ The path segment '{0}' is invalid for an array index.
+
+
+ The JSON patch document was malformed and could not be parsed.
+
+
+ Invalid JsonPatch operation '{0}'.
+
+
+ The provided path segment '{0}' cannot be converted to the target type.
+
+
+ The provided string '{0}' is an invalid path.
+
+
+ The value '{0}' is invalid for target location.
+
+
+ '{0}' must be of type '{1}'.
+
+
+ The type '{0}' which is an array is not supported for json patch operations as it has a fixed size.
+
+
+ The type '{0}' which is a non generic list is not supported for json patch operations. Only generic list types are supported.
+
+
+ The target location specified by path segment '{0}' was not found.
+
+
+ For operation '{0}', the target location specified by path '{1}' was not found.
+
+
+ The test operation is not supported.
+
+
+ The current value '{0}' at position '{2}' is not equal to the test value '{1}'.
+
+
+ The value at '{0}' cannot be null or empty to perform the test operation.
+
+
+ The current value '{0}' at path '{2}' is not equal to the test value '{1}'.
+
+
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/baseline.netcore.json b/src/Features/JsonPatch/src/baseline.netcore.json
new file mode 100644
index 0000000000..3d90a8f017
--- /dev/null
+++ b/src/Features/JsonPatch/src/baseline.netcore.json
@@ -0,0 +1,1985 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.JsonPatch, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": [
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.JsonPatchProperty",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Property",
+ "Parameters": [],
+ "ReturnType": "Newtonsoft.Json.Serialization.JsonProperty",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_Property",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Newtonsoft.Json.Serialization.JsonProperty"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Parent",
+ "Parameters": [],
+ "ReturnType": "System.Object",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_Parent",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "property",
+ "Type": "Newtonsoft.Json.Serialization.JsonProperty"
+ },
+ {
+ "Name": "parent",
+ "Type": "System.Object"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_ContractResolver",
+ "Parameters": [],
+ "ReturnType": "Newtonsoft.Json.Serialization.IContractResolver",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_ContractResolver",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Newtonsoft.Json.Serialization.IContractResolver"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetOperations",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.IList",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Operations",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.List",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_ContractResolver",
+ "Parameters": [],
+ "ReturnType": "Newtonsoft.Json.Serialization.IContractResolver",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_ContractResolver",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Newtonsoft.Json.Serialization.IContractResolver"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Add",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Remove",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Replace",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Test",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyTo",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyTo",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "logErrorAction",
+ "Type": "System.Action"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyTo",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "adapter",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "operations",
+ "Type": "System.Collections.Generic.List"
+ },
+ {
+ "Name": "contractResolver",
+ "Type": "Newtonsoft.Json.Serialization.IContractResolver"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Operations",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.List>",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_ContractResolver",
+ "Parameters": [],
+ "ReturnType": "Newtonsoft.Json.Serialization.IContractResolver",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_ContractResolver",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Newtonsoft.Json.Serialization.IContractResolver"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Add",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Add",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ },
+ {
+ "Name": "position",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Add",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Remove",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Remove",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "position",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Remove",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Replace",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Replace",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ },
+ {
+ "Name": "position",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Replace",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Test",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Test",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ },
+ {
+ "Name": "position",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Test",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionFrom",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionTo",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionFrom",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionTo",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionFrom",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionFrom",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionTo",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionFrom",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionTo",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionFrom",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyTo",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyTo",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "T0"
+ },
+ {
+ "Name": "logErrorAction",
+ "Type": "System.Action"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyTo",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "T0"
+ },
+ {
+ "Name": "adapter",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "operations",
+ "Type": "System.Collections.Generic.List>"
+ },
+ {
+ "Name": "contractResolver",
+ "Type": "Newtonsoft.Json.Serialization.IContractResolver"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": [
+ {
+ "ParameterName": "TModel",
+ "ParameterPosition": 0,
+ "Class": true,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.JsonPatchError",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_AffectedObject",
+ "Parameters": [],
+ "ReturnType": "System.Object",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Operation",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.Operations.Operation",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_ErrorMessage",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "affectedObject",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "errorMessage",
+ "Type": "System.String"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Operations.Operation",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.JsonPatch.Operations.OperationBase",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_value",
+ "Parameters": [],
+ "ReturnType": "System.Object",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_value",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Apply",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "adapter",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ShouldSerializevalue",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "op",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "from",
+ "Type": "System.String"
+ },
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "op",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "from",
+ "Type": "System.String"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Operations.OperationBase",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_OperationType",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.Operations.OperationType",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_path",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_path",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_op",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_op",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_from",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_from",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ShouldSerializefrom",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "op",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "from",
+ "Type": "System.String"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Operations.Operation",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.JsonPatch.Operations.Operation",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Apply",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "T0"
+ },
+ {
+ "Name": "adapter",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "op",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "from",
+ "Type": "System.String"
+ },
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "op",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "from",
+ "Type": "System.String"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": [
+ {
+ "ParameterName": "TModel",
+ "ParameterPosition": 0,
+ "Class": true,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Operations.OperationType",
+ "Visibility": "Public",
+ "Kind": "Enumeration",
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Field",
+ "Name": "Add",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "0"
+ },
+ {
+ "Kind": "Field",
+ "Name": "Remove",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "1"
+ },
+ {
+ "Kind": "Field",
+ "Name": "Replace",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "2"
+ },
+ {
+ "Kind": "Field",
+ "Name": "Move",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "3"
+ },
+ {
+ "Kind": "Field",
+ "Name": "Copy",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "4"
+ },
+ {
+ "Kind": "Field",
+ "Name": "Test",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "5"
+ },
+ {
+ "Kind": "Field",
+ "Name": "Invalid",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "6"
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Helpers.GetValueResult",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_PropertyValue",
+ "Parameters": [],
+ "ReturnType": "System.Object",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_HasError",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "propertyValue",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "hasError",
+ "Type": "System.Boolean"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Exceptions.JsonPatchException",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "System.Exception",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_FailedOperation",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.Operations.Operation",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_AffectedObject",
+ "Parameters": [],
+ "ReturnType": "System.Object",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "jsonPatchError",
+ "Type": "Microsoft.AspNetCore.JsonPatch.JsonPatchError"
+ },
+ {
+ "Name": "innerException",
+ "Type": "System.Exception"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "jsonPatchError",
+ "Type": "Microsoft.AspNetCore.JsonPatch.JsonPatchError"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "message",
+ "Type": "System.String"
+ },
+ {
+ "Name": "innerException",
+ "Type": "System.Exception"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Converters.JsonPatchDocumentConverter",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Newtonsoft.Json.JsonConverter",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "CanConvert",
+ "Parameters": [
+ {
+ "Name": "objectType",
+ "Type": "System.Type"
+ }
+ ],
+ "ReturnType": "System.Boolean",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ReadJson",
+ "Parameters": [
+ {
+ "Name": "reader",
+ "Type": "Newtonsoft.Json.JsonReader"
+ },
+ {
+ "Name": "objectType",
+ "Type": "System.Type"
+ },
+ {
+ "Name": "existingValue",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "serializer",
+ "Type": "Newtonsoft.Json.JsonSerializer"
+ }
+ ],
+ "ReturnType": "System.Object",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "WriteJson",
+ "Parameters": [
+ {
+ "Name": "writer",
+ "Type": "Newtonsoft.Json.JsonWriter"
+ },
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "serializer",
+ "Type": "Newtonsoft.Json.JsonSerializer"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Converters.TypedJsonPatchDocumentConverter",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.JsonPatch.Converters.JsonPatchDocumentConverter",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "ReadJson",
+ "Parameters": [
+ {
+ "Name": "reader",
+ "Type": "Newtonsoft.Json.JsonReader"
+ },
+ {
+ "Name": "objectType",
+ "Type": "System.Type"
+ },
+ {
+ "Name": "existingValue",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "serializer",
+ "Type": "Newtonsoft.Json.JsonSerializer"
+ }
+ ],
+ "ReturnType": "System.Object",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Add",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Remove",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Replace",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapterWithTest",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Test",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Adapters.ObjectAdapter",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapterWithTest"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Add",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Remove",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Replace",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_ContractResolver",
+ "Parameters": [],
+ "ReturnType": "Newtonsoft.Json.Serialization.IContractResolver",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_LogErrorAction",
+ "Parameters": [],
+ "ReturnType": "System.Action",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Test",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapterWithTest",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "contractResolver",
+ "Type": "Newtonsoft.Json.Serialization.IContractResolver"
+ },
+ {
+ "Name": "logErrorAction",
+ "Type": "System.Action"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/test/Adapters/AdapterFactoryTests.cs b/src/Features/JsonPatch/test/Adapters/AdapterFactoryTests.cs
new file mode 100644
index 0000000000..1e961c29e9
--- /dev/null
+++ b/src/Features/JsonPatch/test/Adapters/AdapterFactoryTests.cs
@@ -0,0 +1,73 @@
+using Microsoft.AspNetCore.JsonPatch.Adapters;
+using Microsoft.AspNetCore.JsonPatch.Internal;
+using Moq;
+using Newtonsoft.Json.Serialization;
+using System;
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Text;
+using Xunit;
+
+namespace Microsoft.AspNetCore.JsonPatch.Test.Adapters
+{
+ public class AdapterFactoryTests
+ {
+ [Fact]
+ public void GetListAdapterForListTargets()
+ {
+ // Arrange
+ AdapterFactory factory = new AdapterFactory();
+
+ //Act:
+ IAdapter adapter = factory.Create(new List(), new DefaultContractResolver());
+
+ // Assert
+ Assert.Equal(typeof(ListAdapter), adapter.GetType());
+ }
+
+ [Fact]
+ public void GetDictionaryAdapterForDictionaryObjects()
+ {
+ // Arrange
+ AdapterFactory factory = new AdapterFactory();
+
+ //Act:
+ IAdapter adapter = factory.Create(new Dictionary(), new DefaultContractResolver());
+
+ // Assert
+ Assert.Equal(typeof(DictionaryAdapter), adapter.GetType());
+ }
+
+ private class PocoModel
+ {}
+
+
+ [Fact]
+ public void GetPocoAdapterForGenericObjects()
+ {
+ // Arrange
+ AdapterFactory factory = new AdapterFactory();
+
+ //Act:
+ IAdapter adapter = factory.Create(new PocoModel(), new DefaultContractResolver());
+
+ // Assert
+ Assert.Equal(typeof(PocoAdapter), adapter.GetType());
+ }
+
+
+
+ [Fact]
+ public void GetDynamicAdapterForGenericObjects()
+ {
+ // Arrange
+ AdapterFactory factory = new AdapterFactory();
+
+ //Act:
+ IAdapter adapter = factory.Create(new TestDynamicObject(), new DefaultContractResolver());
+
+ // Assert
+ Assert.Equal(typeof(DynamicObjectAdapter), adapter.GetType());
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/test/Adapters/TestDynamicObject.cs b/src/Features/JsonPatch/test/Adapters/TestDynamicObject.cs
new file mode 100644
index 0000000000..08371f25c6
--- /dev/null
+++ b/src/Features/JsonPatch/test/Adapters/TestDynamicObject.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Text;
+
+namespace Microsoft.AspNetCore.JsonPatch.Test.Adapters
+{
+ public class TestDynamicObject : DynamicObject
+ { }
+}
diff --git a/src/Features/JsonPatch/test/CustomNamingStrategyTests.cs b/src/Features/JsonPatch/test/CustomNamingStrategyTests.cs
new file mode 100644
index 0000000000..ebc45874d9
--- /dev/null
+++ b/src/Features/JsonPatch/test/CustomNamingStrategyTests.cs
@@ -0,0 +1,153 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Dynamic;
+using Newtonsoft.Json.Serialization;
+using Xunit;
+
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ public class CustomNamingStrategyTests
+ {
+ [Fact]
+ public void AddProperty_ToDynamicTestObject_WithCustomNamingStrategy()
+ {
+ // Arrange
+ var contractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new TestNamingStrategy()
+ };
+
+ dynamic targetObject = new DynamicTestObject();
+ targetObject.Test = 1;
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Add("NewInt", 1);
+ patchDocument.ContractResolver = contractResolver;
+
+ // Act
+ patchDocument.ApplyTo(targetObject);
+
+ // Assert
+ Assert.Equal(1, targetObject.customNewInt);
+ Assert.Equal(1, targetObject.Test);
+ }
+
+ [Fact]
+ public void CopyPropertyValue_ToDynamicTestObject_WithCustomNamingStrategy()
+ {
+ // Arrange
+ var contractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new TestNamingStrategy()
+ };
+
+ dynamic targetObject = new DynamicTestObject();
+ targetObject.customStringProperty = "A";
+ targetObject.customAnotherStringProperty = "B";
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Copy("StringProperty", "AnotherStringProperty");
+ patchDocument.ContractResolver = contractResolver;
+
+ // Act
+ patchDocument.ApplyTo(targetObject);
+
+ // Assert
+ Assert.Equal("A", targetObject.customAnotherStringProperty);
+ }
+
+ [Fact]
+ public void MovePropertyValue_ForExpandoObject_WithCustomNamingStrategy()
+ {
+ // Arrange
+ var contractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new TestNamingStrategy()
+ };
+
+ dynamic targetObject = new ExpandoObject();
+ targetObject.customStringProperty = "A";
+ targetObject.customAnotherStringProperty = "B";
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Move("StringProperty", "AnotherStringProperty");
+ patchDocument.ContractResolver = contractResolver;
+
+ // Act
+ patchDocument.ApplyTo(targetObject);
+ var cont = targetObject as IDictionary;
+ cont.TryGetValue("customStringProperty", out var valueFromDictionary);
+
+ // Assert
+ Assert.Equal("A", targetObject.customAnotherStringProperty);
+ Assert.Null(valueFromDictionary);
+ }
+
+ [Fact]
+ public void RemoveProperty_FromDictionaryObject_WithCustomNamingStrategy()
+ {
+ // Arrange
+ var contractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new TestNamingStrategy()
+ };
+
+ var targetObject = new Dictionary()
+ {
+ { "customTest", 1},
+ };
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Remove("Test");
+ patchDocument.ContractResolver = contractResolver;
+
+ // Act
+ patchDocument.ApplyTo(targetObject);
+ var cont = targetObject as IDictionary;
+ cont.TryGetValue("customTest", out var valueFromDictionary);
+
+ // Assert
+ Assert.Equal(0, valueFromDictionary);
+ }
+
+ [Fact]
+ public void ReplacePropertyValue_ForExpandoObject_WithCustomNamingStrategy()
+ {
+ // Arrange
+ var contractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new TestNamingStrategy()
+ };
+
+ dynamic targetObject = new ExpandoObject();
+ targetObject.customTest = 1;
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Replace("Test", 2);
+ patchDocument.ContractResolver = contractResolver;
+
+ // Act
+ patchDocument.ApplyTo(targetObject);
+
+ // Assert
+ Assert.Equal(2, targetObject.customTest);
+ }
+
+ private class TestNamingStrategy : NamingStrategy
+ {
+ public new bool ProcessDictionaryKeys => true;
+
+ public override string GetDictionaryKey(string key)
+ {
+ return "custom" + key;
+ }
+
+ protected override string ResolvePropertyName(string name)
+ {
+ return name;
+ }
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/test/IntegrationTests/AnonymousObjectIntegrationTest.cs b/src/Features/JsonPatch/test/IntegrationTests/AnonymousObjectIntegrationTest.cs
new file mode 100644
index 0000000000..4f290aae2f
--- /dev/null
+++ b/src/Features/JsonPatch/test/IntegrationTests/AnonymousObjectIntegrationTest.cs
@@ -0,0 +1,190 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+using Xunit;
+
+namespace Microsoft.AspNetCore.JsonPatch.IntegrationTests
+{
+ public class AnonymousObjectIntegrationTest
+ {
+ [Fact]
+ public void AddNewProperty_ShouldFail()
+ {
+ // Arrange
+ var targetObject = new { };
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Add("NewProperty", 4);
+
+ // Act
+ var exception = Assert.Throws