MudForm: Support For expressions refering to non-nullable properties#9180
MudForm: Support For expressions refering to non-nullable properties#9180henon merged 5 commits intoMudBlazor:v6from
For expressions refering to non-nullable properties#9180Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## v6 #9180 +/- ##
==========================================
- Coverage 88.82% 88.79% -0.03%
==========================================
Files 416 394 -22
Lines 12356 12302 -54
Branches 2458 2449 -9
==========================================
- Hits 10975 10924 -51
+ Misses 850 847 -3
Partials 531 531 ☔ View full report in Codecov by Sentry. |
|
Sorry, I did not mean to push the second set of changes with commit message "Add handling for validation expressions that refer to non-nullable properties" onto this PR, I've reverted that commit. |
|
@henon should this include a couple of test cases? |
|
Yes please add a test that fails without the fix and works with it. |
|
I've found one more spot where this issue arises (ExpressionExtensions.GetLabelString), added a fix as well as unit tests for all changes that fail without them and suceed with them. Let me expand a little bit more on the reason for this PR now that I have a better understanding of the code. Some components, such as MudDatePicker, only allow binding to a DateTime? field. However, if the corressponding database field is marked as NOT NULL, tools such as EF Core Power Tools generate a non-nullable backing field This component can then be bound to the non-nullable backing field: Note how the type for the For property has to be DateTime? because and MudDatePicker inherits from MudPicker with T=DateTime?: So basically another solution to this problem could be introducing a separate type parameter into MudFormComponent to enable inheriting classes to set differing types for the value and the for fields: |
Yes I agree. There is always the option of composition (wrapping) vs inheriting which should also solve the problem. |
|
Just noticed this is a PR against v6. That makes it even more reasonable not to do any complicated PRs. |
|
To get the code quality fixed please execute |
I think It would be better to do as separate PR and update this one, otherwise too much unrelated changes will be in the PR. I will do that.
Yes, I think we should leave it for v6 only for now. Since we plan to rework the validation anyway, we also have this #9016 |
Wrapping is better in case of Blazor, otherwise you will hit this problem #9052 (comment) |
| "Trimming", | ||
| "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", | ||
| Justification = "Application code does not get trimmed. We expect the members in the expression to not be trimmed.")] | ||
| static Func<object, object> CreateAccessor((Type model, string member) arg) |
There was a problem hiding this comment.
I'm not that big of an expert in Expression area, but would something like this work?
static Func<object, object> CreateAccessor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] Type modelType, string memberName)
{
var parameter = Expression.Parameter(typeof(object), "value");
var memberInfo = modelType.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
?? (MemberInfo?)modelType.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (memberInfo == null)
{
throw new ArgumentException($"No property or field named '{memberName}' found on type '{modelType.FullName}'.");
}
Expression expression = Expression.Convert(parameter, modelType);
if (memberInfo is PropertyInfo propertyInfo)
{
expression = Expression.Property(expression, propertyInfo);
}
else if (memberInfo is FieldInfo fieldInfo)
{
expression = Expression.Field(expression, fieldInfo);
}
expression = Expression.Convert(expression, typeof(object));
var lambda = Expression.Lambda<Func<object, object>>(expression, parameter);
var func = lambda.Compile();
return func;
}To get rid of UnconditionalSuppressMessage?
I think the Expression.PropertyOrField is doing more or less the same, but it's using the unsafe version of GetProperty and GetField that doesn't pass the Type information as it just doesn't have it in it's definition.
Not sure where this thread came from, I am using wrapping not inheritance as you can see the code in the "Describe alternatives you've considered" section of #8931. The CreateAccessor local func comes from the main branch of the aspnetcore repo (defined inside GetModelFromMemberAccess) here: https://github.com/dotnet/aspnetcore/blob/main/src/Components/Forms/src/FieldIdentifier.cs I should have noted this in the comments. |
The question wasn't really about its origin, but rather whether my suggested alternative would work, as it's "safer". However, I'm not going to insist on it since the risk of the application code being trimmed is low. @henon, this is ready for merge. |
For expressions refering to non-nullable properties
|
Thanks everyone |
|
Made v6.21.0 release with this change. |
Description
If a For validation property refers to a non-nullable field, then currently the framework throws errors such as "'Unable to cast object of type 'System.Linq.Expressions.UnaryExpression' to type 'System.Linq.Expressions.MemberExpression'."
This PR fixes this by properly handling validation Expressions that refer to non-nullable properties, more specifically, where For.Body is not a MemberExpression but a UnaryExpression and For.Body.Operand is the MemberExpression.
Resolves #8931
How Has This Been Tested?
visually
Type of Changes
Checklist
dev).Submitting to branch v6 since I am working on a live project and v7 is not ready for production. I can create a new PR for dev if this PR is accepted.