猿问

仅从 API 返回对象的属性子集

假设我有一个数据库,其中存储了此结构的用户详细信息:


public class User

{

    public string UserId { get; set; }

    public string Name { get; set; }

    public string Email { get; set; }

    public string PasswordHash { get; set; }

}

我有一个数据访问层,它包含GetById()等方法,并返回一个User对象。


但是,假设我有一个API,它需要返回用户详细信息,但不是敏感部分,例如PasswordHash。我可以从数据库中获取用户,但随后我需要删除某些字段。什么是“正确”的方法?


我已经想到了几种方法来处理这个问题,其中大部分涉及将User类拆分为包含非敏感数据的BaseClass和包含我希望保密的属性的派生类,然后在返回之前将对象转换或映射到BaseClass,但是这感觉很笨拙和肮脏。


感觉这应该是一个相对常见的情况,所以我错过了一个简单的方法来处理它吗?我正在使用 ASP.Net 核心和MongoDB,但我想这更像是一个普遍的问题。


BIG阳
浏览 98回答 4
4回答

长风秋雁

出于我的目的,似乎最整洁的解决方案是这样的:将 User 类拆分为基类和派生类,并添加构造函数以复制必填字段:public class User{    public User() { }    public User(UserDetails user)    {        this.UserId = user.UserId;        this.Name = user.Name;        this.Email = user.Email;    }    public string UserId { get; set; }    public string Name { get; set; }    public string Email { get; set; }}public class UserDetails : User{    public string PasswordHash { get; set; }}数据访问类将返回一个 UserDetails 对象,然后可以在返回之前对其进行转换:UserDetails userDetails = _dataAccess.GetUser();User userToReturn = new User(userDetails);也可以按照 Daniel 的建议使用自动映射器而不是构造函数方法来完成。我不喜欢这样做,因此我问这个问题,但这似乎是最整洁的解决方案,需要最少的重复。

Helenr

有两种方法可以做到这一点:使用相同的类,并且仅填充要发送的属性。这样做的问题是,值类型将具有默认值(属性将作为 发送,如果这可能不准确)。int0对要发送到客户端的数据使用其他类。这基本上就是丹尼尔在评论中得到的 - 你有一个不同的模型,由客户“查看”。第二种选择是最常见的。如果您使用的是 Linq,则可以使用以下命令映射值:Select()users.Select(u => new UserModel { Name = u.Name, Email = u.Email });基本类型不会像您希望的那样工作。如果将派生类型强制转换为其父类型并对其进行序列化,它仍会序列化派生类型的属性。以这个为例:public class UserBase {    public string Name { get; set; }    public string Email { get; set; }}public class User : UserBase {    public string UserId { get; set; }    public string PasswordHash { get; set; }}var user = new User() {    UserId = "Secret",    PasswordHash = "Secret",    Name = "Me",    Email = "something"};var serialized = JsonConvert.SerializeObject((UserBase) user);请注意,序列化时强制转换。即便如此,结果是:{    "UserId": "Secret",    "PasswordHash": "Secret",    "Name": "Me",    "Email": "something"}它仍然序列化了该类型的属性,即使它被强制转换为 。UserUserBase

qq_笑_17

如果要忽略该属性,只需在模型中添加忽略 annotation,就像这样,当模型序列化时,它将跳过该属性。[JsonIgnore] public string PasswordHash { get; set; }如果你想在运行时忽略(这意味着动态).Newtonsoft.Json中有一个构建函数可用public class User{    public string UserId { get; set; }    public string Name { get; set; }    public string Email { get; set; }    public string PasswordHash { get; set; }    //FYI ShouldSerialize_PROPERTY_NAME_HERE()   public bool ShouldSerializePasswordHash()    {        // use the condtion when it will be serlized        return (PasswordHash != this);    }}它被称为“条件属性序列化”,文档可以在这里找到。

皈依舞

问题是你看错了。API 即使直接与特定的数据库实体一起工作,也不会处理实体。这里有一个关注点分离问题。您的 API 正在处理用户实体的表示形式。实体类本身是数据库的函数。它上面有一些只对数据库重要的东西,重要的是,它上面的东西对你的API无关紧要。尝试使用一个可以满足多个不同应用程序的类是愚蠢的,并且只会导致具有嵌套依赖项的代码变脆。更重要的是,您将如何与此 API 交互?也就是说,如果您的API直接公开您的实体,那么使用此API的任何代码都必须依赖于您的数据层以便它可以访问,或者它必须实现自己的类来表示,并希望它与API实际想要的相匹配。UserUserUser现在想象一下替代方案。创建一个“通用”类库,该类库将在 API 和任何客户端之间共享。在该库中,您可以定义类似 .您的 API 仅绑定到/从 绑定,并将其来回映射到 。现在,您已经完全隔离了数据层。客户端只知道,唯一触及数据层的是你的API。当然,现在您可以限制向API客户端公开的信息,只需通过构建方式即可。更好的是,如果您的应用程序需求发生变化,则可以更改,而不会像每个消费客户端的API冲突一样螺旋式上升。您只需修复API,客户端就会在不知情的情况下继续。如果确实需要进行重大更改,可以执行某些操作,例如创建类以及新版本的 API。如果不创建一个全新的表,就无法创建一个全新的表,然后这会在标识中引发冲突。UserResourceUserResourceUserUserResourceUserUserResourceUserUserResource2User2长话短说,使用 API 的正确方法是始终使用单独的 DTO 类,甚至多个 DTO 类。API 永远不应该直接使用实体类,否则你只会感到痛苦。
随时随地看视频慕课网APP
我要回答