我在 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;
}
我看到您已将映射配置定义为:
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);