Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 815
dark_perfect Avatar asked Jun 14 '13 16:06

dark_perfect


1 Answers

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());
like image 174
sharp-soft Avatar answered Oct 11 '22 16:10

sharp-soft