GraphQL-dotnet字段解析前验证问题:无法获取输入值
问题分析与解决方案
我来帮你拆解下遇到的两个核心问题:context.Inputs始终为null,以及控制器中query.Variables.ToInputs()返回null,这其实分别和变量绑定逻辑、GraphQL.NET 2.4.0的验证阶段生命周期有关,下面逐个解决:
一、解决query.Variables.ToInputs()返回null的问题
这个问题大概率是你的GraphQLQuery类定义或者前端请求格式不正确导致的:
1. 检查GraphQLQuery类定义
确保它能正确绑定前端传入的variables字段,示例定义如下:
public class GraphQLQuery { public string Query { get; set; } public string OperationName { get; set; } // 用JObject或Dictionary<string, object>都可以,要匹配JSON格式的变量 public JObject Variables { get; set; } }
2. 确认前端请求格式
前端发送的JSON请求体必须包含variables字段(即使无变量也要传{}),比如:
{ "query": "mutation($input: UserInput!) { createUser(input: $input) { id } }", "operationName": "createUser", "variables": { "input": { "phone": "1234567890" } } }
如果前端没传variables或者格式错误,query.Variables会为null,进而导致ToInputs()返回null。
二、解决自定义IValidationRule中无法获取变量值的问题
在GraphQL.NET 2.4.0中,验证阶段是在变量解析之前执行的,框架默认不会把解析后的变量值传入ValidationContext,所以context.Inputs自然是null。
这里有两种解决方案,优先推荐第一种:
方案1:优先选择——在InputObjectGraphType字段上直接添加验证
这种方式更简洁,符合GraphQL.NET的设计逻辑,不需要处理复杂的AST遍历,直接在字段定义时嵌入验证:
public class UserInputType : InputObjectGraphType { public UserInputType() { Name = "UserInput"; Field<StringGraphType>("phone") // 保留你的元数据标识(如果需要其他逻辑识别) .Metadata(nameof(PhoneNumberValidationRule), true) // 添加字段级验证逻辑 .Validate(context => { var phoneValue = context.Value as string; // 这里可以直接拿到字段的实际传入值 if (!string.IsNullOrEmpty(phoneValue) && !phoneValue.IsValidPhoneNumber()) { return new ValidationError( "InvalidPhoneNumber", "The supplied phone number is not valid.", context.FieldName); } // 验证通过返回null return null; }); // 其他字段定义... } }
这种方式会在字段解析时自动触发验证,错误会被收集到查询结果的errors列表中,完全符合你的需求。
方案2:手动传递解析后的变量到验证规则
如果你必须在IValidationRule中统一处理验证,需要先在控制器中解析变量,再把解析后的Inputs传递给验证规则:
1. 修改PhoneNumberValidationRule,添加接收变量的属性
public class PhoneNumberValidationRule : IValidationRule { // 添加这个属性,接收控制器传递的解析后变量 public Inputs Inputs { get; set; } public INodeVisitor Validate(ValidationContext context) { return new EnterLeaveListener(_ => { _.Match<Argument>(argAst => { var argDef = context.TypeInfo.GetArgument(); if (argDef == null) return; var inputType = argDef.ResolvedType.GetNamedType() as InputObjectGraphType; if (inputType == null) return; // 递归收集所有嵌套的输入字段(修正你之前的字段收集逻辑) var allFields = new List<FieldType>(); CollectInputFields(inputType, allFields); // 筛选带指定元数据的字段 foreach (var field in allFields.Where(f => f.HasMetadata(nameof(PhoneNumberValidationRule)))) { // 从传递的Inputs中获取对应值 var argValue = Inputs?.GetValue(argAst.Name.Value); var fieldValue = argValue?[field.Name]; if (fieldValue != null) { var phoneNumber = fieldValue.ToString(); if (!phoneNumber.IsValidPhoneNumber()) { context.ReportError(new ValidationError( context.OriginalQuery, "InvalidPhoneNumber", "The supplied phone number is not valid.", argAst)); } } } }); }); } // 递归收集所有输入字段,处理NonNull和List嵌套 private void CollectInputFields(IComplexGraphType inputType, List<FieldType> fields) { foreach (var field in inputType.Fields) { fields.Add(field); var resolvedType = field.ResolvedType.GetNamedType(); if (resolvedType is IComplexGraphType nestedInputType) { CollectInputFields(nestedInputType, fields); } } } }
2. 在控制器中传递解析后的变量到验证规则
[Route("graphql")] [HttpPost] public async Task<IActionResult> PostAsync([FromBody]GraphQLQuery query) { if (!ModelState.IsValid || query == null) { return Json("Проблем в формата на изпратената на заявка!"); } // 解析变量(现在应该能正常返回值,前提是GraphQLQuery定义正确) var inputs = query.Variables?.ToInputs() ?? new Inputs(); // 给自定义验证规则传递解析后的Inputs var validationRules = DocumentValidator.CoreRules() .Concat(_validationRules.Select(rule => { if (rule is PhoneNumberValidationRule phoneRule) { phoneRule.Inputs = inputs; } return rule; })) .ToList(); var result = await _executer.ExecuteAsync(o => { o.Schema = _schema; o.Query = query.Query; o.OperationName = query.OperationName; o.Inputs = inputs; o.ExposeExceptions = true; o.EnableMetrics = true; o.ComplexityConfiguration = new GraphQL.Validation.Complexity.ComplexityConfiguration { MaxDepth = 15 }; o.UserContext = new GraphQLUserContext { User = User }; o.ValidationRules = validationRules; o.Listeners.Add(_dataLoaderDocumentListener); }).ConfigureAwait(false); if (result.Errors?.Count > 0) { _logger.LogWarning($"Errors: {JsonConvert.SerializeObject(result.Errors)}"); return BadRequest(result); } return Ok(result); }
总结
优先选择方案1,它更简洁、符合框架设计,不需要处理AST遍历和变量传递的复杂逻辑。如果必须在统一的验证规则中处理,再考虑方案2。
内容的提问来源于stack exchange,提问作者Stefan PEev
相关产品推荐
相关产品推荐

