You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建自定义模型绑定器属性实现自动填充用户信息

问题

我想实现通过属性标记自动填充DTO中的Uploader对象,现有模型如下:

public class AddDocumentDTO
{
    [Required]
    [CustomAccessTokenAttribute]
    public required User Uploader {get; set;}
    
    [Required]
    public required int Id {get; set;}

    [Required]
    public required IFormFile File {get; set;}
}

Uploader需要通过AccessToken中的Claim查询数据库获取,逻辑如下:

public async Task<UserDTO> GetByAccessToken()
{
    var Id = _httpContextAccessor.HttpContext!.User.Claims.Where(claim => claim.Type.ToString() == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier").FirstOrDefault()!.Value.ToString();
    var userPOCO =  await _userRepository.GetAsync(Id);
    var userDTO = new UserDTO() {
        InternalUserNumber = userPOCO.INTERNAL_USER_NUMBER,
        Username = userPOCO.USERNAME!,
        FirstName = userPOCO.FIRST_NAME!,
        LastName = userPOCO.LAST_NAME!,
        EmailAddress = userPOCO.EMAIL_ADDRESS!,
        WorkGroup = userPOCO.WORK_GROUP!,
        IsActive = userPOCO.ACTIVE_USER!,
        IsArmsUser = userPOCO.ARMS_USER,
        IsSysAdmin = userPOCO.SYSTEM_ADMIN,
        Avatar = userPOCO.AVATAR
    };
    return userDTO;
}

核心需求:

  • 用属性标记指示模型绑定自动执行上述逻辑填充Uploader,且该属性能通用到多个模型
  • 确认请求到达模型绑定阶段时,已通过Authorize过滤器,安全性是否有保障
  • 替代目前在仓储/服务层手动传递或填充User参数的方式,实现自动化

解决方案

1. 实现自定义模型绑定器与标记属性

首先创建一个自定义属性,用于标记需要自动填充的用户类型属性:

[AttributeUsage(AttributeTargets.Property)]
public class BindUserFromAccessTokenAttribute : Attribute, IModelBinderProvider
{
    public IModelBinder? GetBinder(ModelBinderProviderContext context)
    {
        // 匹配需要自动填充的用户类型(这里替换成你的实际UserDTO类型)
        if (context.Metadata.ModelType == typeof(UserDTO))
        {
            return new BinderTypeModelBinder(typeof(UserFromAccessTokenBinder));
        }
        return null;
    }
}

然后实现对应的模型绑定器,封装用户查询逻辑:

public class UserFromAccessTokenBinder : IModelBinder
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly IUserRepository _userRepository;

    public UserFromAccessTokenBinder(IHttpContextAccessor httpContextAccessor, IUserRepository userRepository)
    {
        _httpContextAccessor = httpContextAccessor;
        _userRepository = userRepository;
    }

    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        // 从Claims中获取用户ID
        var userIdClaim = _httpContextAccessor.HttpContext?.User.Claims
            .FirstOrDefault(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier");

        if (userIdClaim == null || string.IsNullOrEmpty(userIdClaim.Value))
        {
            bindingContext.ModelState.AddModelError(bindingContext.FieldName, "无法获取用户身份信息");
            return;
        }

        try
        {
            // 查询数据库获取用户信息
            var userPOCO = await _userRepository.GetAsync(userIdClaim.Value);
            if (userPOCO == null)
            {
                bindingContext.ModelState.AddModelError(bindingContext.FieldName, "用户不存在");
                return;
            }

            // 转换为DTO并绑定
            var userDTO = new UserDTO() {
                InternalUserNumber = userPOCO.INTERNAL_USER_NUMBER,
                Username = userPOCO.USERNAME!,
                FirstName = userPOCO.FIRST_NAME!,
                LastName = userPOCO.LAST_NAME!,
                EmailAddress = userPOCO.EMAIL_ADDRESS!,
                WorkGroup = userPOCO.WORK_GROUP!,
                IsActive = userPOCO.ACTIVE_USER!,
                IsArmsUser = userPOCO.ARMS_USER,
                IsSysAdmin = userPOCO.SYSTEM_ADMIN,
                Avatar = userPOCO.AVATAR
            };

            bindingContext.Result = ModelBindingResult.Success(userDTO);
        }
        catch (Exception ex)
        {
            bindingContext.ModelState.AddModelError(bindingContext.FieldName, $"获取用户信息失败:{ex.Message}");
        }
    }
}

2. 注册模型绑定器

在Program.cs中注册自定义模型绑定器提供者,确保它被优先使用:

builder.Services.AddControllers(options =>
{
    options.ModelBinderProviders.Insert(0, new BindUserFromAccessTokenAttribute());
});

3. 在DTO中通用使用属性标记

修改你的DTO,用自定义属性替代原有标记,该属性可直接复用在其他模型的用户属性上:

public class AddDocumentDTO
{
    [Required]
    [BindUserFromAccessToken]
    public required UserDTO Uploader { get; set; }
    
    [Required]
    public required int Id { get; set; }

    [Required]
    public required IFormFile File { get; set; }
}

4. 安全性确认

只要控制器或Action添加了[Authorize]过滤器,请求到达模型绑定阶段前就已经完成身份验证:

  • 未携带有效AccessToken的请求会被直接拦截,无法进入模型绑定流程
  • 经过验证的请求,HttpContext.User中的Claims由认证系统生成,是可信的,基于此查询用户安全可靠
  • 绑定器中已添加空值校验,避免因Claims缺失导致的异常

5. 简化服务层调用

实现自动填充后,服务层方法无需再手动接收User参数,直接从DTO中获取即可:

public async Task AddDocument(AddDocumentDTO dto)
{
    // 直接使用dto.Uploader,无需手动传递或查询
    await _documentRepository.AddAsync(new DocumentPOCO
    {
        UploaderId = dto.Uploader.InternalUserNumber,
        // 其他字段赋值
    });
}

内容的提问来源于stack exchange,提问作者Qiuzman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 03:51:21