зеркало из https://github.com/aspnet/Mvc.git
Initial Json Patch check-in
This commit is contained in:
Родитель
5818c0b5b7
Коммит
f1e1d8f4df
|
@ -1,7 +1,7 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.22609.0
|
||||
VisualStudioVersion = 14.0.22710.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
||||
EndProject
|
||||
|
@ -60,6 +60,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Common
|
|||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.TestCommon", "test\Microsoft.AspNet.Mvc.TestCommon\Microsoft.AspNet.Mvc.TestCommon.xproj", "{F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.JsonPatch.Test", "test\Microsoft.AspNet.JsonPatch.Test\Microsoft.AspNet.JsonPatch.Test.xproj", "{81C20848-E063-4E12-AC40-0B55A532C16C}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.JsonPatch", "src\Microsoft.AspNet.JsonPatch\Microsoft.AspNet.JsonPatch.xproj", "{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -318,6 +322,29 @@ Global
|
|||
{F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|x86.Build.0 = Release|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Release|x86.Build.0 = Release|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -346,5 +373,7 @@ Global
|
|||
{4DA2D7C1-A7B6-4C01-B57D-89E6EA4609DE} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
|
||||
{0449D6D2-BE1B-4E29-8E1B-444420802C03} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||
{F504357E-C2E1-4818-BA5C-9A2EAC25FEE5} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
35
Mvc.sln
35
Mvc.sln
|
@ -1,5 +1,4 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.22711.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
|
@ -156,6 +155,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CorsWebSite", "test\WebSite
|
|||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CorsMiddlewareWebSite", "test\WebSites\CorsMiddlewareWebSite\CorsMiddlewareWebSite.xproj", "{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.JsonPatch.Test", "test\Microsoft.AspNet.JsonPatch.Test\Microsoft.AspNet.JsonPatch.Test.xproj", "{81C20848-E063-4E12-AC40-0B55A532C16C}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.JsonPatch", "src\Microsoft.AspNet.JsonPatch\Microsoft.AspNet.JsonPatch.xproj", "{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -898,6 +901,30 @@ Global
|
|||
{F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|x86.Build.0 = Release|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C}.Release|x86.Build.0 = Release|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Release|x86.Build.0 = Release|Any CPU
|
||||
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
|
@ -996,7 +1023,9 @@ Global
|
|||
{BCDB13A6-7D6E-485E-8424-A156432B71AC} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
{8AEB631E-AB74-4D2E-83FB-8931EE10D9D3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
{F504357E-C2E1-4818-BA5C-9A2EAC25FEE5} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||
{81C20848-E063-4E12-AC40-0B55A532C16C} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
|
||||
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
EndGlobal
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch.Adapters
|
||||
{
|
||||
public interface IObjectAdapter<T>
|
||||
where T : class
|
||||
{
|
||||
void Add(Microsoft.AspNet.JsonPatch.Operations.Operation<T> operation, T objectToApplyTo);
|
||||
void Copy(Microsoft.AspNet.JsonPatch.Operations.Operation<T> operation, T objectToApplyTo);
|
||||
void Move(Microsoft.AspNet.JsonPatch.Operations.Operation<T> operation, T objectToApplyTo);
|
||||
void Remove(Microsoft.AspNet.JsonPatch.Operations.Operation<T> operation, T objectToApplyTo);
|
||||
void Replace(Microsoft.AspNet.JsonPatch.Operations.Operation<T> operation, T objectToApplyTo);
|
||||
void Test(Microsoft.AspNet.JsonPatch.Operations.Operation<T> operation, T objectToApplyTo);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,713 @@
|
|||
using System;
|
||||
using Microsoft.AspNet.JsonPatch.Operations;
|
||||
using Microsoft.AspNet.JsonPatch.Helpers;
|
||||
using Microsoft.AspNet.JsonPatch.Exceptions;
|
||||
using System.Reflection;
|
||||
using System.Collections;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch.Adapters
|
||||
{
|
||||
public class SimpleObjectAdapter<T> : IObjectAdapter<T> where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// The "add" operation performs one of the following functions,
|
||||
/// depending upon what the target location references:
|
||||
///
|
||||
/// o If the target location specifies an array index, a new value is
|
||||
/// inserted into the array at the specified index.
|
||||
///
|
||||
/// o If the target location specifies an object member that does not
|
||||
/// already exist, a new member is added to the object.
|
||||
///
|
||||
/// o If the target location specifies an object member that 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" ] }
|
||||
///
|
||||
/// When the operation is applied, the target location MUST reference one
|
||||
/// of:
|
||||
///
|
||||
/// o The root of the target document - whereupon the specified value
|
||||
/// becomes the entire content of the target document.
|
||||
///
|
||||
/// o A member to add to an existing object - whereupon the supplied
|
||||
/// value is added to that object at the indicated location. If the
|
||||
/// member already exists, it is replaced by the specified value.
|
||||
///
|
||||
/// o An element to add to an existing array - whereupon the supplied
|
||||
/// value is added to the array at the indicated location. Any
|
||||
/// elements at or above the specified index are shifted one position
|
||||
/// to the right. The specified index MUST NOT be greater than the
|
||||
/// number of elements in the array. If the "-" character is used to
|
||||
/// index the end of the array (see [RFC6901]), this has the effect of
|
||||
/// appending the value to the array.
|
||||
///
|
||||
/// Because this operation is designed to add to existing objects and
|
||||
/// arrays, its target location will often not exist. Although the
|
||||
/// pointer's error handling algorithm will thus be invoked, this
|
||||
/// specification defines the error handling behavior for "add" pointers
|
||||
/// to ignore that error and add the value as specified.
|
||||
///
|
||||
/// However, the object itself or an array containing it does need to
|
||||
/// exist, and it remains an error for that not to be the case. For
|
||||
/// example, an "add" with a target location of "/a/b" starting with this
|
||||
/// document:
|
||||
///
|
||||
/// { "a": { "foo": 1 } }
|
||||
///
|
||||
/// is not an error, because "a" exists, and "b" will be added to its
|
||||
/// value. It is an error in this document:
|
||||
///
|
||||
/// { "q": { "bar": 2 } }
|
||||
///
|
||||
/// because "a" does not exist.
|
||||
/// </summary>
|
||||
/// <param name="operation">The add operation</param>
|
||||
/// <param name="objectApplyTo">Object to apply the operation to</param>
|
||||
public void Add(Operation<T> operation, T objectToApplyTo)
|
||||
{
|
||||
Add(operation.path, operation.value, objectToApplyTo, operation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
private void Add(string path, object value, T objectToApplyTo, Operation<T> operationToReport)
|
||||
{
|
||||
// add, in this implementation, does not just "add" properties - that's
|
||||
// technically impossible; It can however be used to add items to arrays,
|
||||
// or to replace values.
|
||||
|
||||
// first up: if the path ends in a numeric value, we're inserting in a list and
|
||||
// that value represents the position; if the path ends in "-", we're appending
|
||||
// to the list.
|
||||
|
||||
var appendList = false;
|
||||
var positionAsInteger = -1;
|
||||
var actualPathToProperty = path;
|
||||
|
||||
if (path.EndsWith("/-"))
|
||||
{
|
||||
appendList = true;
|
||||
actualPathToProperty = path.Substring(0, path.Length - 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
positionAsInteger = PropertyHelpers.GetNumericEnd(path);
|
||||
|
||||
if (positionAsInteger > -1)
|
||||
{
|
||||
actualPathToProperty = path.Substring(0,
|
||||
path.IndexOf('/' + positionAsInteger.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
var pathProperty = PropertyHelpers
|
||||
.FindProperty(objectToApplyTo, actualPathToProperty);
|
||||
|
||||
|
||||
// does property at path exist?
|
||||
if (pathProperty == null)
|
||||
{
|
||||
throw new JsonPatchException<T>(operationToReport,
|
||||
string.Format("Patch failed: property at location path: {0} does not exist", path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
|
||||
// it exists. If it' an array, add to that array. If it's not, we replace.
|
||||
|
||||
// is the path an array (but not a string (= char[]))? In this case,
|
||||
// the path must end with "/position" or "/-", which we already determined before.
|
||||
|
||||
if (appendList || positionAsInteger > -1)
|
||||
{
|
||||
|
||||
var isNonStringArray = !(pathProperty.PropertyType == typeof(string))
|
||||
&& typeof(IList).IsAssignableFrom(pathProperty.PropertyType);
|
||||
|
||||
// what if it's an array but there's no position??
|
||||
if (isNonStringArray)
|
||||
{
|
||||
// now, get the generic type of the enumerable
|
||||
var genericTypeOfArray = PropertyHelpers.GetEnumerableType(pathProperty.PropertyType);
|
||||
|
||||
var conversionResult = PropertyHelpers.ConvertToActualType(genericTypeOfArray, value);
|
||||
|
||||
if (!conversionResult.CanBeConverted)
|
||||
{
|
||||
throw new JsonPatchException<T>(operationToReport,
|
||||
string.Format("Patch failed: provided value is invalid for array property type at location path: {0}",
|
||||
path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
|
||||
// get value (it can be cast, we just checked that)
|
||||
var array = PropertyHelpers.GetValue(pathProperty, objectToApplyTo, actualPathToProperty) as IList;
|
||||
|
||||
if (appendList)
|
||||
{
|
||||
array.Add(conversionResult.ConvertedInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
// specified index must not be greater than the amount of items in the array
|
||||
if (positionAsInteger <= array.Count)
|
||||
{
|
||||
array.Insert(positionAsInteger, conversionResult.ConvertedInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonPatchException<T>(operationToReport,
|
||||
string.Format("Patch failed: provided path is invalid for array property type at " +
|
||||
"location path: {0}: position doesn't exist in array",
|
||||
path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonPatchException<T>(operationToReport,
|
||||
string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array",
|
||||
path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var conversionResultTuple = PropertyHelpers.ConvertToActualType(pathProperty.PropertyType, value);
|
||||
|
||||
// conversion successful
|
||||
if (conversionResultTuple.CanBeConverted)
|
||||
{
|
||||
PropertyHelpers.SetValue(pathProperty, objectToApplyTo, actualPathToProperty,
|
||||
conversionResultTuple.ConvertedInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonPatchException<T>(operationToReport,
|
||||
string.Format("Patch failed: provided value is invalid for property type at location path: {0}",
|
||||
path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The "move" operation removes the value at a specified location and
|
||||
/// adds it to the target location.
|
||||
///
|
||||
/// The operation object MUST contain a "from" member, which is a string
|
||||
/// containing a JSON Pointer value that 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" }
|
||||
///
|
||||
/// This operation is functionally identical to a "remove" operation on
|
||||
/// the "from" location, followed immediately by an "add" operation at
|
||||
/// the target location with the value that was just removed.
|
||||
///
|
||||
/// The "from" location MUST NOT be a proper prefix of the "path"
|
||||
/// location; i.e., a location cannot be moved into one of its children.
|
||||
/// </summary>
|
||||
/// <param name="operation">The move operation</param>
|
||||
/// <param name="objectApplyTo">Object to apply the operation to</param>
|
||||
public void Move(Operation<T> operation, T objectToApplyTo)
|
||||
{
|
||||
|
||||
// get value at from location
|
||||
object valueAtFromLocation = null;
|
||||
var positionAsInteger = -1;
|
||||
var actualFromProperty = operation.from;
|
||||
|
||||
|
||||
positionAsInteger = PropertyHelpers.GetNumericEnd(operation.from);
|
||||
|
||||
if (positionAsInteger > -1)
|
||||
{
|
||||
actualFromProperty = operation.from.Substring(0,
|
||||
operation.from.IndexOf('/' + positionAsInteger.ToString()));
|
||||
}
|
||||
|
||||
var fromProperty = PropertyHelpers
|
||||
.FindProperty(objectToApplyTo, actualFromProperty);
|
||||
|
||||
|
||||
// does property at from exist?
|
||||
if (fromProperty == null)
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: property at location from: {0} does not exist", operation.from),
|
||||
objectToApplyTo);
|
||||
}
|
||||
|
||||
|
||||
// is the path an array (but not a string (= char[]))? In this case,
|
||||
// the path must end with "/position" or "/-", which we already determined before.
|
||||
|
||||
if (positionAsInteger > -1)
|
||||
{
|
||||
|
||||
var isNonStringArray = !(fromProperty.PropertyType == typeof(string))
|
||||
&& typeof(IList).IsAssignableFrom(fromProperty.PropertyType);
|
||||
|
||||
if (isNonStringArray)
|
||||
{
|
||||
// now, get the generic type of the enumerable
|
||||
var genericTypeOfArray = PropertyHelpers.GetEnumerableType(fromProperty.PropertyType);
|
||||
|
||||
// get value (it can be cast, we just checked that)
|
||||
var array = PropertyHelpers.GetValue(fromProperty, objectToApplyTo, actualFromProperty) as IList;
|
||||
|
||||
if (array.Count <= positionAsInteger)
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: provided from path is invalid for array property type at location from: {0}: invalid position",
|
||||
operation.from),
|
||||
objectToApplyTo);
|
||||
}
|
||||
|
||||
valueAtFromLocation = array[positionAsInteger];
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: provided from path is invalid for array property type at location from: {0}: expected array",
|
||||
operation.from),
|
||||
objectToApplyTo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no list, just get the value
|
||||
|
||||
// set the new value
|
||||
|
||||
valueAtFromLocation = PropertyHelpers.GetValue(fromProperty, objectToApplyTo, actualFromProperty);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// remove that value
|
||||
|
||||
Remove(operation.from, objectToApplyTo, operation);
|
||||
|
||||
// add that value to the path location
|
||||
|
||||
Add(operation.path, valueAtFromLocation, objectToApplyTo, operation);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The "remove" operation removes the value at the target location.
|
||||
///
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="operation">The remove operation</param>
|
||||
/// <param name="objectApplyTo">Object to apply the operation to</param>
|
||||
public void Remove(Operation<T> operation, T objectToApplyTo)
|
||||
{
|
||||
Remove(operation.path, objectToApplyTo, operation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
private void Remove(string path, T objectToApplyTo, Operation<T> operationToReport)
|
||||
{
|
||||
var removeFromList = false;
|
||||
var positionAsInteger = -1;
|
||||
var actualPathToProperty = path;
|
||||
|
||||
if (path.EndsWith("/-"))
|
||||
{
|
||||
removeFromList = true;
|
||||
actualPathToProperty = path.Substring(0, path.Length - 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
positionAsInteger = PropertyHelpers.GetNumericEnd(path);
|
||||
|
||||
if (positionAsInteger > -1)
|
||||
{
|
||||
actualPathToProperty = path.Substring(0,
|
||||
path.IndexOf('/' + positionAsInteger.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
var pathProperty = PropertyHelpers
|
||||
.FindProperty(objectToApplyTo, actualPathToProperty);
|
||||
|
||||
// does the target location exist?
|
||||
if (pathProperty == null)
|
||||
{
|
||||
throw new JsonPatchException<T>(operationToReport,
|
||||
string.Format("Patch failed: property at location path: {0} does not exist", path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
|
||||
// get the property, and remove it - in this case, for DTO's, that means setting
|
||||
// it to null or its default value; in case of an array, remove at provided index
|
||||
// or at the end.
|
||||
if (removeFromList || positionAsInteger > -1)
|
||||
{
|
||||
|
||||
var isNonStringArray = !(pathProperty.PropertyType == typeof(string))
|
||||
&& typeof(IList).IsAssignableFrom(pathProperty.PropertyType);
|
||||
|
||||
// what if it's an array but there's no position??
|
||||
if (isNonStringArray)
|
||||
{
|
||||
// now, get the generic type of the enumerable
|
||||
var genericTypeOfArray = PropertyHelpers.GetEnumerableType(pathProperty.PropertyType);
|
||||
|
||||
// TODO: nested!
|
||||
// get value (it can be cast, we just checked that)
|
||||
var array = PropertyHelpers.GetValue(pathProperty, objectToApplyTo, actualPathToProperty) as IList;
|
||||
|
||||
if (removeFromList)
|
||||
{
|
||||
array.RemoveAt(array.Count - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (positionAsInteger < array.Count)
|
||||
{
|
||||
array.RemoveAt(positionAsInteger);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonPatchException<T>(operationToReport,
|
||||
string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size",
|
||||
path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonPatchException<T>(operationToReport,
|
||||
string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array",
|
||||
path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// setting the value to "null" will use the default value in case of value types, and
|
||||
// null in case of reference types
|
||||
PropertyHelpers.SetValue(pathProperty, objectToApplyTo, actualPathToProperty, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The "test" operation tests that a value at the target location is
|
||||
/// equal to a specified value.
|
||||
///
|
||||
/// The operation object MUST contain a "value" member that conveys the
|
||||
/// 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.
|
||||
///
|
||||
/// Here, "equal" means that the value at the target location and the
|
||||
/// value conveyed by "value" are of the same JSON type, and that they
|
||||
/// are considered equal by the following rules for that type:
|
||||
///
|
||||
/// o strings: are considered equal if they contain the same number of
|
||||
/// Unicode characters and their code points are byte-by-byte equal.
|
||||
///
|
||||
/// o numbers: are considered equal if their values are numerically
|
||||
/// equal.
|
||||
///
|
||||
/// o arrays: are considered equal if they contain the same number of
|
||||
/// values, and if each value can be considered equal to the value at
|
||||
/// the corresponding position in the other array, using this list of
|
||||
/// type-specific rules.
|
||||
///
|
||||
/// o objects: are considered equal if they contain the same number of
|
||||
/// members, and if each member can be considered equal to a member in
|
||||
/// the other object, by comparing their keys (as strings) and their
|
||||
/// values (using this list of type-specific rules).
|
||||
///
|
||||
/// o literals (false, true, and null): are considered equal if they are
|
||||
/// the same.
|
||||
///
|
||||
/// Note that the comparison that is done is a logical comparison; e.g.,
|
||||
/// whitespace between the member values of an array is not significant.
|
||||
///
|
||||
/// Also, note that ordering of the serialization of object members is
|
||||
/// not significant.
|
||||
///
|
||||
/// Note that we divert from the rules here - we use .NET's comparison,
|
||||
/// not the one above. In a future version, a "strict" setting might
|
||||
/// be added (configurable), that takes into account above rules.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// { "op": "test", "path": "/a/b/c", "value": "foo" }
|
||||
/// </summary>
|
||||
/// <param name="operation">The test operation</param>
|
||||
/// <param name="objectApplyTo">Object to apply the operation to</param>
|
||||
public void Test(Operation<T> operation, T objectToApplyTo)
|
||||
{
|
||||
// get value at path location
|
||||
|
||||
object valueAtPathLocation = null;
|
||||
var positionInPathAsInteger = -1;
|
||||
var actualPathProperty = operation.path;
|
||||
|
||||
|
||||
positionInPathAsInteger = PropertyHelpers.GetNumericEnd(operation.path);
|
||||
|
||||
if (positionInPathAsInteger > -1)
|
||||
{
|
||||
actualPathProperty = operation.path.Substring(0,
|
||||
operation.path.IndexOf('/' + positionInPathAsInteger.ToString()));
|
||||
}
|
||||
|
||||
var pathProperty = PropertyHelpers
|
||||
.FindProperty(objectToApplyTo, actualPathProperty);
|
||||
|
||||
// does property at path exist?
|
||||
if (pathProperty == null)
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: property at location path: {0} does not exist", operation.path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
|
||||
// get the property path
|
||||
|
||||
Type typeOfFinalPropertyAtPathLocation;
|
||||
|
||||
// is the path an array (but not a string (= char[]))? In this case,
|
||||
// the path must end with "/position" or "/-", which we already determined before.
|
||||
|
||||
if (positionInPathAsInteger > -1)
|
||||
{
|
||||
|
||||
var isNonStringArray = !(pathProperty.PropertyType == typeof(string))
|
||||
&& typeof(IList).IsAssignableFrom(pathProperty.PropertyType);
|
||||
|
||||
if (isNonStringArray)
|
||||
{
|
||||
// now, get the generic type of the enumerable
|
||||
typeOfFinalPropertyAtPathLocation = PropertyHelpers.GetEnumerableType(pathProperty.PropertyType);
|
||||
|
||||
// get value (it can be cast, we just checked that)
|
||||
var array = PropertyHelpers.GetValue(pathProperty, objectToApplyTo, actualPathProperty) as IList;
|
||||
|
||||
if (array.Count <= positionInPathAsInteger)
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: provided from path is invalid for array property type at location path: {0}: invalid position",
|
||||
operation.path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
|
||||
valueAtPathLocation = array[positionInPathAsInteger];
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: provided from path is invalid for array property type at location path: {0}: expected array",
|
||||
operation.path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no list, just get the value
|
||||
valueAtPathLocation = PropertyHelpers.GetValue(pathProperty, objectToApplyTo, actualPathProperty);
|
||||
typeOfFinalPropertyAtPathLocation = pathProperty.PropertyType;
|
||||
}
|
||||
|
||||
|
||||
|
||||
var conversionResultTuple = PropertyHelpers.ConvertToActualType(typeOfFinalPropertyAtPathLocation, operation.value);
|
||||
|
||||
// conversion successful
|
||||
if (conversionResultTuple.CanBeConverted)
|
||||
{
|
||||
// COMPARE - TODO
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: provided value is invalid for property type at location path: {0}",
|
||||
operation.path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The "replace" operation replaces the value at the target location
|
||||
/// with a new value. The operation object MUST contain a "value" member
|
||||
/// whose content 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 }
|
||||
///
|
||||
/// This operation is functionally identical to a "remove" operation for
|
||||
/// a value, followed immediately by an "add" operation at the same
|
||||
/// location with the replacement value.
|
||||
///
|
||||
/// Note: even though it's the same functionally, we do not call remove + add
|
||||
/// for performance reasons (multiple checks of same requirements).
|
||||
/// </summary>
|
||||
/// <param name="operation">The replace operation</param>
|
||||
/// <param name="objectApplyTo">Object to apply the operation to</param>
|
||||
public void Replace(Operation<T> operation, T objectToApplyTo)
|
||||
{
|
||||
|
||||
Remove(operation.path, objectToApplyTo, operation);
|
||||
Add(operation.path, operation.value, objectToApplyTo, operation);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The "copy" operation copies the value at a specified location to the
|
||||
/// target location.
|
||||
///
|
||||
/// The operation object MUST contain a "from" member, which is a string
|
||||
/// containing a JSON Pointer value that 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" }
|
||||
///
|
||||
/// This operation is functionally identical to an "add" operation at the
|
||||
/// target location using the value specified in the "from" member.
|
||||
///
|
||||
/// Note: even though it's the same functionally, we do not call add with
|
||||
/// the value specified in from for performance reasons (multiple checks of same requirements).
|
||||
/// </summary>
|
||||
/// <param name="operation">The copy operation</param>
|
||||
/// <param name="objectApplyTo">Object to apply the operation to</param>
|
||||
public void Copy(Operation<T> operation, T objectToApplyTo)
|
||||
{
|
||||
|
||||
// get value at from location
|
||||
object valueAtFromLocation = null;
|
||||
var positionAsInteger = -1;
|
||||
var actualFromProperty = operation.from;
|
||||
|
||||
|
||||
positionAsInteger = PropertyHelpers.GetNumericEnd(operation.from);
|
||||
|
||||
if (positionAsInteger > -1)
|
||||
{
|
||||
actualFromProperty = operation.from.Substring(0,
|
||||
operation.from.IndexOf('/' + positionAsInteger.ToString()));
|
||||
}
|
||||
|
||||
|
||||
var fromProperty = PropertyHelpers
|
||||
.FindProperty(objectToApplyTo, actualFromProperty);
|
||||
|
||||
// does property at from exist?
|
||||
if (fromProperty == null)
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: property at location from: {0} does not exist", operation.from),
|
||||
objectToApplyTo);
|
||||
}
|
||||
|
||||
// get the property path
|
||||
|
||||
// is the path an array (but not a string (= char[]))? In this case,
|
||||
// the path must end with "/position" or "/-", which we already determined before.
|
||||
|
||||
if (positionAsInteger > -1)
|
||||
{
|
||||
|
||||
var isNonStringArray = !(fromProperty.PropertyType == typeof(string))
|
||||
&& typeof(IList).IsAssignableFrom(fromProperty.PropertyType);
|
||||
|
||||
if (isNonStringArray)
|
||||
{
|
||||
// now, get the generic type of the enumerable
|
||||
var genericTypeOfArray = PropertyHelpers.GetEnumerableType(fromProperty.PropertyType);
|
||||
|
||||
// get value (it can be cast, we just checked that)
|
||||
var array = PropertyHelpers.GetValue(fromProperty, objectToApplyTo, actualFromProperty) as IList;
|
||||
|
||||
if (array.Count <= positionAsInteger)
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: provided from path is invalid for array property type at location from: {0}: invalid position",
|
||||
operation.from),
|
||||
objectToApplyTo);
|
||||
}
|
||||
|
||||
valueAtFromLocation = array[positionAsInteger];
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: provided from path is invalid for array property type at location from: {0}: expected array",
|
||||
operation.from),
|
||||
objectToApplyTo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no list, just get the value
|
||||
|
||||
// set the new value
|
||||
|
||||
valueAtFromLocation = PropertyHelpers.GetValue(fromProperty, objectToApplyTo, actualFromProperty);
|
||||
|
||||
}
|
||||
|
||||
// add operation to target location with that value.
|
||||
|
||||
Add(operation.path, valueAtFromLocation, objectToApplyTo, operation);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
using Microsoft.AspNet.JsonPatch.Exceptions;
|
||||
using Microsoft.AspNet.JsonPatch.Operations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch.Converters
|
||||
{
|
||||
public class TypedJsonPatchDocumentConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
|
||||
JsonSerializer serializer)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
return null;
|
||||
|
||||
var genericType = objectType.GetGenericArguments()[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.
|
||||
JsonReader 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);
|
||||
|
||||
return container;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new JsonPatchException("The JsonPatchDocument was malformed and could not be parsed.", 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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch.Exceptions
|
||||
{
|
||||
public class JsonPatchException : Exception
|
||||
{
|
||||
public new Exception InnerException { get; internal set; }
|
||||
|
||||
public object AffectedObject { get; private set; }
|
||||
|
||||
private string _message = "";
|
||||
public override string Message
|
||||
{
|
||||
get
|
||||
{
|
||||
return _message;
|
||||
}
|
||||
}
|
||||
|
||||
public JsonPatchException()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public JsonPatchException(string message, Exception innerException)
|
||||
{
|
||||
_message = message;
|
||||
InnerException = innerException;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.JsonPatch.Operations;
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch.Exceptions
|
||||
{
|
||||
public class JsonPatchException<T> : JsonPatchException where T : class
|
||||
{
|
||||
public Operation<T> FailedOperation { get; private set; }
|
||||
public new T AffectedObject { get; private set; }
|
||||
|
||||
private string _message = "";
|
||||
public override string Message
|
||||
{
|
||||
get
|
||||
{
|
||||
return _message;
|
||||
}
|
||||
}
|
||||
|
||||
public JsonPatchException()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public JsonPatchException(Operation<T> operation, string message, T affectedObject)
|
||||
{
|
||||
FailedOperation = operation;
|
||||
_message = message;
|
||||
AffectedObject = affectedObject;
|
||||
}
|
||||
|
||||
public JsonPatchException(Operation<T> operation, string message, T affectedObject, Exception innerException)
|
||||
: this(operation, message, affectedObject)
|
||||
{
|
||||
InnerException = innerException;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch.Helpers
|
||||
{
|
||||
internal class ConversionResult
|
||||
{
|
||||
public bool CanBeConverted { get; private set; }
|
||||
public object ConvertedInstance { get; private set; }
|
||||
|
||||
public ConversionResult(bool canBeConverted, object convertedInstance)
|
||||
{
|
||||
CanBeConverted = canBeConverted;
|
||||
ConvertedInstance = convertedInstance;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Globalization;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch.Helpers
|
||||
{
|
||||
internal static class ExpressionHelpers
|
||||
{
|
||||
public static string GetPath<T, TProp>(Expression<Func<T, TProp>> expr) where T : class
|
||||
{
|
||||
return "/" + GetPath(expr.Body, true);
|
||||
}
|
||||
|
||||
private static string GetPath(Expression expr, bool firstTime)
|
||||
{
|
||||
switch (expr.NodeType)
|
||||
{
|
||||
case ExpressionType.ArrayIndex:
|
||||
var binaryExpression = (BinaryExpression)expr;
|
||||
|
||||
if (ContinueWithSubPath(binaryExpression.Left.NodeType, false))
|
||||
{
|
||||
var leftFromBinaryExpression = GetPath(binaryExpression.Left, false);
|
||||
return leftFromBinaryExpression + "/" + binaryExpression.Right.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return binaryExpression.Right.ToString();
|
||||
}
|
||||
case ExpressionType.Call:
|
||||
var methodCallExpression = (MethodCallExpression)expr;
|
||||
|
||||
if (ContinueWithSubPath(methodCallExpression.Object.NodeType, false))
|
||||
{
|
||||
var leftFromMemberCallExpression = GetPath(methodCallExpression.Object, false);
|
||||
return leftFromMemberCallExpression + "/" +
|
||||
GetIndexerInvocation(methodCallExpression.Arguments[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetIndexerInvocation(methodCallExpression.Arguments[0]);
|
||||
}
|
||||
case ExpressionType.Convert:
|
||||
return GetPath(((UnaryExpression)expr).Operand, false);
|
||||
case ExpressionType.MemberAccess:
|
||||
var memberExpression = expr as MemberExpression;
|
||||
|
||||
if (ContinueWithSubPath(memberExpression.Expression.NodeType, false))
|
||||
{
|
||||
var left = GetPath(memberExpression.Expression, false);
|
||||
return left + "/" + memberExpression.Member.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
return memberExpression.Member.Name;
|
||||
}
|
||||
case ExpressionType.Parameter:
|
||||
// Fits "x => x" (the whole document which is "" as JSON pointer)
|
||||
return firstTime ? "" : null;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ContinueWithSubPath(ExpressionType expressionType, bool firstTime)
|
||||
{
|
||||
if (firstTime)
|
||||
{
|
||||
return (expressionType == ExpressionType.ArrayIndex
|
||||
|| expressionType == ExpressionType.Call
|
||||
|| expressionType == ExpressionType.Convert
|
||||
|| expressionType == ExpressionType.MemberAccess
|
||||
|| expressionType == ExpressionType.Parameter);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (expressionType == ExpressionType.ArrayIndex
|
||||
|| expressionType == ExpressionType.Call
|
||||
|| expressionType == ExpressionType.Convert
|
||||
|| expressionType == ExpressionType.MemberAccess);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetIndexerInvocation(Expression expression)
|
||||
{
|
||||
var converted = Expression.Convert(expression, typeof(object));
|
||||
var fakeParameter = Expression.Parameter(typeof(object), null);
|
||||
var lambda = Expression.Lambda<Func<object, object>>(converted, fakeParameter);
|
||||
Func<object, object> func;
|
||||
|
||||
func = lambda.Compile();
|
||||
|
||||
return Convert.ToString(func(null), CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch.Helpers
|
||||
{
|
||||
internal static class PropertyHelpers
|
||||
{
|
||||
public static object GetValue(PropertyInfo propertyToGet, object targetObject, string pathToProperty)
|
||||
{
|
||||
// it is possible the path refers to a nested property. In that case, we need to
|
||||
// get from a different target object: the nested object.
|
||||
|
||||
var splitPath = pathToProperty.Split('/');
|
||||
|
||||
// skip the first one if it's empty
|
||||
var startIndex = (string.IsNullOrWhiteSpace(splitPath[0]) ? 1 : 0);
|
||||
|
||||
for (int i = startIndex; i < splitPath.Length - 1; i++)
|
||||
{
|
||||
var propertyInfoToGet = GetPropertyInfo(targetObject, splitPath[i]
|
||||
, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
|
||||
targetObject = propertyInfoToGet.GetValue(targetObject, null);
|
||||
}
|
||||
|
||||
return propertyToGet.GetValue(targetObject, null);
|
||||
}
|
||||
|
||||
public static bool SetValue(PropertyInfo propertyToSet, object targetObject, string pathToProperty, object value)
|
||||
{
|
||||
// it is possible the path refers to a nested property. In that case, we need to
|
||||
// set on a different target object: the nested object.
|
||||
var splitPath = pathToProperty.Split('/');
|
||||
|
||||
// skip the first one if it's empty
|
||||
var startIndex = (string.IsNullOrWhiteSpace(splitPath[0]) ? 1 : 0);
|
||||
|
||||
for (int i = startIndex; i < splitPath.Length - 1; i++)
|
||||
{
|
||||
var propertyInfoToGet = GetPropertyInfo(targetObject, splitPath[i]
|
||||
, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
|
||||
targetObject = propertyInfoToGet.GetValue(targetObject, null);
|
||||
}
|
||||
|
||||
propertyToSet.SetValue(targetObject, value, null);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static PropertyInfo FindProperty(object targetObject, string propertyPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var splitPath = propertyPath.Split('/');
|
||||
|
||||
// skip the first one if it's empty
|
||||
var startIndex = (string.IsNullOrWhiteSpace(splitPath[0]) ? 1 : 0);
|
||||
|
||||
for (int i = startIndex; i < splitPath.Length - 1; i++)
|
||||
{
|
||||
var propertyInfoToGet = GetPropertyInfo(targetObject, splitPath[i]
|
||||
, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
|
||||
targetObject = propertyInfoToGet.GetValue(targetObject, null);
|
||||
}
|
||||
|
||||
var propertyToFind = targetObject.GetType().GetProperty(splitPath.Last(),
|
||||
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
return propertyToFind;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// will result in JsonPatchException in calling class, as expected
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal static ConversionResult ConvertToActualType(Type propertyType, object value)
|
||||
{
|
||||
try
|
||||
{
|
||||
var o = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), propertyType);
|
||||
|
||||
return new ConversionResult(true, o);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return new ConversionResult(false, null);
|
||||
}
|
||||
}
|
||||
|
||||
internal static Type GetEnumerableType(Type type)
|
||||
{
|
||||
if (type == null) throw new ArgumentNullException();
|
||||
foreach (Type interfaceType in type.GetInterfaces())
|
||||
{
|
||||
#if NETFX_CORE || ASPNETCORE50
|
||||
if (interfaceType.GetTypeInfo().IsGenericType &&
|
||||
interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
{
|
||||
return interfaceType.GetGenericArguments()[0];
|
||||
}
|
||||
#else
|
||||
if (interfaceType.GetTypeInfo().IsGenericType &&
|
||||
interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
{
|
||||
return interfaceType.GetGenericArguments()[0];
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static int GetNumericEnd(string path)
|
||||
{
|
||||
var possibleIndex = path.Substring(path.LastIndexOf("/") + 1);
|
||||
var castedIndex = -1;
|
||||
|
||||
if (int.TryParse(possibleIndex, out castedIndex))
|
||||
{
|
||||
return castedIndex;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static PropertyInfo GetPropertyInfo(object targetObject, string propertyName,
|
||||
BindingFlags bindingFlags)
|
||||
{
|
||||
return targetObject.GetType().GetProperty(propertyName, bindingFlags);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.JsonPatch.Operations;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch
|
||||
{
|
||||
public interface IJsonPatchDocument
|
||||
{
|
||||
List<Operation> GetOperations();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>4d55f4d8-633b-462f-a5b1-feb84bd2d534</ProjectGuid>
|
||||
<RootNamespace>Microsoft.AspNet.JsonPatch</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNet.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)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.JsonPatch.Operations
|
||||
{
|
||||
public class OperationBase
|
||||
{
|
||||
[JsonIgnore]
|
||||
public OperationType OperationType
|
||||
{
|
||||
get
|
||||
{
|
||||
return (OperationType)Enum.Parse(typeof(OperationType), op, true);
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty("path")]
|
||||
public string path { get; set; }
|
||||
|
||||
[JsonProperty("op")]
|
||||
public string op { get; set; }
|
||||
|
||||
[JsonProperty("from")]
|
||||
public string from { get; set; }
|
||||
|
||||
public OperationBase()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public OperationBase(string op, string path, string from)
|
||||
{
|
||||
this.op = op;
|
||||
this.path = path;
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
public bool ShouldSerializefrom()
|
||||
{
|
||||
return (OperationType == Operations.OperationType.Move
|
||||
|| OperationType == OperationType.Copy);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch.Operations
|
||||
{
|
||||
public enum OperationType
|
||||
{
|
||||
Add,
|
||||
Remove,
|
||||
Replace,
|
||||
Move,
|
||||
Copy,
|
||||
Test
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.JsonPatch.Adapters;
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch.Operations
|
||||
{
|
||||
public class Operation<T> : Operation where T : class
|
||||
{
|
||||
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(T objectToApplyTo, IObjectAdapter<T> 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:
|
||||
adapter.Test(this, objectToApplyTo);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShouldSerializevalue()
|
||||
{
|
||||
return (OperationType == Operations.OperationType.Add
|
||||
|| OperationType == OperationType.Replace
|
||||
|| OperationType == OperationType.Test);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,377 @@
|
|||
using Microsoft.AspNet.JsonPatch.Adapters;
|
||||
using Microsoft.AspNet.JsonPatch.Converters;
|
||||
using Microsoft.AspNet.JsonPatch.Helpers;
|
||||
using Microsoft.AspNet.JsonPatch.Operations;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Microsoft.AspNet.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<T> : IJsonPatchDocument where T : class
|
||||
{
|
||||
public List<Operation<T>> Operations { get; private set; }
|
||||
|
||||
public JsonPatchDocument()
|
||||
{
|
||||
Operations = new List<Operation<T>>();
|
||||
}
|
||||
|
||||
// Create from list of operations
|
||||
public JsonPatchDocument(List<Operation<T>> operations)
|
||||
{
|
||||
Operations = operations;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add operation. Will result in, for example,
|
||||
/// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">path</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Add<TProp>(Expression<Func<T, TProp>> path, TProp value)
|
||||
{
|
||||
Operations.Add(new Operation<T>("add", ExpressionHelpers.GetPath<T, TProp>(path).ToLower(), null, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add value to list at given position
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">path</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <param name="position">position</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Add<TProp>(Expression<Func<T, IList<TProp>>> path, TProp value, int position)
|
||||
{
|
||||
Operations.Add(new Operation<T>("add", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/" + position, null, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// At value at end of list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">path</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Add<TProp>(Expression<Func<T, IList<TProp>>> path, TProp value)
|
||||
{
|
||||
Operations.Add(new Operation<T>("add", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/-", null, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove value at target location. Will result in, for example,
|
||||
/// { "op": "remove", "path": "/a/b/c" }
|
||||
/// </summary>
|
||||
/// <param name="remove"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Remove<TProp>(Expression<Func<T, TProp>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("remove", ExpressionHelpers.GetPath<T, TProp>(path).ToLower(), null));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove value from list at given position
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="position">position</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Remove<TProp>(Expression<Func<T, IList<TProp>>> path, int position)
|
||||
{
|
||||
Operations.Add(new Operation<T>("remove", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/" + position, null));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove value from end of list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Remove<TProp>(Expression<Func<T, IList<TProp>>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("remove", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/-", null));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace value. Will result in, for example,
|
||||
/// { "op": "replace", "path": "/a/b/c", "value": 42 }
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Replace<TProp>(Expression<Func<T, TProp>> path, TProp value)
|
||||
{
|
||||
Operations.Add(new Operation<T>("replace", ExpressionHelpers.GetPath<T, TProp>(path).ToLower(), null, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace value in a list at given position
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="position">position</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Replace<TProp>(Expression<Func<T, IList<TProp>>> path, TProp value, int position)
|
||||
{
|
||||
Operations.Add(new Operation<T>("replace", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/" + position, null, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace value at end of a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Replace<TProp>(Expression<Func<T, IList<TProp>>> path, TProp value)
|
||||
{
|
||||
Operations.Add(new Operation<T>("replace", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/-", null, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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" }
|
||||
/// </summary>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Move<TProp>(Expression<Func<T, TProp>> from, Expression<Func<T, TProp>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("move", ExpressionHelpers.GetPath<T, TProp>(path).ToLower()
|
||||
, ExpressionHelpers.GetPath<T, TProp>(from).ToLower()));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move from a position in a list to a new location
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Move<TProp>(Expression<Func<T, IList<TProp>>> from, int positionFrom, Expression<Func<T, TProp>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("move", ExpressionHelpers.GetPath<T, TProp>(path).ToLower()
|
||||
, ExpressionHelpers.GetPath<T, IList<TProp>>(from).ToLower() + "/" + positionFrom));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move from a property to a location in a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Move<TProp>(Expression<Func<T, TProp>> from,
|
||||
Expression<Func<T, IList<TProp>>> path, int positionTo)
|
||||
{
|
||||
Operations.Add(new Operation<T>("move", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower()
|
||||
+ "/" + positionTo
|
||||
, ExpressionHelpers.GetPath<T, TProp>(from).ToLower()));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move from a position in a list to another location in a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Move<TProp>(Expression<Func<T, IList<TProp>>> from, int positionFrom,
|
||||
Expression<Func<T, IList<TProp>>> path, int positionTo)
|
||||
{
|
||||
Operations.Add(new Operation<T>("move", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower()
|
||||
+ "/" + positionTo
|
||||
, ExpressionHelpers.GetPath<T, IList<TProp>>(from).ToLower() + "/" + positionFrom));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move from a position in a list to the end of another list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Move<TProp>(Expression<Func<T, IList<TProp>>> from, int positionFrom,
|
||||
Expression<Func<T, IList<TProp>>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("move", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower()
|
||||
+ "/-"
|
||||
, ExpressionHelpers.GetPath<T, IList<TProp>>(from).ToLower() + "/" + positionFrom));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move to the end of a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Move<TProp>(Expression<Func<T, TProp>> from, Expression<Func<T, IList<TProp>>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("move", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/-"
|
||||
, ExpressionHelpers.GetPath<T, TProp>(from).ToLower()));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy the value at specified location to the target location. Willr esult in, for example:
|
||||
/// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
|
||||
/// </summary>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Copy<TProp>(Expression<Func<T, TProp>> from, Expression<Func<T, TProp>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("copy", ExpressionHelpers.GetPath<T, TProp>(path).ToLower()
|
||||
, ExpressionHelpers.GetPath<T, TProp>(from).ToLower()));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy from a position in a list to a new location
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Copy<TProp>(Expression<Func<T, IList<TProp>>> from, int positionFrom, Expression<Func<T, TProp>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("copy", ExpressionHelpers.GetPath<T, TProp>(path).ToLower()
|
||||
, ExpressionHelpers.GetPath<T, IList<TProp>>(from).ToLower() + "/" + positionFrom));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy from a property to a location in a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Copy<TProp>(Expression<Func<T, TProp>> from,
|
||||
Expression<Func<T, IList<TProp>>> path, int positionTo)
|
||||
{
|
||||
Operations.Add(new Operation<T>("copy", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower()
|
||||
+ "/" + positionTo
|
||||
, ExpressionHelpers.GetPath<T, TProp>(from).ToLower()));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy from a position in a list to a new location in a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Copy<TProp>(Expression<Func<T, IList<TProp>>> from, int positionFrom,
|
||||
Expression<Func<T, IList<TProp>>> path, int positionTo)
|
||||
{
|
||||
Operations.Add(new Operation<T>("copy", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower()
|
||||
+ "/" + positionTo
|
||||
, ExpressionHelpers.GetPath<T, IList<TProp>>(from).ToLower() + "/" + positionFrom));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy from a position in a list to the end of another list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Copy<TProp>(Expression<Func<T, IList<TProp>>> from, int positionFrom,
|
||||
Expression<Func<T, IList<TProp>>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("copy", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower()
|
||||
+ "/-"
|
||||
, ExpressionHelpers.GetPath<T, IList<TProp>>(from).ToLower() + "/" + positionFrom));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy to the end of a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Copy<TProp>(Expression<Func<T, TProp>> from, Expression<Func<T, IList<TProp>>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("copy", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/-"
|
||||
, ExpressionHelpers.GetPath<T, TProp>(from).ToLower()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public void ApplyTo(T objectToApplyTo)
|
||||
{
|
||||
ApplyTo(objectToApplyTo, new SimpleObjectAdapter<T>());
|
||||
}
|
||||
|
||||
public void ApplyTo(T objectToApplyTo, IObjectAdapter<T> adapter)
|
||||
{
|
||||
|
||||
// apply each operation in order
|
||||
foreach (var op in Operations)
|
||||
{
|
||||
op.Apply(objectToApplyTo, adapter);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Operation> GetOperations()
|
||||
{
|
||||
var allOps = new List<Operation>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
|
||||
"Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" },
|
||||
"Microsoft.Framework.NotNullAttribute.Internal": { "version": "1.0.0-*", "type": "build" },
|
||||
"Microsoft.Framework.PropertyHelper.Internal": { "version": "1.0.0-*", "type": "build" },
|
||||
"Newtonsoft.Json": "6.0.6"
|
||||
},
|
||||
"frameworks": {
|
||||
"dnx451": { },
|
||||
"dnxcore50": {
|
||||
"dependencies": {
|
||||
"Microsoft.CSharp": "4.0.0-beta-*",
|
||||
"System.Collections": "4.0.10-beta-*",
|
||||
"System.Collections.Concurrent": "4.0.10-beta-*",
|
||||
"System.Globalization": "4.0.10-beta-*",
|
||||
"System.Runtime.Extensions": "4.0.10-beta-*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.ComponentModel.DataAnnotations;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Framework.Internal;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class JsonPatchInputFormatter : InputFormatter
|
||||
{
|
||||
private const int DefaultMaxDepth = 32;
|
||||
private JsonSerializerSettings _jsonSerializerSettings;
|
||||
|
||||
public JsonPatchInputFormatter()
|
||||
{
|
||||
SupportedEncodings.Add(Encodings.UTF8EncodingWithoutBOM);
|
||||
SupportedEncodings.Add(Encodings.UTF16EncodingLittleEndian);
|
||||
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/jsonpatch"));
|
||||
|
||||
_jsonSerializerSettings = new JsonSerializerSettings
|
||||
{
|
||||
MissingMemberHandling = MissingMemberHandling.Ignore,
|
||||
|
||||
// Limit the object graph we'll consume to a fixed depth. This prevents stackoverflow exceptions
|
||||
// from deserialization errors that might occur from deeply nested objects.
|
||||
MaxDepth = DefaultMaxDepth,
|
||||
|
||||
// Do not change this setting
|
||||
// Setting this to None prevents Json.NET from loading malicious, unsafe, or security-sensitive types
|
||||
TypeNameHandling = TypeNameHandling.None
|
||||
};
|
||||
|
||||
_jsonSerializerSettings.ContractResolver = new JsonContractResolver();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="JsonSerializerSettings"/> used to configure the <see cref="JsonSerializer"/>.
|
||||
/// </summary>
|
||||
public JsonSerializerSettings SerializerSettings
|
||||
{
|
||||
get { return _jsonSerializerSettings; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_jsonSerializerSettings = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<object> ReadRequestBodyAsync([NotNull] InputFormatterContext context)
|
||||
{
|
||||
var type = context.ModelType;
|
||||
var request = context.ActionContext.HttpContext.Request;
|
||||
MediaTypeHeaderValue requestContentType = null;
|
||||
MediaTypeHeaderValue.TryParse(request.ContentType, out requestContentType);
|
||||
|
||||
// Get the character encoding for the content
|
||||
// Never non-null since SelectCharacterEncoding() throws in error / not found scenarios
|
||||
var effectiveEncoding = SelectCharacterEncoding(requestContentType);
|
||||
|
||||
using (var jsonReader = CreateJsonReader(context, request.Body, effectiveEncoding))
|
||||
{
|
||||
jsonReader.CloseInput = false;
|
||||
|
||||
var jsonSerializer = CreateJsonSerializer();
|
||||
|
||||
EventHandler<Newtonsoft.Json.Serialization.ErrorEventArgs> errorHandler = null;
|
||||
errorHandler = (sender, e) =>
|
||||
{
|
||||
var exception = e.ErrorContext.Error;
|
||||
context.ActionContext.ModelState.TryAddModelError(e.ErrorContext.Path, e.ErrorContext.Error);
|
||||
|
||||
// Error must always be marked as handled
|
||||
// Failure to do so can cause the exception to be rethrown at every recursive level and
|
||||
// overflow the stack for x64 CLR processes
|
||||
e.ErrorContext.Handled = true;
|
||||
};
|
||||
jsonSerializer.Error += errorHandler;
|
||||
|
||||
try
|
||||
{
|
||||
var contractObject = SerializerSettings.ContractResolver.ResolveContract(type);
|
||||
return Task.FromResult(jsonSerializer.Deserialize(jsonReader, type));
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Clean up the error handler in case CreateJsonSerializer() reuses a serializer
|
||||
if (errorHandler != null)
|
||||
{
|
||||
jsonSerializer.Error -= errorHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called during deserialization to get the <see cref="JsonReader"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="InputFormatterContext"/> for the read.</param>
|
||||
/// <param name="readStream">The <see cref="Stream"/> from which to read.</param>
|
||||
/// <param name="effectiveEncoding">The <see cref="Encoding"/> to use when reading.</param>
|
||||
/// <returns>The <see cref="JsonReader"/> used during deserialization.</returns>
|
||||
public virtual JsonReader CreateJsonReader([NotNull] InputFormatterContext context,
|
||||
[NotNull] Stream readStream,
|
||||
[NotNull] Encoding effectiveEncoding)
|
||||
{
|
||||
return new JsonTextReader(new StreamReader(readStream, effectiveEncoding));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called during deserialization to get the <see cref="JsonSerializer"/>.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="JsonSerializer"/> used during serialization and deserialization.</returns>
|
||||
public virtual JsonSerializer CreateJsonSerializer()
|
||||
{
|
||||
return JsonSerializer.Create(SerializerSettings);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,7 +22,8 @@
|
|||
"Microsoft.Framework.PropertyActivator.Internal": { "version": "1.0.0-*", "type": "build" },
|
||||
"Microsoft.Framework.PropertyHelper.Internal": { "version": "1.0.0-*", "type": "build" },
|
||||
"Microsoft.Framework.Runtime.Interfaces": "1.0.0-*",
|
||||
"Microsoft.Framework.WebEncoders": "1.0.0-*"
|
||||
"Microsoft.Framework.WebEncoders": "1.0.0-*",
|
||||
"Microsoft.AspNet.JsonPatch": "1.0.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"dnx451": {},
|
||||
|
|
|
@ -60,4 +60,4 @@ namespace Microsoft.AspNet.Builder
|
|||
return app.UseRouter(routes.Build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
// Set up default input formatters.
|
||||
options.InputFormatters.Add(new JsonInputFormatter());
|
||||
options.InputFormatters.Add(new JsonPatchInputFormatter());
|
||||
|
||||
// Set up ValueProviders
|
||||
options.ValueProviderFactories.Add(new RouteValueValueProviderFactory());
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="__ToolsVersion__" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>81c20848-e063-4e12-ac40-0b55a532c16c</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'" Label="Configuration">
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" Label="Configuration">
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch.Test
|
||||
{
|
||||
public class NestedDTO
|
||||
{
|
||||
public string StringProperty { get; set; }
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch.Test
|
||||
{
|
||||
public class SimpleDTO
|
||||
{
|
||||
public List<int> IntegerList { get; set; }
|
||||
public int IntegerValue { get; set; }
|
||||
public string StringProperty { get; set; }
|
||||
public string AnotherStringProperty { get; set; }
|
||||
public decimal DecimalValue { get; set; }
|
||||
|
||||
public double DoubleValue { get; set; }
|
||||
|
||||
public float FloatValue { get; set; }
|
||||
|
||||
public Guid GuidValue { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch.Test
|
||||
{
|
||||
|
||||
public class SimpleDTOWithNestedDTO
|
||||
{
|
||||
public int IntegerValue { get; set; }
|
||||
|
||||
public NestedDTO NestedDTO { get; set; }
|
||||
|
||||
public SimpleDTO SimpleDTO { get; set; }
|
||||
|
||||
public SimpleDTOWithNestedDTO()
|
||||
{
|
||||
this.NestedDTO = new NestedDTO();
|
||||
this.SimpleDTO = new SimpleDTO();
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"compilationOptions": {
|
||||
"warningsAsErrors": true
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
|
||||
"Microsoft.AspNet.JsonPatch": "1.0.0-*",
|
||||
"Microsoft.AspNet.Mvc.Core": "6.0.0-*",
|
||||
"Microsoft.AspNet.Testing": "1.0.0-*",
|
||||
"Moq": "4.2.1312.1622",
|
||||
"Newtonsoft.Json": "6.0.6",
|
||||
"xunit.runner.aspnet": "2.0.0-aspnet-*"
|
||||
},
|
||||
"commands": {
|
||||
"test": "xunit.runner.aspnet"
|
||||
},
|
||||
"frameworks": {
|
||||
"dnx451": { }
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче