JsonPatch в веб-API ASP.NET Core

В этой статье описывается обработка запросов JSON Patch в веб-API ASP.NET Core.

Установка пакета

Поддержка исправлений JSON в веб-API ASP.NET Core основана Newtonsoft.Json на пакете Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet. Чтобы включить поддержку исправлений JSON:

  • Установите пакет NuGet Microsoft.AspNetCore.Mvc.NewtonsoftJson.

  • Вызовите процедуру AddNewtonsoftJson. Например:

    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddControllers()
        .AddNewtonsoftJson();
    
    var app = builder.Build();
    
    app.UseHttpsRedirection();
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    

AddNewtonsoftJson заменяет входные и выходные средства форматирования на основе по умолчанию System.Text.Json, используемые для форматирования всего содержимого JSON. Этот метод расширения совместим со следующими методами регистрации службы MVC:

JsonPatch требует установки заголовка Content-Type application/json-patch+jsonв значение .

Добавление поддержки исправления JSON при использовании System.Text.Json

Модуль форматирования входных данных на основе не поддерживает исправление System.Text.JsonJSON. Чтобы добавить поддержку исправления JSON с помощью Newtonsoft.Json, оставляя другие входные и выходные форматировщики без изменений:

  • Установите пакет NuGet Microsoft.AspNetCore.Mvc.NewtonsoftJson.

  • Обновление Program.cs:

    using JsonPatchSample;
    using Microsoft.AspNetCore.Mvc.Formatters;
    
    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddControllers(options =>
    {
        options.InputFormatters.Insert(0, MyJPIF.GetJsonPatchInputFormatter());
    });
    
    var app = builder.Build();
    
    app.UseHttpsRedirection();
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Formatters;
    using Microsoft.Extensions.Options;
    
    namespace JsonPatchSample;
    
    public static class MyJPIF
    {
        public static NewtonsoftJsonPatchInputFormatter GetJsonPatchInputFormatter()
        {
            var builder = new ServiceCollection()
                .AddLogging()
                .AddMvc()
                .AddNewtonsoftJson()
                .Services.BuildServiceProvider();
    
            return builder
                .GetRequiredService<IOptions<MvcOptions>>()
                .Value
                .InputFormatters
                .OfType<NewtonsoftJsonPatchInputFormatter>()
                .First();
        }
    }
    

Предыдущий код создает экземпляр NewtonsoftJsonPatchInputFormatter и вставляет его в качестве первой записи в коллекцию MvcOptions.InputFormatters . Этот порядок регистрации гарантирует, что:

  • NewtonsoftJsonPatchInputFormatter обрабатывает запросы на исправление JSON.
  • Существующие System.Text.Jsonвходные и форматирующие модули обрабатывают все остальные запросы и ответы JSON.

Используйте Newtonsoft.Json.JsonConvert.SerializeObject метод для сериализации JsonPatchDocument.

Метод HTTP-запроса PATCH

Методы PUT и PATCH используются для обновления существующего ресурса. Различие между ними заключается в том, что PUT заменяет весь ресурс, а PATCH указывает только изменения.

JSON PATCH

Формат JSON Patch используется для указания обновлений, применяемых к ресурсу. Документ JSON Patch содержит массив операций. Каждая операция определяет определенный тип изменения. Примеры таких изменений включают добавление элемента массива или замена значения свойства.

Например, следующие документы JSON представляют ресурс, документ исправления JSON для ресурса и результат применения операций исправления.

Пример ресурса

{
  "customerName": "John",
  "orders": [
    {
      "orderName": "Order0",
      "orderType": null
    },
    {
      "orderName": "Order1",
      "orderType": null
    }
  ]
}

Пример JSON Patch

[
  {
    "op": "add",
    "path": "/customerName",
    "value": "Barry"
  },
  {
    "op": "add",
    "path": "/orders/-",
    "value": {
      "orderName": "Order2",
      "orderType": null
    }
  }
]

В приведенном выше документе JSON:

  • свойство op указывает тип операции;
  • свойство path указывает обновляемый элемент;
  • свойство value предоставляет новое значение.

Ресурс после обновления

Ниже показан ресурс в том состоянии, которое он принимает после применения приведенного выше документа JSON Patch.

{
  "customerName": "Barry",
  "orders": [
    {
      "orderName": "Order0",
      "orderType": null
    },
    {
      "orderName": "Order1",
      "orderType": null
    },
    {
      "orderName": "Order2",
      "orderType": null
    }
  ]
}

Изменения, внесенные при применении документа исправления JSON к ресурсу, являются атомарными. Если любая операция в списке завершается ошибкой, операция в списке не применяется.

Синтаксис Path

Свойство Path в объекте операции содержит косые черты между уровнями. Например, "/address/zipCode".

Для указания элементов массива используются числовые индексы, начиная с нуля. Первый элемент массива addresses будет обозначаться как /addresses/0. В add конце массива используйте дефис (-) вместо номера индекса: /addresses/-

Операции

В следующей таблице перечислены поддерживаемые операции, которые определены в спецификации JSON Patch.

Операция Примечания.
add Добавляет свойство или элемент массива. Для существующего свойства устанавливает значение.
remove Удаляет свойство или элемент массива.
replace Действует так же, как remove с последующим add в том же расположении.
move Действует так же, как remove из источника с последующим add, в котором указаны место назначения и значение из источника.
copy Действует так же, как add, в котором указаны место назначения и значение из источника.
test Возвращает успешный код состояния, если значение path совпадает с предоставленным value.

Исправление JSON в ASP.NET Core

Реализация ASP.NET Core для JSON Patch предоставляется в пакете NuGet Microsoft.AspNetCore.JsonPatch.

Код метода действия

В контроллере API есть метод действия для JSON Patch, который:

  • помечен атрибутом HttpPatch;
  • принимает JsonPatchDocument<TModel> обычно с указанием [FromBody];
  • вызывает ApplyTo(Object) для целевого документа, чтобы применить изменения.

Приведем пример:

[HttpPatch]
public IActionResult JsonPatchWithModelState(
    [FromBody] JsonPatchDocument<Customer> patchDoc)
{
    if (patchDoc != null)
    {
        var customer = CreateCustomer();

        patchDoc.ApplyTo(customer, ModelState);

        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        return new ObjectResult(customer);
    }
    else
    {
        return BadRequest(ModelState);
    }
}

Этот код из примера приложения работает со следующей Customer моделью:

namespace JsonPatchSample.Models;

public class Customer
{
    public string? CustomerName { get; set; }
    public List<Order>? Orders { get; set; }
}
namespace JsonPatchSample.Models;

public class Order
{
    public string OrderName { get; set; }
    public string OrderType { get; set; }
}

Этот пример метода действия:

  • Создает документ Customer.
  • применяет изменения;
  • возвращает результат в тексте ответа.

В реальном приложении код будет извлекать данные из хранилища, например из базы данных, и обновлять эту базу данных после применения исправлений.

Состояние модели

Указанный выше пример метода действия вызывает перегрузку ApplyTo, которая принимает состояние модели в качестве одного из параметров. В этом случае вы получаете в ответах сообщения об ошибках. В следующем примере показан текст ответа с кодом 400 "Неверный запрос" для операции test:

{
  "Customer": [
    "The current value 'John' at path 'customerName' != test value 'Nancy'."
  ]
}

Динамические объекты

В следующем примере метода действия показано, как применить исправление к динамическому объекту:

[HttpPatch]
public IActionResult JsonPatchForDynamic([FromBody]JsonPatchDocument patch)
{
    dynamic obj = new ExpandoObject();
    patch.ApplyTo(obj);

    return Ok(obj);
}

Операция добавления

  • Если path указывает на элемент массива, новый элемент вставляется перед тем, который указан в параметре path.
  • Если path указывает на свойство, задается значение свойства.
  • Если path указывает на несуществующее расположение:
    • Если обновляемый ресурс является динамическим объектом, добавляется свойство.
    • Если обновляемый ресурс является статическим объектом, запрос завершается ошибкой.

Следующий пример документа с исправлениями устанавливает значение CustomerName и добавляет объект Order в конец массива Orders.

[
  {
    "op": "add",
    "path": "/customerName",
    "value": "Barry"
  },
  {
    "op": "add",
    "path": "/orders/-",
    "value": {
      "orderName": "Order2",
      "orderType": null
    }
  }
]

Операция удаления

  • Если path указывает на элемент массива, элемент удаляется.
  • Если path указывает на свойство:
    • Если обновляемый ресурс является динамическим объектом, свойство удаляется.
    • Если ресурс для исправления является статическим объектом:
      • Если свойство может принимать значения NULL, ему присваивается значение NULL.
      • Если свойство не может принимать значения NULL, ему присваивается значение default<T>.

В следующем примере наборов документов исправлений CustomerName задано значение NULL и удаляется Orders[0]:

[
  {
    "op": "remove",
    "path": "/customerName"
  },
  {
    "op": "remove",
    "path": "/orders/0"
  }
]

Операция замены

Эта операция функционально идентична операции remove с последующей add.

Следующий пример документа исправления задает значение CustomerName и заменяет Orders[0]новый Order объект:

[
  {
    "op": "replace",
    "path": "/customerName",
    "value": "Barry"
  },
  {
    "op": "replace",
    "path": "/orders/0",
    "value": {
      "orderName": "Order2",
      "orderType": null
    }
  }
]

Операция перемещения

  • Если path указывает на элемент массива, элемент from копируется в расположение элемента path, а затем выполняется операция remove для элемента from.
  • Если path указывает на свойство, значение свойства from копируется в свойство path, а затем выполняется операция remove для свойства from.
  • Если path указывает на несуществующее свойство:
    • Если обновляемый ресурс является статическим объектом, запрос завершается ошибкой.
    • Если исправляемый ресурс является динамическим объектом, значение from копируется в местоположение, указанное параметром path, а затем выполняется операция remove для свойства from.

Следующий пример документа исправления выполняет следующие действия:

  • копирует значение Orders[0].OrderName в CustomerName;
  • присваивает Orders[0].OrderName значение NULL;
  • перемещает Orders[1] в расположение перед Orders[0].
[
  {
    "op": "move",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "move",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

Операция копирования

Эта операция функционально аналогична операции move без последнего шага remove.

Следующий пример документа исправления выполняет следующие действия:

  • копирует значение Orders[0].OrderName в CustomerName;
  • вставляет копию Orders[1] перед Orders[0].
[
  {
    "op": "copy",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "copy",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

Операция тестирования

Если значение в расположении, которое задано параметром path, отличается от предоставленного в value значения, запрос завершится ошибкой. В этом случае сбой распространяется на весь запрос PATCH, даже если все остальные операции в документе с исправлениями могли быть выполнены успешно.

Операция test традиционно используется для предотвращения обновлений при возможных конфликтах параллелизма.

Следующий пример документа с исправлениями не изменяет прежнее значение CustomerName (John), так как тест завершается ошибкой:

[
  {
    "op": "test",
    "path": "/customerName",
    "value": "Nancy"
  },
  {
    "op": "add",
    "path": "/customerName",
    "value": "Barry"
  }
]

Получение кода

Просмотреть или скачать образец кода. (Инструкция по скачиванию.)

Чтобы проверить этот пример, запустите приложение и отправьте HTTP-запросы со следующими параметрами:

  • URL-адрес: http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate
  • Метод HTTP: PATCH
  • заголовок: Content-Type: application/json-patch+json;
  • Текст: скопируйте и вставьте один из примеров документов исправлений JSON из папки проекта JSON .

Дополнительные ресурсы

В этой статье описывается обработка запросов JSON Patch в веб-API ASP.NET Core.

Установка пакета

Чтобы включить поддержку исправлений JSON в приложении, выполните следующие действия.

  1. Установите пакет NuGet Microsoft.AspNetCore.Mvc.NewtonsoftJson.

  2. Обновите метод проекта Startup.ConfigureServices для вызова AddNewtonsoftJson. Например:

    services
        .AddControllersWithViews()
        .AddNewtonsoftJson();
    

AddNewtonsoftJson совместим с методами регистрации службы MVC:

Исправление JSON, AddNewtonsoftJson и System.Text.Json

AddNewtonsoftJsonSystem.Text.Jsonзаменяет средства форматирования входных и выходных данных на основе, используемые для форматирования всего содержимого JSON. Чтобы добавить поддержку исправления JSON с помощью Newtonsoft.Json, оставляя другие форматировщики без изменений, обновите метод проекта Startup.ConfigureServices следующим образом:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
    {
        options.InputFormatters.Insert(0, GetJsonPatchInputFormatter());
    });
}

private static NewtonsoftJsonPatchInputFormatter GetJsonPatchInputFormatter()
{
    var builder = new ServiceCollection()
        .AddLogging()
        .AddMvc()
        .AddNewtonsoftJson()
        .Services.BuildServiceProvider();

    return builder
        .GetRequiredService<IOptions<MvcOptions>>()
        .Value
        .InputFormatters
        .OfType<NewtonsoftJsonPatchInputFormatter>()
        .First();
}

Для предыдущего кода требуется Microsoft.AspNetCore.Mvc.NewtonsoftJson пакет и следующие using инструкции:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using System.Linq;

Используйте Newtonsoft.Json.JsonConvert.SerializeObject метод для сериализации JsonPatchDocument.

Метод HTTP-запроса PATCH

Методы PUT и PATCH используются для обновления существующего ресурса. Различие между ними заключается в том, что PUT заменяет весь ресурс, а PATCH указывает только изменения.

JSON PATCH

Формат JSON Patch используется для указания обновлений, применяемых к ресурсу. Документ JSON Patch содержит массив операций. Каждая операция определяет определенный тип изменения. Примеры таких изменений включают добавление элемента массива или замена значения свойства.

Например, следующие документы JSON представляют ресурс, документ исправления JSON для ресурса и результат применения операций исправления.

Пример ресурса

{
  "customerName": "John",
  "orders": [
    {
      "orderName": "Order0",
      "orderType": null
    },
    {
      "orderName": "Order1",
      "orderType": null
    }
  ]
}

Пример JSON Patch

[
  {
    "op": "add",
    "path": "/customerName",
    "value": "Barry"
  },
  {
    "op": "add",
    "path": "/orders/-",
    "value": {
      "orderName": "Order2",
      "orderType": null
    }
  }
]

В приведенном выше документе JSON:

  • свойство op указывает тип операции;
  • свойство path указывает обновляемый элемент;
  • свойство value предоставляет новое значение.

Ресурс после обновления

Ниже показан ресурс в том состоянии, которое он принимает после применения приведенного выше документа JSON Patch.

{
  "customerName": "Barry",
  "orders": [
    {
      "orderName": "Order0",
      "orderType": null
    },
    {
      "orderName": "Order1",
      "orderType": null
    },
    {
      "orderName": "Order2",
      "orderType": null
    }
  ]
}

Изменения, внесенные при применении документа исправления JSON к ресурсу, являются атомарными. Если любая операция в списке завершается ошибкой, операция в списке не применяется.

Синтаксис Path

Свойство Path в объекте операции содержит косые черты между уровнями. Например, "/address/zipCode".

Для указания элементов массива используются числовые индексы, начиная с нуля. Первый элемент массива addresses будет обозначаться как /addresses/0. В add конце массива используйте дефис (-) вместо номера индекса: /addresses/-

Операции

В следующей таблице перечислены поддерживаемые операции, которые определены в спецификации JSON Patch.

Операция Примечания.
add Добавляет свойство или элемент массива. Для существующего свойства устанавливает значение.
remove Удаляет свойство или элемент массива.
replace Действует так же, как remove с последующим add в том же расположении.
move Действует так же, как remove из источника с последующим add, в котором указаны место назначения и значение из источника.
copy Действует так же, как add, в котором указаны место назначения и значение из источника.
test Возвращает успешный код состояния, если значение path совпадает с предоставленным value.

Исправление JSON в ASP.NET Core

Реализация ASP.NET Core для JSON Patch предоставляется в пакете NuGet Microsoft.AspNetCore.JsonPatch.

Код метода действия

В контроллере API есть метод действия для JSON Patch, который:

  • помечен атрибутом HttpPatch;
  • принимает JsonPatchDocument<T> обычно с указанием [FromBody];
  • вызывает ApplyTo для целевого документа, чтобы применить изменения.

Приведем пример:

[HttpPatch]
public IActionResult JsonPatchWithModelState(
    [FromBody] JsonPatchDocument<Customer> patchDoc)
{
    if (patchDoc != null)
    {
        var customer = CreateCustomer();

        patchDoc.ApplyTo(customer, ModelState);

        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        return new ObjectResult(customer);
    }
    else
    {
        return BadRequest(ModelState);
    }
}

Этот код из примера приложения работает со следующей Customer моделью:

using System.Collections.Generic;

namespace JsonPatchSample.Models
{
    public class Customer
    {
        public string CustomerName { get; set; }
        public List<Order> Orders { get; set; }
    }
}
namespace JsonPatchSample.Models
{
    public class Order
    {
        public string OrderName { get; set; }
        public string OrderType { get; set; }
    }
}

Этот пример метода действия:

  • Создает документ Customer.
  • применяет изменения;
  • возвращает результат в тексте ответа.

В реальном приложении код будет извлекать данные из хранилища, например из базы данных, и обновлять эту базу данных после применения исправлений.

Состояние модели

Указанный выше пример метода действия вызывает перегрузку ApplyTo, которая принимает состояние модели в качестве одного из параметров. В этом случае вы получаете в ответах сообщения об ошибках. В следующем примере показан текст ответа с кодом 400 "Неверный запрос" для операции test:

{
    "Customer": [
        "The current value 'John' at path 'customerName' is not equal to the test value 'Nancy'."
    ]
}

Динамические объекты

В следующем примере метода действия показано, как применить исправление к динамическому объекту:

[HttpPatch]
public IActionResult JsonPatchForDynamic([FromBody]JsonPatchDocument patch)
{
    dynamic obj = new ExpandoObject();
    patch.ApplyTo(obj);

    return Ok(obj);
}

Операция добавления

  • Если path указывает на элемент массива, новый элемент вставляется перед тем, который указан в параметре path.
  • Если path указывает на свойство, задается значение свойства.
  • Если path указывает на несуществующее расположение:
    • Если обновляемый ресурс является динамическим объектом, добавляется свойство.
    • Если обновляемый ресурс является статическим объектом, запрос завершается ошибкой.

Следующий пример документа с исправлениями устанавливает значение CustomerName и добавляет объект Order в конец массива Orders.

[
  {
    "op": "add",
    "path": "/customerName",
    "value": "Barry"
  },
  {
    "op": "add",
    "path": "/orders/-",
    "value": {
      "orderName": "Order2",
      "orderType": null
    }
  }
]

Операция удаления

  • Если path указывает на элемент массива, элемент удаляется.
  • Если path указывает на свойство:
    • Если обновляемый ресурс является динамическим объектом, свойство удаляется.
    • Если ресурс для исправления является статическим объектом:
      • Если свойство может принимать значения NULL, ему присваивается значение NULL.
      • Если свойство не может принимать значения NULL, ему присваивается значение default<T>.

В следующем примере наборов документов исправлений CustomerName задано значение NULL и удаляется Orders[0]:

[
  {
    "op": "remove",
    "path": "/customerName"
  },
  {
    "op": "remove",
    "path": "/orders/0"
  }
]

Операция замены

Эта операция функционально идентична операции remove с последующей add.

Следующий пример документа исправления задает значение CustomerName и заменяет Orders[0]новый Order объект:

[
  {
    "op": "replace",
    "path": "/customerName",
    "value": "Barry"
  },
  {
    "op": "replace",
    "path": "/orders/0",
    "value": {
      "orderName": "Order2",
      "orderType": null
    }
  }
]

Операция перемещения

  • Если path указывает на элемент массива, элемент from копируется в расположение элемента path, а затем выполняется операция remove для элемента from.
  • Если path указывает на свойство, значение свойства from копируется в свойство path, а затем выполняется операция remove для свойства from.
  • Если path указывает на несуществующее свойство:
    • Если обновляемый ресурс является статическим объектом, запрос завершается ошибкой.
    • Если исправляемый ресурс является динамическим объектом, значение from копируется в местоположение, указанное параметром path, а затем выполняется операция remove для свойства from.

Следующий пример документа исправления выполняет следующие действия:

  • копирует значение Orders[0].OrderName в CustomerName;
  • присваивает Orders[0].OrderName значение NULL;
  • перемещает Orders[1] в расположение перед Orders[0].
[
  {
    "op": "move",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "move",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

Операция копирования

Эта операция функционально аналогична операции move без последнего шага remove.

Следующий пример документа исправления выполняет следующие действия:

  • копирует значение Orders[0].OrderName в CustomerName;
  • вставляет копию Orders[1] перед Orders[0].
[
  {
    "op": "copy",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "copy",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

Операция тестирования

Если значение в расположении, которое задано параметром path, отличается от предоставленного в value значения, запрос завершится ошибкой. В этом случае сбой распространяется на весь запрос PATCH, даже если все остальные операции в документе с исправлениями могли быть выполнены успешно.

Операция test традиционно используется для предотвращения обновлений при возможных конфликтах параллелизма.

Следующий пример документа с исправлениями не изменяет прежнее значение CustomerName (John), так как тест завершается ошибкой:

[
  {
    "op": "test",
    "path": "/customerName",
    "value": "Nancy"
  },
  {
    "op": "add",
    "path": "/customerName",
    "value": "Barry"
  }
]

Получение кода

Просмотреть или скачать образец кода. (Инструкция по скачиванию.)

Чтобы проверить этот пример, запустите приложение и отправьте HTTP-запросы со следующими параметрами:

  • URL-адрес: http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate
  • Метод HTTP: PATCH
  • заголовок: Content-Type: application/json-patch+json;
  • Текст: скопируйте и вставьте один из примеров документов исправлений JSON из папки проекта JSON .

Дополнительные ресурсы