C# AutoMapper父类型列表未映射子类问题求助
尝试映射FirewallRule的子类,但控制器接收请求时,firewallDto中的DestinationPorts未被映射,数据被截断,仅保留FirewallRule层级结构。试过多种AutoMapper映射配置均无效,直接读取请求体时数据结构正常,推测是映射配置存在问题或遗漏关键设置。需求:请求体含DestinationPorts时映射为NetworkRule,含FqdnTags时映射为ApplicationRule。
请求体示例
{ "id": 0, "name": "string", "priority": 0, "action": "string", "rules": [ { "ruleType": "NetworkRule", "name": "myName", "description": "mydescription", "DestinationPorts": [""] } ] }
控制器接收后的结构
{ "id": 0, "name": "string", "priority": 0, "action": "string", "rules": [ { "ruleType": "NetworkRule", "name": "myName", "description": "mydescription" } ] }
代码定义
实体类与DTO类
// Classes public class Firewall { public string Name { get; set; } public int Priority { get; set; } public string Action { get; set; } public List<FirewallRule> Rules { get; set; } } public class FirewallRule { public string RuleType { get; set; } public string Name { get; set; } public string Description { get; set; } } public class ApplicationRule : FirewallRule { public string[] FqdnTags { get; set; } } public class NetworkRule : FirewallRule { public string[] DestinationPorts { get; set; } } public class FirewallDto { public string Name { get; set; } public int Priority { get; set; } public string Action { get; set; } public List<FirewallRuleDto> Rules { get; set; } } public class FirewallRuleDto { public string RuleType { get; set; } public string Name { get; set; } public string Description { get; set; } } public class NetworkRuleDto : FirewallRuleDto { public string[] DestinationPorts { get; set; } } public class ApplicationRuleDto : FirewallRuleDto { public string[] FqdnTags { get; set; } }
AutoMapper映射配置
// Mapping CreateMap<Firewall, FirewallDto>(). ReverseMap(); CreateMap<FirewallRule, FirewallRuleDto>() .Include<NetworkRule, NetworkRuleDto>() .Include<ApplicationRule, ApplicationRuleDto>(). ForMember(dest => dest.RuleType, opt => opt.MapFrom(src => src.RuleType)); CreateMap<NetworkRule, NetworkRuleDto>() .ReverseMap(); CreateMap<ApplicationRule, ApplicationRuleDto>() .ReverseMap(); CreateMap<FirewallRuleDto, FirewallRule>() .Include<NetworkRuleDto, NetworkRule>() .Include<ApplicationRuleDto, ApplicationRule>(); CreateMap<FirewallDto, Firewall>(). ForMember(d => d.Rules, op => op.MapFrom(s => s.Rules));
控制器代码
// Controller public async Task<ActionResult<Firewall>> NewFirewall(FirewallDto firewallDto) { // code }
解决方案
问题核心有两点:一是JSON序列化/反序列化时无法自动识别子类,控制器接收FirewallRuleDto列表时,所有子类实例都会被反序列化为基类FirewallRuleDto,导致子类属性丢失;二是AutoMapper的映射配置缺少基于RuleType的类型转换逻辑。
步骤1:配置JSON序列化支持多态
在ASP.NET Core的Program.cs中配置System.Text.Json支持多态,指定基于RuleType字段识别子类:
builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.TypeInfoResolverChain.Insert(0, FirewallRuleDtoPolymorphicResolver.Default); }); // 自定义多态解析器 public class FirewallRuleDtoPolymorphicResolver : DefaultJsonTypeInfoResolver { public static FirewallRuleDtoPolymorphicResolver Default { get; } = new FirewallRuleDtoPolymorphicResolver(); public FirewallRuleDtoPolymorphicResolver() { Modifiers.Add(typeInfo => { if (typeInfo.Type == typeof(FirewallRuleDto)) { typeInfo.PolymorphismOptions = new JsonPolymorphismOptions { TypeDiscriminatorPropertyName = "RuleType", IgnoreUnrecognizedTypeDiscriminators = true, DerivedTypes = { new JsonDerivedType(typeof(NetworkRuleDto), "NetworkRule"), new JsonDerivedType(typeof(ApplicationRuleDto), "ApplicationRule") } }; } }); } }
这段配置让JSON序列化器在遇到FirewallRuleDto类型时,根据RuleType字段的值自动反序列化为对应的子类(NetworkRuleDto或ApplicationRuleDto),确保子类属性不丢失。
步骤2:修正AutoMapper映射配置
移除重复的FirewallDto到Firewall映射,确保子类映射正确生效,同时明确正向和反向映射的Include关系:
// 正向映射:实体到DTO CreateMap<Firewall, FirewallDto>().ReverseMap(); CreateMap<FirewallRule, FirewallRuleDto>() .Include<NetworkRule, NetworkRuleDto>() .Include<ApplicationRule, ApplicationRuleDto>(); CreateMap<NetworkRule, NetworkRuleDto>().ReverseMap(); CreateMap<ApplicationRule, ApplicationRuleDto>().ReverseMap(); // 反向映射:DTO到实体 CreateMap<FirewallRuleDto, FirewallRule>() .Include<NetworkRuleDto, NetworkRule>() .Include<ApplicationRuleDto, ApplicationRule>() .ConstructUsing((dto, context) => { // 基于RuleType创建对应的实体子类实例 return dto.RuleType switch { "NetworkRule" => new NetworkRule(), "ApplicationRule" => new ApplicationRule(), _ => new FirewallRule() }; });
添加ConstructUsing方法,让AutoMapper在反向映射时,根据DTO的RuleType字段创建对应的实体子类实例,确保子类属性能正确映射。
步骤3:验证控制器接收逻辑
现在控制器接收FirewallDto时,Rules列表中的项会被正确反序列化为NetworkRuleDto或ApplicationRuleDto,再通过AutoMapper映射到对应的实体子类,属性不会丢失。
内容的提问来源于stack exchange,提问作者ICos

