设想
我正在更新我的 .NET API 以对所有数据库键字段进行编码,这样顺序键就不会暴露给最终用户。我为此使用hashids.org并构建了辅助方法来快速解码/编码我的自动映射器映射中的属性。但是,API 有多个版本,只有最新版本应该使用此功能进行更新,这意味着我不能简单地覆盖现有的类。我已经实现了一些可行的解决方案,但它们都有我希望清除的不良代码气味。
解决方案
我目前正在控制器层执行编码。我也可以在数据访问层看到这样做的好处,但我觉得在该层存在更多泄漏/错过转换的风险,特别是因为 API 有许多不同的数据源。另外,隐藏密钥是外部世界的一个问题,控制器是看门人,所以在那里感觉很合适。
应用程序目前有以下模型模式,无法更改:Model(DB 中存在的模型)> ValueObject(服务模型,VO)> DTO(API 模型)。
(1) 初步尝试
下面是一个需要支持编码和解码状态的类的示例,其中Utils.Encode()
和Utils.Decode()
是辅助方法,它们将使用 Hashids 在 int 和 string 之间转换字段。
//EquipmentDTO.cs
public class EquipmentDTO //encoded class
{
public string Id {get; set;}
public string Name {get; set;}
}
public class EquipmentUnencodedDTO //decoded class
{
public int Id {get; set;}
public string Name {get; set;}
}
//Automapper.cs
CreateMap<EquipmentUnencodedDTO, EquipmentDTO>()
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => Utils.Encode(src.Id)));
CreateMap<EquipmentDTO, EquipmentUnencodedDTO>()
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => Utils.Decode(src.Id)));
CreateMap<EquipmentVO, EquipmentDTO>() //mapping from service model to controller model
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => Utils.Encode(src.Id)));
CreateMap<EquipmentDTO, EquipmentVO>()
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => Utils.Decode(src.Id)));
CreateMap<Equipment, EquipmentVO>() //mapping from DB model to service model
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => src.Id));
- 我选择制作现有
EquipmentDTO
的编码版本,因为我希望它成为新标准,这最终会导致EquipmentUnencodedDTO
旧控制器最终得到更新而被弃用和删除。 - 我选择不复制
CreateMap<EquipmentVO, EquipmentDTO>
(CreateMap<EquipmentVO, EquipmentUnencodedDTO>
反之亦然),因为它会导致 AutoMapper 文件中出现大量重复,这已经很大(尽管这可能不是真正的问题?) - 我不喜欢这个解决方案,因为在我的旧控制器中,映射现在很混乱。例如,在 POST 中,未编码的输入 DTO 必须通过以下方式转换为服务模型:
Mapper.Map<EquipmentVO>(Mapper.Map<EquipmentDTO>(unencodedEquipmentInput))
这非常难看。- 话虽如此,这应该是一个暂时的问题,那么这是一个真正的问题吗?
- 如果我创建了这个问题就会消失
CreateMap<EquipmentVO, EquipmentUnencodedDTO>
- 我不喜欢这个解决方案,因为我的类有很多重复的字段在编码和解码版本之间没有变化
(2) 第二次尝试
上面的两个要点使我对此进行了重构:
public class EquipmentDTO
{
public string Id {get; set;}
public string Name {get; set;}
public Decoded Decode(){
return Mapper.Map<Decoded>(this);
}
public class Decoded: EquipmentDTO {
public new int Id {get; set;}
public EquipmentDTO Encode(){
return Mapper.Map<EquipmentDTO>(this);
}
}
}
// Automappers are the same, except EquipmentUnencodedDTO is now EquipmentDTO.Decoded
- 我喜欢现在在编码和解码状态之间切换是多么简单,将我上面的双重映射减少到:
Mapper.Map<EquipmentVO>(unencodedEquipmentInput.Encode());
- 我喜欢嵌套类,因为它编码了两个类之间的关系,并且在识别哪些字段被编码/解码方面做得更好
- 我觉得这闻起来更糟
(3) 下一次尝试
我的下一个尝试是将已解码类的缺失映射添加到服务模型中,并撤消尝试 #2 中的更改。这创建了大量重复的映射代码,我仍然被两个类中的重复属性所困扰,没有明确指示哪些字段被解码/编码,而且感觉比必要的要麻烦得多。
感谢您的任何建议!