I am using a Blazor WebAssembly (WASM) client to perform an update via an .NET Core REST API. To do this I am sending a JsonPatchDocument<T> via an HTTP PATCH request, where T is one of my application's data transfer objects (DTOs).
It is not working. I get back a 500 internal server error status code in my Blazor application. I get a little bit more detail in Postman, but not enough for me to understand the problem.
Here is the calling code in my Blazor WASM application:
@code
{
[Parameter]
public int BookId { get; set; } = 101;
private async Task HandleClickAsync()
{
string newTitle = "How to make JsonPatchDocument work with Blazor - Second Edition";
var patchDocument = new JsonPatchDocument<Book>()
.Replace(c => c.Title, newTitle);
var json = JsonSerializer.Serialize(patchDocument);
var content = new StringContent(json, Encoding.UTF8, "application/json-patch+json");
var response = await HttpClient.PatchAsync($"https://localhost:44367/api/books/{BookId}", content);
if (response.IsSuccessStatusCode)
{
// Handle success
}
else if (response.StatusCode == HttpStatusCode.NotFound)
{
// Handle not found
}
else
{
// Handle unexpected failures
}
}
}
And here is my controller method:
[ApiController]
[Route("api/[controller]")]
public class BooksController : ControllerBase
{
[HttpPatch("{id:int}")]
public async Task<ActionResult> PatchAsync(
int id,
[FromBody] JsonPatchDocument<Book> patch)
{
// We're just going to fake an asynchronous database call and return a 200 status code to the client
await Task.FromResult(true);
return Ok();
}
}
Here is my DTO:
public class Book
{
public int Id { get; set; }
public string Title { get; set; }
}
The patch document I'm sending, when serialized to JSON, looks like this:
{"Operations":[{"value":"How to make JsonPatchDocument work with Blazor - Second Edition","OperationType":2,"path":"/Title","op":"replace","from":null}],"ContractResolver":{}}
The error detail that I'm seeing in Postman is:
System.NotSupportedException: Deserialization of interface types is not supported. Type 'Newtonsoft.Json.Serialization.IContractResolver'
at System.Text.Json.ThrowHelper.ThrowNotSupportedException_DeserializeCreateObjectDelegateIsNull(Type invalidType)
at System.Text.Json.JsonSerializer.HandleStartObject(JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.JsonSerializer.ReadCore(JsonSerializerOptions options, Utf8JsonReader& reader, ReadStack& readStack)
at System.Text.Json.JsonSerializer.ReadCore(JsonReaderState& readerState, Boolean isFinalBlock, ReadOnlySpan`1 buffer, JsonSerializerOptions options, ReadStack& readStack)
at System.Text.Json.JsonSerializer.ReadAsync[TValue](Stream utf8Json, Type returnType, JsonSerializerOptions options, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonInputFormatter.ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonInputFormatter.ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
at Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinder.BindModelAsync(ModelBindingContext bindingContext)
at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(ActionContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, Object value)
at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<<CreateBinderDelegate>g__Bind|0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Builder.Extensions.MapMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
HEADERS
=======
Accept: */*
Accept-Encoding: gzip, deflate, br
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 175
Content-Type: application/json
Host: localhost:44367
User-Agent: PostmanRuntime/7.26.3
Postman-Token: b4444f41-b80f-4ef5-92d5-2416d68d471e
None of my projects depend directly Newtonsoft. I don't know if the Microsoft libraries that I reference in turn depend on Newtonsoft though. The error suggests maybe they do.
The behaviour can be observed in this little repository on GitHub: https://github.com/BenjaminCharlton/JsonPatchDocumentWithBlazor
Does anybody know why it won't work and/or what will fix it, please?
Thank you
JsonPatchDocumenthas aNewtonsoft.Jsondependency. Your error says aboutSystem.Text.Json, try to switch toNewtonsoft.Jsoninstead