-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Validation fixes for Blazor #14972
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Validation fixes for Blazor #14972
Conversation
pranavkm
commented
Oct 13, 2019
- Ensure validation result that are not associated with a member are recorded. Fixes CompareAttribute ignored with OnValidSubmit EditForm #10643
- Add support for showing model-specific errors to ValidationSummary
- Add support for nested validation and a more suitable CompareAttribute. Fixes Validation doesn't automatically traverse members #10526
9365a69
to
04d1ebe
Compare
eng/ProjectReferences.props
Outdated
@@ -137,6 +137,7 @@ | |||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor" ProjectPath="$(RepoRoot)src\Components\Blazor\Blazor\src\Microsoft.AspNetCore.Blazor.csproj" RefProjectPath="$(RepoRoot)src\Components\Blazor\Blazor\ref\Microsoft.AspNetCore.Blazor.csproj" /> | |||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.HttpClient" ProjectPath="$(RepoRoot)src\Components\Blazor\Http\src\Microsoft.AspNetCore.Blazor.HttpClient.csproj" RefProjectPath="$(RepoRoot)src\Components\Blazor\Http\ref\Microsoft.AspNetCore.Blazor.HttpClient.csproj" /> | |||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.Server" ProjectPath="$(RepoRoot)src\Components\Blazor\Server\src\Microsoft.AspNetCore.Blazor.Server.csproj" RefProjectPath="$(RepoRoot)src\Components\Blazor\Server\ref\Microsoft.AspNetCore.Blazor.Server.csproj" /> | |||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.DataAnnotations.Validation" ProjectPath="$(RepoRoot)src\Components\Blazor\Validation\src\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a new package we are RTMing in 3.1?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be an experimental package in the same vein as HttpClient
. The plan is to leave this as a preview \ experimental package in 3.x and collaborate with CoreFx to figure out solutions for 5.0.
/// <summary> | ||
/// A <see cref="ValidationAttribute"/> that compares two properties | ||
/// </summary> | ||
public sealed class BlazorCompareAttribute : CompareAttribute |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this because the original CompareAttribute doesn't include the member name?
If this is something that users are expected to use in their models, can we find a better name for this? BlazorCompareAttribute is highly confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this because the original CompareAttribute doesn't include the member name?
Yup. I'm open to suggestions. I kinda went with a whimsical name to indicate this is meant to go away at some point.
|
||
namespace Microsoft.AspNetCore.Components.Forms | ||
{ | ||
public class BlazorDataAnnotationsValidator : ComponentBase |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this meant to appear in the user markup?
I would try to pick a better name that makes it clear what it does, even if its a bit longer than the current identifier. Some options are:
- GraphDataAnnotationsValidator
- ObjectGraphDataAnnotationsValidator
- RecursiveDataAnnotationsValidator
In essence, something that clearly states what this does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to avoid Recursive
because we don't recurse in to the model like MVC does. We only navigate the object graph if you encounter the attribute. The other two sound neat, I'll run it by @danroth27
ValidateField(EditContext, _validationMessageStore, eventArgs.FieldIdentifier); | ||
} | ||
|
||
internal void ValidateObject(object value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't you have to keep track of the objects you've already inspected to prevent potential infinite recursion issues?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you're right. Unlike MVC, the user determines what objects the validator needs to traverse, so it's much more difficult to get in to a recursion. However, you could have a trivial model like this and get in to to recursion with no other way to represent it:
public class Person
{
[Required]
public string Name { get; set; }
[ValidateComplexType]
public Person Spouse { get; set; }
}
src/Components/test/E2ETest/Tests/FormsTestWithExperimentalValiator.cs
Outdated
Show resolved
Hide resolved
src/Components/test/testassets/BasicTestApp/FormsTest/ExperimentalValidationComponent.razor
Show resolved
Hide resolved
<option value="BasicTestApp.FormsTest.TypicalValidationComponent">Typical validation</option> | ||
<option value="BasicTestApp.FormsTest.TypicalValidationComponentUsingExperimentalValidator">Typical validation using experimental validator</option> | ||
<option value="BasicTestApp.FormsTest.ExperimentalValidationComponent">Experimental validation</option> | ||
<option value="BasicTestApp.GlobalizationBindCases">Globalization Bind Cases</option> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be better to duplicate the tests for the experimental validator completely instead of doing if...else
in some of the infrastructure if the goal here is that for 5.0 this becomes the de-facto framework behavior? That way then its a matter of removing code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought it'd be less work than maintaining two copies of these. Do you feel strongly about this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks good to me, there are some small details we might need to polish, but this is a good start.
b9c1d01
to
3a2a956
Compare
/// A <see cref="ValidationAttribute"/> that compares two properties | ||
/// </summary> | ||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] | ||
public sealed class ComparePropertyAttribute : CompareAttribute |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class should be in the System.ComponentModel.DataAnnotations project, not Blazor.
To add this validation to a property in MyApp.Shared
means the shared app has to reference the Blazor assemblies - which isn't good for a contracts assembly that could be used in different types of client apps.
@dougbu I removed the
|
This is expected because you didn't revert the change to the src/ project stating that it has an associated ref/ project. Get rid of all ref/-related changes unless a ref/ assembly is mandatory e.g. because the assembly will now ship in SharedFx. |
These parts of the PR seem totally clear and look perfectly good to proceed with as-is.
For this part, I'd be interested to discuss the design. The main thing for me is I'm surprised we want to add an "experimental" validator that people have to decide to use or not. What's the reason why we can't bake the support for a The idea of having a Could you clarify what level of consideration was given to baking this feature directly into 3.1? I think it would be really neat and clean if we could do that instead.
I agree that if we are shipping an unsupported "experimental validation" package then it's totally fine to put this in here. But as per my comment above, I'm still wondering if we could put your |
My plan is to work with CoreFx and move these types - |
This sounds like a good plan. If you ask me, I'd rather have these in core fx than have us own them and maintain them. If we change our minds, we can do the move in preview3 or after 3.1 |
} | ||
|
||
internal void ValidateObject(object value, HashSet<object> visited) | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have a test for this that validates object graphs with cycles? (I haven't been able to find it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me.
I'd rather keep this as an experimental package for preview2 and then figure out how to merge it.
88e7b7c
to
648b559
Compare
return; | ||
} | ||
|
||
var validationResults = new List<ValidationResult>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to add the following code....
if (value.GetType().Assembly == typeof(object).Assembly)
return;
Otherwise you will validate every string in a string[]
, and then every character of every string.
@danroth27 @SteveSandersonMS @rynowak I don't think this approach is ideal. It solves the problem of nested objects not being validated, however, if you are writing something like a wizard UI then you will want to only validate the current UI. This PR approach results in properties being validated that cannot be corrected by the user, or forces the developer to use DataAnnotationsValidator which has the problem of not being able to validate nested objects referenced by inputs. I suggested a potential solution here This proposal would not only solve the nested-validation problem, but would solve #14426 too (validated fields based on the UI rather than the object). Please do not go with the approach in this PR. It adds an alternative inflexible solution, resulting in two inflexible solutions where neither of them are suitable in all use cases, and neither of them are suitable for Wizard UIs.
If we go with this approach I think we are going to see questions about how to validate wizard UI appearing repeatedly - and the suggestion to write a class per wizard step will be frowned upon because 1: It's more work to write classes for your UI than it is to just use the object that is sent to the Server. Please take another look at my suggestion, I gave consideration to a solution that works in all situations. Even if you don't use my approach verbatim, it might give you an alternative idea. Either way, the approach in this PR is going to cause problems. Wizard UIs are very common, and Blazor will not support their development without pain to the developer. |
Fogive me if I am asking in the wrong place, but I need to validate nested objects and can't find anything in the official docs, and all threads I find here have been closed or state that the work was completed in 3.1. I have 3.1 and don't seem to be able to validate nested object properties. |
@benm-eras it's easy for us to miss comments on closed issues \ PRs. I'd recommend filing a new issue the next time around. Docs for validation are available here: https://docs.microsoft.com/en-us/aspnet/core/blazor/forms-validation?view=aspnetcore-3.1#nested-models-collection-types-and-complex-types |
@benm-eras my library will validate a tree, specific properties, and even integrate with FluentValidation. https://github.com/mrpmorris/blazor-validation/blob/master/README.md |