Umbraco REST API的PublishedContentController处理嵌套内容时抛出异常
Umbraco PublishedContentController无法序列化NestedContent属性:问题原因与解决方案
我帮你拆解下这个问题的核心原因,再给你几个可行的解决办法:
问题根源
你遇到的这个序列化错误,本质是PublishedContentController和ContentController处理NestedContent的逻辑差异导致的:
ContentController返回的是数据库里的原始IContent对象,NestedContent属性是以JSON字符串的形式直接存储的,序列化时就是简单输出这个字符串,不会有循环引用的问题。PublishedContentController返回的是经过Umbraco发布管道处理后的IPublishedContent对象,NestedContent会被自动解析成List<IPublishedContent>集合。而这些嵌套的内容实例内部可能包含对父内容的引用,或者自身结构存在循环依赖,Newtonsoft.Json默认配置没处理这种情况,所以抛出了Self referencing loop detected异常。
解决方案
这里提供三种可行的解决方法,你可以根据项目需求选择:
1. 全局配置JSON序列化忽略自引用循环
这是最简单的全局解决办法,修改JsonSerializerSettings让序列化器自动忽略循环引用:
在项目的Global.asax.cs中,更新Application_Start方法:
protected void Application_Start() { // 保留原有Umbraco初始化代码 AreaRegistration.RegisterAllAreas(); // 配置JSON序列化处理循环引用 GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; // 其他初始化代码 }
如果项目使用Owin启动(Startup.cs),则在Configuration方法中添加配置:
public void Configuration(IAppBuilder app) { var config = new HttpConfiguration(); // 配置循环引用处理 config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; app.UseWebApi(config); // 其他Umbraco Owin配置代码 }
这个方法会全局生效,所有Web API的JSON序列化都会忽略循环引用,既能解决NestedContent的问题,也能避免其他潜在的循环引用错误。
2. 自定义PublishedContentController处理NestedContent
如果你不想全局修改序列化规则,可以创建自定义的PublishedContentController,手动把NestedContent属性转换成JSON字符串(和ContentController的行为一致):
using System.Collections.Generic; using System.Web.Http; using Umbraco.Web; using Umbraco.Web.WebApi; public class CustomPublishedContentController : PublishedContentController { public override IHttpActionResult GetById(int id) { var publishedContent = Umbraco.TypedContent(id); if (publishedContent == null) { return NotFound(); } // 构建自定义返回对象,手动处理属性 var response = new Dictionary<string, object> { ["id"] = publishedContent.Id, ["name"] = publishedContent.Name, ["contentTypeAlias"] = publishedContent.ContentType.Alias, ["parentId"] = publishedContent.Parent?.Id ?? -1, ["createDate"] = publishedContent.CreateDate, ["updateDate"] = publishedContent.UpdateDate }; var properties = new Dictionary<string, object>(); foreach (var property in publishedContent.Properties) { // 判断是否是NestedContent属性 if (property.PropertyType.PropertyEditorAlias == "Umbraco.NestedContent") { // 从原始内容中获取存储的JSON字符串 var rawNestedValue = Umbraco.Content(publishedContent.Id).GetPropertyValue<string>(property.Alias); properties[property.Alias] = rawNestedValue; } else { // 其他属性直接使用发布后的值 properties[property.Alias] = property.Value; } } response["properties"] = properties; return Ok(response); } // 可按需重写其他方法,比如 GetByRoute 等 }
之后需要通过Web API路由配置,让自定义控制器替换原有的PublishedContentController路由。
3. 使用DTO(数据传输对象)映射IPublishedContent
第三种方法是创建专门的DTO类,只映射你需要的字段,避免序列化整个IPublishedContent对象:
// 定义DTO类 public class PublishedContentDto { public int Id { get; set; } public string Name { get; set; } public string ContentTypeAlias { get; set; } public int ParentId { get; set; } public Dictionary<string, object> Properties { get; set; } // 按需添加其他字段 } // 在自定义控制器中使用DTO public class DtoPublishedContentController : UmbracoApiController { public IHttpActionResult GetById(int id) { var content = Umbraco.TypedContent(id); if (content == null) { return NotFound(); } var dto = new PublishedContentDto { Id = content.Id, Name = content.Name, ContentTypeAlias = content.ContentType.Alias, ParentId = content.Parent?.Id ?? -1, Properties = new Dictionary<string, object>() }; foreach (var prop in content.Properties) { if (prop.PropertyType.PropertyEditorAlias == "Umbraco.NestedContent") { // 获取原始JSON字符串 var rawValue = Umbraco.Content(content.Id).GetPropertyValue<string>(prop.Alias); dto.Properties[prop.Alias] = rawValue; } else { dto.Properties[prop.Alias] = prop.Value; } } return Ok(dto); } }
这种方法能完全控制返回的数据结构,避免不必要的字段被序列化,彻底解决循环引用问题。
内容的提问来源于stack exchange,提问作者Igor Besedin
相关产品推荐
相关产品推荐

