我有一个DTO类,例如,看起来像这样:
public class ExampleDto
{
[DataMember(Name = "Date", IsRequired = true, Order = 1), Required]
public DateTime Date { get; set; }
[DataMember(Name = "ParentExample", IsRequired = false, Order = 2, EmitDefaultValue = false)]
public Guid? ParentExampleId { get; set; }
}
例如,如果用户提供的日期不正确,例如:
<?xml version="1.0" encoding="UTF-8" ?>
<ExampleDto xmlns="http://customurl/">
<Date>2012-05-25T18:23:INCORRECTDATE</Date>
<ParentExample>B62F10A8-4998-4626-B5B0-4B9118E11BEC</ParentExample>
</ExampleDto>
或者只是一个空体,然后传递给动作的ExampleDto参数将为null(在前一种情况下,ModelState将有错误)。
我将一个CustomValidationAttribute应用于该类,因此类声明如下所示:
[CustomValidation(typeof(CustomExampleValidator), "Validate")]
public class ExampleDto
现在我添加了这个,如果ExampleDto参数为null(由于空体或序列化问题),则抛出ArgumentNullException:
<?xml version="1.0" encoding="UTF-8"?>
<Response xmlns="http://customurl" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Type>Failure</Type>
<Message>An unknown error has occurred</Message>
<Errors>
<Error>
<Message>System.ArgumentNullException</Message>
<MessageDetail>Value cannot be null. Parameter name:
instance</MessageDetail>
<StackTrace> at System.ComponentModel.DataAnnotations.ValidationContext..ctor(Object
instance, IServiceProvider serviceProvider,
IDictionary`2 items) at System.Web.Http.Validation.Validators.DataAnnotationsModelValidator.Validate(ModelMetadata
metadata, Object container) at System.Web.Http.Validation.DefaultBodyModelValidator.ShallowValidate(ModelMetadata
metadata, ValidationContext validationContext,
Object container) at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(ModelMetadata
metadata, ValidationContext validationContext,
Object container) at System.Web.Http.Validation.DefaultBodyModelValidator.Validate(Object
model, Type type, ModelMetadataProvider metadataProvider,
HttpActionContext actionContext, String keyPrefix)
at System.Web.Http.ModelBinding.FormatterParameterBinding.<>c__DisplayClass1.<ExecuteBindingAsync>b__0(Object
model) at System.Threading.Tasks.TaskHelpersExtensions.<>c__DisplayClass36`1.<>c__DisplayClass38.<Then>b__35()
at System.Threading.Tasks.TaskHelpersExtensions.<>c__DisplayClass49.<ToAsyncVoidTask>b__48()
at System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func`1
func, CancellationToken cancellationToken)</StackTrace>
</Error>
</Errors>
</Response>
Reflector显示在ValidationContext的构造函数中对对象执行null参数检查,就在执行CustomValidationAttribute之前。这看起来有点离奇,因为null参数可以作为控制器动作的参数,不是吗?我认为这里的任何空参数检查应该在用户代码中执行,或者由验证属性显式执行,而不是由框架执行。
如果用户提交了正确的XML / JSON,则不会抛出此异常并且CustomValidationAttribute按预期执行,但用户不能始终信任提交正确的XML / JSON,并且会因为他们的努力而看到ArgumentNullException的钝角,而不是一个我能够回归自己。
我很难找到其他经历过这种情况的人。有很多在属性级别应用“复合”验证器的例子,但是对我来说更有意义的是在类级别应用验证(因为如果特定属性不为空,则需要多个属性,如果需要,则需要其他属性不同的属性不是null),我找不到任何说法在类级别应用的验证属性不受支持。
我有同样的问题。不幸的是有很多ValidationAttributes。因此,在发布之前将所有这些重写为IValidatableObject并不可行。所以我的快速而肮脏的解决方案是在过滤器中捕获这些异常并发回适当的响应:
public class GeneralExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
var exceptionType = context.Exception.GetType();
HttpResponseMessage response = null;
if(exceptionType == typeof(ArgumentNullException)
&& context.Exception.StackTrace.TrimStart().StartsWith("at System.ComponentModel.DataAnnotations.ValidationContext..ctor"))
{
response = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(context.Exception.Message)
};
}
else
{
response = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent(context.Exception.Message),
ReasonPhrase = "Unhandled exception"
};
}
context.Response = response;
_errorLogger.LogError(response?.ReasonPhrase, context.Exception);
}
}
并在WebApiConfig.cs中全局注册文件管理器:
config.Filters.Add(new GeneralExceptionFilterAttribute());