Replace of Add operation (jsonpatch dynamic support)

This commit is contained in:
KevinDockx 2015-07-28 12:40:43 +02:00 committed by Ryan Nowak
parent 9d467810b5
commit 94fad918a3
10 changed files with 625 additions and 110 deletions

View File

@ -115,109 +115,222 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
[NotNull] object objectToApplyTo,
[NotNull] Operation 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 = GetNumericEnd(path);
if (positionAsInteger > -1)
{
actualPathToProperty = path.Substring(0,
path.LastIndexOf('/' + positionAsInteger.ToString()));
}
}
var patchProperty = FindPropertyAndParent(objectToApplyTo, actualPathToProperty);
// does property at path exist?
if (!CheckIfPropertyExists(patchProperty, objectToApplyTo, operationToReport, path))
// get path result
var pathResult = GetActualPropertyPath(
path,
objectToApplyTo,
operationToReport);
if (pathResult == null)
{
return;
}
// 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 appendList = pathResult.ExecuteAtEnd;
var positionAsInteger = pathResult.NumericEnd;
var actualPathToProperty = pathResult.PathToProperty;
var treeAnalysisResult = new ObjectTreeAnalysisResult(
objectToApplyTo,
actualPathToProperty,
ContractResolver);
if (!treeAnalysisResult.IsValidPathForAdd)
{
// what if it's an array but there's no position??
if (IsNonStringArray(patchProperty.Property.PropertyType))
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatPropertyCannotBeAdded(path)));
return;
}
if (treeAnalysisResult.UseDynamicLogic)
{
var container = treeAnalysisResult.Container;
if (container.ContainsCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent))
{
// now, get the generic type of the IList<> from Property type.
var genericTypeOfArray = GetIListType(patchProperty.Property.PropertyType);
var conversionResult = ConvertToActualType(genericTypeOfArray, value);
if (!CheckIfPropertyCanBeSet(conversionResult, objectToApplyTo, operationToReport, path))
// Existing property.
// If it's not an array, we need to check if the value fits the property type
//
// If it's an array, we need to check if the value fits in that array type,
// and add it at the correct position (if allowed).
if (appendList || positionAsInteger > -1)
{
return;
}
// get the actual type
var propertyValue = container.GetValueForCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent);
var typeOfPathProperty = value.GetType();
// get value (it can be cast, we just checked that)
var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
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
if (!IsNonStringArray(typeOfPathProperty))
{
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
return;
}
// now, get the generic type of the enumerable
var genericTypeOfArray = GetIListType(typeOfPathProperty);
var conversionResult = ConvertToActualType(genericTypeOfArray, value);
if (!conversionResult.CanBeConverted)
{
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidValueForProperty(value, path)));
return;
}
// get value (it can be cast, we just checked that)
var array = treeAnalysisResult.Container.GetValueForCaseInsensitiveKey(
treeAnalysisResult.PropertyPathInParent) as IList;
if (appendList)
{
array.Add(conversionResult.ConvertedInstance);
treeAnalysisResult.Container.SetValueForCaseInsensitiveKey(
treeAnalysisResult.PropertyPathInParent, array);
}
else
{
// specified index must not be greater than
// the amount of items in the array
if (positionAsInteger > array.Count)
{
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidIndexForArrayProperty(
operationToReport.op,
path)));
return;
}
array.Insert(positionAsInteger, conversionResult.ConvertedInstance);
treeAnalysisResult.Container.SetValueForCaseInsensitiveKey(
treeAnalysisResult.PropertyPathInParent, array);
}
}
else
{
// get the actual type
var typeOfPathProperty = treeAnalysisResult.Container
.GetValueForCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent).GetType();
// can the value be converted to the actual type?
var conversionResult = ConvertToActualType(typeOfPathProperty, value);
if (conversionResult.CanBeConverted)
{
treeAnalysisResult.Container.SetValueForCaseInsensitiveKey(
treeAnalysisResult.PropertyPathInParent,
conversionResult.ConvertedInstance);
}
else
{
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidValueForProperty(conversionResult.ConvertedInstance, path)));
return;
}
}
}
else
{
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
return;
// New property - add it.
treeAnalysisResult.Container.Add(treeAnalysisResult.PropertyPathInParent, value);
}
}
else
{
var conversionResultTuple = ConvertToActualType(
patchProperty.Property.PropertyType,
value);
// If it's an array, add to that array. If it's not, we replace.
// Is conversion successful
if (!CheckIfPropertyCanBeSet(conversionResultTuple, objectToApplyTo, operationToReport, 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.
var patchProperty = treeAnalysisResult.JsonPatchProperty;
if (appendList || positionAsInteger > -1)
{
return;
}
if (!IsNonStringArray(patchProperty.Property.PropertyType))
{
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
return;
}
patchProperty.Property.ValueProvider.SetValue(
// now, get the generic type of the IList<> from Property type.
var genericTypeOfArray = GetIListType(patchProperty.Property.PropertyType);
var conversionResult = ConvertToActualType(genericTypeOfArray, value);
if (!conversionResult.CanBeConverted)
{
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidValueForProperty(conversionResult.ConvertedInstance, path)));
return;
}
if (!patchProperty.Property.Readable)
{
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatCannotReadProperty(path)));
return;
}
var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
if (appendList)
{
array.Add(conversionResult.ConvertedInstance);
}
else if (positionAsInteger <= array.Count)
{
array.Insert(positionAsInteger, conversionResult.ConvertedInstance);
}
else
{
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
return;
}
}
else
{
var conversionResultTuple = ConvertToActualType(
patchProperty.Property.PropertyType,
value);
if (!conversionResultTuple.CanBeConverted)
{
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidValueForProperty(value, path)));
return;
}
if (!patchProperty.Property.Writable)
{
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatCannotUpdateProperty(path)));
return;
}
patchProperty.Property.ValueProvider.SetValue(
patchProperty.Parent,
conversionResultTuple.ConvertedInstance);
}
}
}
@ -646,7 +759,7 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
// if property is of IList type then get the array index from splitPath and get the
// object at the indexed position from the list.
if (GetIListType(property.PropertyType) != null)
if (GetIListType(property.PropertyType) != null)
{
var index = int.Parse(splitPath[++i]);
targetObject = ((IList)targetObject)[index];
@ -722,5 +835,45 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
return -1;
}
private ActualPropertyPathResult GetActualPropertyPath(
[NotNull] string propertyPath,
[NotNull] object objectToApplyTo,
[NotNull] Operation operationToReport)
{
if (propertyPath.EndsWith("/-"))
{
return new ActualPropertyPathResult(-1, propertyPath.Substring(0, propertyPath.Length - 2), true);
}
else
{
var possibleIndex = propertyPath.Substring(propertyPath.LastIndexOf("/") + 1);
int castedIndex = -1;
if (int.TryParse(possibleIndex, out castedIndex))
{
// has numeric end.
if (castedIndex > -1)
{
var pathToProperty = propertyPath.Substring(
0,
propertyPath.LastIndexOf('/' + castedIndex.ToString()));
return new ActualPropertyPathResult(castedIndex, pathToProperty, false);
}
else
{
// negative position - invalid path
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatNegativeIndexForArrayProperty(operationToReport.op, propertyPath)));
return null;
}
}
return new ActualPropertyPathResult(-1, propertyPath, false);
}
}
}
}

View File

@ -0,0 +1,22 @@
// 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.AspNet.JsonPatch.Helpers
{
internal class ActualPropertyPathResult
{
public int NumericEnd { get; private set; }
public string PathToProperty { get; set; }
public bool ExecuteAtEnd { get; set; }
public ActualPropertyPathResult(
int numericEnd,
string pathToProperty,
bool executeAtEnd)
{
NumericEnd = numericEnd;
PathToProperty = pathToProperty;
ExecuteAtEnd = executeAtEnd;
}
}
}

View File

@ -0,0 +1,72 @@
// 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;
namespace Microsoft.AspNet.JsonPatch.Helpers
{
// Helper methods to allow case-insensitive key search
internal static class ExpandoObjectDictionaryExtensions
{
internal static void SetValueForCaseInsensitiveKey(this IDictionary<string, object> propertyDictionary,
string key, object value)
{
foreach (KeyValuePair<string, object> kvp in propertyDictionary)
{
if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase))
{
propertyDictionary[kvp.Key] = value;
break;
}
}
}
internal static void RemoveValueForCaseInsensitiveKey(this IDictionary<string, object> propertyDictionary,
string key)
{
string realKey = null;
foreach (KeyValuePair<string, object> kvp in propertyDictionary)
{
if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase))
{
realKey = kvp.Key;
break;
}
}
if (realKey != null)
{
propertyDictionary.Remove(realKey);
}
}
internal static object GetValueForCaseInsensitiveKey(this IDictionary<string, object> propertyDictionary,
string key)
{
foreach (KeyValuePair<string, object> kvp in propertyDictionary)
{
if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase))
{
return kvp.Value;
}
}
throw new ArgumentException(Resources.FormatDictionaryKeyNotFound(key));
}
internal static bool ContainsCaseInsensitiveKey(this IDictionary<string, object> propertyDictionary,
string key)
{
foreach (KeyValuePair<string, object> kvp in propertyDictionary)
{
if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
}
}

View File

@ -0,0 +1,210 @@
// 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 System.Linq;
using Newtonsoft.Json.Serialization;
namespace Microsoft.AspNet.JsonPatch.Helpers
{
internal class ObjectTreeAnalysisResult
{
// either the property is part of the container dictionary,
// or we have a direct reference to a JsonPatchProperty instance
public bool UseDynamicLogic { get; private set; }
public bool IsValidPathForAdd { get; private set; }
public bool IsValidPathForRemove { get; private set; }
public IDictionary<string, object> Container { get; private set; }
public string PropertyPathInParent { get; private set; }
public JsonPatchProperty JsonPatchProperty { get; private set; }
public ObjectTreeAnalysisResult(
object objectToSearch,
string propertyPath,
IContractResolver contractResolver)
{
// construct the analysis result.
// split the propertypath, and if necessary, remove the first
// empty item (that's the case when it starts with a "/")
var propertyPathTree = propertyPath.Split(
new char[] { '/' },
StringSplitOptions.RemoveEmptyEntries);
// we've now got a split up property tree "base/property/otherproperty/..."
int lastPosition = 0;
object targetObject = objectToSearch;
for (int i = 0; i < propertyPathTree.Length; i++)
{
lastPosition = i;
// if the current target object is an ExpandoObject (IDictionary<string, object>),
// we cannot use the ContractResolver.
var dictionary = targetObject as IDictionary<string, object>;
if (dictionary != null)
{
// find the value in the dictionary
if (dictionary.ContainsCaseInsensitiveKey(propertyPathTree[i]))
{
var possibleNewTargetObject = dictionary.GetValueForCaseInsensitiveKey(propertyPathTree[i]);
// unless we're at the last item, we should set the targetobject
// to the new object. If we're at the last item, we need to stop
if (i != propertyPathTree.Length - 1)
{
targetObject = possibleNewTargetObject;
}
}
else
{
break;
}
}
else
{
// if the current part of the path is numeric, this means we're trying
// to get the propertyInfo of a specific object in an array. To allow
// for this, the previous value (targetObject) must be an IEnumerable, and
// the position must exist.
int numericValue = -1;
if (int.TryParse(propertyPathTree[i], out numericValue))
{
var element = GetElementAtFromObject(targetObject, numericValue);
if (element != null)
{
targetObject = element;
}
else
{
break;
}
}
else
{
var jsonContract = (JsonObjectContract)contractResolver.ResolveContract(targetObject.GetType());
// does the property exist?
var attemptedProperty = jsonContract
.Properties
.FirstOrDefault(p => string.Equals(p.PropertyName, propertyPathTree[i], StringComparison.OrdinalIgnoreCase));
if (attemptedProperty != null)
{
// unless we're at the last item, we should continue searching.
// If we're at the last item, we need to stop
if ((i != propertyPathTree.Length - 1))
{
targetObject = attemptedProperty.ValueProvider.GetValue(targetObject);
}
}
else
{
// property cannot be found, and we're not working with dynamics.
// Stop, and return invalid path.
break;
}
}
}
}
if (propertyPathTree.Length - lastPosition != 1)
{
IsValidPathForAdd = false;
IsValidPathForRemove = false;
return;
}
// two things can happen now. The targetproperty can be an IDictionary - in that
// case, it's valid for add if there's 1 item left in the propertyPathTree.
//
// it can also be a property info. In that case, if there's nothing left in the path
// tree we're at the end, if there's one left we can try and set that.
if (targetObject is IDictionary<string, object>)
{
UseDynamicLogic = true;
Container = (IDictionary<string, object>)targetObject;
IsValidPathForAdd = true;
PropertyPathInParent = propertyPathTree[propertyPathTree.Length - 1];
// to be able to remove this property, it must exist
IsValidPathForRemove = Container.ContainsCaseInsensitiveKey(PropertyPathInParent);
}
else if (targetObject is IList)
{
System.Diagnostics.Debugger.Launch();
UseDynamicLogic = false;
int index;
if (!Int32.TryParse(propertyPathTree[propertyPathTree.Length - 1], out index))
{
// We only support indexing into a list
IsValidPathForAdd = false;
IsValidPathForRemove = false;
return;
}
IsValidPathForAdd = true;
IsValidPathForRemove = ((IList)targetObject).Count > index;
PropertyPathInParent = propertyPathTree[propertyPathTree.Length - 1];
}
else
{
UseDynamicLogic = false;
var property = propertyPathTree[propertyPathTree.Length - 1];
var jsonContract = (JsonObjectContract)contractResolver.ResolveContract(targetObject.GetType());
var attemptedProperty = jsonContract
.Properties
.FirstOrDefault(p => string.Equals(p.PropertyName, property, StringComparison.OrdinalIgnoreCase));
if (attemptedProperty == null)
{
IsValidPathForAdd = false;
IsValidPathForRemove = false;
}
else
{
IsValidPathForAdd = true;
IsValidPathForRemove = true;
JsonPatchProperty = new JsonPatchProperty(attemptedProperty, targetObject);
PropertyPathInParent = property;
}
}
}
private object GetElementAtFromObject(object targetObject, int numericValue)
{
if (numericValue > -1)
{
// Check if the targetobject is an IEnumerable,
// and if the position is valid.
if (targetObject is IEnumerable)
{
var indexable = ((IEnumerable)targetObject).Cast<object>();
if (indexable.Count() >= numericValue)
{
return indexable.ElementAt(numericValue);
}
else { return null; }
}
else { return null; }
}
else
{
return null;
}
}
}
}

View File

@ -58,6 +58,23 @@ namespace Microsoft.AspNet.JsonPatch
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidIndexForArrayProperty"), p0, p1);
}
/// <summary>
/// For operation '{0}' on array property at path '{1}', the index is negative.
/// </summary>
internal static string NegativeIndexForArrayProperty
{
get { return GetString("NegativeIndexForArrayProperty"); }
}
/// <summary>
/// For operation '{0}' on array property at path '{1}', the index is negative.
/// </summary>
internal static string FormatNegativeIndexForArrayProperty(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("NegativeIndexForArrayProperty"), p0, p1);
}
/// <summary>
/// The type '{0}' was malformed and could not be parsed.
/// </summary>
@ -186,6 +203,23 @@ namespace Microsoft.AspNet.JsonPatch
return string.Format(CultureInfo.CurrentCulture, GetString("PropertyDoesNotExist"), p0);
}
/// <summary>
/// The key '{0}' was not found.
/// </summary>
internal static string DictionaryKeyNotFound
{
get { return GetString("DictionaryKeyNotFound"); }
}
/// <summary>
/// The key '{0}' was not found.
/// </summary>
internal static string FormatDictionaryKeyNotFound(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("DictionaryKeyNotFound"), p0);
}
/// <summary>
/// The test operation is not supported.
/// </summary>

View File

@ -123,6 +123,9 @@
<data name="CannotUpdateProperty" xml:space="preserve">
<value>The property at path '{0}' could not be updated.</value>
</data>
<data name="DictionaryKeyNotFound" xml:space="preserve">
<value>The key '{0}' was not found.</value>
</data>
<data name="InvalidIndexForArrayProperty" xml:space="preserve">
<value>For operation '{0}' on array property at path '{1}', the index is larger than the array size.</value>
</data>
@ -138,6 +141,9 @@
<data name="InvalidValueForProperty" xml:space="preserve">
<value>The value '{0}' is invalid for property at path '{1}'.</value>
</data>
<data name="NegativeIndexForArrayProperty" xml:space="preserve">
<value>For operation '{0}' on array property at path '{1}', the index is negative.</value>
</data>
<data name="ParameterMustMatchType" xml:space="preserve">
<value>'{0}' must be of type '{1}'.</value>
</data>

View File

@ -470,7 +470,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
// Act & Assert
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", exception.Message);
Assert.Equal(
"For operation 'add' on array property at path '/simpledto/integerlist/-1', the index is negative.",
exception.Message);
}
[Fact]
@ -497,7 +499,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
{
deserialized.ApplyTo(doc);
});
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", exception.Message);
Assert.Equal(
"For operation 'add' on array property at path '/simpledto/integerlist/-1', the index is negative.",
exception.Message);
}
[Fact]
@ -523,7 +527,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
//Assert
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", logger.ErrorMessage);
Assert.Equal(
"For operation 'add' on array property at path '/simpledto/integerlist/-1', the index is negative.",
logger.ErrorMessage);
}
[Fact]
@ -1316,7 +1322,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
// Act & Assert
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", exception.Message);
Assert.Equal(
"Property does not exist at path '/simpledto/integerlist/-1'.",
exception.Message);
}
[Fact]
@ -1340,7 +1348,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
// Act & Assert
var exception = Assert.Throws<JsonPatchException>(() => { deserialized.ApplyTo(doc); });
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", exception.Message);
Assert.Equal(
"Property does not exist at path '/simpledto/integerlist/-1'.",
exception.Message);
}
[Fact]
@ -1366,7 +1376,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
// Assert
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", logger.ErrorMessage);
Assert.Equal(
"For operation 'replace' on array property at path '/simpledto/integerlist/-1', the index is negative.",
logger.ErrorMessage);
}
[Fact]

View File

@ -312,7 +312,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
// Act & Assert
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
Assert.Equal("Property does not exist at path '/integerlist/-1'.", exception.Message);
Assert.Equal(
"For operation 'add' on array property at path '/integerlist/-1', the index is negative.",
exception.Message);
}
[Fact]
@ -333,7 +335,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
// Act & Assert
var exception = Assert.Throws<JsonPatchException>(() => { deserialized.ApplyTo(doc); });
Assert.Equal("Property does not exist at path '/integerlist/-1'.", exception.Message);
Assert.Equal(
"For operation 'add' on array property at path '/integerlist/-1', the index is negative.",
exception.Message);
}
[Fact]
@ -354,7 +358,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
patchDoc.ApplyTo(doc, logger.LogErrorMessage);
// Assert
Assert.Equal("Property does not exist at path '/integerlist/-1'.", logger.ErrorMessage);
Assert.Equal(
"For operation 'add' on array property at path '/integerlist/-1', the index is negative.",
logger.ErrorMessage);
}
[Fact]

View File

@ -33,7 +33,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var client = server.CreateClient();
var input = "[{ \"op\": \"add\", " +
"\"path\": \"Customer/Orders/2\", " +
"\"path\": \"Orders/2\", " +
"\"value\": { \"OrderName\": \"Name2\" }}]";
var request = new HttpRequestMessage
{
@ -62,7 +62,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var client = server.CreateClient();
var input = "[{ \"op\": \"replace\", " +
"\"path\": \"Customer/Orders/0/OrderName\", " +
"\"path\": \"Orders/0/OrderName\", " +
"\"value\": \"ReplacedOrder\" }]";
var request = new HttpRequestMessage
{
@ -91,8 +91,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var client = server.CreateClient();
var input = "[{ \"op\": \"copy\", " +
"\"path\": \"Customer/Orders/1/OrderName\", " +
"\"from\": \"Customer/Orders/0/OrderName\"}]";
"\"path\": \"Orders/1/OrderName\", " +
"\"from\": \"Orders/0/OrderName\"}]";
var request = new HttpRequestMessage
{
Content = new StringContent(input, Encoding.UTF8, "application/json-patch+json"),
@ -120,8 +120,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var client = server.CreateClient();
var input = "[{ \"op\": \"move\", " +
"\"path\": \"Customer/Orders/1/OrderName\", " +
"\"from\": \"Customer/Orders/0/OrderName\"}]";
"\"path\": \"Orders/1/OrderName\", " +
"\"from\": \"Orders/0/OrderName\"}]";
var request = new HttpRequestMessage
{
Content = new StringContent(input, Encoding.UTF8, "application/json-patch+json"),
@ -151,7 +151,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var client = server.CreateClient();
var input = "[{ \"op\": \"remove\", " +
"\"path\": \"Customer/Orders/1/OrderName\"}]";
"\"path\": \"Orders/1/OrderName\"}]";
var request = new HttpRequestMessage
{
Content = new StringContent(input, Encoding.UTF8, "application/json-patch+json"),
@ -179,13 +179,13 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var client = server.CreateClient();
var input = "[{ \"op\": \"add\", "+
"\"path\": \"Customer/Orders/2\", " +
"\"path\": \"Orders/2\", " +
"\"value\": { \"OrderName\": \"Name2\" }}, " +
"{\"op\": \"copy\", " +
"\"from\": \"Customer/Orders/2\", " +
"\"path\": \"Customer/Orders/3\" }, " +
"\"from\": \"Orders/2\", " +
"\"path\": \"Orders/3\" }, " +
"{\"op\": \"replace\", " +
"\"path\": \"Customer/Orders/2/OrderName\", " +
"\"path\": \"Orders/2/OrderName\", " +
"\"value\": \"ReplacedName\" }]";
var request = new HttpRequestMessage
{
@ -213,46 +213,46 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
new object[] {
"http://localhost/jsonpatch/JsonPatchWithModelStateAndPrefix?prefix=Patch",
"[{ \"op\": \"add\", " +
"\"path\": \"Customer/Orders/5\", " +
"\"path\": \"Orders/5\", " +
"\"value\": { \"OrderName\": \"Name5\" }}]",
"{\"Patch.Customer\":[\"For operation 'add' on array property at path " +
"'Customer/Orders/5', the index is larger than the array size.\"]}"
"'Orders/5', the index is larger than the array size.\"]}"
},
new object[] {
"http://localhost/jsonpatch/JsonPatchWithModelState",
"[{ \"op\": \"add\", " +
"\"path\": \"Customer/Orders/5\", " +
"\"path\": \"Orders/5\", " +
"\"value\": { \"OrderName\": \"Name5\" }}]",
"{\"Customer\":[\"For operation 'add' on array property at path " +
"'Customer/Orders/5', the index is larger than the array size.\"]}"
"'Orders/5', the index is larger than the array size.\"]}"
},
new object[] {
"http://localhost/jsonpatch/JsonPatchWithModelStateAndPrefix?prefix=Patch",
"[{ \"op\": \"add\", " +
"\"path\": \"Customer/Orders/2\", " +
"\"path\": \"Orders/2\", " +
"\"value\": { \"OrderName\": \"Name2\" }}, " +
"{\"op\": \"copy\", " +
"\"from\": \"Customer/Orders/4\", " +
"\"path\": \"Customer/Orders/3\" }, " +
"\"from\": \"Orders/4\", " +
"\"path\": \"Orders/3\" }, " +
"{\"op\": \"replace\", " +
"\"path\": \"Customer/Orders/2/OrderName\", " +
"\"path\": \"Orders/2/OrderName\", " +
"\"value\": \"ReplacedName\" }]",
"{\"Patch.Customer\":[\"For operation 'copy' on array property at path " +
"'Customer/Orders/4', the index is larger than the array size.\"]}"
"'Orders/4', the index is larger than the array size.\"]}"
},
new object[] {
"http://localhost/jsonpatch/JsonPatchWithModelState",
"[{ \"op\": \"add\", " +
"\"path\": \"Customer/Orders/2\", " +
"\"path\": \"Orders/2\", " +
"\"value\": { \"OrderName\": \"Name2\" }}, " +
"{\"op\": \"copy\", " +
"\"from\": \"Customer/Orders/4\", " +
"\"path\": \"Customer/Orders/3\" }, " +
"\"from\": \"Orders/4\", " +
"\"path\": \"Orders/3\" }, " +
"{\"op\": \"replace\", " +
"\"path\": \"Customer/Orders/2/OrderName\", " +
"\"path\": \"Orders/2/OrderName\", " +
"\"value\": \"ReplacedName\" }]",
"{\"Customer\":[\"For operation 'copy' on array property at path " +
"'Customer/Orders/4', the index is larger than the array size.\"]}"
"'Orders/4', the index is larger than the array size.\"]}"
}
};
}
@ -288,7 +288,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var client = server.CreateClient();
var input = "{ \"op\": \"add\", " +
"\"path\": \"Customer/Orders/2\", " +
"\"path\": \"Orders/2\", " +
"\"value\": { \"OrderName\": \"Name2\" }}";
var request = new HttpRequestMessage
{
@ -313,7 +313,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var client = server.CreateClient();
var input = "[{ \"op\": \"add\", " +
"\"path\": \"Customer/Orders/2\", " +
"\"path\": \"Orders/2\", " +
"\"value\": { \"OrderType\": \"Type2\" }}]";
var request = new HttpRequestMessage
{
@ -339,7 +339,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var client = server.CreateClient();
var input = "[{ \"op\": \"add\", " +
"\"path\": \"Product/ProductCategory\", " +
"\"path\": \"ProductCategory\", " +
"\"value\": { \"CategoryName\": \"Name2\" }}]";
var request = new HttpRequestMessage
{

View File

@ -25,7 +25,7 @@ namespace Microsoft.AspNet.Mvc
// Assert
var error = Assert.Single(modelState["Customer"].Errors);
Assert.Equal("Property does not exist at path 'Customer/CustomerId'.", error.ErrorMessage);
Assert.Equal("The property at path 'Customer/CustomerId' could not be added.", error.ErrorMessage);
}
[Fact]
@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc
// Assert
var error = Assert.Single(modelState["jsonpatch.Customer"].Errors);
Assert.Equal("Property does not exist at path 'Customer/CustomerId'.", error.ErrorMessage);
Assert.Equal("The property at path 'Customer/CustomerId' could not be added.", error.ErrorMessage);
}
public class Customer