Removed reflection code; used JsonContract instead and added new JsonPatchInputFormatter
This commit is contained in:
parent
f1e1d8f4df
commit
a5da5b3acd
|
|
@ -0,0 +1,54 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using Microsoft.AspNet.JsonPatch;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace MvcSample.Web.Controllers
|
||||||
|
{
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class JsonPatchController : Controller
|
||||||
|
{
|
||||||
|
public ActionResult Index()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPatch]
|
||||||
|
public IActionResult Patch([FromBody] JsonPatchDocument<Customer> patchDoc)
|
||||||
|
{
|
||||||
|
var customer = new Customer
|
||||||
|
{
|
||||||
|
Name = "John",
|
||||||
|
Orders = new List<Order>()
|
||||||
|
{
|
||||||
|
new Order
|
||||||
|
{
|
||||||
|
OrderName = "Order1"
|
||||||
|
},
|
||||||
|
new Order
|
||||||
|
{
|
||||||
|
OrderName = "Order2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
patchDoc.ApplyTo(customer);
|
||||||
|
|
||||||
|
return new ObjectResult(customer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Customer
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public List<Order> Orders { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Order
|
||||||
|
{
|
||||||
|
public string OrderName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
<head>
|
||||||
|
<script src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.0.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
var currentObj = {
|
||||||
|
"Name": "John",
|
||||||
|
"Orders": [{"OrderName": "Order1"},
|
||||||
|
{"OrderName": "Order2"}]
|
||||||
|
};
|
||||||
|
$('#currentlabel').text(JSON.stringify(currentObj))
|
||||||
|
|
||||||
|
$('#addorder').on("click", function () {
|
||||||
|
var obj = [{
|
||||||
|
"op": "Add",
|
||||||
|
"path": "Customer/Orders/2",
|
||||||
|
"value": { "OrderName": "Name3" }
|
||||||
|
}]
|
||||||
|
$.ajax({
|
||||||
|
url: 'jsonpatch',
|
||||||
|
type: 'PATCH',
|
||||||
|
data: JSON.stringify(obj),
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'application/json-patch+json',
|
||||||
|
success: function (data) {
|
||||||
|
$('#addlabel').text(JSON.stringify(data))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#removeorder').on("click", function () {
|
||||||
|
var obj = [{
|
||||||
|
"op": "Remove",
|
||||||
|
"path": "Customer/Name"
|
||||||
|
}]
|
||||||
|
$.ajax({
|
||||||
|
url: 'jsonpatch',
|
||||||
|
type: 'PATCH',
|
||||||
|
data: JSON.stringify(obj),
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'application/json-patch+json',
|
||||||
|
success: function (data) {
|
||||||
|
$('#removelabel').text(JSON.stringify(data))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#moveorder').on("click", function () {
|
||||||
|
var obj = [{
|
||||||
|
"op": "Move",
|
||||||
|
"from": "Customer/Orders/0",
|
||||||
|
"path": "Customer/Orders/1"
|
||||||
|
}]
|
||||||
|
$.ajax({
|
||||||
|
url: 'jsonpatch',
|
||||||
|
type: 'PATCH',
|
||||||
|
data: JSON.stringify(obj),
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'application/json-patch+json',
|
||||||
|
success: function (data) {
|
||||||
|
$('#movelabel').text(JSON.stringify(data))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#replaceorder').on("click", function () {
|
||||||
|
var obj = [{
|
||||||
|
"op": "Replace",
|
||||||
|
"path": "Customer/Name",
|
||||||
|
"value": "ReplacedName"
|
||||||
|
}]
|
||||||
|
$.ajax({
|
||||||
|
url: 'jsonpatch',
|
||||||
|
type: 'PATCH',
|
||||||
|
data: JSON.stringify(obj),
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'application/json-patch+json',
|
||||||
|
success: function (data) {
|
||||||
|
$('#replacelabel').text(JSON.stringify(data))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<label>Current Customer</label>
|
||||||
|
<br />
|
||||||
|
<label id="currentlabel" name="currentlabel"></label>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button id="addorder" type="submit" class="btn btn-primary"> Add </button>
|
||||||
|
<br />
|
||||||
|
<label id="addlabel" name="addlabel"></label>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button id="removeorder" type="submit" class="btn btn-primary"> Remove </button>
|
||||||
|
<br />
|
||||||
|
<label id="removelabel" name="removelabel"></label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button id="moveorder" type="submit" class="btn btn-primary"> Move </button>
|
||||||
|
<br />
|
||||||
|
<label id="movelabel" name="movelabel"></label>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button id="replaceorder" type="submit" class="btn btn-primary"> Replace </button>
|
||||||
|
<br />
|
||||||
|
<label id="replacelabel" name="replacelabel"></label>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
using System;
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
using Microsoft.AspNet.JsonPatch.Operations;
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
using Microsoft.AspNet.JsonPatch.Helpers;
|
|
||||||
using Microsoft.AspNet.JsonPatch.Exceptions;
|
using System;
|
||||||
using System.Reflection;
|
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNet.JsonPatch.Exceptions;
|
||||||
|
using Microsoft.AspNet.JsonPatch.Helpers;
|
||||||
|
using Microsoft.AspNet.JsonPatch.Operations;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Serialization;
|
using Newtonsoft.Json.Serialization;
|
||||||
|
|
||||||
|
|
@ -11,6 +14,13 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
||||||
{
|
{
|
||||||
public class SimpleObjectAdapter<T> : IObjectAdapter<T> where T : class
|
public class SimpleObjectAdapter<T> : IObjectAdapter<T> where T : class
|
||||||
{
|
{
|
||||||
|
public IContractResolver ContractResolver { get; set; }
|
||||||
|
|
||||||
|
public SimpleObjectAdapter(IContractResolver contractResolver)
|
||||||
|
{
|
||||||
|
ContractResolver = contractResolver;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The "add" operation performs one of the following functions,
|
/// The "add" operation performs one of the following functions,
|
||||||
/// depending upon what the target location references:
|
/// depending upon what the target location references:
|
||||||
|
|
@ -89,7 +99,6 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
||||||
// first up: if the path ends in a numeric value, we're inserting in a list and
|
// 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
|
// that value represents the position; if the path ends in "-", we're appending
|
||||||
// to the list.
|
// to the list.
|
||||||
|
|
||||||
var appendList = false;
|
var appendList = false;
|
||||||
var positionAsInteger = -1;
|
var positionAsInteger = -1;
|
||||||
var actualPathToProperty = path;
|
var actualPathToProperty = path;
|
||||||
|
|
@ -110,47 +119,30 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var pathProperty = PropertyHelpers
|
var patchProperty = PropertyHelpers
|
||||||
.FindProperty(objectToApplyTo, actualPathToProperty);
|
.FindPropertyAndParent(objectToApplyTo, actualPathToProperty, ContractResolver);
|
||||||
|
|
||||||
|
|
||||||
// does property at path exist?
|
// does property at path exist?
|
||||||
if (pathProperty == null)
|
CheckIfPropertyExists(patchProperty, objectToApplyTo, operationToReport, path);
|
||||||
{
|
|
||||||
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.
|
// 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,
|
// 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.
|
// the path must end with "/position" or "/-", which we already determined before.
|
||||||
|
|
||||||
if (appendList || positionAsInteger > -1)
|
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??
|
// what if it's an array but there's no position??
|
||||||
if (isNonStringArray)
|
if (IsNonStringArray(patchProperty))
|
||||||
{
|
{
|
||||||
// now, get the generic type of the enumerable
|
// now, get the generic type of the enumerable
|
||||||
var genericTypeOfArray = PropertyHelpers.GetEnumerableType(pathProperty.PropertyType);
|
var genericTypeOfArray = PropertyHelpers.GetEnumerableType(
|
||||||
|
patchProperty.Property.PropertyType);
|
||||||
|
|
||||||
var conversionResult = PropertyHelpers.ConvertToActualType(genericTypeOfArray, value);
|
var conversionResult = PropertyHelpers.ConvertToActualType(genericTypeOfArray, value);
|
||||||
|
|
||||||
if (!conversionResult.CanBeConverted)
|
CheckIfPropertyCanBeSet(conversionResult, objectToApplyTo, operationToReport, path);
|
||||||
{
|
|
||||||
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)
|
// get value (it can be cast, we just checked that)
|
||||||
var array = PropertyHelpers.GetValue(pathProperty, objectToApplyTo, actualPathToProperty) as IList;
|
var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
|
||||||
|
|
||||||
if (appendList)
|
if (appendList)
|
||||||
{
|
{
|
||||||
|
|
@ -167,41 +159,34 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
||||||
{
|
{
|
||||||
throw new JsonPatchException<T>(operationToReport,
|
throw new JsonPatchException<T>(operationToReport,
|
||||||
string.Format("Patch failed: provided path is invalid for array property type at " +
|
string.Format("Patch failed: provided path is invalid for array property type at " +
|
||||||
"location path: {0}: position doesn't exist in array",
|
"location path: {0}: position larger than array size",
|
||||||
path),
|
path),
|
||||||
objectToApplyTo);
|
objectToApplyTo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new JsonPatchException<T>(operationToReport,
|
throw new JsonPatchException<T>(operationToReport,
|
||||||
string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array",
|
string.Format("Patch failed: provided path is invalid for array property type at location " +
|
||||||
|
"path: {0}: expected array",
|
||||||
path),
|
path),
|
||||||
objectToApplyTo);
|
objectToApplyTo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var conversionResultTuple = PropertyHelpers.ConvertToActualType(pathProperty.PropertyType, value);
|
var conversionResultTuple = PropertyHelpers.ConvertToActualType(
|
||||||
|
patchProperty.Property.PropertyType,
|
||||||
|
value);
|
||||||
|
|
||||||
// conversion successful
|
// Is conversion successful
|
||||||
if (conversionResultTuple.CanBeConverted)
|
CheckIfPropertyCanBeSet(conversionResultTuple, objectToApplyTo, operationToReport, path);
|
||||||
{
|
|
||||||
PropertyHelpers.SetValue(pathProperty, objectToApplyTo, actualPathToProperty,
|
patchProperty.Property.ValueProvider.SetValue(
|
||||||
|
patchProperty.Parent,
|
||||||
conversionResultTuple.ConvertedInstance);
|
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>
|
/// <summary>
|
||||||
|
|
@ -229,13 +214,11 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
||||||
/// <param name="objectApplyTo">Object to apply the operation to</param>
|
/// <param name="objectApplyTo">Object to apply the operation to</param>
|
||||||
public void Move(Operation<T> operation, T objectToApplyTo)
|
public void Move(Operation<T> operation, T objectToApplyTo)
|
||||||
{
|
{
|
||||||
|
|
||||||
// get value at from location
|
// get value at from location
|
||||||
object valueAtFromLocation = null;
|
object valueAtFromLocation = null;
|
||||||
var positionAsInteger = -1;
|
var positionAsInteger = -1;
|
||||||
var actualFromProperty = operation.from;
|
var actualFromProperty = operation.from;
|
||||||
|
|
||||||
|
|
||||||
positionAsInteger = PropertyHelpers.GetNumericEnd(operation.from);
|
positionAsInteger = PropertyHelpers.GetNumericEnd(operation.from);
|
||||||
|
|
||||||
if (positionAsInteger > -1)
|
if (positionAsInteger > -1)
|
||||||
|
|
@ -244,51 +227,40 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
||||||
operation.from.IndexOf('/' + positionAsInteger.ToString()));
|
operation.from.IndexOf('/' + positionAsInteger.ToString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
var fromProperty = PropertyHelpers
|
var patchProperty = PropertyHelpers
|
||||||
.FindProperty(objectToApplyTo, actualFromProperty);
|
.FindPropertyAndParent(objectToApplyTo, actualFromProperty, ContractResolver);
|
||||||
|
|
||||||
|
|
||||||
// does property at from exist?
|
// does property at from exist?
|
||||||
if (fromProperty == null)
|
CheckIfPropertyExists(patchProperty, objectToApplyTo, operation, operation.from);
|
||||||
{
|
|
||||||
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,
|
// 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.
|
// the path must end with "/position" or "/-", which we already determined before.
|
||||||
|
|
||||||
if (positionAsInteger > -1)
|
if (positionAsInteger > -1)
|
||||||
{
|
{
|
||||||
|
if (IsNonStringArray(patchProperty))
|
||||||
var isNonStringArray = !(fromProperty.PropertyType == typeof(string))
|
|
||||||
&& typeof(IList).IsAssignableFrom(fromProperty.PropertyType);
|
|
||||||
|
|
||||||
if (isNonStringArray)
|
|
||||||
{
|
{
|
||||||
// now, get the generic type of the enumerable
|
// now, get the generic type of the enumerable
|
||||||
var genericTypeOfArray = PropertyHelpers.GetEnumerableType(fromProperty.PropertyType);
|
var genericTypeOfArray = PropertyHelpers.GetEnumerableType(patchProperty.Property.PropertyType);
|
||||||
|
|
||||||
// get value (it can be cast, we just checked that)
|
// get value (it can be cast, we just checked that)
|
||||||
var array = PropertyHelpers.GetValue(fromProperty, objectToApplyTo, actualFromProperty) as IList;
|
var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
|
||||||
|
|
||||||
if (array.Count <= positionAsInteger)
|
if (array.Count <= positionAsInteger)
|
||||||
{
|
{
|
||||||
throw new JsonPatchException<T>(operation,
|
throw new JsonPatchException<T>(operation,
|
||||||
string.Format("Patch failed: provided from path is invalid for array property type at location from: {0}: invalid position",
|
string.Format("Patch failed: provided from path is invalid for array property type at " +
|
||||||
|
"location from: {0}: invalid position",
|
||||||
operation.from),
|
operation.from),
|
||||||
objectToApplyTo);
|
objectToApplyTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
valueAtFromLocation = array[positionAsInteger];
|
valueAtFromLocation = array[positionAsInteger];
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new JsonPatchException<T>(operation,
|
throw new JsonPatchException<T>(operation,
|
||||||
string.Format("Patch failed: provided from path is invalid for array property type at location from: {0}: expected array",
|
string.Format("Patch failed: provided from path is invalid for array property type at " +
|
||||||
|
"location from: {0}: expected array",
|
||||||
operation.from),
|
operation.from),
|
||||||
objectToApplyTo);
|
objectToApplyTo);
|
||||||
}
|
}
|
||||||
|
|
@ -296,22 +268,15 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// no list, just get the value
|
// no list, just get the value
|
||||||
|
|
||||||
// set the new value
|
// set the new value
|
||||||
|
valueAtFromLocation = patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
|
||||||
valueAtFromLocation = PropertyHelpers.GetValue(fromProperty, objectToApplyTo, actualFromProperty);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// remove that value
|
// remove that value
|
||||||
|
|
||||||
Remove(operation.from, objectToApplyTo, operation);
|
Remove(operation.from, objectToApplyTo, operation);
|
||||||
|
|
||||||
// add that value to the path location
|
// add that value to the path location
|
||||||
|
|
||||||
Add(operation.path, valueAtFromLocation, objectToApplyTo, operation);
|
Add(operation.path, valueAtFromLocation, objectToApplyTo, operation);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -359,35 +324,25 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var pathProperty = PropertyHelpers
|
var patchProperty = PropertyHelpers
|
||||||
.FindProperty(objectToApplyTo, actualPathToProperty);
|
.FindPropertyAndParent(objectToApplyTo, actualPathToProperty, ContractResolver);
|
||||||
|
|
||||||
// does the target location exist?
|
// does the target location exist?
|
||||||
if (pathProperty == null)
|
CheckIfPropertyExists(patchProperty, objectToApplyTo, operationToReport, path);
|
||||||
{
|
|
||||||
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
|
// 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
|
// it to null or its default value; in case of an array, remove at provided index
|
||||||
// or at the end.
|
// or at the end.
|
||||||
if (removeFromList || positionAsInteger > -1)
|
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??
|
// what if it's an array but there's no position??
|
||||||
if (isNonStringArray)
|
if (IsNonStringArray(patchProperty))
|
||||||
{
|
{
|
||||||
// now, get the generic type of the enumerable
|
// now, get the generic type of the enumerable
|
||||||
var genericTypeOfArray = PropertyHelpers.GetEnumerableType(pathProperty.PropertyType);
|
var genericTypeOfArray = PropertyHelpers.GetEnumerableType(patchProperty.Property.PropertyType);
|
||||||
|
|
||||||
// TODO: nested!
|
|
||||||
// get value (it can be cast, we just checked that)
|
// get value (it can be cast, we just checked that)
|
||||||
var array = PropertyHelpers.GetValue(pathProperty, objectToApplyTo, actualPathToProperty) as IList;
|
var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
|
||||||
|
|
||||||
if (removeFromList)
|
if (removeFromList)
|
||||||
{
|
{
|
||||||
|
|
@ -402,33 +357,37 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new JsonPatchException<T>(operationToReport,
|
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",
|
string.Format("Patch failed: provided path is invalid for array property type at " +
|
||||||
|
"location path: {0}: position larger than array size",
|
||||||
path),
|
path),
|
||||||
objectToApplyTo);
|
objectToApplyTo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new JsonPatchException<T>(operationToReport,
|
throw new JsonPatchException<T>(operationToReport,
|
||||||
string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array",
|
string.Format("Patch failed: provided path is invalid for array property type at " +
|
||||||
|
"location path: {0}: expected array",
|
||||||
path),
|
path),
|
||||||
objectToApplyTo);
|
objectToApplyTo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
// setting the value to "null" will use the default value in case of value types, and
|
// setting the value to "null" will use the default value in case of value types, and
|
||||||
// null in case of reference types
|
// null in case of reference types
|
||||||
PropertyHelpers.SetValue(pathProperty, objectToApplyTo, actualPathToProperty, null);
|
object value = null;
|
||||||
|
|
||||||
|
if (patchProperty.Property.PropertyType.GetTypeInfo().IsValueType
|
||||||
|
&& Nullable.GetUnderlyingType(patchProperty.Property.PropertyType) == null)
|
||||||
|
{
|
||||||
|
value = Activator.CreateInstance(patchProperty.Property.PropertyType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
patchProperty.Property.ValueProvider.SetValue(patchProperty.Parent, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The "test" operation tests that a value at the target location is
|
/// The "test" operation tests that a value at the target location is
|
||||||
|
|
@ -482,12 +441,10 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
||||||
public void Test(Operation<T> operation, T objectToApplyTo)
|
public void Test(Operation<T> operation, T objectToApplyTo)
|
||||||
{
|
{
|
||||||
// get value at path location
|
// get value at path location
|
||||||
|
|
||||||
object valueAtPathLocation = null;
|
object valueAtPathLocation = null;
|
||||||
var positionInPathAsInteger = -1;
|
var positionInPathAsInteger = -1;
|
||||||
var actualPathProperty = operation.path;
|
var actualPathProperty = operation.path;
|
||||||
|
|
||||||
|
|
||||||
positionInPathAsInteger = PropertyHelpers.GetNumericEnd(operation.path);
|
positionInPathAsInteger = PropertyHelpers.GetNumericEnd(operation.path);
|
||||||
|
|
||||||
if (positionInPathAsInteger > -1)
|
if (positionInPathAsInteger > -1)
|
||||||
|
|
@ -496,53 +453,44 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
||||||
operation.path.IndexOf('/' + positionInPathAsInteger.ToString()));
|
operation.path.IndexOf('/' + positionInPathAsInteger.ToString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
var pathProperty = PropertyHelpers
|
var patchProperty = PropertyHelpers
|
||||||
.FindProperty(objectToApplyTo, actualPathProperty);
|
.FindPropertyAndParent(objectToApplyTo, actualPathProperty, ContractResolver);
|
||||||
|
|
||||||
// does property at path exist?
|
// does property at path exist?
|
||||||
if (pathProperty == null)
|
CheckIfPropertyExists(patchProperty, objectToApplyTo, operation, operation.path);
|
||||||
{
|
|
||||||
throw new JsonPatchException<T>(operation,
|
|
||||||
string.Format("Patch failed: property at location path: {0} does not exist", operation.path),
|
|
||||||
objectToApplyTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the property path
|
// get the property path
|
||||||
|
|
||||||
Type typeOfFinalPropertyAtPathLocation;
|
Type typeOfFinalPropertyAtPathLocation;
|
||||||
|
|
||||||
// is the path an array (but not a string (= char[]))? In this case,
|
// 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.
|
// the path must end with "/position" or "/-", which we already determined before.
|
||||||
|
|
||||||
if (positionInPathAsInteger > -1)
|
if (positionInPathAsInteger > -1)
|
||||||
{
|
{
|
||||||
|
if (IsNonStringArray(patchProperty))
|
||||||
var isNonStringArray = !(pathProperty.PropertyType == typeof(string))
|
|
||||||
&& typeof(IList).IsAssignableFrom(pathProperty.PropertyType);
|
|
||||||
|
|
||||||
if (isNonStringArray)
|
|
||||||
{
|
{
|
||||||
// now, get the generic type of the enumerable
|
// now, get the generic type of the enumerable
|
||||||
typeOfFinalPropertyAtPathLocation = PropertyHelpers.GetEnumerableType(pathProperty.PropertyType);
|
typeOfFinalPropertyAtPathLocation = PropertyHelpers
|
||||||
|
.GetEnumerableType(patchProperty.Property.PropertyType);
|
||||||
|
|
||||||
// get value (it can be cast, we just checked that)
|
// get value (it can be cast, we just checked that)
|
||||||
var array = PropertyHelpers.GetValue(pathProperty, objectToApplyTo, actualPathProperty) as IList;
|
var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
|
||||||
|
|
||||||
if (array.Count <= positionInPathAsInteger)
|
if (array.Count <= positionInPathAsInteger)
|
||||||
{
|
{
|
||||||
throw new JsonPatchException<T>(operation,
|
throw new JsonPatchException<T>(operation,
|
||||||
string.Format("Patch failed: provided from path is invalid for array property type at location path: {0}: invalid position",
|
string.Format("Patch failed: provided from path is invalid for array property type at " +
|
||||||
|
"location path: {0}: invalid position",
|
||||||
operation.path),
|
operation.path),
|
||||||
objectToApplyTo);
|
objectToApplyTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
valueAtPathLocation = array[positionInPathAsInteger];
|
valueAtPathLocation = array[positionInPathAsInteger];
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new JsonPatchException<T>(operation,
|
throw new JsonPatchException<T>(operation,
|
||||||
string.Format("Patch failed: provided from path is invalid for array property type at location path: {0}: expected array",
|
string.Format("Patch failed: provided from path is invalid for array property type at " +
|
||||||
|
"location path: {0}: expected array",
|
||||||
operation.path),
|
operation.path),
|
||||||
objectToApplyTo);
|
objectToApplyTo);
|
||||||
}
|
}
|
||||||
|
|
@ -550,31 +498,19 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// no list, just get the value
|
// no list, just get the value
|
||||||
valueAtPathLocation = PropertyHelpers.GetValue(pathProperty, objectToApplyTo, actualPathProperty);
|
valueAtPathLocation = patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
|
||||||
typeOfFinalPropertyAtPathLocation = pathProperty.PropertyType;
|
typeOfFinalPropertyAtPathLocation = patchProperty.Property.PropertyType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var conversionResultTuple = PropertyHelpers.ConvertToActualType(
|
||||||
|
typeOfFinalPropertyAtPathLocation,
|
||||||
|
operation.value);
|
||||||
|
|
||||||
|
// Is conversion successful
|
||||||
|
CheckIfPropertyCanBeSet(conversionResultTuple, objectToApplyTo, operation, operation.path);
|
||||||
|
|
||||||
var conversionResultTuple = PropertyHelpers.ConvertToActualType(typeOfFinalPropertyAtPathLocation, operation.value);
|
//Compare
|
||||||
|
|
||||||
// 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>
|
/// <summary>
|
||||||
/// The "replace" operation replaces the value at the target location
|
/// The "replace" operation replaces the value at the target location
|
||||||
|
|
@ -598,13 +534,10 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
||||||
/// <param name="objectApplyTo">Object to apply the operation to</param>
|
/// <param name="objectApplyTo">Object to apply the operation to</param>
|
||||||
public void Replace(Operation<T> operation, T objectToApplyTo)
|
public void Replace(Operation<T> operation, T objectToApplyTo)
|
||||||
{
|
{
|
||||||
|
|
||||||
Remove(operation.path, objectToApplyTo, operation);
|
Remove(operation.path, objectToApplyTo, operation);
|
||||||
Add(operation.path, operation.value, objectToApplyTo, operation);
|
Add(operation.path, operation.value, objectToApplyTo, operation);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The "copy" operation copies the value at a specified location to the
|
/// The "copy" operation copies the value at a specified location to the
|
||||||
/// target location.
|
/// target location.
|
||||||
|
|
@ -629,13 +562,11 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
||||||
/// <param name="objectApplyTo">Object to apply the operation to</param>
|
/// <param name="objectApplyTo">Object to apply the operation to</param>
|
||||||
public void Copy(Operation<T> operation, T objectToApplyTo)
|
public void Copy(Operation<T> operation, T objectToApplyTo)
|
||||||
{
|
{
|
||||||
|
|
||||||
// get value at from location
|
// get value at from location
|
||||||
object valueAtFromLocation = null;
|
object valueAtFromLocation = null;
|
||||||
var positionAsInteger = -1;
|
var positionAsInteger = -1;
|
||||||
var actualFromProperty = operation.from;
|
var actualFromProperty = operation.from;
|
||||||
|
|
||||||
|
|
||||||
positionAsInteger = PropertyHelpers.GetNumericEnd(operation.from);
|
positionAsInteger = PropertyHelpers.GetNumericEnd(operation.from);
|
||||||
|
|
||||||
if (positionAsInteger > -1)
|
if (positionAsInteger > -1)
|
||||||
|
|
@ -644,52 +575,41 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
||||||
operation.from.IndexOf('/' + positionAsInteger.ToString()));
|
operation.from.IndexOf('/' + positionAsInteger.ToString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var patchProperty = PropertyHelpers
|
||||||
var fromProperty = PropertyHelpers
|
.FindPropertyAndParent(objectToApplyTo, actualFromProperty, ContractResolver);
|
||||||
.FindProperty(objectToApplyTo, actualFromProperty);
|
|
||||||
|
|
||||||
// does property at from exist?
|
// does property at from exist?
|
||||||
if (fromProperty == null)
|
CheckIfPropertyExists(patchProperty, objectToApplyTo, operation, operation.from);
|
||||||
{
|
|
||||||
throw new JsonPatchException<T>(operation,
|
|
||||||
string.Format("Patch failed: property at location from: {0} does not exist", operation.from),
|
|
||||||
objectToApplyTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the property path
|
// get the property path
|
||||||
|
|
||||||
// is the path an array (but not a string (= char[]))? In this case,
|
// 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.
|
// the path must end with "/position" or "/-", which we already determined before.
|
||||||
|
|
||||||
if (positionAsInteger > -1)
|
if (positionAsInteger > -1)
|
||||||
{
|
{
|
||||||
|
if (IsNonStringArray(patchProperty))
|
||||||
var isNonStringArray = !(fromProperty.PropertyType == typeof(string))
|
|
||||||
&& typeof(IList).IsAssignableFrom(fromProperty.PropertyType);
|
|
||||||
|
|
||||||
if (isNonStringArray)
|
|
||||||
{
|
{
|
||||||
// now, get the generic type of the enumerable
|
// now, get the generic type of the enumerable
|
||||||
var genericTypeOfArray = PropertyHelpers.GetEnumerableType(fromProperty.PropertyType);
|
var genericTypeOfArray = PropertyHelpers.GetEnumerableType(patchProperty.Property.PropertyType);
|
||||||
|
|
||||||
// get value (it can be cast, we just checked that)
|
// get value (it can be cast, we just checked that)
|
||||||
var array = PropertyHelpers.GetValue(fromProperty, objectToApplyTo, actualFromProperty) as IList;
|
var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
|
||||||
|
|
||||||
if (array.Count <= positionAsInteger)
|
if (array.Count <= positionAsInteger)
|
||||||
{
|
{
|
||||||
throw new JsonPatchException<T>(operation,
|
throw new JsonPatchException<T>(operation,
|
||||||
string.Format("Patch failed: provided from path is invalid for array property type at location from: {0}: invalid position",
|
string.Format("Patch failed: provided from path is invalid for array property type at " +
|
||||||
|
"location from: {0}: invalid position",
|
||||||
operation.from),
|
operation.from),
|
||||||
objectToApplyTo);
|
objectToApplyTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
valueAtFromLocation = array[positionAsInteger];
|
valueAtFromLocation = array[positionAsInteger];
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new JsonPatchException<T>(operation,
|
throw new JsonPatchException<T>(operation,
|
||||||
string.Format("Patch failed: provided from path is invalid for array property type at location from: {0}: expected array",
|
string.Format("Patch failed: provided from path is invalid for array property type at " +
|
||||||
|
"location from: {0}: expected array",
|
||||||
operation.from),
|
operation.from),
|
||||||
objectToApplyTo);
|
objectToApplyTo);
|
||||||
}
|
}
|
||||||
|
|
@ -697,17 +617,54 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// no list, just get the value
|
// no list, just get the value
|
||||||
|
|
||||||
// set the new value
|
// set the new value
|
||||||
|
valueAtFromLocation = patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
|
||||||
valueAtFromLocation = PropertyHelpers.GetValue(fromProperty, objectToApplyTo, actualFromProperty);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add operation to target location with that value.
|
// add operation to target location with that value.
|
||||||
|
|
||||||
Add(operation.path, valueAtFromLocation, objectToApplyTo, operation);
|
Add(operation.path, valueAtFromLocation, objectToApplyTo, operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckIfPropertyExists(
|
||||||
|
JsonPatchProperty patchProperty,
|
||||||
|
T objectToApplyTo,
|
||||||
|
Operation<T> operation,
|
||||||
|
string propertyPath)
|
||||||
|
{
|
||||||
|
if (patchProperty == null)
|
||||||
|
{
|
||||||
|
throw new JsonPatchException<T>(
|
||||||
|
operation,
|
||||||
|
string.Format("Patch failed: property at location {0} does not exist", propertyPath),
|
||||||
|
objectToApplyTo);
|
||||||
|
}
|
||||||
|
if (patchProperty.Property.Ignored)
|
||||||
|
{
|
||||||
|
throw new JsonPatchException<T>(
|
||||||
|
operation,
|
||||||
|
string.Format("Patch failed: cannot update property at location {0}", propertyPath),
|
||||||
|
objectToApplyTo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsNonStringArray(JsonPatchProperty patchProperty)
|
||||||
|
{
|
||||||
|
return !(patchProperty.Property.PropertyType == typeof(string))
|
||||||
|
&& typeof(IList).GetTypeInfo().IsAssignableFrom(
|
||||||
|
patchProperty.Property.PropertyType.GetTypeInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckIfPropertyCanBeSet(
|
||||||
|
ConversionResult result,
|
||||||
|
T objectToApplyTo,
|
||||||
|
Operation<T> operation,
|
||||||
|
string path)
|
||||||
|
{
|
||||||
|
var errorMessage = "Patch failed: provided value is invalid for property type at location path: ";
|
||||||
|
if (!result.CanBeConverted)
|
||||||
|
{
|
||||||
|
throw new JsonPatchException<T>(operation, string.Format(errorMessage + "{0}", path), objectToApplyTo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
using Microsoft.AspNet.JsonPatch.Exceptions;
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
using Microsoft.AspNet.JsonPatch.Operations;
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNet.JsonPatch.Exceptions;
|
||||||
|
using Microsoft.AspNet.JsonPatch.Operations;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Newtonsoft.Json.Serialization;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.JsonPatch.Converters
|
namespace Microsoft.AspNet.JsonPatch.Converters
|
||||||
{
|
{
|
||||||
|
|
@ -12,7 +16,6 @@ namespace Microsoft.AspNet.JsonPatch.Converters
|
||||||
{
|
{
|
||||||
public override bool CanConvert(Type objectType)
|
public override bool CanConvert(Type objectType)
|
||||||
{
|
{
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -22,7 +25,9 @@ namespace Microsoft.AspNet.JsonPatch.Converters
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (reader.TokenType == JsonToken.Null)
|
if (reader.TokenType == JsonToken.Null)
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var genericType = objectType.GetGenericArguments()[0];
|
var genericType = objectType.GetGenericArguments()[0];
|
||||||
|
|
||||||
|
|
@ -30,7 +35,6 @@ namespace Microsoft.AspNet.JsonPatch.Converters
|
||||||
var jObject = JArray.Load(reader);
|
var jObject = JArray.Load(reader);
|
||||||
|
|
||||||
// Create target object for Json => list of operations, typed to genericType
|
// Create target object for Json => list of operations, typed to genericType
|
||||||
|
|
||||||
var genericOperation = typeof(Operation<>);
|
var genericOperation = typeof(Operation<>);
|
||||||
var concreteOperationType = genericOperation.MakeGenericType(genericType);
|
var concreteOperationType = genericOperation.MakeGenericType(genericType);
|
||||||
|
|
||||||
|
|
@ -39,7 +43,6 @@ namespace Microsoft.AspNet.JsonPatch.Converters
|
||||||
|
|
||||||
var targetOperations = Activator.CreateInstance(concreteList);
|
var targetOperations = Activator.CreateInstance(concreteList);
|
||||||
|
|
||||||
|
|
||||||
//Create a new reader for this jObject, and set all properties to match the original reader.
|
//Create a new reader for this jObject, and set all properties to match the original reader.
|
||||||
JsonReader jObjectReader = jObject.CreateReader();
|
JsonReader jObjectReader = jObject.CreateReader();
|
||||||
jObjectReader.Culture = reader.Culture;
|
jObjectReader.Culture = reader.Culture;
|
||||||
|
|
@ -51,16 +54,14 @@ namespace Microsoft.AspNet.JsonPatch.Converters
|
||||||
serializer.Populate(jObjectReader, targetOperations);
|
serializer.Populate(jObjectReader, targetOperations);
|
||||||
|
|
||||||
// container target: the typed JsonPatchDocument.
|
// container target: the typed JsonPatchDocument.
|
||||||
var container = Activator.CreateInstance(objectType, targetOperations);
|
var container = Activator.CreateInstance(objectType, targetOperations, new DefaultContractResolver());
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
throw new JsonPatchException("The JsonPatchDocument was malformed and could not be parsed.", ex);
|
throw new JsonPatchException("The JsonPatchDocument was malformed and could not be parsed.", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||||
|
|
@ -72,7 +73,6 @@ namespace Microsoft.AspNet.JsonPatch.Converters
|
||||||
|
|
||||||
// write out the operations, no envelope
|
// write out the operations, no envelope
|
||||||
serializer.Serialize(writer, lst);
|
serializer.Serialize(writer, lst);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
// 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.Serialization;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.JsonPatch
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Metadata for JsonProperty.
|
||||||
|
/// </summary>
|
||||||
|
public class JsonPatchProperty
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance.
|
||||||
|
/// </summary>
|
||||||
|
public JsonPatchProperty(JsonProperty property, object parent)
|
||||||
|
{
|
||||||
|
Property = property;
|
||||||
|
Parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets JsonProperty.
|
||||||
|
/// </summary>
|
||||||
|
public JsonProperty Property { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets Parent.
|
||||||
|
/// </summary>
|
||||||
|
public object Parent { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,59 +1,21 @@
|
||||||
using Newtonsoft.Json;
|
// 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;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Dynamic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Linq.Expressions;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Serialization;
|
using Newtonsoft.Json.Serialization;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.JsonPatch.Helpers
|
namespace Microsoft.AspNet.JsonPatch.Helpers
|
||||||
{
|
{
|
||||||
internal static class PropertyHelpers
|
internal static class PropertyHelpers
|
||||||
{
|
{
|
||||||
public static object GetValue(PropertyInfo propertyToGet, object targetObject, string pathToProperty)
|
public static JsonPatchProperty FindPropertyAndParent(
|
||||||
{
|
object targetObject,
|
||||||
// it is possible the path refers to a nested property. In that case, we need to
|
string propertyPath,
|
||||||
// get from a different target object: the nested object.
|
IContractResolver contractResolver)
|
||||||
|
|
||||||
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
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -62,17 +24,37 @@ namespace Microsoft.AspNet.JsonPatch.Helpers
|
||||||
// skip the first one if it's empty
|
// skip the first one if it's empty
|
||||||
var startIndex = (string.IsNullOrWhiteSpace(splitPath[0]) ? 1 : 0);
|
var startIndex = (string.IsNullOrWhiteSpace(splitPath[0]) ? 1 : 0);
|
||||||
|
|
||||||
for (int i = startIndex; i < splitPath.Length - 1; i++)
|
for (int i = startIndex; i < splitPath.Length; i++)
|
||||||
{
|
{
|
||||||
var propertyInfoToGet = GetPropertyInfo(targetObject, splitPath[i]
|
var jsonContract = (JsonObjectContract)contractResolver.ResolveContract(targetObject.GetType());
|
||||||
, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
|
|
||||||
targetObject = propertyInfoToGet.GetValue(targetObject, null);
|
foreach (var property in jsonContract.Properties)
|
||||||
|
{
|
||||||
|
if (string.Equals(property.PropertyName, splitPath[i], StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (i == (splitPath.Length - 1))
|
||||||
|
{
|
||||||
|
return new JsonPatchProperty(property, targetObject);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
targetObject = property.ValueProvider.GetValue(targetObject);
|
||||||
|
|
||||||
|
// 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 (typeof(IList).GetTypeInfo().IsAssignableFrom(property.PropertyType.GetTypeInfo()))
|
||||||
|
{
|
||||||
|
var index = int.Parse(splitPath[++i]);
|
||||||
|
targetObject = ((IList)targetObject)[index];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var propertyToFind = targetObject.GetType().GetProperty(splitPath.Last(),
|
break;
|
||||||
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return propertyToFind;
|
return null;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
|
@ -100,20 +82,11 @@ namespace Microsoft.AspNet.JsonPatch.Helpers
|
||||||
if (type == null) throw new ArgumentNullException();
|
if (type == null) throw new ArgumentNullException();
|
||||||
foreach (Type interfaceType in type.GetInterfaces())
|
foreach (Type interfaceType in type.GetInterfaces())
|
||||||
{
|
{
|
||||||
#if NETFX_CORE || ASPNETCORE50
|
|
||||||
if (interfaceType.GetTypeInfo().IsGenericType &&
|
if (interfaceType.GetTypeInfo().IsGenericType &&
|
||||||
interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||||
{
|
{
|
||||||
return interfaceType.GetGenericArguments()[0];
|
return interfaceType.GetGenericArguments()[0];
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
if (interfaceType.GetTypeInfo().IsGenericType &&
|
|
||||||
interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
|
||||||
{
|
|
||||||
return interfaceType.GetGenericArguments()[0];
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -130,11 +103,5 @@ namespace Microsoft.AspNet.JsonPatch.Helpers
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PropertyInfo GetPropertyInfo(object targetObject, string propertyName,
|
|
||||||
BindingFlags bindingFlags)
|
|
||||||
{
|
|
||||||
return targetObject.GetType().GetProperty(propertyName, bindingFlags);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,11 +3,14 @@
|
||||||
|
|
||||||
using Microsoft.AspNet.JsonPatch.Operations;
|
using Microsoft.AspNet.JsonPatch.Operations;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json.Serialization;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.JsonPatch
|
namespace Microsoft.AspNet.JsonPatch
|
||||||
{
|
{
|
||||||
public interface IJsonPatchDocument
|
public interface IJsonPatchDocument
|
||||||
{
|
{
|
||||||
|
IContractResolver ContractResolver { get; set; }
|
||||||
|
|
||||||
List<Operation> GetOperations();
|
List<Operation> GetOperations();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
using Microsoft.AspNet.JsonPatch.Adapters;
|
// 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.Collections.Generic;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using Microsoft.AspNet.JsonPatch.Adapters;
|
||||||
using Microsoft.AspNet.JsonPatch.Converters;
|
using Microsoft.AspNet.JsonPatch.Converters;
|
||||||
using Microsoft.AspNet.JsonPatch.Helpers;
|
using Microsoft.AspNet.JsonPatch.Helpers;
|
||||||
using Microsoft.AspNet.JsonPatch.Operations;
|
using Microsoft.AspNet.JsonPatch.Operations;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System;
|
using Newtonsoft.Json.Serialization;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq.Expressions;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.JsonPatch
|
namespace Microsoft.AspNet.JsonPatch
|
||||||
{
|
{
|
||||||
|
|
@ -18,15 +22,20 @@ namespace Microsoft.AspNet.JsonPatch
|
||||||
{
|
{
|
||||||
public List<Operation<T>> Operations { get; private set; }
|
public List<Operation<T>> Operations { get; private set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public IContractResolver ContractResolver { get; set; }
|
||||||
|
|
||||||
public JsonPatchDocument()
|
public JsonPatchDocument()
|
||||||
{
|
{
|
||||||
Operations = new List<Operation<T>>();
|
Operations = new List<Operation<T>>();
|
||||||
|
ContractResolver = new DefaultContractResolver();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create from list of operations
|
// Create from list of operations
|
||||||
public JsonPatchDocument(List<Operation<T>> operations)
|
public JsonPatchDocument(List<Operation<T>> operations, IContractResolver contractResolver)
|
||||||
{
|
{
|
||||||
Operations = operations;
|
Operations = operations;
|
||||||
|
ContractResolver = contractResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -39,7 +48,11 @@ namespace Microsoft.AspNet.JsonPatch
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public JsonPatchDocument<T> Add<TProp>(Expression<Func<T, TProp>> path, TProp value)
|
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));
|
Operations.Add(new Operation<T>(
|
||||||
|
"add",
|
||||||
|
ExpressionHelpers.GetPath<T, TProp>(path).ToLower(),
|
||||||
|
from: null,
|
||||||
|
value: value));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,7 +66,10 @@ namespace Microsoft.AspNet.JsonPatch
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public JsonPatchDocument<T> Add<TProp>(Expression<Func<T, IList<TProp>>> path, TProp value, int position)
|
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));
|
Operations.Add(new Operation<T>(
|
||||||
|
"add",
|
||||||
|
ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/" + position,
|
||||||
|
null, value));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -338,7 +354,7 @@ namespace Microsoft.AspNet.JsonPatch
|
||||||
|
|
||||||
public void ApplyTo(T objectToApplyTo)
|
public void ApplyTo(T objectToApplyTo)
|
||||||
{
|
{
|
||||||
ApplyTo(objectToApplyTo, new SimpleObjectAdapter<T>());
|
ApplyTo(objectToApplyTo, new SimpleObjectAdapter<T>(ContractResolver));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyTo(T objectToApplyTo, IObjectAdapter<T> adapter)
|
public void ApplyTo(T objectToApplyTo, IObjectAdapter<T> adapter)
|
||||||
|
|
@ -373,5 +389,4 @@ namespace Microsoft.AspNet.JsonPatch
|
||||||
return allOps;
|
return allOps;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,126 +1,45 @@
|
||||||
using System;
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
using System.IO;
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.JsonPatch;
|
||||||
using Microsoft.Framework.Internal;
|
using Microsoft.Framework.Internal;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
public class JsonPatchInputFormatter : InputFormatter
|
public class JsonPatchInputFormatter : JsonInputFormatter
|
||||||
{
|
{
|
||||||
private const int DefaultMaxDepth = 32;
|
|
||||||
private JsonSerializerSettings _jsonSerializerSettings;
|
|
||||||
|
|
||||||
public JsonPatchInputFormatter()
|
public JsonPatchInputFormatter()
|
||||||
{
|
{
|
||||||
SupportedEncodings.Add(Encodings.UTF8EncodingWithoutBOM);
|
// Clear all values and only include json-patch+json value.
|
||||||
SupportedEncodings.Add(Encodings.UTF16EncodingLittleEndian);
|
SupportedMediaTypes.Clear();
|
||||||
|
|
||||||
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/jsonpatch"));
|
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json-patch+json"));
|
||||||
|
|
||||||
_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 />
|
/// <inheritdoc />
|
||||||
public override Task<object> ReadRequestBodyAsync([NotNull] InputFormatterContext context)
|
public async override Task<object> ReadRequestBodyAsync([NotNull] InputFormatterContext context)
|
||||||
{
|
{
|
||||||
var type = context.ModelType;
|
var jsonPatchDocument = (IJsonPatchDocument)(await base.ReadRequestBodyAsync(context));
|
||||||
var request = context.ActionContext.HttpContext.Request;
|
if (jsonPatchDocument != null)
|
||||||
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;
|
jsonPatchDocument.ContractResolver = SerializerSettings.ContractResolver;
|
||||||
|
|
||||||
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>
|
return (object)jsonPatchDocument;
|
||||||
/// 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>
|
/// <inheritdoc />
|
||||||
/// Called during deserialization to get the <see cref="JsonSerializer"/>.
|
public override bool CanRead(InputFormatterContext context)
|
||||||
/// </summary>
|
|
||||||
/// <returns>The <see cref="JsonSerializer"/> used during serialization and deserialization.</returns>
|
|
||||||
public virtual JsonSerializer CreateJsonSerializer()
|
|
||||||
{
|
{
|
||||||
return JsonSerializer.Create(SerializerSettings);
|
if (!typeof(IJsonPatchDocument).IsAssignableFrom(context.ModelType) ||
|
||||||
|
!context.ModelType.IsGenericType())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.CanRead(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -201,6 +201,69 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
||||||
Assert.Equal(new List<int>() { 4, 1, 2, 3 }, doc.SimpleDTO.IntegerList);
|
Assert.Equal(new List<int>() { 4, 1, 2, 3 }, doc.SimpleDTO.IntegerList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddToComplextTypeListSpecifyIndex()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var doc = new SimpleDTOWithNestedDTO()
|
||||||
|
{
|
||||||
|
SimpleDTOList = new List<SimpleDTO>()
|
||||||
|
{
|
||||||
|
new SimpleDTO
|
||||||
|
{
|
||||||
|
StringProperty = "String1"
|
||||||
|
},
|
||||||
|
new SimpleDTO
|
||||||
|
{
|
||||||
|
StringProperty = "String2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// create patch
|
||||||
|
var patchDoc = new JsonPatchDocument<SimpleDTOWithNestedDTO>();
|
||||||
|
patchDoc.Add<string>(o => o.SimpleDTOList[0].StringProperty, "ChangedString1");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
patchDoc.ApplyTo(doc);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("ChangedString1", doc.SimpleDTOList[0].StringProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddToComplextTypeListSpecifyIndexWithSerialization()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var doc = new SimpleDTOWithNestedDTO()
|
||||||
|
{
|
||||||
|
SimpleDTOList = new List<SimpleDTO>()
|
||||||
|
{
|
||||||
|
new SimpleDTO
|
||||||
|
{
|
||||||
|
StringProperty = "String1"
|
||||||
|
},
|
||||||
|
new SimpleDTO
|
||||||
|
{
|
||||||
|
StringProperty = "String2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// create patch
|
||||||
|
var patchDoc = new JsonPatchDocument<SimpleDTOWithNestedDTO>();
|
||||||
|
patchDoc.Add<string>(o => o.SimpleDTOList[0].StringProperty, "ChangedString1");
|
||||||
|
|
||||||
|
var serialized = JsonConvert.SerializeObject(patchDoc);
|
||||||
|
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument<SimpleDTOWithNestedDTO>>(serialized);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
deserialized.ApplyTo(doc);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("ChangedString1", doc.SimpleDTOList[0].StringProperty);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AddToListInvalidPositionTooLarge()
|
public void AddToListInvalidPositionTooLarge()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
using System;
|
// 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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.JsonPatch.Test
|
namespace Microsoft.AspNet.JsonPatch.Test
|
||||||
|
|
@ -10,11 +13,8 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
||||||
public string StringProperty { get; set; }
|
public string StringProperty { get; set; }
|
||||||
public string AnotherStringProperty { get; set; }
|
public string AnotherStringProperty { get; set; }
|
||||||
public decimal DecimalValue { get; set; }
|
public decimal DecimalValue { get; set; }
|
||||||
|
|
||||||
public double DoubleValue { get; set; }
|
public double DoubleValue { get; set; }
|
||||||
|
|
||||||
public float FloatValue { get; set; }
|
public float FloatValue { get; set; }
|
||||||
|
|
||||||
public Guid GuidValue { get; set; }
|
public Guid GuidValue { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.JsonPatch.Test
|
namespace Microsoft.AspNet.JsonPatch.Test
|
||||||
{
|
{
|
||||||
|
|
||||||
public class SimpleDTOWithNestedDTO
|
public class SimpleDTOWithNestedDTO
|
||||||
{
|
{
|
||||||
public int IntegerValue { get; set; }
|
public int IntegerValue { get; set; }
|
||||||
|
|
@ -12,10 +13,13 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
||||||
|
|
||||||
public SimpleDTO SimpleDTO { get; set; }
|
public SimpleDTO SimpleDTO { get; set; }
|
||||||
|
|
||||||
|
public List<SimpleDTO> SimpleDTOList { get; set; }
|
||||||
|
|
||||||
public SimpleDTOWithNestedDTO()
|
public SimpleDTOWithNestedDTO()
|
||||||
{
|
{
|
||||||
this.NestedDTO = new NestedDTO();
|
this.NestedDTO = new NestedDTO();
|
||||||
this.SimpleDTO = new SimpleDTO();
|
this.SimpleDTO = new SimpleDTO();
|
||||||
|
this.SimpleDTOList = new List<SimpleDTO>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,154 @@
|
||||||
|
// 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.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.JsonPatch;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public class JsonPatchInputFormatterTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task JsonPatchInputFormatter_ReadsOneOperation_Successfully()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = new JsonPatchInputFormatter();
|
||||||
|
var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]";
|
||||||
|
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
|
||||||
|
var actionContext = GetActionContext(contentBytes);
|
||||||
|
var context = new InputFormatterContext(actionContext, typeof(JsonPatchDocument<Customer>));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var model = await formatter.ReadAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var patchDoc = Assert.IsType<JsonPatchDocument<Customer>>(model);
|
||||||
|
Assert.Equal("add", patchDoc.Operations[0].op);
|
||||||
|
Assert.Equal("Customer/Name", patchDoc.Operations[0].path);
|
||||||
|
Assert.Equal("John", patchDoc.Operations[0].value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task JsonPatchInputFormatter_ReadsMultipleOperations_Successfully()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = new JsonPatchInputFormatter();
|
||||||
|
var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}," +
|
||||||
|
"{\"op\": \"remove\", \"path\" : \"Customer/Name\"}]";
|
||||||
|
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
|
||||||
|
var actionContext = GetActionContext(contentBytes);
|
||||||
|
var context = new InputFormatterContext(actionContext, typeof(JsonPatchDocument<Customer>));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var model = await formatter.ReadAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var patchDoc = Assert.IsType<JsonPatchDocument<Customer>>(model);
|
||||||
|
Assert.Equal("add", patchDoc.Operations[0].op);
|
||||||
|
Assert.Equal("Customer/Name", patchDoc.Operations[0].path);
|
||||||
|
Assert.Equal("John", patchDoc.Operations[0].value);
|
||||||
|
Assert.Equal("remove", patchDoc.Operations[1].op);
|
||||||
|
Assert.Equal("Customer/Name", patchDoc.Operations[1].path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/json-patch+json", true)]
|
||||||
|
[InlineData("application/json", false)]
|
||||||
|
[InlineData("application/*", true)]
|
||||||
|
[InlineData("*/*", true)]
|
||||||
|
public void CanRead_ReturnsTrueOnlyForJsonPatchContentType(string requestContentType, bool expectedCanRead)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = new JsonPatchInputFormatter();
|
||||||
|
var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}]";
|
||||||
|
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
|
||||||
|
var actionContext = GetActionContext(contentBytes, contentType: requestContentType);
|
||||||
|
var formatterContext = new InputFormatterContext(actionContext, typeof(JsonPatchDocument<Customer>));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = formatter.CanRead(formatterContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedCanRead, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(typeof(Customer))]
|
||||||
|
[InlineData(typeof(IJsonPatchDocument))]
|
||||||
|
public void CanRead_ReturnsFalse_NonJsonPatchContentType(Type modelType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = new JsonPatchInputFormatter();
|
||||||
|
var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}]";
|
||||||
|
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
|
||||||
|
var actionContext = GetActionContext(contentBytes, contentType: "application/json-patch+json");
|
||||||
|
var formatterContext = new InputFormatterContext(actionContext, modelType);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = formatter.CanRead(formatterContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task JsonPatchInputFormatter_ReturnsModelStateErrors_InvalidModelType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var exceptionMessage = "Cannot deserialize the current JSON array (e.g. [1,2,3]) into type " +
|
||||||
|
"'Microsoft.AspNet.Mvc.JsonPatchInputFormatterTest+Customer' because the type requires a JSON object ";
|
||||||
|
|
||||||
|
var formatter = new JsonPatchInputFormatter();
|
||||||
|
var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}]";
|
||||||
|
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
|
||||||
|
var actionContext = GetActionContext(contentBytes, contentType: "application/json-patch+json");
|
||||||
|
var context = new InputFormatterContext(actionContext, typeof(Customer));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var model = await formatter.ReadAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Contains(exceptionMessage, actionContext.ModelState[""].Errors[0].Exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ActionContext GetActionContext(byte[] contentBytes,
|
||||||
|
string contentType = "application/json-patch+json")
|
||||||
|
{
|
||||||
|
return new ActionContext(GetHttpContext(contentBytes, contentType),
|
||||||
|
new AspNet.Routing.RouteData(),
|
||||||
|
new ActionDescriptor());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HttpContext GetHttpContext(byte[] contentBytes,
|
||||||
|
string contentType = "application/json-patch+json")
|
||||||
|
{
|
||||||
|
var request = new Mock<HttpRequest>();
|
||||||
|
var headers = new Mock<IHeaderDictionary>();
|
||||||
|
request.SetupGet(r => r.Headers).Returns(headers.Object);
|
||||||
|
request.SetupGet(f => f.Body).Returns(new MemoryStream(contentBytes));
|
||||||
|
request.SetupGet(f => f.ContentType).Returns(contentType);
|
||||||
|
|
||||||
|
var httpContext = new Mock<HttpContext>();
|
||||||
|
httpContext.SetupGet(c => c.Request).Returns(request.Object);
|
||||||
|
httpContext.SetupGet(c => c.Request).Returns(request.Object);
|
||||||
|
return httpContext.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Customer
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -103,8 +103,9 @@ namespace Microsoft.AspNet.Mvc
|
||||||
setup.Configure(mvcOptions);
|
setup.Configure(mvcOptions);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(1, mvcOptions.InputFormatters.Count);
|
Assert.Equal(2, mvcOptions.InputFormatters.Count);
|
||||||
Assert.IsType<JsonInputFormatter>(mvcOptions.InputFormatters[0].Instance);
|
Assert.IsType<JsonInputFormatter>(mvcOptions.InputFormatters[0].Instance);
|
||||||
|
Assert.IsType<JsonPatchInputFormatter>(mvcOptions.InputFormatters[1].Instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,10 @@ namespace FiltersWebSite.Controllers
|
||||||
|
|
||||||
public void OnResourceExecuting(ResourceExecutingContext context)
|
public void OnResourceExecuting(ResourceExecutingContext context)
|
||||||
{
|
{
|
||||||
var jsonFormatter = context.InputFormatters.OfType<JsonInputFormatter>().Single();
|
// InputFormatters collection contains JsonInputFormatter and JsonPatchInputFormatter. Picking
|
||||||
|
// JsonInputFormatter by matching the typename
|
||||||
|
var jsonFormatter = context.InputFormatters.OfType<JsonInputFormatter>()
|
||||||
|
.Where(t => t.GetType() == typeof(JsonInputFormatter)).FirstOrDefault();
|
||||||
|
|
||||||
context.InputFormatters.Clear();
|
context.InputFormatters.Clear();
|
||||||
context.InputFormatters.Add(jsonFormatter);
|
context.InputFormatters.Add(jsonFormatter);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue