I have a DTO class that looks 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, for example, the user provides an invalid date, for example:
<?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 just a simple body, then the ExampleDto argument passed to the action will be null (and in the first case the ModelState will have errors).
I applied the CustomValidationAttribute attribute to the class, so the class declaration is as follows:
[CustomValidation(typeof(CustomExampleValidator), "Validate")] public class ExampleDto
Now that I have added this, if the ExampleDto argument is null (due to an empty body or serialization problem), an ArgumentNullException is raised:
<?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>
The reflector shows that checking the null argument is performed against the object in the ValidationContext constructor, just before the CustomValidationAttribute is executed. This seems a little strange, because null arguments are acceptable as arguments for controller actions, no? I think that any checks of the null argument MUST be performed in the user code or explicitly using the verification attributes, and not using the framework.
If the user sends the correct XML / JSON, this exception is not thrown, and CustomValidationAttribute is executed as expected, but users cannot always be trusted to send the correct XML / JSON, and get a dumb looking ArgumentNullException for their effort, and not the one I I can return it to myself.
I'm struggling to find someone who has experienced this. There are many examples of using βcompoundβ validators at the property level, but for me it is more appropriate to apply class-level validation here (since several properties are required if a particular property is not null and others are needed if another property is not null), and I I canβt find anything to say that the validation attributes used at the class level are not supported.