如何解决 ASP.NET Core API 中的“UserId is Null”错误?

问题描述 投票:0回答:1

我在 ASP.NET Core API 中遇到问题,其中 UserId 在 CreateStoryCommand 中似乎为空,尽管存在于 CreateStoryRequest 和 StoryController 中。这是我收到的错误消息:

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
  "title": "A 'Not Found' error has occurred.",
  "status": 404,
  "traceId": "00-e985c50c5dfd8a5c3fc981580dd24c3f-ae4d5bd6e3d3f789-00",
  "errorCodes": "customValue"
}

这是我的代码结构的概述:

StoryEntity:包含 UserId 等属性。 StoryController:处理 HTTP POST 请求以创建故事。 CreateStoryRequest:包括Content、Image、UserId等属性。 CreateStoryCommand:代表创建故事的命令,包含UserId。 CreateStoryCommandHandler:处理创建故事的逻辑。 CreateStoryAsync:创建新故事实体的方法。

尽管确保 UserId 出现在请求和控制器中,但它在命令处理程序中仍然显示为 null。我该如何排查并解决这个问题? 以下是相关代码片段供参考:

public class StoryEntity
{
    public Guid Id { get; set; }
    public string? Content { get; set; } 
    public string? Image { get; set; } 
    // public string? Video { get; set; } 
    private DateTime _createdAt;
    public DateTime CreatedAt
    {
        get { return _createdAt; }
        set { _createdAt = DateTime.SpecifyKind(value, DateTimeKind.Utc); }
    }
    
    public DateTime ExpiresAt => CreatedAt.AddHours(24); 

    public bool IsExpired => DateTime.UtcNow > ExpiresAt;
    
    [ForeignKey("UserEntity")]
    public Guid UserId { get; set; } 
    public UserEntity User { get; set; }
}

    [Route("api/[controller]")]
[ApiController]
public class StoryController : ApiController 
{
    private readonly ISender _mediatr;
    private readonly IMapper _mapper;
    private readonly IConfiguration _configuration;
    
    public StoryController(ISender mediatr, IMapper mapper, IConfiguration configuration)
    {
        _mediatr = mediatr;
        _mapper = mapper;
        _configuration = configuration;
    }

    [HttpPost("create")]
    public async Task<IActionResult> CreateAsync([FromQuery] CreateStoryRequest request)
    {
        byte[] image = null;
        if (request.Image != null && request.Image.Length > 0)
        {
            using (MemoryStream memoryStream = new MemoryStream())
            {
                await request.Image.CopyToAsync(memoryStream);
                image = memoryStream.ToArray();
            }
        }
        
        // var createStoryCommand = _mapper.Map<CreateStoryCommand>(request);
        // var createStoryResult = await _mediatr.Send(createStoryCommand); 
        var createStoryResult = await _mediatr.Send(_mapper
            .Map<CreateStoryCommand>((request, image)));

        return createStoryResult.Match(
            success => Ok(success), 
            error => Problem(error)); 
    }


    public class StoryMappingConfig 
{
    public StoryMappingConfig(TypeAdapterConfig config)
    {
        config.NewConfig<(CreateStoryRequest request, string UserId, byte[] Image), CreateStoryCommand>()
            .Map(dest => dest.Image, src => src.Image)
            .Map(dest => dest.UserId, src => src.UserId)
            .Map(dest => dest, src => src.request);
    }
}

    public record CreateStoryCommand(
    string? Content,
    byte[]? Image,
    Guid UserId
    // string? Video
    ) : IRequest<ErrorOr<Unit>>;

    
    public class CreateStoryCommandHandler : IRequestHandler<CreateStoryCommand, ErrorOr<Unit>>
{
    private readonly IStoryRepository _storyRepository;
    private readonly IImageStorageService _imageStorageService;
    private readonly IUserRepository _userRepository;

    public CreateStoryCommandHandler(IStoryRepository storyRepository, IImageStorageService imageStorageService, IUserRepository userRepository)
    {
        _storyRepository = storyRepository;
        _imageStorageService = imageStorageService;
        _userRepository = userRepository;
    }

    public async Task<ErrorOr<Unit>> Handle(CreateStoryCommand request, CancellationToken cancellationToken)
    {
        var userOrError = await _userRepository.GetUserByIdAsync(request.UserId.ToString()); 
        
        if (userOrError.IsError)
        {
            return userOrError.Errors;
        }
        
        var user = userOrError.Value;
        
        var storyEntity = new StoryEntity
        {
            Content = request.Content,
            CreatedAt = DateTime.Now,
            UserId = request.UserId,
        };

        var storyResult = await _storyRepository.CreateStoryAsync(storyEntity);
        
        if (storyResult.IsError)
        {
            return storyResult.Errors;
        }

        if (request.Image != null)
        {
            var imageName = await _imageStorageService.AddStoryImageAsync(request.Image);
            if (imageName == null)
            {
                return Error.Unexpected("Avatar saving error");
            }
        
            storyEntity.Image = imageName;
        }


        var result = await _storyRepository.SaveStoryAsync(storyEntity);
        
        if (result.IsError)
        {
            return result.Errors;
        }
        
        return result;
    }


    public async Task<ErrorOr<Guid>> CreateStoryAsync(StoryEntity story)
{
    try
    {
        _context.Stories.Add(story);
        await _context.SaveChangesAsync();
        return story.Id;
    }
    catch (Exception ex)
    {
        return Error.Failure(ex.Message);
    }
}

    
     public CreateStoryCommandValidator()
    {
        RuleFor(r => r.UserId)
            .NotEmpty().WithMessage("UserId must not be empty").When(r => r.UserId != Guid.Empty);
        
        RuleFor(r => r.Content)
            .MaximumLength(1000).WithMessage("Content must not exceed 1000 characters.");

        RuleFor(r => r.Image)
            .Custom((image, context) =>
            {
                if (image != null && image.Length > (2 * 1024 * 1024))
                {
                    context.AddFailure("Image", "File size must not exceed 2MB");
                }
            }); 
    }
}

    public record CreateStoryRequest
{
    [StringLength(1000, ErrorMessage = "{PropertyName} cannot exceed 1000 characters")]
    public string? Content { get; init; }
    
    [FileSize(2 * 1024 * 1024)]
    public IFormFile? Image { get; init; }
    
    [Required(ErrorMessage = "{PropertyName} must not be empty")]
    public required Guid UserId { get; init; }
}

    
    public async Task<ErrorOr<UserEntity>> CreateUserAsync(UserEntity userEntity, string password, string role)
{
    userEntity.UserName = $"{userEntity.Email}".ToLower();

    var createUserResult = await _userManager.CreateAsync(userEntity, password);

    if (!createUserResult.Succeeded)
    {
        foreach (var error in createUserResult.Errors)
        {
            Console.WriteLine($"Error creating user: {error.Description}");
        }
        return Error.Failure("Error creating user");
    }

    var addToRoleResult = await _userManager.AddToRoleAsync(userEntity, role);

    if (!addToRoleResult.Succeeded)
    {
        foreach (var error in addToRoleResult.Errors)
        {
            Console.WriteLine($"Error adding user to role: {error.Description}");
        }
        return Error.Failure("Error adding user to role");
    }

    return userEntity;
}
c# asp.net webapi
1个回答
0
投票

我看到您已将映射配置定义为:

config.NewConfig<(CreateStoryRequest request, string UserId, byte[] Image), CreateStoryCommand>()

但是你用 2 个参数调用它:

var createStoryResult = await _mediatr.Send(_mapper
        .Map<CreateStoryCommand>((request, image)));

从您的映射定义来看,您将

request
分配给
dest
,然后设置
dest.UserId => src.UserId
,我认为它会尝试从丢失的参数中获取?

        .Map(dest => dest.Image, src => src.Image)
        .Map(dest => dest.UserId, src => src.UserId)
        .Map(dest => dest, src => src.request);
© www.soinside.com 2019 - 2024. All rights reserved.