.NET Web API: Class level validation attribute causes Web API to throw ArgumentNullException if instance is null
Asked Answered
B

1

6

I have a DTO class that looks, for example, like this:

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; }
}

If, as an example, a user provides an incorrect date, such as this:

<?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>

or simply just an empty body, then the ExampleDto argument passed into the action will be null (and in the former case, the ModelState will have errors).

I applied a CustomValidationAttribute to the class, so the class declaration looks like this:

[CustomValidation(typeof(CustomExampleValidator), "Validate")]
public class ExampleDto

Now that I've added this, if the ExampleDto argument is null (because of an empty body, or a serialization problem), an ArgumentNullException is thrown:

<?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 shows that a null argument check is performed against the object in the constructor of the ValidationContext, just before the CustomValidationAttribute is executed. This seems a bit bizarre, because null arguments are acceptable as arguments to controller actions, no? I think any null argument checks here SHOULD be performed in user code or explicitly by validation attributes, instead of by the framework.

If the user submits correct XML/JSON, then this exception isn't thrown and the CustomValidationAttribute executes as expected, but the users can't always be trusted to submit correct XML/JSON, and will get an obtuse looking ArgumentNullException for their efforts, instead of one that I'm able to return myself.

I'm struggling to find anyone else who has experienced this. There are plenty of examples of applying "compound" validators at property level, but it makes more sense for me to apply the validation at class level here (because multiple properties are required IF a specific property is not null, and others are required IF a different property is not null), and I can't find anything to say that validation attributes applied at class level are unsupported.

Bulbar answered 14/6, 2013 at 16:55 Comment(8)
As a workaround, I have explicitly implemented IValidatableObject to perform the validation. I prefer using validation attributes though, so am still looking for an explanation for this.Bulbar
I tried to replicate your issue but without luck... Look at this image: tallmaris.com/blog/wp-content/uploads/2013/06/Capture.png. As you can see the value has arrived to the validator and it's not null. When it reaches the controller the IsValid will be false because of the date of course. The posted info for that result were: var example = new { Chi = "YYY", PatientStatus = 1, Date = "2012-05-25T18:23:XXXX", ParentExample = "B62F10A8-4998-4626-B5B0-4B9118E11BEC" }; (then Json serialized and uploaded).Fosdick
Hi Tallmaris, thanks for giving it a go. The exception isn't thrown if the value isn't null, only if it IS null. Try posting with var example = null;, or submitting the json manually and with a syntax error in it.Bulbar
Ok, I see what you mean now... Yes, if this is a concern (and I can understand why it is) an alternative that pops into mind is to avoid the class-wide validation and just validate the whole object inside the methods that receive the POST data.Fosdick
Cool. Always nice to know I'm not doing something silly! One last question: Do you think it's a bug?Bulbar
Hard to say... I don't think it's a bug in the way the ValidationContext works but more in how the Model Binder does its job by simply returning a null when it cannot bind properly... but what wouls the correct behaviour be? Certainly it's a concern that a client can generate an exception in my server code by simply posting wrong data (which in my opinion should generate a 400 error, not a 500). It is probably worth to put it on the Connect MS site to see what the .net team think.Fosdick
@Bulbar Did you get any further regarding whether this is a bug or not? Having the same problem over here...Spoilsman
I didn't get anywhere with it, to be honest. My workaround was for the class to implement the IValidatableObject interface, through which you implement a method that returns an IEnumerable<ValidationResult>. It accomplishes the same thing, but I think it's slightly less readable.Bulbar
M
1

I have the same issue. Unfortunately with quite a few ValidationAttributes. So rewriting all of them to IValidatableObject just before a release is not really feasible. So my quick and dirty solution was to catch these exceptions in a filter and send back a proper Response:

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);
    }
}

And register the filer globally in WebApiConfig.cs:

config.Filters.Add(new GeneralExceptionFilterAttribute());
Margaretmargareta answered 27/12, 2017 at 11:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.