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
Allow MudDatePicker to bind to non-nullable DateTime field with working validation #8931
Comments
Hi. It has been discussed multiple times where people requested I always recommend against directly using database entities as models with UI components, except for DataGrid (only if it can directly work with EF). It's not a good practice. Instead, always opt to map the entity to a DTO and vice versa. This can save you from bugs and reduce debugging efforts in the future. Examples of problems you can run into when using EF entities include over-posting attacks, the N+1 problem, and tight coupling between your UI form data and database tables etc etc etc. |
FluentUI for example uses only |
First of all I appreciate MudBlazor and I think it holds promise to become a great library. I also appreciate your quick response and your suggestions to always opt to map the entity to a DTO. I understand that this is a great approach for many projects, unfortunately it is not a good fit for the one I am working on now. Getting back to the issue, it is not my place to argue about your design decisions, I can only explain my case as a user of MudBlazor. I am in fact trying to use MudDatePicker in a DataGrid, but doing so is taking a lot of work at the moment (for example, configuring a proper filtertemplate to enable the user to filter for rows where the date is on or past a given period etc.). The issue I raised here was another blocker I ran into and I think many other users will too. If I understand the official EF Core documentation right, it is accepted convention to back required properties with non-nullable fields. I would argue that having separate In any case I have no problem using the method I described above where I create a custom component that can be bound to a non-nullable property. Others can create their own custom components if they need to bind to DateTimeOffset or Date or Time or whatever. I think that's a good approach, MudBlazor gives the basic components and it is left up to users to implement their more-specific use cases. I would however ask that the two places where you cast If you are unwilling to do the above, then at least allow us to override
With this approach, the MudDatePicker would still be bound to a DateTime? property, so you would not have to deal with any issues with binding to a non-nullable property. It is just the For attribute that would be set to the true underlying DateTime field. This way you would at least allow us to work around the problem. As a side-note, I also played around with creating a |
Now that you've mentioned [Parameter]
public DateTime? Date
{
get => _value;
set => SetDateAsync(value, true);
} and everything revolves around that single value; you can't have two for validation. Since this logic is delicate, we aren't planning to change it for now; the validation logic rewrite has been pushed to the v8 milestone.
Isn't this already possible? public class MyMudFormComponent<T, U> : MudFormComponent<T, U>
{
public MyMudFormComponent(MudBlazor.Converter<T, U> converter) : base(converter)
{
}
protected override void OnParametersSet()
{
base.OnParametersSet();
}
} Unless you mean something else. However, as I mentioned, this is being addressed in #8567. We don't have a solution here and now, except for simply wrapping cc: @henon if you have anything to add. |
FYI @ArieGato your PR should fix this issue. As for validating multiple values, I have already noticed that this is a shortcoming of our validation system which needs to be changed in v8 for components such as |
By "allow us to override MudFormComponent.OnParametersSet()" I mean that I could work around this issue if the following were set as protected instead of private in MudFormComponent.cs:
Specifically, then the following approach would work without throwing "System.InvalidCastException: 'Unable to cast object of type 'System.Linq.Expressions.UnaryExpression' to type 'System.Linq.Expressions.MemberExpression'.' on line 654 of MudFormComponent.cs and errors in several other places. (1) I create a component that inherits from MudDatePicker but overrides OnParametersSet, correctly handling the case where For= points to a non-nullable DateTime field:
ValidateModelWithFullPathOfMember only needs to be overridden because ExpressionExtensions.GetFullPathOfMember needs a one-line fix to be able to handle UnaryExpressions. (2) GetFullPathOfMemberV2 is a fixed version of GetFullPathOfMember defined in a helper class:
The only change is this: (3) ZenDatePicker.razor now builds on ZenDatePickerBase.cs:
(4) This can now be used in a form or dialog to bind either nullable or non-nullable datetime backing property:
Works great, but for this to work I need the fields/members/methods I name at the top to be set as protected and not private. |
In general, I've run into a lot of walls with MudBlazor where I could easily work around a problem but I cannot because so many properties/fields/methods are market private or internal instead of protected. |
This is just the basics of encapsulation. Without it, we would hinder the framework's ability to evolve without every change becoming a breaking one. Most of them can be removed in v8. Perhaps some of them make sense to be protected, like the upd: Haha @henon, we replied at same time almost the same way. |
@lieszkol The reason why we want to keep as many internals private or internal is because making them protected de-facto makes them public and we can't rename, change or remove them w/o a breaking change. That's the rationale behind keeping the public API surface as small as possible. That being said, we can make some things protected if you need that, no problem. The trade-off for giving more access to internals would be that we can't guarantee to keep the protected API stable to not constrict us when we need to change internals. But of course it would be best for everyone if we'd just fix the underlying problem (if possible) instead of keeping the component in a less-than-optimal state and have every user work around it. As said above a PR is in the works to solve exactly this problem. #8567 |
Feature request type
Enhance component
Component name
MudDatePicker
Is your feature request related to a problem?
A similar topic has been raised as a discussion on github a year ago.
Using EFCorePowerTools & database first approach, after reverse engineering the DB,
DATETIME NOT NULL
columns are generated as non-nullable datetimes:public DateTime InsertedOn { get; set; }
I am new to MudBlazor & perhaps I'm on the wrong track, but I've spent days trying to get MudDatePicker to work with such an entity. I have not been able to configure EFCorePowerTools to generate the field as DateTime? and I'm not convinced that is a good method anyway, since such a field always has a value & the UI library shouldn't dictate the semantics of the model.
Describe the solution you'd like
My request is for MudDatePicker to natively support binding to a DateTime field, including properly handling data validation with Fluent validators when For tag references a non-nullable field.
Have you seen this feature anywhere else?
Most other Blazor UI libs support this scenario.
Describe alternatives you've considered
I was able to create a component that can be bound to either a nullable or a non-nullable DateTime field, "ZenDatePicker.razor", as follows:
This can then be used to bind either to nullable or non-nullable datetime fields:
InsertedOn is just used as an example here, so I'd prefer to avoid arguments over whether a field such as InsertedOn should be set in the database and not on the client side etc.
The above workaround works fine except for one issue. If
For="@(() => entity.InsertedOn)"
is set then MudBlazor throws exceptions in several places:(1) ExpressionExtensions.cs:
this could be fixed by updating the above line to:
var memberExpression = (expression.Body as MemberExpression ?? (MemberExpression)((UnaryExpression)expression.Body).Operand);
(2) MudFormComponent.cs:
the first error is thrown at
var expression = (MemberExpression)For.Body;
, this could be fixed by updating the last line to:var expression = (For.Body as MemberExpression ?? (MemberExpression)((UnaryExpression)For.Body).Operand);
However, a second error is thrown at
FieldIdentifier.Create(For);
which I have no idea how to fix.The problem is that FieldIdentifier.Create calls ParseAccessor:
But the unaryExpression.Type == typeof(object) fails, so accessorBody = unaryExpression.Operand; is never called and an error is thrown: "The provided expression contains a UnaryExpression which is not supported. FieldIdentifier only supports simple member accessors (fields, properties) of an object."
Pull Request
Code of Conduct
The text was updated successfully, but these errors were encountered: