使用 .NET 5 Web API 有条件地 exclude/include 来自 dto 的属性(字段级访问)
Conditionally exclude/include properties from dto using .NET 5 Web API (Field Level Access)
我正在开发具有默认 System.Text.Json
模型绑定的 .NET 5 Web Api。
对于几个 dto,我需要这样的东西:
public class MyDto
{
public string Name { get; set; }
public string PublicDetails { get; set; }
[IncludeForRoles("admin", "staff")]
public string InternalDetails { get; set; }
}
如果角色不是“admin”或“staff”的用户调用 returns 上述 dto 的端点,属性 “InternalDetails”应该在模型绑定时被忽略,并且不被添加到序列化的 dto。
有没有标准的方法来做到这一点?如果没有,我如何手动实现此行为?
我建议使用 Newtonsoft.JSON library for serialization and implement custom ContractResolver 来动态决定要序列化哪些字段。
有一个 [JsonIgnore]
属性,它接受一个条件,如果属性为 null 或默认,则将忽略这些属性。
假设您将知道控制器中的角色,只有当他们具有相关角色时才获取InternalDetails 信息,否则将其保留为null。这也减少了查询时间,因为您不会获得不需要的信息。
我找到了一种满足要求但有以下缺点的解决方法:
- 从数据库加载不需要的数据(不能使用Automapper的
.ProjectTo
)
- 数据在映射后被修改(无法将服务注入 Automapper 的
Profile
,请参阅 here)
- 必须向每个 属性 添加两个属性,在某些情况下应忽略这些属性。
你看,这不是一个漂亮优雅的解决方案。欢迎提出更好的建议!
属性:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class IncludeForRolesAttribute : Attribute
{
public IncludeForRolesAttribute(params string[] roleNames)
{
RoleNames = roleNames;
}
public string[] RoleNames { get; }
}
Automapper 映射操作:
public class RoleBasedSetNullAction<TSource, TDestination> : IMappingAction<TSource, TDestination>
{
private readonly ITokenAccessor _tokenAccessor;
public RoleBasedSetNullAction(ITokenAccessor tokenAccessor)
{
_tokenAccessor = tokenAccessor ?? throw new ArgumentNullException(nameof(tokenAccessor));
}
public void Process(TSource source, TDestination destination, ResolutionContext context)
{
IEnumerable<PropertyInfo> props = typeof(TDestination).GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(IncludeForRolesAttribute)));
foreach (PropertyInfo prop in props)
{
IncludeForRolesAttribute attr = prop.GetCustomAttribute<IncludeForRolesAttribute>();
if (!_tokenAccessor.UserRoles.Intersect(attr.RoleNames).Any())
{
prop.SetValue(destination, null);
}
}
}
}
DTO:
public class MyDto : BaseEntityDto
{
public string Name { get; set; }
public string PublicDetails { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
[IncludeForRoles("admin", "staff")]
public string InternalDetails { get; set; }
}
Automapper 映射配置文件:
public class MyDtoMappingProfile : Profile
{
public MyDtoMappingProfile()
{
CreateMap<MyEntity, MyDto>()
.AfterMap<RoleBasedSetNullAction<MyEntity, MyDto>>();
}
}
我正在开发具有默认 System.Text.Json
模型绑定的 .NET 5 Web Api。
对于几个 dto,我需要这样的东西:
public class MyDto
{
public string Name { get; set; }
public string PublicDetails { get; set; }
[IncludeForRoles("admin", "staff")]
public string InternalDetails { get; set; }
}
如果角色不是“admin”或“staff”的用户调用 returns 上述 dto 的端点,属性 “InternalDetails”应该在模型绑定时被忽略,并且不被添加到序列化的 dto。 有没有标准的方法来做到这一点?如果没有,我如何手动实现此行为?
我建议使用 Newtonsoft.JSON library for serialization and implement custom ContractResolver 来动态决定要序列化哪些字段。
有一个 [JsonIgnore]
属性,它接受一个条件,如果属性为 null 或默认,则将忽略这些属性。
假设您将知道控制器中的角色,只有当他们具有相关角色时才获取InternalDetails 信息,否则将其保留为null。这也减少了查询时间,因为您不会获得不需要的信息。
我找到了一种满足要求但有以下缺点的解决方法:
- 从数据库加载不需要的数据(不能使用Automapper的
.ProjectTo
) - 数据在映射后被修改(无法将服务注入 Automapper 的
Profile
,请参阅 here) - 必须向每个 属性 添加两个属性,在某些情况下应忽略这些属性。
你看,这不是一个漂亮优雅的解决方案。欢迎提出更好的建议!
属性:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class IncludeForRolesAttribute : Attribute
{
public IncludeForRolesAttribute(params string[] roleNames)
{
RoleNames = roleNames;
}
public string[] RoleNames { get; }
}
Automapper 映射操作:
public class RoleBasedSetNullAction<TSource, TDestination> : IMappingAction<TSource, TDestination>
{
private readonly ITokenAccessor _tokenAccessor;
public RoleBasedSetNullAction(ITokenAccessor tokenAccessor)
{
_tokenAccessor = tokenAccessor ?? throw new ArgumentNullException(nameof(tokenAccessor));
}
public void Process(TSource source, TDestination destination, ResolutionContext context)
{
IEnumerable<PropertyInfo> props = typeof(TDestination).GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(IncludeForRolesAttribute)));
foreach (PropertyInfo prop in props)
{
IncludeForRolesAttribute attr = prop.GetCustomAttribute<IncludeForRolesAttribute>();
if (!_tokenAccessor.UserRoles.Intersect(attr.RoleNames).Any())
{
prop.SetValue(destination, null);
}
}
}
}
DTO:
public class MyDto : BaseEntityDto
{
public string Name { get; set; }
public string PublicDetails { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
[IncludeForRoles("admin", "staff")]
public string InternalDetails { get; set; }
}
Automapper 映射配置文件:
public class MyDtoMappingProfile : Profile
{
public MyDtoMappingProfile()
{
CreateMap<MyEntity, MyDto>()
.AfterMap<RoleBasedSetNullAction<MyEntity, MyDto>>();
}
}