.NET 4.8下如何重写属性JSON序列化逻辑,将对象序列化为指定字符串
.NET 4.8 实体统一序列化实现方案
你目前所有实体都继承自AbstractBase,完全可以在基类层面实现通用逻辑,不需要在每个子类重复写代码,两种可落地的方案按改动成本从低到高排列:
方案1:直接在抽象基类新增只读计算属性(零配置、改动最小)
这是成本最低的实现方式,不需要额外序列化组件、不需要改全局配置,只需要在现有AbstractBase类的基础上做小幅调整:
- 保留现有两个导航属性上的
[JsonIgnore]标记,继续禁止完整User对象的序列化输出 - 新增两个只读的计算属性,直接绑定序列化输出规则,调用User类的
GetFullName()方法取姓名
调整后的基类代码片段:
[JsonIgnore] public virtual User CreatedByUser { get; set; } [JsonIgnore] public virtual User LastModifiedByUser { get; set; } // 新增:序列化时直接输出创建人全名 [JsonProperty(PropertyName = "CreatedByUserName")] public string CreatedByUserName => CreatedByUser?.GetFullName() ?? string.Empty; // 新增:序列化时直接输出最后修改人全名 [JsonProperty(PropertyName = "LastModifiedByUserName")] public string LastModifiedByUserName => LastModifiedByUser?.GetFullName() ?? string.Empty;
这种方式的特性:
- 所有继承
AbstractBase的实体自动携带这两个序列化字段,完全不需要在子类重复定义 - 空安全兜底:导航属性未加载、为null的时候返回空字符串,不会触发空引用异常
- 输出的JSON字段名统一,前端不需要针对不同实体做字段适配
注意点:
- 因为是直接访问导航属性,需要确保序列化触发时,EF的DbContext仍处于存活状态,避免懒加载触发失败的问题,最好在业务查询阶段就Include对应的两个导航属性
- 如果存在不希望输出这两个姓名字段的特殊序列化场景,需要单独给属性加条件控制,灵活性相对有限
方案2:自定义Newtonsoft.Json契约解析器(灵活性更高)
如果需要灵活控制不同序列化场景的输出规则,不需要把计算属性直接硬编码在实体类模型里,可以实现自定义的DefaultContractResolver,全局拦截所有继承自AbstractBase的类型的序列化逻辑,动态注入姓名字段。
实现核心逻辑:
- 重写属性创建逻辑,仅对继承
AbstractBase的实体做特殊处理,不影响其他类型的序列化 - 保留原有导航属性的
[JsonIgnore]排除规则,不输出完整User对象 - 动态为实体注入两个可序列化的姓名字段,取值逻辑统一调用User的
GetFullName()方法
核心实现代码参考:
public class AuditEntityContractResolver : DefaultContractResolver { protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) { // 跳过非实体类型的处理,不影响其他序列化逻辑 if (!typeof(AbstractBase).IsAssignableFrom(type)) { return base.CreateProperties(type, memberSerialization); } var properties = base.CreateProperties(type, memberSerialization); // 动态注入创建人姓名字段 properties.Add(new JsonProperty { PropertyName = "CreatedByUserName", PropertyType = typeof(string), Readable = true, Writable = false, ValueProvider = new DelegateValueProvider<AbstractBase>(entity => entity.CreatedByUser?.GetFullName() ?? string.Empty) }); // 动态注入最后修改人姓名字段 properties.Add(new JsonProperty { PropertyName = "LastModifiedByUserName", PropertyType = typeof(string), Readable = true, Writable = false, ValueProvider = new DelegateValueProvider<AbstractBase>(entity => entity.LastModifiedByUser?.GetFullName() ?? string.Empty) }); return properties; } } // 通用值提供器,统一处理取值逻辑 internal class DelegateValueProvider<T> : IValueProvider where T : class { private readonly Func<T, object> _valueGetter; public DelegateValueProvider(Func<T, object> valueGetter) { _valueGetter = valueGetter; } public object GetValue(object target) => _valueGetter(target as T); public void SetValue(object target, object value) { /* 只读属性无需实现写入逻辑 */ } }
只需要在全局JSON配置里注册这个解析器即可全局生效:
// Web API 全局序列化配置示例 GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new AuditEntityContractResolver();
这种方式的特性:
- 实体类本身不需要新增任何计算属性,不会污染实体的领域模型
- 可以灵活通过解析器的判断逻辑,控制不同场景下是否注入姓名字段,适配性更强
内容的提问来源于stack exchange,提问作者China Syndrome
相关产品推荐
相关产品推荐

