说我有一个数据库,用于存储此结构的用户详细信息:
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,但我想这更多是一个普遍的问题。
答案 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)
有两种方法可以做到这一点:
int
属性将以0
的形式发送,这可能不正确)。第二个选项是最常见的。如果您使用的是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绝不应该直接消耗实体类,否则您将一无所获。