Asp.Net Mvc 中未为 Url 绑定调用自定义验证属性,但适用于 FromBody 绑定
Custom validation attribute is not called in Asp.Net Mvc for Url binding, but works for FromBody binding
我在 Mvc 中观察到一个奇怪的行为,不确定它是错误还是功能。
我有一个客户验证属性 MyAttribute
并使用它,例如像这样:
public async Task<IActionResult> GetData([MyAttribute] string myparam)
令人惊讶的是,如果此参数不在 url 中,则永远不会调用属性(IsValid
函数),但如果参数在 Url 中,则会调用它,但为空(例如 myParam=
)。如果我有一个必需的属性,它也会被调用:
public async Task<IActionResult> GetData([MyAttribute, Required] string myparam)
现在,如果我有这样的请求 class:
public class MyRequest
{
[MyAttribute]
public string Test {get;set;}
}
public async Task<IActionResult> GetData([FromBody] MyRequest myparam)
然后即使没有 Required
参数也会调用该属性。
这里某处有错误还是有意为之?
首先,如果我们使用Asp.net 核心源代码进行调试,将会非常有帮助。在此处查看更多信息:debug-net-core-source-visual-studio-2019。
这个问题在源码中的关键点是:EnforceBindRequiredAndValidate。
这是源代码:
private void EnforceBindRequiredAndValidate(
ObjectModelValidator baseObjectValidator,
ActionContext actionContext,
ParameterDescriptor parameter,
ModelMetadata metadata,
ModelBindingContext modelBindingContext,
ModelBindingResult modelBindingResult)
{
RecalculateModelMetadata(parameter, modelBindingResult, ref metadata);
if (!modelBindingResult.IsModelSet && metadata.IsBindingRequired)
{
// Enforce BindingBehavior.Required (e.g., [BindRequired])
var modelName = modelBindingContext.FieldName;
var message = metadata.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(modelName);
actionContext.ModelState.TryAddModelError(modelName, message);
}
else if (modelBindingResult.IsModelSet)
{
// Enforce any other validation rules
baseObjectValidator.Validate(
actionContext,
modelBindingContext.ValidationState,
modelBindingContext.ModelName,
modelBindingResult.Model,
metadata);
}
else if (metadata.IsRequired)
{
// We need to special case the model name for cases where a 'fallback' to empty
// prefix occurred but binding wasn't successful. For these cases there will be no
// entry in validation state to match and determine the correct key.
//
// See https://github.com/aspnet/Mvc/issues/7503
//
// This is to avoid adding validation errors for an 'empty' prefix when a simple
// type fails to bind. The fix for #7503 uncovered this issue, and was likely the
// original problem being worked around that regressed #7503.
var modelName = modelBindingContext.ModelName;
if (string.IsNullOrEmpty(modelBindingContext.ModelName) &&
parameter.BindingInfo?.BinderModelName == null)
{
// If we get here then this is a fallback case. The model name wasn't explicitly set
// and we ended up with an empty prefix.
modelName = modelBindingContext.FieldName;
}
// Run validation, we expect this to validate [Required].
baseObjectValidator.Validate(
actionContext,
modelBindingContext.ValidationState,
modelName,
modelBindingResult.Model,
metadata);
}
}
如果我们添加 required 属性 条件 'else if(metadata.IsRequired)' 将被命中,然后它将调用 Validate 函数。
这里是issue请求和普通请求的区别:
modelBindingResult 的值非常重要。如果结果包含成功,那么它将在函数 EnforceBindRequiredAndValidate 中调用验证
从源代码中我们可以发现这是设计使然的行为。
var modelBindingContext = DefaultModelBindingContext.CreateBindingContext(
actionContext,
valueProvider,
metadata,
parameter.BindingInfo,
parameter.Name);
modelBindingResult 来自上面的上下文。你会发现我们需要提供相关的provider和parameter然后modelBindingResult.IsModelSet会被设置为true,此时函数validate会被调用
我在 Mvc 中观察到一个奇怪的行为,不确定它是错误还是功能。
我有一个客户验证属性 MyAttribute
并使用它,例如像这样:
public async Task<IActionResult> GetData([MyAttribute] string myparam)
令人惊讶的是,如果此参数不在 url 中,则永远不会调用属性(IsValid
函数),但如果参数在 Url 中,则会调用它,但为空(例如 myParam=
)。如果我有一个必需的属性,它也会被调用:
public async Task<IActionResult> GetData([MyAttribute, Required] string myparam)
现在,如果我有这样的请求 class:
public class MyRequest
{
[MyAttribute]
public string Test {get;set;}
}
public async Task<IActionResult> GetData([FromBody] MyRequest myparam)
然后即使没有 Required
参数也会调用该属性。
这里某处有错误还是有意为之?
首先,如果我们使用Asp.net 核心源代码进行调试,将会非常有帮助。在此处查看更多信息:debug-net-core-source-visual-studio-2019。 这个问题在源码中的关键点是:EnforceBindRequiredAndValidate。 这是源代码:
private void EnforceBindRequiredAndValidate(
ObjectModelValidator baseObjectValidator,
ActionContext actionContext,
ParameterDescriptor parameter,
ModelMetadata metadata,
ModelBindingContext modelBindingContext,
ModelBindingResult modelBindingResult)
{
RecalculateModelMetadata(parameter, modelBindingResult, ref metadata);
if (!modelBindingResult.IsModelSet && metadata.IsBindingRequired)
{
// Enforce BindingBehavior.Required (e.g., [BindRequired])
var modelName = modelBindingContext.FieldName;
var message = metadata.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(modelName);
actionContext.ModelState.TryAddModelError(modelName, message);
}
else if (modelBindingResult.IsModelSet)
{
// Enforce any other validation rules
baseObjectValidator.Validate(
actionContext,
modelBindingContext.ValidationState,
modelBindingContext.ModelName,
modelBindingResult.Model,
metadata);
}
else if (metadata.IsRequired)
{
// We need to special case the model name for cases where a 'fallback' to empty
// prefix occurred but binding wasn't successful. For these cases there will be no
// entry in validation state to match and determine the correct key.
//
// See https://github.com/aspnet/Mvc/issues/7503
//
// This is to avoid adding validation errors for an 'empty' prefix when a simple
// type fails to bind. The fix for #7503 uncovered this issue, and was likely the
// original problem being worked around that regressed #7503.
var modelName = modelBindingContext.ModelName;
if (string.IsNullOrEmpty(modelBindingContext.ModelName) &&
parameter.BindingInfo?.BinderModelName == null)
{
// If we get here then this is a fallback case. The model name wasn't explicitly set
// and we ended up with an empty prefix.
modelName = modelBindingContext.FieldName;
}
// Run validation, we expect this to validate [Required].
baseObjectValidator.Validate(
actionContext,
modelBindingContext.ValidationState,
modelName,
modelBindingResult.Model,
metadata);
}
}
如果我们添加 required 属性 条件 'else if(metadata.IsRequired)' 将被命中,然后它将调用 Validate 函数。
这里是issue请求和普通请求的区别:
从源代码中我们可以发现这是设计使然的行为。
var modelBindingContext = DefaultModelBindingContext.CreateBindingContext(
actionContext,
valueProvider,
metadata,
parameter.BindingInfo,
parameter.Name);
modelBindingResult 来自上面的上下文。你会发现我们需要提供相关的provider和parameter然后modelBindingResult.IsModelSet会被设置为true,此时函数validate会被调用