使用Fluent Validation验证用户输入旧密码与数据库存储密码一致性的问题及代码优化咨询
Let's fix this step by step — your core logic is on the right track, but we need to address async handling issues (which are causing potential deadlocks) and adjust the FluentValidation setup to work properly with asynchronous operations.
The main problem: Blocking async calls with .Result
Your current IsSameAsOldPassword method uses .Result to wait for async FindByIdAsync and CheckPasswordAsync calls. In ASP.NET environments (especially with traditional SynchronizationContext), this is a common cause of deadlocks — it blocks the thread while waiting for an async operation that needs the same context to complete.
Fix 1: Switch to async validation with MustAsync
FluentValidation has built-in support for async validation rules using MustAsync instead of Must. Let's rewrite the rule and validation method to be fully async:
// Update the RuleSet to use async validation RuleSet(() => RuleFor(x => x.OldPassword) .MustAsync(async (model, oldPassword, cancellationToken) => await IsSameAsOldPasswordAsync(model.Id, oldPassword, cancellationToken)) .WithMessage("Old password incorrect"), RuleSets.Update); // Async version of the password check method private async Task<bool> IsSameAsOldPasswordAsync(Guid id, string oldPassword, CancellationToken cancellationToken) { var user = await _userManager.FindByIdAsync(id.ToString(), cancellationToken); // Handle edge case where user isn't found (adds robustness) if (user == null) return false; return await _userManager.CheckPasswordAsync(user, oldPassword); }
Key improvements here:
- No more blocking calls: We use
awaitproperly to avoid thread deadlocks and follow async/await best practices. - CancellationToken support: Passing the cancellation token lets the validation abort if the request is canceled, which is standard for async operations.
- Explicit null check: Added a safety check for missing users (even if your ID should always be valid, this prevents unexpected null reference errors).
Fix 2: Execute the validator with async methods
When running the validator, make sure you use the async validation method instead of the synchronous one:
var validationResult = await _validator.ValidateAsync(model, options => options.IncludeRuleSets(RuleSets.Update)); if (!validationResult.IsValid) { // Handle validation errors (e.g., return bad request with error messages) }
Additional recommendations:
- Reusable validation logic: If this password check is used across multiple validators, consider creating a custom
PropertyValidatorto encapsulate the logic for easier reuse. - Error message clarity: Adjust the message to be more user-friendly if needed (e.g., "The old password you entered is incorrect").
- Avoid hardcoded strings: If using localization, move error messages to a resource file instead of keeping them inline.
内容的提问来源于stack exchange,提问作者adym

