6

例如,我有一个 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 参数将为空(在前一种情况下,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.&lt;&gt;c__DisplayClass1.&lt;ExecuteBindingAsync&gt;b__0(Object
                model) at System.Threading.Tasks.TaskHelpersExtensions.&lt;&gt;c__DisplayClass36`1.&lt;&gt;c__DisplayClass38.&lt;Then&gt;b__35()
                at System.Threading.Tasks.TaskHelpersExtensions.&lt;&gt;c__DisplayClass49.&lt;ToAsyncVoidTask&gt;b__48()
                at System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func`1
                func, CancellationToken cancellationToken)</StackTrace>
        </Error>
    </Errors>
</Response>

Reflector 显示在执行 CustomValidationAttribute 之前,对 ValidationContext 的构造函数中的对象执行空参数检查。这似乎有点奇怪,因为空参数可以作为控制器操作的参数,不是吗?我认为这里的任何空参数检查都应该在用户代码中或通过验证属性显式执行,而不是由框架执行。

如果用户提交正确的 XML/JSON,则不会引发此异常,并且 CustomValidationAttribute 会按预期执行,但不能始终信任用户提交正确的 XML/JSON,并且会因为他们的努力而得到一个看起来很钝的 ArgumentNullException,而不是我能够自己返回的。

我正在努力寻找其他经历过这种情况的人。有很多在属性级别应用“复合”验证器的示例,但对我来说,在这里应用类级别的验证更有意义(因为如果特定属性不为空,则需要多个属性,如果特定属性不为空,则需要其他属性不同的属性不为空),我找不到任何可以说在类级别应用的验证属性不受支持。

4

1 回答 1

1

我有同样的问题。不幸的是,有很多 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());
于 2017-12-27T11:19:31.287 回答