You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 4.8下如何重写属性JSON序列化逻辑,将对象序列化为指定字符串

.NET 4.8 实体统一序列化实现方案

你目前所有实体都继承自AbstractBase,完全可以在基类层面实现通用逻辑,不需要在每个子类重复写代码,两种可落地的方案按改动成本从低到高排列:

方案1:直接在抽象基类新增只读计算属性(零配置、改动最小)

这是成本最低的实现方式,不需要额外序列化组件、不需要改全局配置,只需要在现有AbstractBase类的基础上做小幅调整:

  1. 保留现有两个导航属性上的[JsonIgnore]标记,继续禁止完整User对象的序列化输出
  2. 新增两个只读的计算属性,直接绑定序列化输出规则,调用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的类型的序列化逻辑,动态注入姓名字段。

实现核心逻辑:

  1. 重写属性创建逻辑,仅对继承AbstractBase的实体做特殊处理,不影响其他类型的序列化
  2. 保留原有导航属性的[JsonIgnore]排除规则,不输出完整User对象
  3. 动态为实体注入两个可序列化的姓名字段,取值逻辑统一调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.01 03:06:03