Skip to content
This repository was archived by the owner on Nov 7, 2018. It is now read-only.

Add DataAnnotations based validation #272

Merged
merged 8 commits into from
Aug 28, 2018
Merged

Add DataAnnotations based validation #272

merged 8 commits into from
Aug 28, 2018

Conversation

HaoK
Copy link
Member

@HaoK HaoK commented Aug 7, 2018

Add DataAnnotations validator support: (dotnet/aspnetcore#3385)

Basic support works pretty much as expected:

        [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
        public class FromAttribute : ValidationAttribute
        {
            public string Accepted { get; set; }

            public override bool IsValid(object value)
                => value == null || value.ToString() == Accepted;
        }

        private class AnnotatedOptions
        {
            [Required]
            public string Required { get; set; }

            [StringLength(5, ErrorMessage = "Too long.")]
            public string StringLength { get; set; }

            [Range(-5, 5, ErrorMessage = "Out of range.")]
            public int IntRange { get; set; }

            [From(Accepted = "USA")]
            public string Custom { get; set; }
        }

        [Fact]
        public void CanValidateDataAnnotations()
        {
            var services = new ServiceCollection();
            services.AddOptions<AnnotatedOptions>()
                .Configure(o =>
                {
                    o.StringLength = "111111";
                    o.IntRange = 10;
                    o.Custom = "nowhere";
                })
                .ValidateDataAnnotations();

            var sp = services.BuildServiceProvider();

            var error = Assert.Throws<OptionsValidationException>(() => sp.GetRequiredService<IOptions<AnnotatedOptions>>().Value);
            ValidateFailure<AnnotatedOptions>(error, Options.DefaultName, "The Required field is required. : Too long. : Out of range. : The field Custom is invalid.");
        }

Basically you get a single error message with all of the data validation errors smushed together, since its considered a single options validator.

Thoughts? @ajcvickers @davidfowl @Eilon @DamianEdwards

@HaoK HaoK added this to the 2.0.0-preview2 milestone Aug 7, 2018
@HaoK
Copy link
Member Author

HaoK commented Aug 7, 2018

We can split this out into a separate Options.DataAnnotations package if we still care about keeping dependencies of the core options package lean...

@ajcvickers
Copy link

Code seems good, but not sure about taking a dependency on DataAnnotations--we probably shouldn't do it now unless we want to keep it in 3.0. But if other things already have the dependency and will continue to have it in 3.0, then their probably won't be a problem.

@davidfowl
Copy link
Member

No not in core options. This should be another package reference unlesss we make it part of net standard

@DamianEdwards
Copy link
Member

+1 for new package layer

@HaoK
Copy link
Member Author

HaoK commented Aug 16, 2018

Microsoft.Extensions.Options.DataAnnotations for the new package name?

Copy link
Contributor

@Eilon Eilon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks pretty straightforward and simple. Just some doc comment comments!

namespace Microsoft.Extensions.Options
{
/// <summary>
/// Implementation of <see cref="IValidateOptions{TOptions}"/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can maybe elaborate on this a bit more? This comment is just stating what can be seen from the type declaration anyway.

/// <typeparam name="TOptions">The instance being validated.</typeparam>
public class DataAnnotationValidateOptions<TOptions> : IValidateOptions<TOptions> where TOptions : class
{
public DataAnnotationValidateOptions(string name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need doc comments on all public APIs.

public static class OptionsBuilderDataAnnotationsExtensions
{
/// <summary>
/// Register this options instance for the built in DataAnnotationsAttribute
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The term DataAnnotationsAttribute looks like it's being used as a type name, but it isn't a type. Rephrase somehow?

@HaoK
Copy link
Member Author

HaoK commented Aug 24, 2018

Fixed new doc comments and removed the doc comment excludes so added all the missing doc comments as well

}


var errors = String.Join(" : ", validationResults.Select(r => r.ErrorMessage));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit icky. I'm guessing there's no "list of errors" that we can send a list of these failures to? How about put one on each line instead?

"Validation of the object XYZ failed:
{reason 1}
{reason 2}
{reason 3}"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be also useful here to use ValidationResult.MemberNames and format that in some way so that the user knows which property(s) failed.

Copy link
Member Author

@HaoK HaoK Aug 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I'll try to clean this up, yeah the data annotation validations are treated as a single validaiton failure along side an arbitrary amount of other validators, which is why it gets turned into a single string, since it potentially would need to be aggregated with other non data-annotation based validation errors.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think at least putting 1 per line would be a good separation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifically only for data annotations errors? or all of the aggregated errors as well? I'll add a new test that demonstrates what it looks like so its easier to review in the PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah let's see it in the test then I'll review.

@@ -3,7 +3,7 @@
<PropertyGroup>
<Description>Provides additional configuration specific functionality related to Options.</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<NoWarn>$(NoWarn);CS1591</NoWarn>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YES! Another project finally has full docs!

@HaoK
Copy link
Member Author

HaoK commented Aug 27, 2018

Updated the new errors are more wordy now but hopefully more clear:

"DataAnnotation validation failed for members {Required} with error 'The Required field is required.'.
DataAnnotation validation failed for members {StringLength} with error 'Too long.'.
DataAnnotation validation failed for members {IntRange} with error 'Out of range.'.
DataAnnotation validation failed for members {Custom} with error 'The field Custom is invalid.'."

Copy link
Contributor

@Eilon Eilon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few small comments, then looks good to me.

@@ -49,7 +49,10 @@ public ValidateOptionsResult Validate(string name, TOptions options)
}


var errors = String.Join(" : ", validationResults.Select(r => r.ErrorMessage));
var errors = String.Join(Environment.NewLine,
validationResults.Select(r => "DataAnnotation validation failed for members {" +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe get rid of the {? A bit too nerdy 😄

var errors = String.Join(Environment.NewLine,
validationResults.Select(r => "DataAnnotation validation failed for members {" +
String.Join(",", r.MemberNames) +
"} with error '" + r.ErrorMessage + "'."));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with *the* error

DataAnnotation validation failed for members {IntRange} with error 'Out of range.'.
DataAnnotation validation failed for members {Custom} with error 'The field Custom is invalid.'.",
"I don't want to go to nowhere!");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a case where there is more than 1 member as well? Maybe CompareValidator does that? Or write a dummy custom validator that does that?

@HaoK
Copy link
Member Author

HaoK commented Aug 28, 2018

Just as FYI, multi member error looks like:

DataAnnotation validation failed for members Dep1,Dep2 with the error 'Dep1 != Dep2'.

Copy link
Contributor

@Eilon Eilon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK literally just asking for 1 more space, then good to go as far as I'm concerned. THanks!

return ValidateOptionsResult.Fail(errors);
return ValidateOptionsResult.Fail(String.Join(Environment.NewLine,
validationResults.Select(r => "DataAnnotation validation failed for members " +
String.Join(",", r.MemberNames) +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK last nit, a space after the comma 😄

@HaoK HaoK merged commit dfcde1b into release/2.2 Aug 28, 2018
@HaoK HaoK deleted the haok/data branch August 28, 2018 22:46
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants