如何解决ASP.NET Core API中「UserId为Null」的错误?
问题现象
在ASP.NET Core API中创建故事时,CreateStoryRequest和StoryController中能正确获取到UserId,但CreateStoryCommand中的UserId始终为Null,最终触发404错误:
{ "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:处理创建故事的POST请求
- CreateStoryRequest:接收前端请求的DTO,包含
Content、Image、UserId - CreateStoryCommand:MediatR命令,包含创建故事所需参数
- CreateStoryCommandHandler:命令处理逻辑,负责创建故事实体并校验用户存在性
- CreateStoryAsync:仓储层创建故事实体的方法
核心问题分析
从提供的代码片段来看,问题出在映射配置与参数传递不匹配,以及验证器规则逻辑错误:
1. 映射配置与控制器传参不匹配
StoryMappingConfig中定义的映射规则是针对元组(CreateStoryRequest request, string UserId, byte[] Image)到CreateStoryCommand:
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); } }
但在StoryController的CreateAsync方法中,传入的元组只有两个元素:(request, image),并没有传递UserId:
var createStoryResult = await _mediatr.Send(_mapper .Map<CreateStoryCommand>((request, image)));
这导致映射时无法获取到UserId,最终CreateStoryCommand中的UserId为Null。
2. 命令验证器逻辑错误
CreateStoryCommandValidator中的UserId验证规则逻辑倒置:
RuleFor(r => r.UserId) .NotEmpty().WithMessage("UserId must not be empty").When(r => r.UserId != Guid.Empty);
这里的When(r => r.UserId != Guid.Empty)表示只有当UserId不为空时才验证NotEmpty,完全违背了验证的初衷。当UserId为Null(或Empty Guid)时,验证规则不会触发,导致非法参数直接进入Handler,查询用户时返回404。
解决步骤
步骤1:修正映射传参或映射配置
方案A:修正控制器中的映射传参
在控制器中传递包含UserId的完整元组,确保映射能获取到UserId:
var createStoryResult = await _mediatr.Send(_mapper .Map<CreateStoryCommand>((request, request.UserId, image)));
方案B:调整映射配置(更简洁)
修改映射配置,直接从CreateStoryRequest和Image参数映射,无需单独传递UserId:
public class StoryMappingConfig { public StoryMappingConfig(TypeAdapterConfig config) { config.NewConfig<(CreateStoryRequest request, byte[] Image), CreateStoryCommand>() .Map(dest => dest.Image, src => src.Image) .Map(dest => dest.UserId, src => src.request.UserId) .Map(dest => dest.Content, src => src.request.Content); } }
控制器中保持原有传参即可:
var createStoryResult = await _mediatr.Send(_mapper .Map<CreateStoryCommand>((request, image)));
步骤2:修正命令验证器规则
移除错误的When条件,确保UserId为空时触发验证:
public CreateStoryCommandValidator() { RuleFor(r => r.UserId) .NotEmpty().WithMessage("UserId must not be 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"); } }); }
步骤3:调试确认参数传递
在StoryController的CreateAsync方法中添加日志或断点,确认request.UserId不为空;在CreateStoryCommandHandler的Handle方法中添加断点,验证request.UserId已正确传递。
额外优化建议
- 避免在Handler中调用
request.UserId.ToString(),直接使用Guid类型查询用户,减少类型转换:var userOrError = await _userRepository.GetUserByIdAsync(request.UserId); - 将
CreatedAt = DateTime.Now改为CreatedAt = DateTime.UtcNow,确保时间一致性(实体类中已处理DateTimeKind.Utc,直接用UtcNow更规范)。
内容的提问来源于stack exchange,提问作者Julia

