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

时间:2019-01-31 15:10:09

标签: c# asp.net-core .net-core

说我有一个数据库,用于存储此结构的用户详细信息:

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,该API需要返回用户详细信息,而不是敏感部分(例如PasswordHash)。我可以从数据库中获取用户,但随后我需要删除某些字段。这样做的“正确”方法是什么?

我想到了几种解决方法,其中包括将User类分为具有非敏感数据的BaseClass和包含要保密的属性的派生类,然后转换或映射对象返回给BaseClass之前,但是感觉笨拙又肮脏。

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

4 个答案:

答案 0 :(得分:1)

看来对我而言,最简洁的解决方案是这样的:

将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的建议使用AutoMapper代替构造方法来完成。不喜欢这样做,所以为什么我问这个问题,但这似乎是最简洁的解决方案,并且需要的重复最少。

答案 1 :(得分:1)

如果要忽略该属性,只需在模型中添加 ignore annotatio (忽略注释) n,它将在模型序列化时跳过该属性。

[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);
    }
}

它称为“条件属性序列化” 和文档can be found here. 希望有帮助

答案 2 :(得分:0)

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

  1. 使用相同的类,仅填充要发送的属性。这样做的问题在于,值类型将具有默认值(int属性将以0的形式发送,这可能不正确)。
  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"
}

即使将类型强制转换为User,它仍然从UserBase类型对属性进行序列化。

答案 3 :(得分:0)

问题是您正在查看此错误。即使直接与特定的数据库实体一起使用,API也不会处理实体。这里有一个关注点分离问题。您的API正在处理用户实体的表示形式。实体类本身是数据库的功能。它具有仅与数据库有关的内容,而且重要的是,与您的API无关的内容。试图拥有一个可以满足多种不同应用的类是愚蠢的,只会导致具有嵌套依赖关系的易碎代码。

更重要的是,您将如何与此API进行交互?也就是说,如果您的API直接公开了您的User实体,那么使用此API的任何代码要么必须依赖您的数据层,以便它可以访问User,要么必须实现自己的代表{ {1}},并希望它与API的实际需求匹配。

现在想象替代方案。您创建一个“通用”类库,该类库将在您的API和任何客户端之间共享。在该库中,您定义了类似User的东西。您的API仅绑定到UserResource或从UserResource绑定,并将其来回映射到User。现在,您已经完全隔离了数据层。客户仅了解UserResource,并且唯一涉及您数据层的是您的API。而且,当然,现在,您可以通过构建User的方式来限制向API客户端公开UserResource上的哪些信息。更妙的是,如果您的应用程序需求需要更改,User可以更改而不会因每个使用客户端的API冲突而加剧。您只需修复您的API,客户端就不会察觉。如果确实需要进行重大更改,则可以执行类似创建UserResource2类以及新版本API的操作。您无法创建User2,而不会创建一个全新的表,这将导致Identity冲突。

总之,使用API​​的正确方法是始终使用单独的DTO类,甚至多个DTO类。 API绝不应该直接消耗实体类,否则您将一无所获。