【问题标题】:Unexpected error using JsonPatchDocument with Blazor将 JsonPatchDocument 与 Blazor 一起使用时出现意外错误
【发布时间】:2021-01-30 06:49:12
【问题描述】:

我正在使用 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

我的项目都没有直接依赖于 Newtonsoft。我不知道我引用的 Microsoft 库是否依赖于 Newtonsoft。该错误表明他们可能会这样做。

可以在 GitHub 上的这个小存储库中观察到该行为: https://github.com/BenjaminCharlton/JsonPatchDocumentWithBlazor

请问有人知道为什么它不起作用和/或什么可以解决它吗?

谢谢

【问题讨论】:

  • JsonPatchDocument 具有 Newtonsoft.Json 依赖项。您的错误是关于System.Text.Json,请尝试改用Newtonsoft.Json
  • 我现在在一个新分支上试了一下。我删除了 System.Text.Json 和 System.Net.Http.Json。然后,在服务器和客户端应用程序上,我安装了 Microsoft.AspNetCore.Mvc.NewtonsoftJson 和 Newtonsoft.Json。为了与该库兼容,我需要将客户端应用程序的项目文件从 netstandard2.1 更改为 netcoreapp3.1。最后,我使用 Newtonsoft APIs 而不是 System.Text.Json APIs 重新编码了一些部分。
  • 两个分支都构建良好,但都不能正常运行。它们有不同的运行时错误。在新分支上,它具有 TypeLoadExceptions,我认为这与 Mono 运行时中对 .NET Core 某些部分的不完全支持有关。您认为这是否意味着 Blazor 目前无法使用 JsonPatchDocument?
  • 我做了更多的调查(嗯,更像是反复试验)。我对 JSON 字符串上的属性“ContractResolver”:{} 感到好奇。我并没有特别要求。它只是默认放在 Blazor WASM 客户端上。手动删除它会使事情变得更好。我只需要首先找出如何阻止它被放在那里。

标签: .net-core asp.net-core-webapi blazor blazor-client-side blazor-webassembly


【解决方案1】:

我设法解决了这个困难,Pavel 和 Enet 的意见很有用,谢谢。

对于遇到相同问题的其他人,以下是您需要了解的解决方法:

  1. 截至目前(2020 年末),将 .NET Core 从 Newtonsoft.Json 迁移到 System.Text.Json 的努力尚未完成。包Microsoft.AspNetCore.JsonPatch 仍然依赖于Newtonsoft.Json

  2. .NET Core 开发团队知道 GitHub 报告了很多问题。但他们都被关闭了,没有任何行动。显然,将Microsoft.AspNetCore.JsonPatch 切换到System.Text.Json 需要付出太多努力。

  3. 要将 Newtonsoft 用于 JsonPatches,但不能用于您应该在 Web API/服务器项目的 Startup 类中使用的任何其他 there is a nice little hack described here。特别注意Startup.ConfigureServices内部调用的GetJsonPatchInputFormatter辅助方法的使用@

  4. 但这本身可能无法解决您的 Blazor WASM/客户端项目将收到的 50X 和 40X HTTP 错误,因为如果您使用 System.Text.Json 序列化您的 JsonPatch,它会添加一个空的 ContractResolver 对象到JSON 字符串的结尾(看起来像 ,"ContractResolver":{} ),它在服务器端中断。由于某种原因,该请求与您所做的任何控制器路由都不匹配。

  5. 要解决此问题,您还必须在 Blazor 客户端上使用 Newtonsoft.Json。您不必将它用于所有事情;你只需要用它来序列化你所有的JsonPatches。 Newtonsoft.JsonSystem.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);
     }
    

    }

就是这样。这个解决方法对我有用,我希望它也对你有用。

【讨论】:

    猜你喜欢
    • 2014-07-12
    • 1970-01-01
    • 2014-06-08
    • 2019-09-18
    • 2012-09-16
    • 2017-04-29
    • 2021-06-25
    • 2018-06-16
    • 1970-01-01
    相关资源
    最近更新 更多