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;
|
||||
using Microsoft.AspNet.JsonPatch.Operations;
|
||||
using Microsoft.AspNet.JsonPatch.Helpers;
|
||||
using Microsoft.AspNet.JsonPatch.Exceptions;
|
||||
using System.Reflection;
|
||||
// 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;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.JsonPatch.Exceptions;
|
||||
using Microsoft.AspNet.JsonPatch.Helpers;
|
||||
using Microsoft.AspNet.JsonPatch.Operations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
|
|
@ -11,6 +14,13 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
|||
{
|
||||
public class SimpleObjectAdapter<T> : IObjectAdapter<T> where T : class
|
||||
{
|
||||
public IContractResolver ContractResolver { get; set; }
|
||||
|
||||
public SimpleObjectAdapter(IContractResolver contractResolver)
|
||||
{
|
||||
ContractResolver = contractResolver;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The "add" operation performs one of the following functions,
|
||||
/// 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
|
||||
// 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;
|
||||
|
|
@ -110,47 +119,30 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
|||
}
|
||||
}
|
||||
|
||||
var pathProperty = PropertyHelpers
|
||||
.FindProperty(objectToApplyTo, actualPathToProperty);
|
||||
|
||||
var patchProperty = PropertyHelpers
|
||||
.FindPropertyAndParent(objectToApplyTo, actualPathToProperty, ContractResolver);
|
||||
|
||||
// does property at path exist?
|
||||
if (pathProperty == null)
|
||||
{
|
||||
throw new JsonPatchException<T>(operationToReport,
|
||||
string.Format("Patch failed: property at location path: {0} does not exist", path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
CheckIfPropertyExists(patchProperty, objectToApplyTo, operationToReport, path);
|
||||
|
||||
// it exists. If it' an array, add to that array. If it's not, we replace.
|
||||
|
||||
// is the path an array (but not a string (= char[]))? In this case,
|
||||
// the path must end with "/position" or "/-", which we already determined before.
|
||||
|
||||
if (appendList || positionAsInteger > -1)
|
||||
{
|
||||
|
||||
var isNonStringArray = !(pathProperty.PropertyType == typeof(string))
|
||||
&& typeof(IList).IsAssignableFrom(pathProperty.PropertyType);
|
||||
|
||||
// what if it's an array but there's no position??
|
||||
if (isNonStringArray)
|
||||
if (IsNonStringArray(patchProperty))
|
||||
{
|
||||
// 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);
|
||||
|
||||
if (!conversionResult.CanBeConverted)
|
||||
{
|
||||
throw new JsonPatchException<T>(operationToReport,
|
||||
string.Format("Patch failed: provided value is invalid for array property type at location path: {0}",
|
||||
path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
CheckIfPropertyCanBeSet(conversionResult, objectToApplyTo, operationToReport, path);
|
||||
|
||||
// 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)
|
||||
{
|
||||
|
|
@ -167,40 +159,33 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
|||
{
|
||||
throw new JsonPatchException<T>(operationToReport,
|
||||
string.Format("Patch failed: provided path is invalid for array property type at " +
|
||||
"location path: {0}: position doesn't exist in array",
|
||||
"location path: {0}: position larger than array size",
|
||||
path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonPatchException<T>(operationToReport,
|
||||
string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array",
|
||||
path),
|
||||
string.Format("Patch failed: provided path is invalid for array property type at location " +
|
||||
"path: {0}: expected array",
|
||||
path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var conversionResultTuple = PropertyHelpers.ConvertToActualType(pathProperty.PropertyType, value);
|
||||
var conversionResultTuple = PropertyHelpers.ConvertToActualType(
|
||||
patchProperty.Property.PropertyType,
|
||||
value);
|
||||
|
||||
// conversion successful
|
||||
if (conversionResultTuple.CanBeConverted)
|
||||
{
|
||||
PropertyHelpers.SetValue(pathProperty, objectToApplyTo, actualPathToProperty,
|
||||
// Is conversion successful
|
||||
CheckIfPropertyCanBeSet(conversionResultTuple, objectToApplyTo, operationToReport, path);
|
||||
|
||||
patchProperty.Property.ValueProvider.SetValue(
|
||||
patchProperty.Parent,
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -229,13 +214,11 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
|||
/// <param name="objectApplyTo">Object to apply the operation to</param>
|
||||
public void Move(Operation<T> operation, T objectToApplyTo)
|
||||
{
|
||||
|
||||
// get value at from location
|
||||
object valueAtFromLocation = null;
|
||||
var positionAsInteger = -1;
|
||||
var actualFromProperty = operation.from;
|
||||
|
||||
|
||||
positionAsInteger = PropertyHelpers.GetNumericEnd(operation.from);
|
||||
|
||||
if (positionAsInteger > -1)
|
||||
|
|
@ -244,74 +227,56 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
|||
operation.from.IndexOf('/' + positionAsInteger.ToString()));
|
||||
}
|
||||
|
||||
var fromProperty = PropertyHelpers
|
||||
.FindProperty(objectToApplyTo, actualFromProperty);
|
||||
|
||||
var patchProperty = PropertyHelpers
|
||||
.FindPropertyAndParent(objectToApplyTo, actualFromProperty, ContractResolver);
|
||||
|
||||
// does property at from exist?
|
||||
if (fromProperty == null)
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: property at location from: {0} does not exist", operation.from),
|
||||
objectToApplyTo);
|
||||
}
|
||||
|
||||
CheckIfPropertyExists(patchProperty, objectToApplyTo, operation, operation.from);
|
||||
|
||||
// is the path an array (but not a string (= char[]))? In this case,
|
||||
// the path must end with "/position" or "/-", which we already determined before.
|
||||
|
||||
if (positionAsInteger > -1)
|
||||
{
|
||||
|
||||
var isNonStringArray = !(fromProperty.PropertyType == typeof(string))
|
||||
&& typeof(IList).IsAssignableFrom(fromProperty.PropertyType);
|
||||
|
||||
if (isNonStringArray)
|
||||
if (IsNonStringArray(patchProperty))
|
||||
{
|
||||
// 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)
|
||||
var array = PropertyHelpers.GetValue(fromProperty, objectToApplyTo, actualFromProperty) as IList;
|
||||
var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
|
||||
|
||||
if (array.Count <= positionAsInteger)
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: provided from path is invalid for array property type at location from: {0}: invalid position",
|
||||
operation.from),
|
||||
objectToApplyTo);
|
||||
string.Format("Patch failed: provided from path is invalid for array property type at " +
|
||||
"location from: {0}: invalid position",
|
||||
operation.from),
|
||||
objectToApplyTo);
|
||||
}
|
||||
|
||||
valueAtFromLocation = array[positionAsInteger];
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: provided from path is invalid for array property type at location from: {0}: expected array",
|
||||
operation.from),
|
||||
string.Format("Patch failed: provided from path is invalid for array property type at " +
|
||||
"location from: {0}: expected array",
|
||||
operation.from),
|
||||
objectToApplyTo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no list, just get the value
|
||||
|
||||
// set the new value
|
||||
|
||||
valueAtFromLocation = PropertyHelpers.GetValue(fromProperty, objectToApplyTo, actualFromProperty);
|
||||
|
||||
valueAtFromLocation = patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
|
||||
}
|
||||
|
||||
|
||||
// remove that value
|
||||
|
||||
Remove(operation.from, objectToApplyTo, operation);
|
||||
|
||||
// add that value to the path location
|
||||
|
||||
Add(operation.path, valueAtFromLocation, objectToApplyTo, operation);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -359,35 +324,25 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
|||
}
|
||||
}
|
||||
|
||||
var pathProperty = PropertyHelpers
|
||||
.FindProperty(objectToApplyTo, actualPathToProperty);
|
||||
var patchProperty = PropertyHelpers
|
||||
.FindPropertyAndParent(objectToApplyTo, actualPathToProperty, ContractResolver);
|
||||
|
||||
// does the target location exist?
|
||||
if (pathProperty == null)
|
||||
{
|
||||
throw new JsonPatchException<T>(operationToReport,
|
||||
string.Format("Patch failed: property at location path: {0} does not exist", path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
CheckIfPropertyExists(patchProperty, objectToApplyTo, operationToReport, path);
|
||||
|
||||
// get the property, and remove it - in this case, for DTO's, that means setting
|
||||
// it to null or its default value; in case of an array, remove at provided index
|
||||
// or at the end.
|
||||
if (removeFromList || positionAsInteger > -1)
|
||||
{
|
||||
|
||||
var isNonStringArray = !(pathProperty.PropertyType == typeof(string))
|
||||
&& typeof(IList).IsAssignableFrom(pathProperty.PropertyType);
|
||||
|
||||
// what if it's an array but there's no position??
|
||||
if (isNonStringArray)
|
||||
if (IsNonStringArray(patchProperty))
|
||||
{
|
||||
// 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)
|
||||
var array = PropertyHelpers.GetValue(pathProperty, objectToApplyTo, actualPathToProperty) as IList;
|
||||
var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
|
||||
|
||||
if (removeFromList)
|
||||
{
|
||||
|
|
@ -402,54 +357,58 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
|||
else
|
||||
{
|
||||
throw new JsonPatchException<T>(operationToReport,
|
||||
string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size",
|
||||
path),
|
||||
objectToApplyTo);
|
||||
string.Format("Patch failed: provided path is invalid for array property type at " +
|
||||
"location path: {0}: position larger than array size",
|
||||
path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonPatchException<T>(operationToReport,
|
||||
string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array",
|
||||
path),
|
||||
string.Format("Patch failed: provided path is invalid for array property type at " +
|
||||
"location path: {0}: expected array",
|
||||
path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// setting the value to "null" will use the default value in case of value types, and
|
||||
// null in case of reference types
|
||||
PropertyHelpers.SetValue(pathProperty, objectToApplyTo, actualPathToProperty, null);
|
||||
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>
|
||||
/// The "test" operation tests that a value at the target location is
|
||||
/// equal to a specified value.
|
||||
///
|
||||
///
|
||||
/// The operation object MUST contain a "value" member that conveys the
|
||||
/// value to be compared to the target location's value.
|
||||
///
|
||||
///
|
||||
/// The target location MUST be equal to the "value" value for the
|
||||
/// operation to be considered successful.
|
||||
///
|
||||
///
|
||||
/// Here, "equal" means that the value at the target location and the
|
||||
/// value conveyed by "value" are of the same JSON type, and that they
|
||||
/// are considered equal by the following rules for that type:
|
||||
///
|
||||
///
|
||||
/// o strings: are considered equal if they contain the same number of
|
||||
/// Unicode characters and their code points are byte-by-byte equal.
|
||||
///
|
||||
///
|
||||
/// o numbers: are considered equal if their values are numerically
|
||||
/// equal.
|
||||
///
|
||||
///
|
||||
/// o arrays: are considered equal if they contain the same number of
|
||||
/// values, and if each value can be considered equal to the value at
|
||||
/// the corresponding position in the other array, using this list of
|
||||
|
|
@ -468,7 +427,7 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
|||
///
|
||||
/// Also, note that ordering of the serialization of object members is
|
||||
/// not significant.
|
||||
///
|
||||
///
|
||||
/// Note that we divert from the rules here - we use .NET's comparison,
|
||||
/// not the one above. In a future version, a "strict" setting might
|
||||
/// be added (configurable), that takes into account above rules.
|
||||
|
|
@ -482,12 +441,10 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
|||
public void Test(Operation<T> operation, T objectToApplyTo)
|
||||
{
|
||||
// get value at path location
|
||||
|
||||
object valueAtPathLocation = null;
|
||||
var positionInPathAsInteger = -1;
|
||||
var actualPathProperty = operation.path;
|
||||
|
||||
|
||||
positionInPathAsInteger = PropertyHelpers.GetNumericEnd(operation.path);
|
||||
|
||||
if (positionInPathAsInteger > -1)
|
||||
|
|
@ -496,146 +453,120 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
|||
operation.path.IndexOf('/' + positionInPathAsInteger.ToString()));
|
||||
}
|
||||
|
||||
var pathProperty = PropertyHelpers
|
||||
.FindProperty(objectToApplyTo, actualPathProperty);
|
||||
var patchProperty = PropertyHelpers
|
||||
.FindPropertyAndParent(objectToApplyTo, actualPathProperty, ContractResolver);
|
||||
|
||||
// does property at path exist?
|
||||
if (pathProperty == null)
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: property at location path: {0} does not exist", operation.path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
CheckIfPropertyExists(patchProperty, objectToApplyTo, operation, operation.path);
|
||||
|
||||
// get the property path
|
||||
|
||||
Type typeOfFinalPropertyAtPathLocation;
|
||||
|
||||
// is the path an array (but not a string (= char[]))? In this case,
|
||||
// the path must end with "/position" or "/-", which we already determined before.
|
||||
|
||||
if (positionInPathAsInteger > -1)
|
||||
{
|
||||
|
||||
var isNonStringArray = !(pathProperty.PropertyType == typeof(string))
|
||||
&& typeof(IList).IsAssignableFrom(pathProperty.PropertyType);
|
||||
|
||||
if (isNonStringArray)
|
||||
if (IsNonStringArray(patchProperty))
|
||||
{
|
||||
// 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)
|
||||
var array = PropertyHelpers.GetValue(pathProperty, objectToApplyTo, actualPathProperty) as IList;
|
||||
var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
|
||||
|
||||
if (array.Count <= positionInPathAsInteger)
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: provided from path is invalid for array property type at location path: {0}: invalid position",
|
||||
operation.path),
|
||||
objectToApplyTo);
|
||||
string.Format("Patch failed: provided from path is invalid for array property type at " +
|
||||
"location path: {0}: invalid position",
|
||||
operation.path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
|
||||
valueAtPathLocation = array[positionInPathAsInteger];
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: provided from path is invalid for array property type at location path: {0}: expected array",
|
||||
operation.path),
|
||||
string.Format("Patch failed: provided from path is invalid for array property type at " +
|
||||
"location path: {0}: expected array",
|
||||
operation.path),
|
||||
objectToApplyTo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no list, just get the value
|
||||
valueAtPathLocation = PropertyHelpers.GetValue(pathProperty, objectToApplyTo, actualPathProperty);
|
||||
typeOfFinalPropertyAtPathLocation = pathProperty.PropertyType;
|
||||
}
|
||||
|
||||
|
||||
|
||||
var conversionResultTuple = PropertyHelpers.ConvertToActualType(typeOfFinalPropertyAtPathLocation, operation.value);
|
||||
|
||||
// conversion successful
|
||||
if (conversionResultTuple.CanBeConverted)
|
||||
{
|
||||
// COMPARE - TODO
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: provided value is invalid for property type at location path: {0}",
|
||||
operation.path),
|
||||
objectToApplyTo);
|
||||
valueAtPathLocation = patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
|
||||
typeOfFinalPropertyAtPathLocation = patchProperty.Property.PropertyType;
|
||||
}
|
||||
|
||||
var conversionResultTuple = PropertyHelpers.ConvertToActualType(
|
||||
typeOfFinalPropertyAtPathLocation,
|
||||
operation.value);
|
||||
|
||||
// Is conversion successful
|
||||
CheckIfPropertyCanBeSet(conversionResultTuple, objectToApplyTo, operation, operation.path);
|
||||
|
||||
//Compare
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The "replace" operation replaces the value at the target location
|
||||
/// with a new value. The operation object MUST contain a "value" member
|
||||
/// whose content specifies the replacement value.
|
||||
///
|
||||
///
|
||||
/// The target location MUST exist for the operation to be successful.
|
||||
///
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
///
|
||||
/// { "op": "replace", "path": "/a/b/c", "value": 42 }
|
||||
///
|
||||
///
|
||||
/// This operation is functionally identical to a "remove" operation for
|
||||
/// a value, followed immediately by an "add" operation at the same
|
||||
/// location with the replacement value.
|
||||
///
|
||||
/// Note: even though it's the same functionally, we do not call remove + add
|
||||
///
|
||||
/// Note: even though it's the same functionally, we do not call remove + add
|
||||
/// for performance reasons (multiple checks of same requirements).
|
||||
/// </summary>
|
||||
/// <param name="operation">The replace operation</param>
|
||||
/// <param name="objectApplyTo">Object to apply the operation to</param>
|
||||
public void Replace(Operation<T> operation, T objectToApplyTo)
|
||||
{
|
||||
|
||||
Remove(operation.path, objectToApplyTo, operation);
|
||||
Add(operation.path, operation.value, objectToApplyTo, operation);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The "copy" operation copies the value at a specified location to the
|
||||
/// target location.
|
||||
///
|
||||
///
|
||||
/// The operation object MUST contain a "from" member, which is a string
|
||||
/// containing a JSON Pointer value that references the location in the
|
||||
/// target document to copy the value from.
|
||||
///
|
||||
///
|
||||
/// The "from" location MUST exist for the operation to be successful.
|
||||
///
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
///
|
||||
/// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
|
||||
///
|
||||
///
|
||||
/// This operation is functionally identical to an "add" operation at the
|
||||
/// target location using the value specified in the "from" member.
|
||||
///
|
||||
/// Note: even though it's the same functionally, we do not call add with
|
||||
///
|
||||
/// Note: even though it's the same functionally, we do not call add with
|
||||
/// the value specified in from for performance reasons (multiple checks of same requirements).
|
||||
/// </summary>
|
||||
/// <param name="operation">The copy operation</param>
|
||||
/// <param name="objectApplyTo">Object to apply the operation to</param>
|
||||
public void Copy(Operation<T> operation, T objectToApplyTo)
|
||||
{
|
||||
|
||||
// get value at from location
|
||||
object valueAtFromLocation = null;
|
||||
var positionAsInteger = -1;
|
||||
var actualFromProperty = operation.from;
|
||||
|
||||
|
||||
positionAsInteger = PropertyHelpers.GetNumericEnd(operation.from);
|
||||
|
||||
if (positionAsInteger > -1)
|
||||
|
|
@ -644,70 +575,96 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
|||
operation.from.IndexOf('/' + positionAsInteger.ToString()));
|
||||
}
|
||||
|
||||
|
||||
var fromProperty = PropertyHelpers
|
||||
.FindProperty(objectToApplyTo, actualFromProperty);
|
||||
var patchProperty = PropertyHelpers
|
||||
.FindPropertyAndParent(objectToApplyTo, actualFromProperty, ContractResolver);
|
||||
|
||||
// does property at from exist?
|
||||
if (fromProperty == null)
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: property at location from: {0} does not exist", operation.from),
|
||||
objectToApplyTo);
|
||||
}
|
||||
CheckIfPropertyExists(patchProperty, objectToApplyTo, operation, operation.from);
|
||||
|
||||
// get the property path
|
||||
|
||||
// is the path an array (but not a string (= char[]))? In this case,
|
||||
// the path must end with "/position" or "/-", which we already determined before.
|
||||
|
||||
if (positionAsInteger > -1)
|
||||
{
|
||||
|
||||
var isNonStringArray = !(fromProperty.PropertyType == typeof(string))
|
||||
&& typeof(IList).IsAssignableFrom(fromProperty.PropertyType);
|
||||
|
||||
if (isNonStringArray)
|
||||
if (IsNonStringArray(patchProperty))
|
||||
{
|
||||
// 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)
|
||||
var array = PropertyHelpers.GetValue(fromProperty, objectToApplyTo, actualFromProperty) as IList;
|
||||
var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
|
||||
|
||||
if (array.Count <= positionAsInteger)
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: provided from path is invalid for array property type at location from: {0}: invalid position",
|
||||
operation.from),
|
||||
objectToApplyTo);
|
||||
string.Format("Patch failed: provided from path is invalid for array property type at " +
|
||||
"location from: {0}: invalid position",
|
||||
operation.from),
|
||||
objectToApplyTo);
|
||||
}
|
||||
|
||||
valueAtFromLocation = array[positionAsInteger];
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonPatchException<T>(operation,
|
||||
string.Format("Patch failed: provided from path is invalid for array property type at location from: {0}: expected array",
|
||||
operation.from),
|
||||
string.Format("Patch failed: provided from path is invalid for array property type at " +
|
||||
"location from: {0}: expected array",
|
||||
operation.from),
|
||||
objectToApplyTo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no list, just get the value
|
||||
|
||||
// set the new value
|
||||
|
||||
valueAtFromLocation = PropertyHelpers.GetValue(fromProperty, objectToApplyTo, actualFromProperty);
|
||||
|
||||
valueAtFromLocation = patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
|
||||
}
|
||||
|
||||
// add operation to target location with that value.
|
||||
|
||||
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,79 +1,79 @@
|
|||
using Microsoft.AspNet.JsonPatch.Exceptions;
|
||||
using Microsoft.AspNet.JsonPatch.Operations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
// 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.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
|
||||
{
|
||||
public class TypedJsonPatchDocumentConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
public class TypedJsonPatchDocumentConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
|
||||
JsonSerializer serializer)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
|
||||
JsonSerializer serializer)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
return null;
|
||||
var genericType = objectType.GetGenericArguments()[0];
|
||||
|
||||
var genericType = objectType.GetGenericArguments()[0];
|
||||
// load jObject
|
||||
var jObject = JArray.Load(reader);
|
||||
|
||||
// load jObject
|
||||
var jObject = JArray.Load(reader);
|
||||
// Create target object for Json => list of operations, typed to genericType
|
||||
var genericOperation = typeof(Operation<>);
|
||||
var concreteOperationType = genericOperation.MakeGenericType(genericType);
|
||||
|
||||
// Create target object for Json => list of operations, typed to genericType
|
||||
var genericList = typeof(List<>);
|
||||
var concreteList = genericList.MakeGenericType(concreteOperationType);
|
||||
|
||||
var genericOperation = typeof(Operation<>);
|
||||
var concreteOperationType = genericOperation.MakeGenericType(genericType);
|
||||
var targetOperations = Activator.CreateInstance(concreteList);
|
||||
|
||||
var genericList = typeof(List<>);
|
||||
var concreteList = genericList.MakeGenericType(concreteOperationType);
|
||||
|
||||
var targetOperations = Activator.CreateInstance(concreteList);
|
||||
|
||||
|
||||
//Create a new reader for this jObject, and set all properties to match the original reader.
|
||||
JsonReader jObjectReader = jObject.CreateReader();
|
||||
jObjectReader.Culture = reader.Culture;
|
||||
jObjectReader.DateParseHandling = reader.DateParseHandling;
|
||||
jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
|
||||
jObjectReader.FloatParseHandling = reader.FloatParseHandling;
|
||||
//Create a new reader for this jObject, and set all properties to match the original reader.
|
||||
JsonReader jObjectReader = jObject.CreateReader();
|
||||
jObjectReader.Culture = reader.Culture;
|
||||
jObjectReader.DateParseHandling = reader.DateParseHandling;
|
||||
jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
|
||||
jObjectReader.FloatParseHandling = reader.FloatParseHandling;
|
||||
|
||||
// Populate the object properties
|
||||
serializer.Populate(jObjectReader, targetOperations);
|
||||
serializer.Populate(jObjectReader, targetOperations);
|
||||
|
||||
// container target: the typed JsonPatchDocument.
|
||||
var container = Activator.CreateInstance(objectType, targetOperations);
|
||||
// container target: the typed JsonPatchDocument.
|
||||
var container = Activator.CreateInstance(objectType, targetOperations, new DefaultContractResolver());
|
||||
|
||||
return container;
|
||||
return container;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new JsonPatchException("The JsonPatchDocument was malformed and could not be parsed.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new JsonPatchException("The JsonPatchDocument was malformed and could not be parsed.", ex);
|
||||
}
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
if (value is IJsonPatchDocument)
|
||||
{
|
||||
var jsonPatchDoc = (IJsonPatchDocument)value;
|
||||
var lst = jsonPatchDoc.GetOperations();
|
||||
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
if (value is IJsonPatchDocument)
|
||||
{
|
||||
var jsonPatchDoc = (IJsonPatchDocument)value;
|
||||
var lst = jsonPatchDoc.GetOperations();
|
||||
|
||||
// write out the operations, no envelope
|
||||
serializer.Serialize(writer, lst);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// write out the operations, no envelope
|
||||
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.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch.Helpers
|
||||
{
|
||||
internal static class PropertyHelpers
|
||||
{
|
||||
public static object GetValue(PropertyInfo propertyToGet, object targetObject, string pathToProperty)
|
||||
{
|
||||
// it is possible the path refers to a nested property. In that case, we need to
|
||||
// get from a different target object: the nested object.
|
||||
|
||||
var splitPath = pathToProperty.Split('/');
|
||||
|
||||
// skip the first one if it's empty
|
||||
var startIndex = (string.IsNullOrWhiteSpace(splitPath[0]) ? 1 : 0);
|
||||
|
||||
for (int i = startIndex; i < splitPath.Length - 1; i++)
|
||||
{
|
||||
var propertyInfoToGet = GetPropertyInfo(targetObject, splitPath[i]
|
||||
, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
|
||||
targetObject = propertyInfoToGet.GetValue(targetObject, null);
|
||||
}
|
||||
|
||||
return propertyToGet.GetValue(targetObject, null);
|
||||
}
|
||||
|
||||
public static bool SetValue(PropertyInfo propertyToSet, object targetObject, string pathToProperty, object value)
|
||||
{
|
||||
// it is possible the path refers to a nested property. In that case, we need to
|
||||
// set on a different target object: the nested object.
|
||||
var splitPath = pathToProperty.Split('/');
|
||||
|
||||
// skip the first one if it's empty
|
||||
var startIndex = (string.IsNullOrWhiteSpace(splitPath[0]) ? 1 : 0);
|
||||
|
||||
for (int i = startIndex; i < splitPath.Length - 1; i++)
|
||||
{
|
||||
var propertyInfoToGet = GetPropertyInfo(targetObject, splitPath[i]
|
||||
, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
|
||||
targetObject = propertyInfoToGet.GetValue(targetObject, null);
|
||||
}
|
||||
|
||||
propertyToSet.SetValue(targetObject, value, null);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static PropertyInfo FindProperty(object targetObject, string propertyPath)
|
||||
public static JsonPatchProperty FindPropertyAndParent(
|
||||
object targetObject,
|
||||
string propertyPath,
|
||||
IContractResolver contractResolver)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -62,17 +24,37 @@ namespace Microsoft.AspNet.JsonPatch.Helpers
|
|||
// 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++)
|
||||
for (int i = startIndex; i < splitPath.Length; i++)
|
||||
{
|
||||
var propertyInfoToGet = GetPropertyInfo(targetObject, splitPath[i]
|
||||
, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
|
||||
targetObject = propertyInfoToGet.GetValue(targetObject, null);
|
||||
var jsonContract = (JsonObjectContract)contractResolver.ResolveContract(targetObject.GetType());
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var propertyToFind = targetObject.GetType().GetProperty(splitPath.Last(),
|
||||
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
return propertyToFind;
|
||||
return null;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
|
@ -100,20 +82,11 @@ namespace Microsoft.AspNet.JsonPatch.Helpers
|
|||
if (type == null) throw new ArgumentNullException();
|
||||
foreach (Type interfaceType in type.GetInterfaces())
|
||||
{
|
||||
#if NETFX_CORE || ASPNETCORE50
|
||||
if (interfaceType.GetTypeInfo().IsGenericType &&
|
||||
interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
{
|
||||
return interfaceType.GetGenericArguments()[0];
|
||||
}
|
||||
#else
|
||||
if (interfaceType.GetTypeInfo().IsGenericType &&
|
||||
interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
{
|
||||
return interfaceType.GetGenericArguments()[0];
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -130,11 +103,5 @@ namespace Microsoft.AspNet.JsonPatch.Helpers
|
|||
|
||||
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 System.Collections.Generic;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch
|
||||
{
|
||||
public interface IJsonPatchDocument
|
||||
{
|
||||
IContractResolver ContractResolver { get; set; }
|
||||
|
||||
List<Operation> GetOperations();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,377 +1,392 @@
|
|||
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.Helpers;
|
||||
using Microsoft.AspNet.JsonPatch.Operations;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch
|
||||
{
|
||||
// Implementation details: the purpose of this type of patch document is to ensure we can do type-checking
|
||||
// when producing a JsonPatchDocument. However, we cannot send this "typed" over the wire, as that would require
|
||||
// including type data in the JsonPatchDocument serialized as JSON (to allow for correct deserialization) - that's
|
||||
// not according to RFC 6902, and would thus break cross-platform compatibility.
|
||||
[JsonConverter(typeof(TypedJsonPatchDocumentConverter))]
|
||||
public class JsonPatchDocument<T> : IJsonPatchDocument where T : class
|
||||
{
|
||||
public List<Operation<T>> Operations { get; private set; }
|
||||
// Implementation details: the purpose of this type of patch document is to ensure we can do type-checking
|
||||
// when producing a JsonPatchDocument. However, we cannot send this "typed" over the wire, as that would require
|
||||
// including type data in the JsonPatchDocument serialized as JSON (to allow for correct deserialization) - that's
|
||||
// not according to RFC 6902, and would thus break cross-platform compatibility.
|
||||
[JsonConverter(typeof(TypedJsonPatchDocumentConverter))]
|
||||
public class JsonPatchDocument<T> : IJsonPatchDocument where T : class
|
||||
{
|
||||
public List<Operation<T>> Operations { get; private set; }
|
||||
|
||||
public JsonPatchDocument()
|
||||
{
|
||||
Operations = new List<Operation<T>>();
|
||||
}
|
||||
[JsonIgnore]
|
||||
public IContractResolver ContractResolver { get; set; }
|
||||
|
||||
// Create from list of operations
|
||||
public JsonPatchDocument(List<Operation<T>> operations)
|
||||
{
|
||||
Operations = operations;
|
||||
}
|
||||
public JsonPatchDocument()
|
||||
{
|
||||
Operations = new List<Operation<T>>();
|
||||
ContractResolver = new DefaultContractResolver();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add operation. Will result in, for example,
|
||||
/// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">path</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Add<TProp>(Expression<Func<T, TProp>> path, TProp value)
|
||||
{
|
||||
Operations.Add(new Operation<T>("add", ExpressionHelpers.GetPath<T, TProp>(path).ToLower(), null, value));
|
||||
return this;
|
||||
}
|
||||
// Create from list of operations
|
||||
public JsonPatchDocument(List<Operation<T>> operations, IContractResolver contractResolver)
|
||||
{
|
||||
Operations = operations;
|
||||
ContractResolver = contractResolver;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add value to list at given position
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">path</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <param name="position">position</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Add<TProp>(Expression<Func<T, IList<TProp>>> path, TProp value, int position)
|
||||
{
|
||||
Operations.Add(new Operation<T>("add", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/" + position, null, value));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Add operation. Will result in, for example,
|
||||
/// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">path</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Add<TProp>(Expression<Func<T, TProp>> path, TProp value)
|
||||
{
|
||||
Operations.Add(new Operation<T>(
|
||||
"add",
|
||||
ExpressionHelpers.GetPath<T, TProp>(path).ToLower(),
|
||||
from: null,
|
||||
value: value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// At value at end of list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">path</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Add<TProp>(Expression<Func<T, IList<TProp>>> path, TProp value)
|
||||
{
|
||||
Operations.Add(new Operation<T>("add", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/-", null, value));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Add value to list at given position
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">path</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <param name="position">position</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Add<TProp>(Expression<Func<T, IList<TProp>>> path, TProp value, int position)
|
||||
{
|
||||
Operations.Add(new Operation<T>(
|
||||
"add",
|
||||
ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/" + position,
|
||||
null, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove value at target location. Will result in, for example,
|
||||
/// { "op": "remove", "path": "/a/b/c" }
|
||||
/// </summary>
|
||||
/// <param name="remove"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Remove<TProp>(Expression<Func<T, TProp>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("remove", ExpressionHelpers.GetPath<T, TProp>(path).ToLower(), null));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// At value at end of list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">path</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Add<TProp>(Expression<Func<T, IList<TProp>>> path, TProp value)
|
||||
{
|
||||
Operations.Add(new Operation<T>("add", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/-", null, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove value from list at given position
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="position">position</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Remove<TProp>(Expression<Func<T, IList<TProp>>> path, int position)
|
||||
{
|
||||
Operations.Add(new Operation<T>("remove", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/" + position, null));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Remove value at target location. Will result in, for example,
|
||||
/// { "op": "remove", "path": "/a/b/c" }
|
||||
/// </summary>
|
||||
/// <param name="remove"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Remove<TProp>(Expression<Func<T, TProp>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("remove", ExpressionHelpers.GetPath<T, TProp>(path).ToLower(), null));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove value from end of list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Remove<TProp>(Expression<Func<T, IList<TProp>>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("remove", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/-", null));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Remove value from list at given position
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="position">position</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Remove<TProp>(Expression<Func<T, IList<TProp>>> path, int position)
|
||||
{
|
||||
Operations.Add(new Operation<T>("remove", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/" + position, null));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace value. Will result in, for example,
|
||||
/// { "op": "replace", "path": "/a/b/c", "value": 42 }
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Replace<TProp>(Expression<Func<T, TProp>> path, TProp value)
|
||||
{
|
||||
Operations.Add(new Operation<T>("replace", ExpressionHelpers.GetPath<T, TProp>(path).ToLower(), null, value));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Remove value from end of list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Remove<TProp>(Expression<Func<T, IList<TProp>>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("remove", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/-", null));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace value in a list at given position
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="position">position</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Replace<TProp>(Expression<Func<T, IList<TProp>>> path, TProp value, int position)
|
||||
{
|
||||
Operations.Add(new Operation<T>("replace", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/" + position, null, value));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Replace value. Will result in, for example,
|
||||
/// { "op": "replace", "path": "/a/b/c", "value": 42 }
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Replace<TProp>(Expression<Func<T, TProp>> path, TProp value)
|
||||
{
|
||||
Operations.Add(new Operation<T>("replace", ExpressionHelpers.GetPath<T, TProp>(path).ToLower(), null, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace value at end of a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Replace<TProp>(Expression<Func<T, IList<TProp>>> path, TProp value)
|
||||
{
|
||||
Operations.Add(new Operation<T>("replace", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/-", null, value));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Replace value in a list at given position
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="position">position</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Replace<TProp>(Expression<Func<T, IList<TProp>>> path, TProp value, int position)
|
||||
{
|
||||
Operations.Add(new Operation<T>("replace", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/" + position, null, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes value at specified location and add it to the target location. Will result in, for example:
|
||||
/// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
|
||||
/// </summary>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Move<TProp>(Expression<Func<T, TProp>> from, Expression<Func<T, TProp>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("move", ExpressionHelpers.GetPath<T, TProp>(path).ToLower()
|
||||
, ExpressionHelpers.GetPath<T, TProp>(from).ToLower()));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Replace value at end of a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Replace<TProp>(Expression<Func<T, IList<TProp>>> path, TProp value)
|
||||
{
|
||||
Operations.Add(new Operation<T>("replace", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/-", null, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move from a position in a list to a new location
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Move<TProp>(Expression<Func<T, IList<TProp>>> from, int positionFrom, Expression<Func<T, TProp>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("move", ExpressionHelpers.GetPath<T, TProp>(path).ToLower()
|
||||
, ExpressionHelpers.GetPath<T, IList<TProp>>(from).ToLower() + "/" + positionFrom));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes value at specified location and add it to the target location. Will result in, for example:
|
||||
/// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
|
||||
/// </summary>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Move<TProp>(Expression<Func<T, TProp>> from, Expression<Func<T, TProp>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("move", ExpressionHelpers.GetPath<T, TProp>(path).ToLower()
|
||||
, ExpressionHelpers.GetPath<T, TProp>(from).ToLower()));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move from a property to a location in a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Move<TProp>(Expression<Func<T, TProp>> from,
|
||||
Expression<Func<T, IList<TProp>>> path, int positionTo)
|
||||
{
|
||||
Operations.Add(new Operation<T>("move", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower()
|
||||
+ "/" + positionTo
|
||||
, ExpressionHelpers.GetPath<T, TProp>(from).ToLower()));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Move from a position in a list to a new location
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Move<TProp>(Expression<Func<T, IList<TProp>>> from, int positionFrom, Expression<Func<T, TProp>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("move", ExpressionHelpers.GetPath<T, TProp>(path).ToLower()
|
||||
, ExpressionHelpers.GetPath<T, IList<TProp>>(from).ToLower() + "/" + positionFrom));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move from a position in a list to another location in a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Move<TProp>(Expression<Func<T, IList<TProp>>> from, int positionFrom,
|
||||
Expression<Func<T, IList<TProp>>> path, int positionTo)
|
||||
{
|
||||
Operations.Add(new Operation<T>("move", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower()
|
||||
+ "/" + positionTo
|
||||
, ExpressionHelpers.GetPath<T, IList<TProp>>(from).ToLower() + "/" + positionFrom));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Move from a property to a location in a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Move<TProp>(Expression<Func<T, TProp>> from,
|
||||
Expression<Func<T, IList<TProp>>> path, int positionTo)
|
||||
{
|
||||
Operations.Add(new Operation<T>("move", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower()
|
||||
+ "/" + positionTo
|
||||
, ExpressionHelpers.GetPath<T, TProp>(from).ToLower()));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move from a position in a list to the end of another list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Move<TProp>(Expression<Func<T, IList<TProp>>> from, int positionFrom,
|
||||
Expression<Func<T, IList<TProp>>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("move", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower()
|
||||
+ "/-"
|
||||
, ExpressionHelpers.GetPath<T, IList<TProp>>(from).ToLower() + "/" + positionFrom));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Move from a position in a list to another location in a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Move<TProp>(Expression<Func<T, IList<TProp>>> from, int positionFrom,
|
||||
Expression<Func<T, IList<TProp>>> path, int positionTo)
|
||||
{
|
||||
Operations.Add(new Operation<T>("move", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower()
|
||||
+ "/" + positionTo
|
||||
, ExpressionHelpers.GetPath<T, IList<TProp>>(from).ToLower() + "/" + positionFrom));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move to the end of a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Move<TProp>(Expression<Func<T, TProp>> from, Expression<Func<T, IList<TProp>>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("move", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/-"
|
||||
, ExpressionHelpers.GetPath<T, TProp>(from).ToLower()));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Move from a position in a list to the end of another list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Move<TProp>(Expression<Func<T, IList<TProp>>> from, int positionFrom,
|
||||
Expression<Func<T, IList<TProp>>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("move", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower()
|
||||
+ "/-"
|
||||
, ExpressionHelpers.GetPath<T, IList<TProp>>(from).ToLower() + "/" + positionFrom));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy the value at specified location to the target location. Willr esult in, for example:
|
||||
/// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
|
||||
/// </summary>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Copy<TProp>(Expression<Func<T, TProp>> from, Expression<Func<T, TProp>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("copy", ExpressionHelpers.GetPath<T, TProp>(path).ToLower()
|
||||
, ExpressionHelpers.GetPath<T, TProp>(from).ToLower()));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Move to the end of a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Move<TProp>(Expression<Func<T, TProp>> from, Expression<Func<T, IList<TProp>>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("move", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/-"
|
||||
, ExpressionHelpers.GetPath<T, TProp>(from).ToLower()));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy from a position in a list to a new location
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Copy<TProp>(Expression<Func<T, IList<TProp>>> from, int positionFrom, Expression<Func<T, TProp>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("copy", ExpressionHelpers.GetPath<T, TProp>(path).ToLower()
|
||||
, ExpressionHelpers.GetPath<T, IList<TProp>>(from).ToLower() + "/" + positionFrom));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Copy the value at specified location to the target location. Willr esult in, for example:
|
||||
/// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
|
||||
/// </summary>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Copy<TProp>(Expression<Func<T, TProp>> from, Expression<Func<T, TProp>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("copy", ExpressionHelpers.GetPath<T, TProp>(path).ToLower()
|
||||
, ExpressionHelpers.GetPath<T, TProp>(from).ToLower()));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy from a property to a location in a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Copy<TProp>(Expression<Func<T, TProp>> from,
|
||||
Expression<Func<T, IList<TProp>>> path, int positionTo)
|
||||
{
|
||||
Operations.Add(new Operation<T>("copy", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower()
|
||||
+ "/" + positionTo
|
||||
, ExpressionHelpers.GetPath<T, TProp>(from).ToLower()));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Copy from a position in a list to a new location
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Copy<TProp>(Expression<Func<T, IList<TProp>>> from, int positionFrom, Expression<Func<T, TProp>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("copy", ExpressionHelpers.GetPath<T, TProp>(path).ToLower()
|
||||
, ExpressionHelpers.GetPath<T, IList<TProp>>(from).ToLower() + "/" + positionFrom));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy from a position in a list to a new location in a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Copy<TProp>(Expression<Func<T, IList<TProp>>> from, int positionFrom,
|
||||
Expression<Func<T, IList<TProp>>> path, int positionTo)
|
||||
{
|
||||
Operations.Add(new Operation<T>("copy", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower()
|
||||
+ "/" + positionTo
|
||||
, ExpressionHelpers.GetPath<T, IList<TProp>>(from).ToLower() + "/" + positionFrom));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Copy from a property to a location in a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Copy<TProp>(Expression<Func<T, TProp>> from,
|
||||
Expression<Func<T, IList<TProp>>> path, int positionTo)
|
||||
{
|
||||
Operations.Add(new Operation<T>("copy", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower()
|
||||
+ "/" + positionTo
|
||||
, ExpressionHelpers.GetPath<T, TProp>(from).ToLower()));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy from a position in a list to the end of another list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Copy<TProp>(Expression<Func<T, IList<TProp>>> from, int positionFrom,
|
||||
Expression<Func<T, IList<TProp>>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("copy", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower()
|
||||
+ "/-"
|
||||
, ExpressionHelpers.GetPath<T, IList<TProp>>(from).ToLower() + "/" + positionFrom));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Copy from a position in a list to a new location in a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Copy<TProp>(Expression<Func<T, IList<TProp>>> from, int positionFrom,
|
||||
Expression<Func<T, IList<TProp>>> path, int positionTo)
|
||||
{
|
||||
Operations.Add(new Operation<T>("copy", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower()
|
||||
+ "/" + positionTo
|
||||
, ExpressionHelpers.GetPath<T, IList<TProp>>(from).ToLower() + "/" + positionFrom));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy to the end of a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Copy<TProp>(Expression<Func<T, TProp>> from, Expression<Func<T, IList<TProp>>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("copy", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/-"
|
||||
, ExpressionHelpers.GetPath<T, TProp>(from).ToLower()));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Copy from a position in a list to the end of another list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Copy<TProp>(Expression<Func<T, IList<TProp>>> from, int positionFrom,
|
||||
Expression<Func<T, IList<TProp>>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("copy", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower()
|
||||
+ "/-"
|
||||
, ExpressionHelpers.GetPath<T, IList<TProp>>(from).ToLower() + "/" + positionFrom));
|
||||
return this;
|
||||
}
|
||||
|
||||
public void ApplyTo(T objectToApplyTo)
|
||||
{
|
||||
ApplyTo(objectToApplyTo, new SimpleObjectAdapter<T>());
|
||||
}
|
||||
/// <summary>
|
||||
/// Copy to the end of a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="positionFrom"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<T> Copy<TProp>(Expression<Func<T, TProp>> from, Expression<Func<T, IList<TProp>>> path)
|
||||
{
|
||||
Operations.Add(new Operation<T>("copy", ExpressionHelpers.GetPath<T, IList<TProp>>(path).ToLower() + "/-"
|
||||
, ExpressionHelpers.GetPath<T, TProp>(from).ToLower()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public void ApplyTo(T objectToApplyTo, IObjectAdapter<T> adapter)
|
||||
{
|
||||
public void ApplyTo(T objectToApplyTo)
|
||||
{
|
||||
ApplyTo(objectToApplyTo, new SimpleObjectAdapter<T>(ContractResolver));
|
||||
}
|
||||
|
||||
// apply each operation in order
|
||||
foreach (var op in Operations)
|
||||
{
|
||||
op.Apply(objectToApplyTo, adapter);
|
||||
}
|
||||
}
|
||||
public void ApplyTo(T objectToApplyTo, IObjectAdapter<T> adapter)
|
||||
{
|
||||
|
||||
public List<Operation> GetOperations()
|
||||
{
|
||||
var allOps = new List<Operation>();
|
||||
// apply each operation in order
|
||||
foreach (var op in Operations)
|
||||
{
|
||||
op.Apply(objectToApplyTo, adapter);
|
||||
}
|
||||
}
|
||||
|
||||
if (Operations != null)
|
||||
{
|
||||
foreach (var op in Operations)
|
||||
{
|
||||
var untypedOp = new Operation();
|
||||
public List<Operation> GetOperations()
|
||||
{
|
||||
var allOps = new List<Operation>();
|
||||
|
||||
untypedOp.op = op.op;
|
||||
untypedOp.value = op.value;
|
||||
untypedOp.path = op.path;
|
||||
untypedOp.from = op.from;
|
||||
if (Operations != null)
|
||||
{
|
||||
foreach (var op in Operations)
|
||||
{
|
||||
var untypedOp = new Operation();
|
||||
|
||||
allOps.Add(untypedOp);
|
||||
}
|
||||
}
|
||||
untypedOp.op = op.op;
|
||||
untypedOp.value = op.value;
|
||||
untypedOp.path = op.path;
|
||||
untypedOp.from = op.from;
|
||||
|
||||
return allOps;
|
||||
}
|
||||
}
|
||||
allOps.Add(untypedOp);
|
||||
}
|
||||
}
|
||||
|
||||
return allOps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
"System.Collections": "4.0.10-beta-*",
|
||||
"System.Collections.Concurrent": "4.0.10-beta-*",
|
||||
"System.Globalization": "4.0.10-beta-*",
|
||||
"System.Runtime.Extensions": "4.0.10-beta-*"
|
||||
"System.Runtime.Extensions": "4.0.10-beta-*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,126 +1,45 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNet.JsonPatch;
|
||||
using Microsoft.Framework.Internal;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class JsonPatchInputFormatter : InputFormatter
|
||||
public class JsonPatchInputFormatter : JsonInputFormatter
|
||||
{
|
||||
private const int DefaultMaxDepth = 32;
|
||||
private JsonSerializerSettings _jsonSerializerSettings;
|
||||
|
||||
public JsonPatchInputFormatter()
|
||||
{
|
||||
SupportedEncodings.Add(Encodings.UTF8EncodingWithoutBOM);
|
||||
SupportedEncodings.Add(Encodings.UTF16EncodingLittleEndian);
|
||||
// Clear all values and only include json-patch+json value.
|
||||
SupportedMediaTypes.Clear();
|
||||
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/jsonpatch"));
|
||||
|
||||
_jsonSerializerSettings = new JsonSerializerSettings
|
||||
{
|
||||
MissingMemberHandling = MissingMemberHandling.Ignore,
|
||||
|
||||
// Limit the object graph we'll consume to a fixed depth. This prevents stackoverflow exceptions
|
||||
// from deserialization errors that might occur from deeply nested objects.
|
||||
MaxDepth = DefaultMaxDepth,
|
||||
|
||||
// Do not change this setting
|
||||
// Setting this to None prevents Json.NET from loading malicious, unsafe, or security-sensitive types
|
||||
TypeNameHandling = TypeNameHandling.None
|
||||
};
|
||||
|
||||
_jsonSerializerSettings.ContractResolver = new JsonContractResolver();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="JsonSerializerSettings"/> used to configure the <see cref="JsonSerializer"/>.
|
||||
/// </summary>
|
||||
public JsonSerializerSettings SerializerSettings
|
||||
{
|
||||
get { return _jsonSerializerSettings; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_jsonSerializerSettings = value;
|
||||
}
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json-patch+json"));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<object> ReadRequestBodyAsync([NotNull] InputFormatterContext context)
|
||||
public async override Task<object> ReadRequestBodyAsync([NotNull] InputFormatterContext context)
|
||||
{
|
||||
var type = context.ModelType;
|
||||
var request = context.ActionContext.HttpContext.Request;
|
||||
MediaTypeHeaderValue requestContentType = null;
|
||||
MediaTypeHeaderValue.TryParse(request.ContentType, out requestContentType);
|
||||
|
||||
// Get the character encoding for the content
|
||||
// Never non-null since SelectCharacterEncoding() throws in error / not found scenarios
|
||||
var effectiveEncoding = SelectCharacterEncoding(requestContentType);
|
||||
|
||||
using (var jsonReader = CreateJsonReader(context, request.Body, effectiveEncoding))
|
||||
var jsonPatchDocument = (IJsonPatchDocument)(await base.ReadRequestBodyAsync(context));
|
||||
if (jsonPatchDocument != null)
|
||||
{
|
||||
jsonReader.CloseInput = false;
|
||||
|
||||
var jsonSerializer = CreateJsonSerializer();
|
||||
|
||||
EventHandler<Newtonsoft.Json.Serialization.ErrorEventArgs> errorHandler = null;
|
||||
errorHandler = (sender, e) =>
|
||||
{
|
||||
var exception = e.ErrorContext.Error;
|
||||
context.ActionContext.ModelState.TryAddModelError(e.ErrorContext.Path, e.ErrorContext.Error);
|
||||
|
||||
// Error must always be marked as handled
|
||||
// Failure to do so can cause the exception to be rethrown at every recursive level and
|
||||
// overflow the stack for x64 CLR processes
|
||||
e.ErrorContext.Handled = true;
|
||||
};
|
||||
jsonSerializer.Error += errorHandler;
|
||||
|
||||
try
|
||||
{
|
||||
var contractObject = SerializerSettings.ContractResolver.ResolveContract(type);
|
||||
return Task.FromResult(jsonSerializer.Deserialize(jsonReader, type));
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Clean up the error handler in case CreateJsonSerializer() reuses a serializer
|
||||
if (errorHandler != null)
|
||||
{
|
||||
jsonSerializer.Error -= errorHandler;
|
||||
}
|
||||
}
|
||||
jsonPatchDocument.ContractResolver = SerializerSettings.ContractResolver;
|
||||
}
|
||||
|
||||
return (object)jsonPatchDocument;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called during deserialization to get the <see cref="JsonReader"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="InputFormatterContext"/> for the read.</param>
|
||||
/// <param name="readStream">The <see cref="Stream"/> from which to read.</param>
|
||||
/// <param name="effectiveEncoding">The <see cref="Encoding"/> to use when reading.</param>
|
||||
/// <returns>The <see cref="JsonReader"/> used during deserialization.</returns>
|
||||
public virtual JsonReader CreateJsonReader([NotNull] InputFormatterContext context,
|
||||
[NotNull] Stream readStream,
|
||||
[NotNull] Encoding effectiveEncoding)
|
||||
/// <inheritdoc />
|
||||
public override bool CanRead(InputFormatterContext context)
|
||||
{
|
||||
return new JsonTextReader(new StreamReader(readStream, effectiveEncoding));
|
||||
}
|
||||
if (!typeof(IJsonPatchDocument).IsAssignableFrom(context.ModelType) ||
|
||||
!context.ModelType.IsGenericType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called during deserialization to get the <see cref="JsonSerializer"/>.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="JsonSerializer"/> used during serialization and deserialization.</returns>
|
||||
public virtual JsonSerializer CreateJsonSerializer()
|
||||
{
|
||||
return JsonSerializer.Create(SerializerSettings);
|
||||
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);
|
||||
}
|
||||
|
||||
[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]
|
||||
public void AddToListInvalidPositionTooLarge()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
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;
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch.Test
|
||||
{
|
||||
public class SimpleDTO
|
||||
{
|
||||
public List<int> IntegerList { get; set; }
|
||||
public int IntegerValue { get; set; }
|
||||
public string StringProperty { get; set; }
|
||||
public string AnotherStringProperty { get; set; }
|
||||
public decimal DecimalValue { get; set; }
|
||||
|
||||
public double DoubleValue { get; set; }
|
||||
|
||||
public float FloatValue { get; set; }
|
||||
|
||||
public Guid GuidValue { get; set; }
|
||||
}
|
||||
public class SimpleDTO
|
||||
{
|
||||
public List<int> IntegerList { get; set; }
|
||||
public int IntegerValue { get; set; }
|
||||
public string StringProperty { get; set; }
|
||||
public string AnotherStringProperty { get; set; }
|
||||
public decimal DecimalValue { get; set; }
|
||||
public double DoubleValue { get; set; }
|
||||
public float FloatValue { get; set; }
|
||||
public Guid GuidValue { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch.Test
|
||||
{
|
||||
|
||||
public class SimpleDTOWithNestedDTO
|
||||
{
|
||||
public int IntegerValue { get; set; }
|
||||
|
|
@ -12,10 +13,13 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
|||
|
||||
public SimpleDTO SimpleDTO { get; set; }
|
||||
|
||||
public List<SimpleDTO> SimpleDTOList { get; set; }
|
||||
|
||||
public SimpleDTOWithNestedDTO()
|
||||
{
|
||||
this.NestedDTO = new NestedDTO();
|
||||
this.SimpleDTO = new SimpleDTO();
|
||||
this.SimpleDTOList = new List<SimpleDTO>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,12 +3,12 @@
|
|||
"warningsAsErrors": true
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
|
||||
"Microsoft.AspNet.JsonPatch": "1.0.0-*",
|
||||
"Microsoft.AspNet.Mvc.Core": "6.0.0-*",
|
||||
"Microsoft.AspNet.Testing": "1.0.0-*",
|
||||
"Moq": "4.2.1312.1622",
|
||||
"Newtonsoft.Json": "6.0.6",
|
||||
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
|
||||
"Microsoft.AspNet.JsonPatch": "1.0.0-*",
|
||||
"Microsoft.AspNet.Mvc.Core": "6.0.0-*",
|
||||
"Microsoft.AspNet.Testing": "1.0.0-*",
|
||||
"Moq": "4.2.1312.1622",
|
||||
"Newtonsoft.Json": "6.0.6",
|
||||
"xunit.runner.aspnet": "2.0.0-aspnet-*"
|
||||
},
|
||||
"commands": {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, mvcOptions.InputFormatters.Count);
|
||||
Assert.Equal(2, mvcOptions.InputFormatters.Count);
|
||||
Assert.IsType<JsonInputFormatter>(mvcOptions.InputFormatters[0].Instance);
|
||||
Assert.IsType<JsonPatchInputFormatter>(mvcOptions.InputFormatters[1].Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -25,7 +25,10 @@ namespace FiltersWebSite.Controllers
|
|||
|
||||
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.Add(jsonFormatter);
|
||||
|
|
|
|||
Loading…
Reference in New Issue