使用 .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>>();
    }
}