1

I got an asp webapi project and I want to use a custom validatorattribute to do some special validation in my api. For that purpose I added a filter into my api (nothing special currently):

public class LogFilter : ActionFilterAttribute
{
    public LogFilter()
    {

    }

    public override void OnActionExecuting(HttpActionContext ActionContext)
    {
        Trace.WriteLine(string.Format("Action Method {0} executing at {1}", ActionContext.ActionDescriptor.ActionName, DateTime.Now.ToShortDateString()), "Web API Logs");

        if (ActionContext.ModelState.IsValid == false)
        {
            ActionContext.Response = ActionContext.Request.CreateErrorResponse(
                HttpStatusCode.BadRequest, ActionContext.ModelState);
        }
    }

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        Trace.WriteLine(string.Format("Action Method {0} executed at {1}", actionExecutedContext.ActionContext.ActionDescriptor.ActionName, DateTime.Now.ToShortDateString()), "Web API Logs");
    }
}

Next step I did is to create a new ValidatorAttribute called GroupValidatorAttribute (There is currently NO special implementation, it is just a minimal working example for demonstrating my problem):

[AttributeUsage(AttributeTargets.Class)]
public class GroupValidatorAttribute : ValidationAttribute
{

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value == null)
        {
            return new ValidationResult("" + validationContext.DisplayName + " cant be null");
        }

        return ValidationResult.Success;
    }
}

So and finaly, we need a model-class using the GroupValidator attribute:

[GroupValidator]
public class Student
{
    public int? Age { get; set; }
    public string name { get; set; }
}

For sure there is also a controller, which has an endpoint with type post, which should get an object of class student (my model) from the body:

    [HttpPost]
    [Route("Body")]
    public HttpResponseMessage BodyTest(int id, [FromBody] Student student)
    {
        return Request.CreateResponse(HttpStatusCode.OK,
            "Suc");
    }

Okay, so far so good. It nearly works as expected, but one case is driving me into madness. If I send (post) an empty body to my endpoint in the api, there occures an System.ArgumentNullException.

This is the full exception:

"ExceptionMessage": "Der Wert darf nicht NULL sein.\r\nParametername: instance", "ExceptionType": "System.ArgumentNullException", "StackTrace": " bei System.ComponentModel.DataAnnotations.ValidationContext..ctor(Object instance, IServiceProvider serviceProvider, IDictionary2 items)\r\n bei System.Web.Http.Validation.Validators.DataAnnotationsModelValidator.Validate(ModelMetadata metadata, Object container)\r\n bei System.Web.Http.Validation.DefaultBodyModelValidator.ShallowValidate(ModelMetadata metadata, BodyModelValidatorContext validationContext, Object container, IEnumerable1 validators)\r\n bei System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(ModelMetadata metadata, BodyModelValidatorContext validationContext, Object container, IEnumerable`1 validators)\r\n bei System.Web.Http.Validation.DefaultBodyModelValidator.Validate(Object model, Type type, ModelMetadataProvider metadataProvider, HttpActionContext actionContext, String keyPrefix)\r\n bei System.Web.Http.ModelBinding.FormatterParameterBinding.d__18.MoveNext()\r\n--- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde ---\r\n bei System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n bei System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n bei System.Web.Http.Controllers.HttpActionBinding.d__12.MoveNext()\r\n--- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde ---\r\n bei System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n bei System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n bei System.Web.Http.Controllers.ActionFilterResult.d__5.MoveNext()\r\n--- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde ---\r\n bei System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n bei System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n bei System.Web.Http.Dispatcher.HttpControllerDispatcher.d__15.MoveNext()"

In my debugger I can see that the methods in my filter and the methods in my validator are NOT hit. Is anybody who can help me?

1 Answer 1

0

The ArgumentNullException("instance") exception is generated from constructor of ValidationContextbecause instance is null in your case.

The code-snippet of constructor of ValidationContext: Source code

public ValidationContext(object instance, IServiceProvider serviceProvider,
                         IDictionary<object, object> items) 
{
    if (instance == null) {
       throw new ArgumentNullException("instance");
    }
    ....

Now, to your question : In my debugger I can see that the methods in my filter and the methods in my validator are NOT hit.

The call stack is something like

DataAnnotationsModelValidator.Validate() ->
            DataAnnotations.ValidationAttribute.GetValidationResult() ->
            GroupValidatorAttribute.IsValid() 

The constructor of ValidationContext is created at Validate() stage (where null exception is raised) but your implementation of IsValid() is called later only after null checking is already performed. Hence, IsValid() is never hit your case.

Sign up to request clarification or add additional context in comments.

5 Comments

Yes, that makes sense. Is there any advise you could give me to solve that problem, without stopping to use the custom validator attribute?
@chris000r Depends, what you want to do. But don't think your GroupValidator is placed for a purpose at Class level.
For demonstration purpose I shrinked the code a little bit. The GroupValidator has a constructor, where you can specifiy the names of the properties the validator should have a look at it. On Validation the GroupValidator shall look, if one of the Properties of the list (from the constructor) is set (not null and a value). So It works very will, but not if the body is empty. There I get the exception listed in the beginning.
@chris000r Sounds like you need [Required] attribute on few properties. Another option you have is to include ExceptionFilterAttribute that will handle cases where parameter is null.
Required is not what I need I think. In case of the student-class (for example) I want at least one attribute not to be null.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.