Skip to content

Latest commit

 

History

History
108 lines (74 loc) · 4.9 KB

Http-Patch.md

File metadata and controls

108 lines (74 loc) · 4.9 KB

HTTP PATCH

In addition to HTTP GET/POST/PUT/DELETE there is support for HTTP PATCH. This allows an update to an entity value where only the properties that have changed are passed to the API. This has the added benefit of being less brittle with respect to versioning as not all properties are required as per a POST/PUT.


Approaches

There are two approaches that are supported:


JSON Merge Patch

This is likely to be the most common approach that will be leveraged. Which is simply the passing of properties that are to be updated within the JSON payload. To nullify a property then it must be explicitly set to null. To support, the JsonEntityMerge is provided to enable.

{
  "title": "Hello!",
  "phoneNumber": "+01-123-456-7890",
  "author": {
    "familyName": null
  },
  "tags": [ "example" ]
}

Arrays

However, the gotcha is with arrays, in that all items must be passed as this is a replacement operation only. Within the specification there is no means to reference a specific item for change; however, a JSON Patch enables through array indexing.

Beef has extended support to enable an indexing of sorts through the use of the IUniqueKey which all generated entities can support. By enabling one or more properties as forming the UniqueKey this is used to match within the existing collection. All items still need to be passed so that the add, change and delete can be determined; however, then only the properties to change for each item need to be provided.


JSON Patch

This is unlikely to be used but is supported if needed.

[
  { "op": "test", "path": "/a/b/c", "value": "foo" },
  { "op": "remove", "path": "/a/b/c" },
  { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
  { "op": "replace", "path": "/a/b/c", "value": 42 },
  { "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
  { "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
]

Code-generation

The code generation supports PATCH operations.

This is a special case operation in that only XxxAgent, XxxServiceAgent and XxxController classes are ever generated. Under the covers the XxxController uses the corresponding Get operation to validate existence, and validate etag for concurrency. The JSON patching then occurs on this entity value. Assuming changes are made, and are valid, the corresponding Update operation is used to perform the actual update; as such, all existing validation and updating logic is reused.

The basics for the Patch operation is as follows. Note that it is similar to the Update operation in that the UniqueKey and WebApiRoute are the key attributes.

<Operation Name="Update" OperationType="Update" UniqueKey="true" Validator="UserValidator" WebApiRoute="{id}" AutoImplement="None" />
<Operation Name="Patch" OperationType="Patch" UniqueKey="true" WebApiRoute="{id}" />

Collection uniqueness

To enable a collection to be updated using a key the UniqueKey attribute needs to be defined for one or more of the properties within an entity. Once this has been done, and re-generated, then support for key merges will be operational.

For example a ContractAddress entity could have a Type property to uniquely identify; therefore, it should set UniqueKey="true" as follows:

<Entity Name="ContactAddress" Text="Contact Addresss" Collection="true" ExcludeAll="true" ExcludeData="false" AutoImplement="Database">
  <Property Name="Type" Type="RefDataNamespace.AddressType" UniqueKey="true" RefDataType="string" DataName="AddressTypeId" />
  <Property Name="Street1" Type="string" />

Service agent support

The generated service agent for Patch has a different method signature to enable to simplify its execution, as follows:

/// <summary>
/// Patches the <see cref="Person"/> object.
/// </summary>
/// <param name="patchOption">The <see cref="WebApiPatchOption"/>.</param>
/// <param name="value">The JSON patch value.</param>
/// <param name="id">The <see cref="Person"/> identifier.</param>
/// <param name="requestOptions">The optional <see cref="WebApiRequestOptions"/>.</param>
/// <returns>A <see cref="WebApiAgentResult"/>.</returns>
public Task<WebApiAgentResult<Person>> PatchAsync(WebApiPatchOption patchOption, JToken value, Guid id, WebApiRequestOptions requestOptions = null)
{
    return PersonServiceAgent.PatchAsync(patchOption, value, id, requestOptions);
}

The sample Demo demonstrates the service agent being within the PersonTest - see method H130_Patch_MergePatch.