为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

C#
BIG阳 2022-08-20 15:58:27
假设我有一个数据库,其中存储了此结构的用户详细信息: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,但我想这更像是一个普遍的问题。
查看完整描述

4 回答

?
长风秋雁

TA贡献1757条经验 获得超7个赞

出于我的目的,似乎最整洁的解决方案是这样的:


将 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 的建议使用自动映射器而不是构造函数方法来完成。我不喜欢这样做,因此我问这个问题,但这似乎是最整洁的解决方案,需要最少的重复。


查看完整回答
反对 回复 2022-08-20
?
Helenr

TA贡献1780条经验 获得超4个赞

有两种方法可以做到这一点:

  1. 使用相同的类,并且仅填充要发送的属性。这样做的问题是,值类型将具有默认值(属性将作为 发送,如果这可能不准确)。int0

  2. 对要发送到客户端的数据使用其他类。这基本上就是丹尼尔在评论中得到的 - 你有一个不同的模型,由客户“查看”。

第二种选择是最常见的。如果您使用的是 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


查看完整回答
反对 回复 2022-08-20
?
qq_笑_17

TA贡献1818条经验 获得超7个赞

如果要忽略该属性,只需在模型中添加忽略 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);

    }

}

它被称为“条件属性序列化”文档可以在这里找到。


查看完整回答
反对 回复 2022-08-20
?
皈依舞

TA贡献1851条经验 获得超3个赞

问题是你看错了。API 即使直接与特定的数据库实体一起工作,也不会处理实体。这里有一个关注点分离问题。您的 API 正在处理用户实体的表示形式。实体类本身是数据库的函数。它上面有一些只对数据库重要的东西,重要的是,它上面的东西对你的API无关紧要。尝试使用一个可以满足多个不同应用程序的类是愚蠢的,并且只会导致具有嵌套依赖项的代码变脆。

更重要的是,您将如何与此 API 交互?也就是说,如果您的API直接公开您的实体,那么使用此API的任何代码都必须依赖于您的数据层以便它可以访问,或者它必须实现自己的类来表示,并希望它与API实际想要的相匹配。UserUserUser

现在想象一下替代方案。创建一个“通用”类库,该类库将在 API 和任何客户端之间共享。在该库中,您可以定义类似 .您的 API 仅绑定到/从 绑定,并将其来回映射到 。现在,您已经完全隔离了数据层。客户端只知道,唯一触及数据层的是你的API。当然,现在您可以限制向API客户端公开的信息,只需通过构建方式即可。更好的是,如果您的应用程序需求发生变化,则可以更改,而不会像每个消费客户端的API冲突一样螺旋式上升。您只需修复API,客户端就会在不知情的情况下继续。如果确实需要进行重大更改,可以执行某些操作,例如创建类以及新版本的 API。如果不创建一个全新的表,就无法创建一个全新的表,然后这会在标识中引发冲突。UserResourceUserResourceUserUserResourceUserUserResourceUserUserResource2User2

长话短说,使用 API 的正确方法是始终使用单独的 DTO 类,甚至多个 DTO 类。API 永远不应该直接使用实体类,否则你只会感到痛苦。


查看完整回答
反对 回复 2022-08-20
  • 4 回答
  • 0 关注
  • 97 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信