将 JsonPatchDocument 与 Blazor 一起使用时出现意外错误
Unexpected error using JsonPatchDocument with Blazor
我正在使用 Blazor WebAssembly (WASM) 客户端通过 .NET Core REST 执行更新 API。为此,我通过 HTTP PATCH 请求发送 JsonPatchDocument<T>
,其中 T
是我的应用程序的数据传输对象 (DTO) 之一。
它不起作用。我在 Blazor 应用程序中返回了 500 内部服务器错误状态代码。我在 Postman 中获得了更多细节,但不足以让我理解问题。
这是我的 Blazor WASM 应用程序中的调用代码:
@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
}
}
}
这是我的控制器方法:
[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();
}
}
这是我的 DTO:
public class Book
{
public int Id { get; set; }
public string Title { get; set; }
}
我发送的补丁文档,当序列化为 JSON 时,看起来像这样:
{"Operations":[{"value":"How to make JsonPatchDocument work with Blazor - Second Edition","OperationType":2,"path":"/Title","op":"replace","from":null}],"ContractResolver":{}}
我在 Postman 中看到的错误详细信息是:
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 我的项目直接依赖于 Newtonsoft。我不知道我引用的 Microsoft 库是否依赖于 Newtonsoft。该错误表明他们可能会这样做。
可以在 GitHub 上的这个小存储库中观察到该行为:
https://github.com/BenjaminCharlton/JsonPatchDocumentWithBlazor
有人知道为什么它不起作用吗and/or请问有什么办法可以解决这个问题?
谢谢
我设法解决了这个困难,Pavel 和 Enet 的输入很有用,谢谢。
对于遇到相同问题的任何其他人,以下是您需要了解的解决问题的信息:
截至目前(2020 年底),将 .NET Core 从 Newtonsoft.Json
迁移到 System.Text.Json
的努力尚未完成。包 Microsoft.AspNetCore.JsonPatch
仍然依赖于 Newtonsoft.Json
.
.NET Core 开发团队知道 GitHub 有很多问题报告此问题。但他们都被关闭了,没有采取任何行动。显然,将 Microsoft.AspNetCore.JsonPatch
切换到 System.Text.Json
需要付出太多努力。
将 Newtonsoft 用于 JsonPatch
es 但不用于任何其他 there is a nice little hack described here 您应该在 Startup
class 的 Web API/server 项目。特别注意 Startup.ConfigureServices
内部调用的 GetJsonPatchInputFormatter
辅助方法的使用
但这本身可能无法解决您的 Blazor WASM/client 项目将收到的 50X 和 40X HTTP 错误,因为如果您序列化您的 JsonPatch
使用 System.Text.Json
它将一个空的 ContractResolver
对象添加到 JSON 字符串的末尾(看起来像 ,"ContractResolver":{}
),这会在服务器端中断。由于某种原因,该请求将与您创建的任何控制器路由不匹配。
要解决此问题,您还必须在 Blazor 客户端上使用 Newtonsoft.Json
。您不必将它用于所有事情;您只需要使用它来序列化所有 JsonPatch
es。 Newtonsoft.Json
比 System.Text.Json
多了几行代码,但我做了一个扩展方法,所以它不会在所有地方重复。扩展方法如下所示:
public static class HttpClientExtensions
{
public static async Task<HttpResponseMessage> PatchAsync<T>(this HttpClient client,
string requestUri,
JsonPatchDocument<T> patchDocument)
where T : class
{
var writer = new StringWriter();
var serializer = new JsonSerializer();
serializer.Serialize(writer, patchDocument);
var json = writer.ToString();
var content = new StringContent(json, Encoding.UTF8, "application/json-patch+json");
return await client.PatchAsync(requestUri, content);
}
}
就是这样。这个解决方法对我有用,我希望对你也有用。
我正在使用 Blazor WebAssembly (WASM) 客户端通过 .NET Core REST 执行更新 API。为此,我通过 HTTP PATCH 请求发送 JsonPatchDocument<T>
,其中 T
是我的应用程序的数据传输对象 (DTO) 之一。
它不起作用。我在 Blazor 应用程序中返回了 500 内部服务器错误状态代码。我在 Postman 中获得了更多细节,但不足以让我理解问题。
这是我的 Blazor WASM 应用程序中的调用代码:
@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
}
}
}
这是我的控制器方法:
[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();
}
}
这是我的 DTO:
public class Book
{
public int Id { get; set; }
public string Title { get; set; }
}
我发送的补丁文档,当序列化为 JSON 时,看起来像这样:
{"Operations":[{"value":"How to make JsonPatchDocument work with Blazor - Second Edition","OperationType":2,"path":"/Title","op":"replace","from":null}],"ContractResolver":{}}
我在 Postman 中看到的错误详细信息是:
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 我的项目直接依赖于 Newtonsoft。我不知道我引用的 Microsoft 库是否依赖于 Newtonsoft。该错误表明他们可能会这样做。
可以在 GitHub 上的这个小存储库中观察到该行为: https://github.com/BenjaminCharlton/JsonPatchDocumentWithBlazor
有人知道为什么它不起作用吗and/or请问有什么办法可以解决这个问题?
谢谢
我设法解决了这个困难,Pavel 和 Enet 的输入很有用,谢谢。
对于遇到相同问题的任何其他人,以下是您需要了解的解决问题的信息:
截至目前(2020 年底),将 .NET Core 从
Newtonsoft.Json
迁移到System.Text.Json
的努力尚未完成。包Microsoft.AspNetCore.JsonPatch
仍然依赖于Newtonsoft.Json
..NET Core 开发团队知道 GitHub 有很多问题报告此问题。但他们都被关闭了,没有采取任何行动。显然,将
Microsoft.AspNetCore.JsonPatch
切换到System.Text.Json
需要付出太多努力。将 Newtonsoft 用于
内部调用的JsonPatch
es 但不用于任何其他 there is a nice little hack described here 您应该在Startup
class 的 Web API/server 项目。特别注意Startup.ConfigureServices
GetJsonPatchInputFormatter
辅助方法的使用但这本身可能无法解决您的 Blazor WASM/client 项目将收到的 50X 和 40X HTTP 错误,因为如果您序列化您的
JsonPatch
使用System.Text.Json
它将一个空的ContractResolver
对象添加到 JSON 字符串的末尾(看起来像,"ContractResolver":{}
),这会在服务器端中断。由于某种原因,该请求将与您创建的任何控制器路由不匹配。要解决此问题,您还必须在 Blazor 客户端上使用
Newtonsoft.Json
。您不必将它用于所有事情;您只需要使用它来序列化所有JsonPatch
es。Newtonsoft.Json
比System.Text.Json
多了几行代码,但我做了一个扩展方法,所以它不会在所有地方重复。扩展方法如下所示:public static class HttpClientExtensions { public static async Task<HttpResponseMessage> PatchAsync<T>(this HttpClient client, string requestUri, JsonPatchDocument<T> patchDocument) where T : class { var writer = new StringWriter(); var serializer = new JsonSerializer(); serializer.Serialize(writer, patchDocument); var json = writer.ToString(); var content = new StringContent(json, Encoding.UTF8, "application/json-patch+json"); return await client.PatchAsync(requestUri, content); }
}
就是这样。这个解决方法对我有用,我希望对你也有用。