如何在 Blazor WebAssembly 应用程序中实现 System.Text.Json.Serialization.ReferenceHandler.Preserve?
如何在 Blazor WebAssembly 应用程序中实现 System.Text.Json.Serialization.ReferenceHandler.Preserve?
据我理解,您可以使用此设置来解决在对象模型中定义循环引用时出现以下错误的问题:
JsonException: 检测到可能的对象循环引用,这是不受支持的。这可能是由于循环引用或对象深度超过最大允许深度32。
然而,我尝试过未能成功实现它以使其工作。如果有人可以提供详细的指导说明,将不胜感激!
我考虑过将应用程序切换到使用Newtonsoft.JSON,但根据我所读到的,这在Blazor WebAssembly应用程序中是不可行的?
2020年12月12日更新
在尝试弄清楚如何实现ReferenceHandler.Preserve时,我找到了以下最相关的文章:
https://github.com/dotnet/runtime/issues/42584
https://github.com/dotnet/aspnetcore/issues/28286
基于这些文章,我尝试了以下解决方案,但都没有成功...
第一次尝试,我在我的Server项目的Startup.cs类中实现了以下代码:
services.AddControllersWithViews().AddJsonOptions(options => { options.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.Preserve; });
第二次尝试,我在我的Server项目的Startup.cs类中实现了以下代码:
services.AddControllersWithViews(options => { options.OutputFormatters.RemoveType(); options.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonSerializerOptions(JsonSerializerDefaults.Web) { ReferenceHandler = ReferenceHandler.Preserve })); });
2020年12月12日上午11:12 CST更新
将我的Server项目更改为目标.NET 5并尝试上述两个代码选项后,我现在在应用程序的每个页面上都遇到以下类型的错误:
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
渲染组件时发生未处理的异常:无法将JSON值转换为BusinessManager.Shared.Models.EmploymentBenefit[]。路径:$ | LineNumber: 0 | BytePositionInLine: 1。
重现原始问题的步骤
创建一个新的Blazor WebAssembly应用程序
在Shared项目中定义一个具有子对象集合的父类,如下所示:
public virtual ListChildren{ get; set; } = new List ();
在Child类中定义一个引用其父类的属性,如下所示:
public virtual Parent Parent{ get; set; }
然后,使用实体框架生成数据库对象。创建一个返回父类及其子对象的Web API函数,如下所示:
[HttpGet("{id}")] public async TaskGet(Guid id) { var returnValue = await db.Parents .Include(aa => aa.Children) .FirstOrDefaultAsync(aa => aa.ParentId== id); return Ok(returnValue); }
然后,通过调用此Web API函数在页面上呈现父类和子对象集合。
问题出现的原因:
在Blazor WebAssembly应用程序中,如果想要实现System.Text.Json.Serialization.ReferenceHandler.Preserve功能,需要在服务器端和客户端分别进行设置。在服务器端,可以通过在Program.cs文件中添加代码来实现ReferenceHandler.Preserve的功能。但是在客户端(Blazor WebAssembly)目前还没有简单的方法来实现该功能。
解决方法:
对于服务器端,需要在Program.cs文件中添加以下代码:
builder.Services.AddControllers().AddJsonOptions(options => { options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve; options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; });
注意:可以使用AddControllersWithViews替代AddControllers。
对于客户端,可以通过扩展方法来实现该功能,如下所示:
public static class HttpClientExtensions { private static readonly JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve, PropertyNameCaseInsensitive = true }; public static TaskGetFromJsonAsync (this HttpClient httpClient, string requestUri) { return httpClient.GetFromJsonAsync (requestUri, JsonSerializerOptions); } }
或者可以通过自定义的HTTP客户端类来实现:
using System.Net.Http; using System.Net.Http.Json; using System.Text.Json; using System.Threading.Tasks; public class JsonHttpClient { private readonly HttpClient _httpClient; private readonly JsonSerializerOptions _jsonSerializerOptions; public JsonHttpClient(HttpClient httpClient) { _httpClient = httpClient; _jsonSerializerOptions = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve, PropertyNameCaseInsensitive = true }; } public async TaskGetFromJsonAsync (string requestUri) { var response = await _httpClient.GetAsync(requestUri); response.EnsureSuccessStatusCode(); using var contentStream = await response.Content.ReadAsStreamAsync(); return await JsonSerializer.DeserializeAsync (contentStream, _jsonSerializerOptions); } }
对于这种方法,还需要在Program.cs文件中添加以下代码:
builder.Services.AddScoped(sp => new JsonHttpClient(sp.GetRequiredService()));
以及在需要使用它的地方(razor页面)添加以下代码:
@JsonHttpClient JsonHttpClient
和
var resultData = await JsonHttpClient.GetFromJsonAsync("api/someendpoint");
需要注意的是,这里只是注入了新的HTTP客户端,并将Http替换为JsonHttpClient。
以上是两种实现ReferenceHandler.Preserve功能的方法,还包括其他有用的方法的代码示例。
问题的原因是在将项目的目标改为.NET 5后,在应用程序的每个页面上出现了错误。错误提示为"The JSON value could not be converted to BusinessManager.Shared.Models.EmploymentBenefit[]",说明在将JSON转换为指定类型时出现了问题。
解决方法是在客户端也启用ReferenceHandler.Preserve。否则,在客户端中的JsonSerializer将无法识别元数据。
以下是解决方法的代码示例:
using System.Net.Http; using System.Text.Json; using System.Threading.Tasks; public class MyService { private readonly HttpClient _httpClient; private readonly JsonSerializerOptions _jsonOptions; public MyService(HttpClient httpClient) { _httpClient = httpClient; _jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, ReferenceHandler = ReferenceHandler.Preserve }; } public async TaskGetMyModelAsync() { var response = await _httpClient.GetAsync("api/myendpoint"); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); var myModel = JsonSerializer.Deserialize (json, _jsonOptions); return myModel; } }
注意上述代码中的ReferenceHandler.Preserve选项,它会保留对象之间的引用关系,以便在进行JSON序列化和反序列化时能够正确处理。