Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Ignore validation of model property #4143

Closed
NickAb opened this issue Feb 24, 2016 · 10 comments
Closed

Ignore validation of model property #4143

NickAb opened this issue Feb 24, 2016 · 10 comments

Comments

@NickAb
Copy link

NickAb commented Feb 24, 2016

Hello,

For implementation of PATCH method where I need to distinguish between property being null or not set, e.g. {"key": null} vs {}.
I have a following model (see below) used to define optional json properties similar to null-able, but it can take any type.
It works for deserialization in tests, but when used inside controller method, then an InvalidOperationException is thrown before my code even runs, because ValidationVisitor from framework iterates over public properties of my model and hits Value property of instance with IsSet==false, which leads to exception.

Is there a way to tell ValidationVisitor to ignore certain property or define a custom validation property?

[JsonConverter(typeof(OptionalJsonConverter))]
public struct Optional<T>
{
    public static Optional<T> Undefined => default(Optional<T>);

    private T _value;

    public Optional(T value)
    {
        IsSet = true;
        _value = value;
    }

    public bool IsSet { get; private set; }

    public T Value
    {
        get
        {
            if (IsSet)
            {
                return _value;
            }
            throw new InvalidOperationException("The value is undefined.");
        }
        set
        {
            IsSet = true;
            _value = value;
        }
    }
}

Here is a stack-trace (minor parts of it is in Russian)

   в WebApi.Optional`1.get_Value() в D:\WebApi\Optional.cs:строка 30
   в Microsoft.Extensions.Internal.PropertyHelper.CallNullSafePropertyGetterByReference[TDeclaringType,TValue](ByRefFunc`2 getter, Object target)
   в Microsoft.AspNet.Mvc.ModelBinding.Validation.DefaultComplexObjectValidationStrategy.Enumerator.MoveNext()
   в Microsoft.AspNet.Mvc.ModelBinding.Validation.ValidationVisitor.VisitChildren(IValidationStrategy strategy)
   в Microsoft.AspNet.Mvc.ModelBinding.Validation.ValidationVisitor.VisitComplexType()
   в Microsoft.AspNet.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model)
   в Microsoft.AspNet.Mvc.ModelBinding.Validation.ValidationVisitor.VisitChildren(IValidationStrategy strategy)
   в Microsoft.AspNet.Mvc.ModelBinding.Validation.ValidationVisitor.VisitComplexType()
   в Microsoft.AspNet.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model)
   в Microsoft.AspNet.Mvc.ModelBinding.Validation.ValidationVisitor.Validate(ModelMetadata metadata, String key, Object model)
   в Microsoft.AspNet.Mvc.ModelBinding.Validation.DefaultObjectValidator.Validate(IModelValidatorProvider validatorProvider, ModelStateDictionary modelState, ValidationStateDictionary validationState, String prefix, Object model)
   в Microsoft.AspNet.Mvc.Controllers.DefaultControllerActionArgumentBinder.<BindModelAsync>d__6.MoveNext()
@rynowak
Copy link
Member

rynowak commented Feb 24, 2016

You can do this on RC2 by implementing something similar to https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationExcludeFilter.cs

And then

services.AddMvc(options => 
{
    options.ModelMetadataDetailsProviders.Add(new MyValidationExcludeFilter());
});

This is the mechanism the framework uses to avoid validation recursing into types like XmlDocument or JObject which have deep graphs, but nothing to validate.

The reason you would need to write your own class is that the build in ValidationExcludeFilter doesn't handle generics in the way that you'd want.

@NickAb
Copy link
Author

NickAb commented Feb 25, 2016

Thanks.

Is attribute based ignore filter in plans or ValidationExcludeFilter will have to be specified in service configuration?
Also, based on link only all instances of type can be ignored, not some of the instances based on some logic (like IsSet not being set). Is it correct and are there any plans to make exclude filters instance aware?

@rynowak rynowak reopened this Feb 25, 2016
@rynowak
Copy link
Member

rynowak commented Feb 25, 2016

Also, based on link only all instances of type can be ignored, not some of the instances based on some logic (like IsSet not being set). Is it correct and are there any plans to make exclude filters instance aware?

Yeah, you're right, this isn't really exactly what you're looking for. Reopening.

@Eilon Eilon removed the question label Mar 7, 2016
@Eilon Eilon added this to the Backlog milestone Mar 7, 2016
@Eilon
Copy link
Member

Eilon commented Mar 7, 2016

Not planned for this release.

@NickAb
Copy link
Author

NickAb commented May 5, 2016

As I understand I can override ValidateNode in ValidationVisitor.cs#L92 and the I will have to implement custom ObjectValidator that inherits from DefaultObjectValidator and replaces visitor in Validate method.
Custom ObjectValidator should be add using DI before calling AddMvc.
Is it correct?

Also, I was wondering I maybe there is a way to affect further validation by editing ValidationContext in first applied validation attribute? Are validation attributes guaranteed to be handled in same order as they applied? And if so, can ValidationContext be edited from one of the attributes to prevent further validation or something like that?

As a note, we still use RC1 as RC2 is not released yet.

@David-Zolv
Copy link

Hullo,

I've just found this issue and am facing the same problem.

PATCH method where I need to distinguish between property being null or not set, e.g. {"key": null} vs {}

ASP.NET Core 1.0.0 ("version": "1.0.0-preview2-003121")

Hitting the same validation visitor trying to get the value of my Optional class and getting an exception thrown at it:

   at Utils.Json.OptionalValues.Optional`1.get_Value() in D:\Dev\Utils\Json\OptionalValues\Optional.cs:line 52
   at Microsoft.Extensions.Internal.PropertyHelper.CallNullSafePropertyGetterByReference[TDeclaringType,TValue](ByRefFunc`2 getter, Object target)
   at Microsoft.AspNetCore.Mvc.Internal.DefaultComplexObjectValidationStrategy.Enumerator.MoveNext()
   at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitChildren(IValidationStrategy strategy)
   at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitComplexType()
   at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model)
   at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitChildren(IValidationStrategy strategy)
   at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitComplexType()
   at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model)
   at Microsoft.AspNetCore.Mvc.Internal.DefaultControllerArgumentBinder.<BindModelAsync>d__8.MoveNext()

From my Value getter:

public T Value
{
    get
    {
        if (HasValue)
        {
            return _value;
        }

        throw new InvalidOperationException("The value is undefined.");
    }
    set
    {
        HasValue = true;
        _value = value;
    }
}

Is there any update on how to customise the ValidationVisitor to have it check the HasValue property before trying to get the Value? Or as a temporary workaround ignore Optional altogether?

Kind regards,

@dougbu
Copy link
Member

dougbu commented Aug 24, 2016

Yes, ignore Optional altogether using a regular SuppressChildValidationMetadataProvider (new ValidationExcludeFilter name):

services.AddMvc(options => 
{
    options.ModelMetadataDetailsProviders.Add(
        new SuppressChildValidationMetadataProvider(typeof(Optional)));
});

@NickAb
Copy link
Author

NickAb commented Aug 24, 2016

But this will ignore all validation of Optional properties. What is desired is to skip property if it was not set and validate it if it was set.

@dougbu
Copy link
Member

dougbu commented Aug 24, 2016

Your question was

Or as a temporary workaround ignore Optional altogether?

My answer was "yes".

If you want to go further, combine the suppression with custom validation of the Optional type. For example, implement IValidatableObject in Optional and have the Validate(ValidationContext) method make decisions on properties to validate further.

@Eilon
Copy link
Member

Eilon commented Dec 28, 2016

Closing this bug in favor of #5642, which covers the broader scenarios.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants