Adding errors in ModelState

This commit is contained in:
Kirthi Krishnamraju 2015-04-06 14:31:27 -07:00
parent 9f97d25e02
commit e48565dcd8
23 changed files with 1004 additions and 117 deletions

17
Mvc.sln
View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.22726.0
VisualStudioVersion = 14.0.22806.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
EndProject
@ -160,6 +160,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.JsonPatch.
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.JsonPatch", "src\Microsoft.AspNet.JsonPatch\Microsoft.AspNet.JsonPatch.xproj", "{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "JsonPatchWebSite", "test\WebSites\JsonPatchWebSite\JsonPatchWebSite.xproj", "{DAB1252D-577C-4912-98BE-1A812BF83F86}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -950,6 +952,18 @@ Global
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Release|x86.ActiveCfg = Release|Any CPU
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Release|x86.Build.0 = Release|Any CPU
{DAB1252D-577C-4912-98BE-1A812BF83F86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DAB1252D-577C-4912-98BE-1A812BF83F86}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DAB1252D-577C-4912-98BE-1A812BF83F86}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{DAB1252D-577C-4912-98BE-1A812BF83F86}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{DAB1252D-577C-4912-98BE-1A812BF83F86}.Debug|x86.ActiveCfg = Debug|Any CPU
{DAB1252D-577C-4912-98BE-1A812BF83F86}.Debug|x86.Build.0 = Debug|Any CPU
{DAB1252D-577C-4912-98BE-1A812BF83F86}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DAB1252D-577C-4912-98BE-1A812BF83F86}.Release|Any CPU.Build.0 = Release|Any CPU
{DAB1252D-577C-4912-98BE-1A812BF83F86}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{DAB1252D-577C-4912-98BE-1A812BF83F86}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{DAB1252D-577C-4912-98BE-1A812BF83F86}.Release|x86.ActiveCfg = Release|Any CPU
{DAB1252D-577C-4912-98BE-1A812BF83F86}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1028,5 +1042,6 @@ Global
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{81C20848-E063-4E12-AC40-0B55A532C16C} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{DAB1252D-577C-4912-98BE-1A812BF83F86} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
EndGlobalSection
EndGlobal

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.JsonPatch;
using Microsoft.AspNet.Mvc;
@ -34,7 +35,12 @@ namespace MvcSample.Web.Controllers
}
};
patchDoc.ApplyTo(customer);
patchDoc.ApplyTo(customer, ModelState);
if (!ModelState.IsValid)
{
return HttpBadRequest(ModelState);
}
return new ObjectResult(customer);
}

View File

@ -7,9 +7,9 @@
"Orders": [{"OrderName": "Order1"},
{"OrderName": "Order2"}]
};
$('#currentlabel').text(JSON.stringify(currentObj))
$('#currentLabel').text(JSON.stringify(currentObj))
$('#addorder').on("click", function () {
$('#addOrder').on("click", function () {
var obj = [{
"op": "Add",
"path": "Customer/Orders/2",
@ -22,12 +22,34 @@
dataType: 'json',
contentType: 'application/json-patch+json',
success: function (data) {
$('#addlabel').text(JSON.stringify(data))
$('#addLabel').text(JSON.stringify(data))
}
});
});
$('#removeorder').on("click", function () {
$('#invalidAddOrder').on("click", function () {
var obj = [{
"op": "Add",
"path": "Customer/Orders/5",
"value": { "OrderName": "Name5" }
}]
$.ajax({
url: 'jsonpatch',
type: 'PATCH',
data: JSON.stringify(obj),
dataType: 'json',
contentType: 'application/json-patch+json',
success: function (data) {
$('#invalidAddLabel').text(data)
},
error: function (request, status, error) {
$('#invalidAddLabel').text(error)
}
});
});
$('#removeOrder').on("click", function () {
var obj = [{
"op": "Remove",
"path": "Customer/Name"
@ -39,12 +61,12 @@
dataType: 'json',
contentType: 'application/json-patch+json',
success: function (data) {
$('#removelabel').text(JSON.stringify(data))
$('#removeLabel').text(JSON.stringify(data))
}
});
});
$('#moveorder').on("click", function () {
$('#moveOrder').on("click", function () {
var obj = [{
"op": "Move",
"from": "Customer/Orders/0",
@ -57,12 +79,12 @@
dataType: 'json',
contentType: 'application/json-patch+json',
success: function (data) {
$('#movelabel').text(JSON.stringify(data))
$('#moveLabel').text(JSON.stringify(data))
}
});
});
$('#replaceorder').on("click", function () {
$('#replaceOrder').on("click", function () {
var obj = [{
"op": "Replace",
"path": "Customer/Name",
@ -75,7 +97,7 @@
dataType: 'json',
contentType: 'application/json-patch+json',
success: function (data) {
$('#replacelabel').text(JSON.stringify(data))
$('#replaceLabel').text(JSON.stringify(data))
}
});
});
@ -86,29 +108,35 @@
<div>
<label>Current Customer</label>
<br />
<label id="currentlabel" name="currentlabel"></label>
<label id="currentLabel" name="currentLabel"></label>
<br />
</div>
<div>
<button id="addorder" type="submit" class="btn btn-primary"> Add </button>
<button id="addOrder" type="submit" class="btn btn-primary"> Add </button>
<br />
<label id="addlabel" name="addlabel"></label>
<label id="addLabel" name="addLabel"></label>
<br />
</div>
<div>
<button id="removeorder" type="submit" class="btn btn-primary"> Remove </button>
<button id="invalidAddOrder" type="submit" class="btn btn-primary"> Invalid Add </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>
<label id="invalidAddLabel" name="invalidAddLabel"></label>
<br />
</div>
<div>
<button id="replaceorder" type="submit" class="btn btn-primary"> Replace </button>
<button id="removeOrder" type="submit" class="btn btn-primary"> Remove </button>
<br />
<label id="replacelabel" name="replacelabel"></label>
<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>

View File

@ -1,16 +1,21 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.JsonPatch.Operations;
namespace Microsoft.AspNet.JsonPatch.Adapters
{
public interface IObjectAdapter<T>
where T : class
/// <summary>
/// Defines the operations that can be performed on a JSON patch document.
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IObjectAdapter<T> where T : class
{
void Add(Microsoft.AspNet.JsonPatch.Operations.Operation<T> operation, T objectToApplyTo);
void Copy(Microsoft.AspNet.JsonPatch.Operations.Operation<T> operation, T objectToApplyTo);
void Move(Microsoft.AspNet.JsonPatch.Operations.Operation<T> operation, T objectToApplyTo);
void Remove(Microsoft.AspNet.JsonPatch.Operations.Operation<T> operation, T objectToApplyTo);
void Replace(Microsoft.AspNet.JsonPatch.Operations.Operation<T> operation, T objectToApplyTo);
void Test(Microsoft.AspNet.JsonPatch.Operations.Operation<T> operation, T objectToApplyTo);
void Add(Operation<T> operation, T objectToApplyTo);
void Copy(Operation<T> operation, T objectToApplyTo);
void Move(Operation<T> operation, T objectToApplyTo);
void Remove(Operation<T> operation, T objectToApplyTo);
void Replace(Operation<T> operation, T objectToApplyTo);
void Test(Operation<T> operation, T objectToApplyTo);
}
}

View File

@ -7,20 +7,34 @@ 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;
namespace Microsoft.AspNet.JsonPatch.Adapters
{
/// <inheritdoc />
public class ObjectAdapter<T> : IObjectAdapter<T> where T : class
{
public IContractResolver ContractResolver { get; set; }
public ObjectAdapter(IContractResolver contractResolver)
/// <summary>
/// Initializes a new instance of <see cref="ObjectAdapter{T}"/>.
/// </summary>
/// <param name="contractResolver">The <see cref="IContractResolver"/>.</param>
/// <param name="logErrorAction">The <see cref="Action"/> for logging <see cref="JsonPatchError{T}"/>.</param>
public ObjectAdapter(IContractResolver contractResolver, Action<JsonPatchError<T>> logErrorAction)
{
ContractResolver = contractResolver;
LogErrorAction = logErrorAction;
}
/// <summary>
/// Gets or sets the <see cref="IContractResolver"/>.
/// </summary>
public IContractResolver ContractResolver { get; }
/// <summary>
/// Action for logging <see cref="JsonPatchError{T}"/>.
/// </summary>
public Action<JsonPatchError<T>> LogErrorAction { get; }
/// <summary>
/// The "add" operation performs one of the following functions,
/// depending upon what the target location references:
@ -123,7 +137,10 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
.FindPropertyAndParent(objectToApplyTo, actualPathToProperty, ContractResolver);
// does property at path exist?
CheckIfPropertyExists(patchProperty, objectToApplyTo, operationToReport, path);
if (!CheckIfPropertyExists(patchProperty, objectToApplyTo, operationToReport, path))
{
return;
}
// it exists. If it' an array, add to that array. If it's not, we replace.
// is the path an array (but not a string (= char[]))? In this case,
@ -139,7 +156,10 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
var conversionResult = PropertyHelpers.ConvertToActualType(genericTypeOfArray, value);
CheckIfPropertyCanBeSet(conversionResult, objectToApplyTo, operationToReport, path);
if (!CheckIfPropertyCanBeSet(conversionResult, objectToApplyTo, operationToReport, path))
{
return;
}
// get value (it can be cast, we just checked that)
var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
@ -157,19 +177,23 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
}
else
{
throw new JsonPatchException<T>(
LogError(new JsonPatchError<T>(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path),
objectToApplyTo);
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
return;
}
}
}
else
{
throw new JsonPatchException<T>(
LogError(new JsonPatchError<T>(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidPathForArrayProperty(operationToReport.op, path),
objectToApplyTo);
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
return;
}
}
else
@ -179,7 +203,10 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
value);
// Is conversion successful
CheckIfPropertyCanBeSet(conversionResultTuple, objectToApplyTo, operationToReport, path);
if (!CheckIfPropertyCanBeSet(conversionResultTuple, objectToApplyTo, operationToReport, path))
{
return;
}
patchProperty.Property.ValueProvider.SetValue(
patchProperty.Parent,
@ -229,7 +256,10 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
.FindPropertyAndParent(objectToApplyTo, actualFromProperty, ContractResolver);
// does property at from exist?
CheckIfPropertyExists(patchProperty, objectToApplyTo, operation, operation.from);
if (!CheckIfPropertyExists(patchProperty, objectToApplyTo, operation, operation.from))
{
return;
}
// 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.
@ -245,20 +275,24 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
if (array.Count <= positionAsInteger)
{
throw new JsonPatchException<T>(
LogError(new JsonPatchError<T>(
objectToApplyTo,
operation,
Resources.FormatInvalidIndexForArrayProperty(operation.op, operation.from),
objectToApplyTo);
Resources.FormatInvalidIndexForArrayProperty(operation.op, operation.from)));
return;
}
valueAtFromLocation = array[positionAsInteger];
}
else
{
throw new JsonPatchException<T>(
LogError(new JsonPatchError<T>(
objectToApplyTo,
operation,
Resources.FormatInvalidPathForArrayProperty(operation.op, operation.from),
objectToApplyTo);
Resources.FormatInvalidPathForArrayProperty(operation.op, operation.from)));
return;
}
}
else
@ -324,7 +358,10 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
.FindPropertyAndParent(objectToApplyTo, actualPathToProperty, ContractResolver);
// does the target location exist?
CheckIfPropertyExists(patchProperty, objectToApplyTo, operationToReport, path);
if (!CheckIfPropertyExists(patchProperty, objectToApplyTo, operationToReport, path))
{
return;
}
// 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
@ -352,19 +389,23 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
}
else
{
throw new JsonPatchException<T>(
LogError(new JsonPatchError<T>(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path),
objectToApplyTo);
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
return;
}
}
}
else
{
throw new JsonPatchException<T>(
LogError(new JsonPatchError<T>(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidPathForArrayProperty(operationToReport.op, path),
objectToApplyTo);
Resources.FormatInvalidPathForArrayProperty(operationToReport.op, path)));
return;
}
}
else
@ -451,10 +492,13 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
.FindPropertyAndParent(objectToApplyTo, actualPathProperty, ContractResolver);
// does property at path exist?
CheckIfPropertyExists(patchProperty, objectToApplyTo, operation, operation.path);
if (!CheckIfPropertyExists(patchProperty, objectToApplyTo, operation, operation.path))
{
return;
}
// get the property path
Type typeOfFinalPropertyAtPathLocation;
Type typeOfFinalPropertyAtPathLocation = null;
// 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.
@ -471,20 +515,24 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
if (array.Count <= positionInPathAsInteger)
{
throw new JsonPatchException<T>(
LogError(new JsonPatchError<T>(
objectToApplyTo,
operation,
Resources.FormatInvalidIndexForArrayProperty(operation.op, operation.path),
objectToApplyTo);
Resources.FormatInvalidIndexForArrayProperty(operation.op, operation.path)));
return;
}
valueAtPathLocation = array[positionInPathAsInteger];
}
else
{
throw new JsonPatchException<T>(
LogError(new JsonPatchError<T>(
objectToApplyTo,
operation,
Resources.FormatInvalidPathForArrayProperty(operation.op, operation.path),
objectToApplyTo);
Resources.FormatInvalidPathForArrayProperty(operation.op, operation.path)));
return;
}
}
else
@ -499,7 +547,10 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
operation.value);
// Is conversion successful
CheckIfPropertyCanBeSet(conversionResultTuple, objectToApplyTo, operation, operation.path);
if (!CheckIfPropertyCanBeSet(conversionResultTuple, objectToApplyTo, operation, operation.path))
{
return;
}
//Compare
}
@ -571,7 +622,10 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
.FindPropertyAndParent(objectToApplyTo, actualFromProperty, ContractResolver);
// does property at from exist?
CheckIfPropertyExists(patchProperty, objectToApplyTo, operation, operation.from);
if (!CheckIfPropertyExists(patchProperty, objectToApplyTo, operation, operation.from))
{
return;
}
// get the property path
// is the path an array (but not a string (= char[]))? In this case,
@ -588,19 +642,24 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
if (array.Count <= positionAsInteger)
{
throw new JsonPatchException<T>(operation,
Resources.FormatInvalidIndexForArrayProperty(operation.op, operation.from),
objectToApplyTo);
LogError(new JsonPatchError<T>(
objectToApplyTo,
operation,
Resources.FormatInvalidIndexForArrayProperty(operation.op, operation.from)));
return;
}
valueAtFromLocation = array[positionAsInteger];
}
else
{
throw new JsonPatchException<T>(
LogError(new JsonPatchError<T>(
objectToApplyTo,
operation,
Resources.FormatInvalidPathForArrayProperty(operation.op, operation.from),
objectToApplyTo);
Resources.FormatInvalidPathForArrayProperty(operation.op, operation.from)));
return;
}
}
else
@ -614,7 +673,7 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
Add(operation.path, valueAtFromLocation, objectToApplyTo, operation);
}
private void CheckIfPropertyExists(
private bool CheckIfPropertyExists(
JsonPatchProperty patchProperty,
T objectToApplyTo,
Operation<T> operation,
@ -622,18 +681,25 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
{
if (patchProperty == null)
{
throw new JsonPatchException<T>(
LogError(new JsonPatchError<T>(
objectToApplyTo,
operation,
Resources.FormatPropertyDoesNotExist(propertyPath),
objectToApplyTo);
Resources.FormatPropertyDoesNotExist(propertyPath)));
return false;
}
if (patchProperty.Property.Ignored)
{
throw new JsonPatchException<T>(
LogError(new JsonPatchError<T>(
objectToApplyTo,
operation,
Resources.FormatCannotUpdateProperty(propertyPath),
objectToApplyTo);
Resources.FormatCannotUpdateProperty(propertyPath)));
return false;
}
return true;
}
private bool IsNonStringArray(JsonPatchProperty patchProperty)
@ -643,7 +709,7 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
patchProperty.Property.PropertyType.GetTypeInfo());
}
private void CheckIfPropertyCanBeSet(
private bool CheckIfPropertyCanBeSet(
ConversionResult result,
T objectToApplyTo,
Operation<T> operation,
@ -651,10 +717,26 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
{
if (!result.CanBeConverted)
{
throw new JsonPatchException<T>(
LogError(new JsonPatchError<T>(
objectToApplyTo,
operation,
Resources.FormatInvalidValueForProperty(result.ConvertedInstance, path),
objectToApplyTo);
Resources.FormatInvalidValueForProperty(result.ConvertedInstance, path)));
return false;
}
return true;
}
private void LogError(JsonPatchError<T> jsonPatchError)
{
if (LogErrorAction != null)
{
LogErrorAction(jsonPatchError);
}
else
{
throw new JsonPatchException<T>(jsonPatchError);
}
}
}

View File

@ -26,15 +26,15 @@ namespace Microsoft.AspNet.JsonPatch.Exceptions
}
public JsonPatchException(Operation<T> operation, string message, T affectedObject)
public JsonPatchException(JsonPatchError<T> jsonPatchError)
{
FailedOperation = operation;
_message = message;
AffectedObject = affectedObject;
FailedOperation = jsonPatchError.Operation;
_message = jsonPatchError.ErrorMessage;
AffectedObject = jsonPatchError.AffectedObject;
}
public JsonPatchException(Operation<T> operation, string message, T affectedObject, Exception innerException)
: this(operation, message, affectedObject)
public JsonPatchException(JsonPatchError<T> jsonPatchError, Exception innerException)
: this(jsonPatchError)
{
InnerException = innerException;
}

View File

@ -0,0 +1,45 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.JsonPatch.Operations;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.JsonPatch
{
/// <summary>
/// Captures error message and the related entity and the operation that caused it.
/// </summary>
public class JsonPatchError<T> where T : class
{
/// <summary>
/// Initializes a new instance of <see cref="JsonPatchError{T}"/>.
/// </summary>
/// <param name="affectedObject">The object that is affected by the error.</param>
/// <param name="operation">The <see cref="Operation{T}"/> that caused the error.</param>
/// <param name="errorMessage">The error message.</param>
public JsonPatchError(
[NotNull] T affectedObject,
[NotNull] Operation<T> operation,
[NotNull] string errorMessage)
{
AffectedObject = affectedObject;
Operation = operation;
ErrorMessage = errorMessage;
}
/// <summary>
/// Gets the object that is affected by the error.
/// </summary>
public T AffectedObject { get; }
/// <summary>
/// Gets the <see cref="Operation{T}"/> that caused the error.
/// </summary>
public Operation<T> Operation { get; }
/// <summary>
/// Gets the error message.
/// </summary>
public string ErrorMessage { get; }
}
}

View File

@ -354,12 +354,16 @@ namespace Microsoft.AspNet.JsonPatch
public void ApplyTo(T objectToApplyTo)
{
ApplyTo(objectToApplyTo, new ObjectAdapter<T>(ContractResolver));
ApplyTo(objectToApplyTo, new ObjectAdapter<T>(ContractResolver, logErrorAction: null));
}
public void ApplyTo(T objectToApplyTo, Action<JsonPatchError<T>> logErrorAction)
{
ApplyTo(objectToApplyTo, new ObjectAdapter<T>(ContractResolver, logErrorAction));
}
public void ApplyTo(T objectToApplyTo, IObjectAdapter<T> adapter)
{
// apply each operation in order
foreach (var op in Operations)
{

View File

@ -0,0 +1,51 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.JsonPatch;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Extensions for <see cref="JsonPatchDocument{T}"/>
/// </summary>
public static class JsonPatchExtensions
{
/// <summary>
/// Applies JSON patch operations on object and logs errors in <see cref="ModelStateDictionary"/>.
/// </summary>
/// <param name="patchDoc">The <see cref="JsonPatchDocument{T}"/>.</param>
/// <param name="objectToApplyTo">The entity on which <see cref="JsonPatchDocument{T}"/> is applied.</param>
/// <param name="modelState">The <see cref="ModelStateDictionary"/> to add errors.</param>
public static void ApplyTo<T>(
[NotNull] this JsonPatchDocument<T> patchDoc,
[NotNull] T objectToApplyTo,
[NotNull] ModelStateDictionary modelState) where T : class
{
patchDoc.ApplyTo(objectToApplyTo, modelState, prefix: string.Empty);
}
/// <summary>
/// Applies JSON patch operations on object and logs errors in <see cref="ModelStateDictionary"/>.
/// </summary>
/// <param name="patchDoc">The <see cref="JsonPatchDocument{T}"/>.</param>
/// <param name="objectToApplyTo">The entity on which <see cref="JsonPatchDocument{T}"/> is applied.</param>
/// <param name="modelState">The <see cref="ModelStateDictionary"/> to add errors.</param>
/// <param name="prefix">The prefix to use when looking up values in <see cref="ModelStateDictionary"/>.</param>
public static void ApplyTo<T>(
[NotNull] this JsonPatchDocument<T> patchDoc,
[NotNull] T objectToApplyTo,
[NotNull] ModelStateDictionary modelState,
string prefix) where T : class
{
patchDoc.ApplyTo(objectToApplyTo, jsonPatchError =>
{
var affectedObjectName = jsonPatchError.AffectedObject.GetType().Name;
var key = string.IsNullOrEmpty(prefix) ? affectedObjectName : prefix + "." + affectedObjectName;
modelState.TryAddModelError(key, jsonPatchError.ErrorMessage);
});
}
}
}

View File

@ -282,8 +282,10 @@ namespace Microsoft.AspNet.JsonPatch.Test
// Act & Assert
var exception = Assert.Throws<JsonPatchException<SimpleDTOWithNestedDTO>>(() => { patchDoc.ApplyTo(doc); });
Assert.Equal("For operation 'add' on array property at path '/simpledto/integerlist/4', the index is " +
"larger than the array size.", exception.Message);
Assert.Equal(
"For operation 'add' on array property at path '/simpledto/integerlist/4', the index is " +
"larger than the array size.",
exception.Message);
}
@ -311,8 +313,39 @@ namespace Microsoft.AspNet.JsonPatch.Test
{
deserialized.ApplyTo(doc);
});
Assert.Equal("For operation 'add' on array property at path '/simpledto/integerlist/4', the index is " +
"larger than the array size.", exception.Message);
Assert.Equal(
"For operation 'add' on array property at path '/simpledto/integerlist/4', the index is " +
"larger than the array size.",
exception.Message);
}
[Fact]
public void AddToListInvalidPositionTooLarge_LogsError()
{
// Arrange
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
};
// create patch
var patchDoc = new JsonPatchDocument<SimpleDTOWithNestedDTO>();
patchDoc.Add<int>(o => o.SimpleDTO.IntegerList, 4, 4);
var logger = new TestErrorLogger<SimpleDTOWithNestedDTO>();
// Act
patchDoc.ApplyTo(doc, logger.LogErrorMessage);
//Assert
Assert.Equal(
"For operation 'add' on array property at path '/simpledto/integerlist/4', the index is larger than " +
"the array size.",
logger.ErrorMessage);
}
[Fact]
@ -363,6 +396,31 @@ namespace Microsoft.AspNet.JsonPatch.Test
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", exception.Message);
}
[Fact]
public void AddToListInvalidPositionTooSmall_LogsError()
{
// Arrange
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
};
// create patch
var patchDoc = new JsonPatchDocument<SimpleDTOWithNestedDTO>();
patchDoc.Add<int>(o => o.SimpleDTO.IntegerList, 4, -1);
var logger = new TestErrorLogger<SimpleDTOWithNestedDTO>();
// Act
patchDoc.ApplyTo(doc, logger.LogErrorMessage);
//Assert
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", logger.ErrorMessage);
}
[Fact]
public void AddToListAppend()
{
@ -528,8 +586,10 @@ namespace Microsoft.AspNet.JsonPatch.Test
// Act & Assert
var exception = Assert.Throws<JsonPatchException<SimpleDTOWithNestedDTO>>(() => { patchDoc.ApplyTo(doc); });
Assert.Equal("For operation 'remove' on array property at path '/simpledto/integerlist/3', the index is " +
"larger than the array size.", exception.Message);
Assert.Equal(
"For operation 'remove' on array property at path '/simpledto/integerlist/3', the index is " +
"larger than the array size.",
exception.Message);
}
[Fact]
@ -556,8 +616,38 @@ namespace Microsoft.AspNet.JsonPatch.Test
{
deserialized.ApplyTo(doc);
});
Assert.Equal("For operation 'remove' on array property at path '/simpledto/integerlist/3', the index is " +
"larger than the array size.", exception.Message);
Assert.Equal(
"For operation 'remove' on array property at path '/simpledto/integerlist/3', the index is " +
"larger than the array size.",
exception.Message);
}
[Fact]
public void RemoveFromListInvalidPositionTooLarge_LogsError()
{
// Arrange
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
};
// create patch
var patchDoc = new JsonPatchDocument<SimpleDTOWithNestedDTO>();
patchDoc.Remove<int>(o => o.SimpleDTO.IntegerList, 3);
var logger = new TestErrorLogger<SimpleDTOWithNestedDTO>();
// Act
patchDoc.ApplyTo(doc, logger.LogErrorMessage);
// Assert
Assert.Equal(
"For operation 'remove' on array property at path '/simpledto/integerlist/3', the index is " +
"larger than the array size.",
logger.ErrorMessage);
}
[Fact]
@ -608,6 +698,31 @@ namespace Microsoft.AspNet.JsonPatch.Test
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", exception.Message);
}
[Fact]
public void RemoveFromListInvalidPositionTooSmall_LogsError()
{
// Arrange
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
};
// create patch
var patchDoc = new JsonPatchDocument<SimpleDTOWithNestedDTO>();
patchDoc.Remove<int>(o => o.SimpleDTO.IntegerList, -1);
var logger = new TestErrorLogger<SimpleDTOWithNestedDTO>();
// Act
patchDoc.ApplyTo(doc, logger.LogErrorMessage);
// Assert
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", logger.ErrorMessage);
}
[Fact]
public void RemoveFromEndOfList()
{
@ -1014,8 +1129,10 @@ namespace Microsoft.AspNet.JsonPatch.Test
// Act & Assert
var exception = Assert.Throws<JsonPatchException<SimpleDTOWithNestedDTO>>(() => { patchDoc.ApplyTo(doc); });
Assert.Equal("For operation 'replace' on array property at path '/simpledto/integerlist/3', the index is " +
"larger than the array size.", exception.Message);
Assert.Equal(
"For operation 'replace' on array property at path '/simpledto/integerlist/3', the index is " +
"larger than the array size.",
exception.Message);
}
[Fact]
@ -1042,8 +1159,38 @@ namespace Microsoft.AspNet.JsonPatch.Test
{
deserialized.ApplyTo(doc);
});
Assert.Equal("For operation 'replace' on array property at path '/simpledto/integerlist/3', the index is " +
"larger than the array size.", exception.Message);
Assert.Equal(
"For operation 'replace' on array property at path '/simpledto/integerlist/3', the index is " +
"larger than the array size.",
exception.Message);
}
[Fact]
public void ReplaceInListInvalid_PositionTooLarge_LogsError()
{
// Arrange
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
};
// create patch
var patchDoc = new JsonPatchDocument<SimpleDTOWithNestedDTO>();
patchDoc.Replace<int>(o => o.SimpleDTO.IntegerList, 5, 3);
var logger = new TestErrorLogger<SimpleDTOWithNestedDTO>();
// Act
patchDoc.ApplyTo(doc, logger.LogErrorMessage);
// Assert
Assert.Equal(
"For operation 'replace' on array property at path '/simpledto/integerlist/3', the index is " +
"larger than the array size.",
logger.ErrorMessage);
}
[Fact]
@ -1091,6 +1238,31 @@ namespace Microsoft.AspNet.JsonPatch.Test
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", exception.Message);
}
[Fact]
public void ReplaceInListInvalidPositionTooSmall_LogsError()
{
// Arrange
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
};
// create patch
var patchDoc = new JsonPatchDocument<SimpleDTOWithNestedDTO>();
patchDoc.Replace<int>(o => o.SimpleDTO.IntegerList, 5, -1);
var logger = new TestErrorLogger<SimpleDTOWithNestedDTO>();
// Act
patchDoc.ApplyTo(doc, logger.LogErrorMessage);
// Assert
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", logger.ErrorMessage);
}
[Fact]
public void Copy()
{

View File

@ -113,8 +113,10 @@ namespace Microsoft.AspNet.JsonPatch.Test
// Act & Assert
var exception = Assert.Throws<JsonPatchException<SimpleDTO>>(() => { patchDoc.ApplyTo(doc); });
Assert.Equal("For operation 'add' on array property at path '/integerlist/4', the index is " +
"larger than the array size.", exception.Message);
Assert.Equal(
"For operation 'add' on array property at path '/integerlist/4', the index is " +
"larger than the array size.",
exception.Message);
}
[Fact]
@ -135,8 +137,35 @@ namespace Microsoft.AspNet.JsonPatch.Test
// Act & Assert
var exception = Assert.Throws<JsonPatchException<SimpleDTO>>(() => { deserialized.ApplyTo(doc); });
Assert.Equal("For operation 'add' on array property at path '/integerlist/4', the index is " +
"larger than the array size.", exception.Message);
Assert.Equal(
"For operation 'add' on array property at path '/integerlist/4', the index is " +
"larger than the array size.",
exception.Message);
}
[Fact]
public void AddToListInvalidPositionTooLarge_LogsError()
{
// Arrange
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
var patchDoc = new JsonPatchDocument<SimpleDTO>();
patchDoc.Add<int>(o => o.IntegerList, 4, 4);
var logger = new TestErrorLogger<SimpleDTO>();
// Act
patchDoc.ApplyTo(doc, logger.LogErrorMessage);
// Assert
Assert.Equal(
"For operation 'add' on array property at path '/integerlist/4', the index is " +
"larger than the array size.",
logger.ErrorMessage);
}
[Fact]
@ -264,6 +293,28 @@ namespace Microsoft.AspNet.JsonPatch.Test
Assert.Equal("Property does not exist at path '/integerlist/-1'.", exception.Message);
}
[Fact]
public void AddToListInvalidPositionTooSmall_LogsError()
{
// Arrange
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
var patchDoc = new JsonPatchDocument<SimpleDTO>();
patchDoc.Add<int>(o => o.IntegerList, 4, -1);
var logger = new TestErrorLogger<SimpleDTO>();
// Act
patchDoc.ApplyTo(doc, logger.LogErrorMessage);
// Assert
Assert.Equal("Property does not exist at path '/integerlist/-1'.", logger.ErrorMessage);
}
[Fact]
public void AddToListAppend()
{
@ -408,8 +459,10 @@ namespace Microsoft.AspNet.JsonPatch.Test
// Act & Assert
var exception = Assert.Throws<JsonPatchException<SimpleDTO>>(() => { patchDoc.ApplyTo(doc); });
Assert.Equal("For operation 'remove' on array property at path '/integerlist/3', the index is " +
"larger than the array size.", exception.Message);
Assert.Equal(
"For operation 'remove' on array property at path '/integerlist/3', the index is " +
"larger than the array size.",
exception.Message);
}
[Fact]
@ -430,8 +483,35 @@ namespace Microsoft.AspNet.JsonPatch.Test
// Act & Assert
var exception = Assert.Throws<JsonPatchException<SimpleDTO>>(() => { deserialized.ApplyTo(doc); });
Assert.Equal("For operation 'remove' on array property at path '/integerlist/3', the index is " +
"larger than the array size.", exception.Message);
Assert.Equal(
"For operation 'remove' on array property at path '/integerlist/3', the index is " +
"larger than the array size.",
exception.Message);
}
[Fact]
public void RemoveFromListInvalidPositionTooLarge_LogsError()
{
// Arrange
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
var patchDoc = new JsonPatchDocument<SimpleDTO>();
patchDoc.Remove<int>(o => o.IntegerList, 3);
var logger = new TestErrorLogger<SimpleDTO>();
// Act
patchDoc.ApplyTo(doc, logger.LogErrorMessage);
// Assert
Assert.Equal(
"For operation 'remove' on array property at path '/integerlist/3', the index is " +
"larger than the array size.",
logger.ErrorMessage);
}
[Fact]
@ -473,6 +553,28 @@ namespace Microsoft.AspNet.JsonPatch.Test
Assert.Equal("Property does not exist at path '/integerlist/-1'.", exception.Message);
}
[Fact]
public void RemoveFromListInvalidPositionTooSmall_LogsError()
{
// Arrange
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
var patchDoc = new JsonPatchDocument<SimpleDTO>();
patchDoc.Remove<int>(o => o.IntegerList, -1);
var logger = new TestErrorLogger<SimpleDTO>();
// Act
patchDoc.ApplyTo(doc, logger.LogErrorMessage);
// Assert
Assert.Equal("Property does not exist at path '/integerlist/-1'.", logger.ErrorMessage);
}
[Fact]
public void RemoveFromEndOfList()
{
@ -971,8 +1073,10 @@ namespace Microsoft.AspNet.JsonPatch.Test
{
patchDoc.ApplyTo(doc);
});
Assert.Equal("For operation 'replace' on array property at path '/integerlist/3', the index is " +
"larger than the array size.", exception.Message);
Assert.Equal(
"For operation 'replace' on array property at path '/integerlist/3', the index is " +
"larger than the array size.",
exception.Message);
}
[Fact]
@ -995,8 +1099,10 @@ namespace Microsoft.AspNet.JsonPatch.Test
{
deserialized.ApplyTo(doc);
});
Assert.Equal("For operation 'replace' on array property at path '/integerlist/3', the index is " +
"larger than the array size.", exception.Message);
Assert.Equal(
"For operation 'replace' on array property at path '/integerlist/3', the index is " +
"larger than the array size.",
exception.Message);
}
[Fact]

View File

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.JsonPatch.Test
{
public class TestErrorLogger<T> where T: class
{
public string ErrorMessage { get; set; }
public void LogErrorMessage(JsonPatchError<T> patchError)
{
ErrorMessage = patchError.ErrorMessage;
}
}
}

View File

@ -0,0 +1,145 @@
// 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.Net.Http;
using System.Text;
using System.Threading.Tasks;
using JsonPatchWebSite;
using JsonPatchWebSite.Models;
using Microsoft.AspNet.Builder;
using Microsoft.Framework.DependencyInjection;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNet.Mvc.FunctionalTests
{
public class JsonPatchTest
{
private const string SiteName = nameof(JsonPatchWebSite);
private readonly Action<IApplicationBuilder> _app = new Startup().Configure;
private readonly Action<IServiceCollection> _configureServices = new Startup().ConfigureServices;
[Theory]
[InlineData("http://localhost/jsonpatch/JsonPatchWithModelState")]
[InlineData("http://localhost/jsonpatch/JsonPatchWithModelStateAndPrefix?prefix=Patch")]
public async Task JsonPatch_ValidAddOperation_List(string url)
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
var input = "[{ \"op\": \"add\", \"path\": \"Customer/Orders/2\", " +
"\"value\": { \"OrderName\": \"Name2\" }}]";
var request = new HttpRequestMessage
{
Content = new StringContent(input, Encoding.UTF8, "application/json-patch+json"),
Method = new HttpMethod("PATCH"),
RequestUri = new Uri(url)
};
// Act
var response = await client.SendAsync(request);
// Assert
var body = await response.Content.ReadAsStringAsync();
var customer = JsonConvert.DeserializeObject<Customer>(body);
Assert.Equal("Name2", customer.Orders[2].OrderName);
}
[Theory]
[InlineData("http://localhost/jsonpatch/JsonPatchWithModelState")]
[InlineData("http://localhost/jsonpatch/JsonPatchWithModelStateAndPrefix?prefix=Patch")]
public async Task JsonPatch_MultipleValidOperations_Success(string url)
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
var input = "[{ \"op\": \"add\", \"path\": \"Customer/Orders/2\", " +
"\"value\": { \"OrderName\": \"Name2\" }}, {\"op\": \"copy\", \"from\": \"Customer/Orders/2\", " +
"\"path\": \"Customer/Orders/3\" }, {\"op\": \"replace\", \"path\": \"Customer/Orders/2/OrderName\", " +
"\"value\": \"ReplacedName\" }]";
var request = new HttpRequestMessage
{
Content = new StringContent(input, Encoding.UTF8, "application/json-patch+json"),
Method = new HttpMethod("PATCH"),
RequestUri = new Uri(url)
};
// Act
var response = await client.SendAsync(request);
// Assert
var body = await response.Content.ReadAsStringAsync();
var customer = JsonConvert.DeserializeObject<Customer>(body);
Assert.Equal("ReplacedName", customer.Orders[2].OrderName);
Assert.Equal("Name2", customer.Orders[3].OrderName);
}
public static IEnumerable<object[]> InvalidJsonPatchData
{
get
{
return new[]
{
new object[] {
"http://localhost/jsonpatch/JsonPatchWithModelStateAndPrefix?prefix=Patch",
"[{ \"op\": \"add\", \"path\": \"Customer/Orders/5\", " +
"\"value\": { \"OrderName\": \"Name5\" }}]",
"{\"Patch.Customer\":[\"For operation 'add' on array property at path " +
"'Customer/Orders/5', the index is larger than the array size.\"]}"
},
new object[] {
"http://localhost/jsonpatch/JsonPatchWithModelState",
"[{ \"op\": \"add\", \"path\": \"Customer/Orders/5\", " +
"\"value\": { \"OrderName\": \"Name5\" }}]",
"{\"Customer\":[\"For operation 'add' on array property at path " +
"'Customer/Orders/5', the index is larger than the array size.\"]}"
},
new object[] {
"http://localhost/jsonpatch/JsonPatchWithModelStateAndPrefix?prefix=Patch",
"[{ \"op\": \"add\", \"path\": \"Customer/Orders/2\", \"value\": " +
"{ \"OrderName\": \"Name2\" }}, {\"op\": \"copy\", \"from\": \"Customer/Orders/4\", " +
"\"path\": \"Customer/Orders/3\" }, {\"op\": \"replace\", \"path\": " +
"\"Customer/Orders/2/OrderName\", \"value\": \"ReplacedName\" }]",
"{\"Patch.Customer\":[\"For operation 'copy' on array property at path " +
"'Customer/Orders/4', the index is larger than the array size.\"]}"
},
new object[] {
"http://localhost/jsonpatch/JsonPatchWithModelState",
"[{ \"op\": \"add\", \"path\": \"Customer/Orders/2\", \"value\": " +
"{ \"OrderName\": \"Name2\" }}, {\"op\": \"copy\", \"from\": \"Customer/Orders/4\", " +
"\"path\": \"Customer/Orders/3\" }, {\"op\": \"replace\", \"path\": " +
"\"Customer/Orders/2/OrderName\", \"value\": \"ReplacedName\" }]",
"{\"Customer\":[\"For operation 'copy' on array property at path " +
"'Customer/Orders/4', the index is larger than the array size.\"]}"
}
};
}
}
[Theory, MemberData("InvalidJsonPatchData")]
public async Task JsonPatch_InvalidOperations_failure(string url, string input, string errorMessage)
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
var request = new HttpRequestMessage
{
Content = new StringContent(input, Encoding.UTF8, "application/json-patch+json"),
Method = new HttpMethod("PATCH"),
RequestUri = new Uri(url)
};
// Act
var response = await client.SendAsync(request);
// Assert
var body = await response.Content.ReadAsStringAsync();
Assert.Equal(errorMessage, body);
}
}
}

View File

@ -28,6 +28,7 @@
"FormatFilterWebSite": "1.0.0-*",
"FormatterWebSite": "1.0.0",
"InlineConstraintsWebSite": "1.0.0",
"JsonPatchWebSite": "1.0.0",
"LoggingWebSite": "1.0.0",
"LowercaseUrlsWebSite": "1.0.0-*",
"Microsoft.AspNet.Mvc": "6.0.0-*",

View File

@ -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 Microsoft.AspNet.JsonPatch;
using Microsoft.AspNet.JsonPatch.Operations;
using Microsoft.AspNet.Mvc.ModelBinding;
using Xunit;
namespace Microsoft.AspNet.Mvc
{
public class JsonPatchExtensionsTest
{
[Fact]
public void ApplyTo_JsonPatchDocument_ModelState()
{
// Arrange
var operation = new Operation<Customer>("add", "Customer/CustomerId", from: null, value: "TestName");
var patchDoc = new JsonPatchDocument<Customer>();
patchDoc.Operations.Add(operation);
var modelState = new ModelStateDictionary();
// Act
patchDoc.ApplyTo(new Customer(), modelState);
// Assert
var error = Assert.Single(modelState["Customer"].Errors);
Assert.Equal("Property does not exist at path 'Customer/CustomerId'.", error.ErrorMessage);
}
[Fact]
public void ApplyTo_JsonPatchDocument_PrefixModelState()
{
// Arrange
var operation = new Operation<Customer>("add", "Customer/CustomerId", from: null, value: "TestName");
var patchDoc = new JsonPatchDocument<Customer>();
patchDoc.Operations.Add(operation);
var modelState = new ModelStateDictionary();
// Act
patchDoc.ApplyTo(new Customer(), modelState, "jsonpatch");
// Assert
var error = Assert.Single(modelState["jsonpatch.Customer"].Errors);
Assert.Equal("Property does not exist at path 'Customer/CustomerId'.", error.ErrorMessage);
}
public class Customer
{
public string CustomerName { get; set; }
}
}
}

View File

@ -0,0 +1,65 @@
// 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 JsonPatchWebSite.Models;
using Microsoft.AspNet.JsonPatch;
using Microsoft.AspNet.Mvc;
namespace JsonPatchWebSite.Controllers
{
[Route("jsonpatch/[action]")]
public class JsonPatchController : Controller
{
[HttpPatch]
public IActionResult JsonPatchWithModelState([FromBody] JsonPatchDocument<Customer> patchDoc)
{
var customer = CreateCustomer();
patchDoc.ApplyTo(customer, ModelState);
if (!ModelState.IsValid)
{
return HttpBadRequest(ModelState);
}
return new ObjectResult(customer);
}
[HttpPatch]
public IActionResult JsonPatchWithModelStateAndPrefix(
[FromBody] JsonPatchDocument<Customer> patchDoc,
string prefix)
{
var customer = CreateCustomer();
patchDoc.ApplyTo(customer, ModelState, prefix);
if (!ModelState.IsValid)
{
return HttpBadRequest(ModelState);
}
return new ObjectResult(customer);
}
private Customer CreateCustomer()
{
return new Customer
{
CustomerName = "John",
Orders = new List<Order>()
{
new Order
{
OrderName = "Order1"
},
new Order
{
OrderName = "Order2"
}
}
};
}
}
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>dab1252d-577c-4912-98be-1a812bf83f86</ProjectGuid>
<RootNamespace>JsonPatchWebSite</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
<DevelopmentServerPort>29149</DevelopmentServerPort>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,14 @@
// 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 JsonPatchWebSite.Models
{
public class Customer
{
public string CustomerName { get; set; }
public List<Order> Orders { get; set; }
}
}

View File

@ -0,0 +1,10 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace JsonPatchWebSite.Models
{
public class Order
{
public string OrderName { get; set; }
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Builder;
using Microsoft.Framework.DependencyInjection;
namespace JsonPatchWebSite
{
public class Startup
{
// Set up application services
public void ConfigureServices(IServiceCollection services)
{
// Add MVC services to the services container
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseCultureReplacer();
// Add MVC to the request pipeline
app.UseMvc();
}
}
}

View File

@ -0,0 +1,19 @@
{
"commands": {
"web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001",
"kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5000"
},
"dependencies": {
"Kestrel": "1.0.0-*",
"Microsoft.AspNet.Mvc": "6.0.0-*",
"Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0",
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
"Microsoft.AspNet.StaticFiles": "1.0.0-*"
},
"frameworks": {
"dnx451": { },
"dnxcore50": { }
},
"webroot": "wwwroot"
}

View File

@ -0,0 +1,4 @@
JsonPatchWebSite
===
This web site illustrates how to use JSON Patch operation on an object.

View File

@ -0,0 +1 @@
HelloWorld