I am attempting to use Fluent validator with DataGrid in Editmode Normal. I want to show the tooltips for each error returned from validation and its only somewhat working. In the photo I simply click Add and then Update, I expect a tooltip for Type, Name, Code, and Hierarchy Name to show up but only the last one (Hierarchy Name) shows.
Once I fill Hierarchy Name, then this happens
Along with other strange behaviour
Fluent Validator for Model: ----------------------------------------------------------------------------------------------------
public FluentValidation.Results.ValidationResult ValidationResults()
{
var validator = new FluentValidator();
return validator.Validate(this);
}
public class FluentValidator : AbstractValidator<CostCenter>
{
public FluentValidator()
{
RuleFor(x => x.CostCenterType).NotEmpty().WithMessage("Type Required");
RuleFor(x => x.Code).NotEmpty().WithMessage("Code Required");
RuleFor(x => x.Code).MaximumLength(PayableCodeBase.MAX_CODE_LENGTH).WithMessage($"Code must be less than {PayableCodeBase.MAX_CODE_LENGTH} characters");
RuleFor(x => x.Name).NotEmpty().WithMessage("Name Required");
RuleFor(x => x.Name).MaximumLength(PayableCodeBase.MAX_NAME_LENGTH).WithMessage($"Name must be less than {PayableCodeBase.MAX_NAME_LENGTH} characters");
RuleFor(x => x.HierarchyDetail).NotNull().SetValidator(new HierarchyDetailNameValidator());
}
}
DataGrid Code: ----------------------------------------------------------------------------------------------------
<SfGrid @ref="_sfGrid" Height="100%" DataSource="_costCenters" EnableVirtualization="true" EnableVirtualMaskRow="true" AllowSorting="true" AllowMultiSorting="true" AllowFiltering="true" Toolbar="_toolbaritems" >
<GridEditSettings AllowAdding="true" AllowEditing="true" AllowDeleting="true" ShowDeleteConfirmDialog="true" AllowEditOnDblClick="true" Mode="Syncfusion.Blazor.Grids.EditMode.Normal">
<Validator>
@{
<Ep.WebApp.Components.GridFluentValidator Context="(ValidatorTemplateContext)context" TValidator="CostCenter.FluentValidator"></Ep.WebApp.Components.GridFluentValidator>
}
</Validator>
</GridEditSettings>
<GridEvents TValue="CostCenter" OnActionBegin="OnActionBeginHandlerAsync" OnActionComplete="OnActionCompleteHandlerAsync" OnActionFailure="OnActionFailureHandler" OnToolbarClick="ToolbarClickHandler"></GridEvents>
<GridFilterSettings Type="Syncfusion.Blazor.Grids.FilterType.FilterBar"></GridFilterSettings>
<GridColumns>
<GridColumn Field="@nameof(CostCenter.Id)" HeaderText="Guid" Visible="false" IsPrimaryKey="true"></GridColumn>
<GridColumn Field="@nameof(CostCenter.CostCenterType)" HeaderText="Type">
<EditTemplate>
<SfDropDownList ID="CostCenterType" AllowFiltering="true" TValue="CostCenter.CostCenterTypes?" TItem="string" DataSource="@_costCenterTypes" @bind-Value="(context as CostCenter).CostCenterType"></SfDropDownList>
</EditTemplate>
<FilterTemplate>
<SfDropDownList ShowClearButton="true" TValue="string" TItem="string" DataSource="@_costCenterTypes" Value="@((string)(context as PredicateModel).Value)">
<DropDownListEvents TItem="string" ValueChange="FilterCostCenterType" TValue="string"></DropDownListEvents>
</SfDropDownList>
</FilterTemplate>
</GridColumn>
<GridColumn Field="@nameof(CostCenter.Code)" HeaderText="Code" FilterSettings="@(new FilterSettings{ Operator = Operator.Contains })"></GridColumn>
<GridColumn Field="@nameof(CostCenter.Name)" HeaderText="Name" FilterSettings="@(new FilterSettings{ Operator = Operator.Contains })"></GridColumn>
<GridColumn Field="HierarchyDetail.Name" HeaderText="Hierarchy Name" FilterSettings="@(new FilterSettings{ Operator = Operator.Contains })">
<EditTemplate>
@{
<SfDropDownList ID="HierarchyDetail___Name" AllowFiltering="true" TValue="string" TItem="string" DataSource="_hierarchyDetailNames" @bind-Value="@((context as CostCenter).HierarchyDetail.Name)">
</SfDropDownList>
}
</EditTemplate>
</GridColumn>
...
</SfGrid>
Fluent Validator and DataGrid middleman (pulled from here: https://www.syncfusion.com/faq/blazor/forms-and-validation/how-do-i-enable-validation-without-using-the-dataannotationvalidator ) : ------------------------------------------------------------------------------------------------
public class GridFluentValidator<TValidator> : ComponentBase where TValidator : IValidator, new()
{
private readonly static char[] _separators = new[] { '.', '[' };
private TValidator _validator;
[Parameter]
public ValidatorTemplateContext Context { get; set; }
[CascadingParameter] private EditContext _editContext { get; set; }
protected override void OnInitialized()
{
_validator = new TValidator();
var messages = new ValidationMessageStore(_editContext);
// Revalidate when any field changes, or if the entire form requests validation
// (e.g., on submit)
_editContext.OnFieldChanged += (sender, eventArgs)
=> ValidateModel((EditContext)sender, messages);
_editContext.OnValidationRequested += (sender, eventArgs)
=> ValidateModel((EditContext)sender, messages);
}
private void ValidateModel(EditContext editContext, ValidationMessageStore messages)
{
var model = new ValidationContext<object>(editContext.Model);
var validationResult = _validator.Validate(model);
messages.Clear();
foreach (var error in validationResult.Errors)
{
var fieldIdentifier = ToFieldIdentifier(editContext, error.PropertyName);
messages.Add(fieldIdentifier, error.ErrorMessage);
Context.ShowValidationMessage(error.PropertyName.Replace(".", "___"), false, error.ErrorMessage);
}
_editContext.NotifyValidationStateChanged();
}
private static FieldIdentifier ToFieldIdentifier(EditContext editContext, string propertyPath)
{
// This method parses property paths like 'SomeProp.MyCollection[123].ChildProp'
// and returns a FieldIdentifier which is an (instance, propName) pair. For example,
// it would return the pair (SomeProp.MyCollection[123], "ChildProp"). It traverses
// as far into the propertyPath as it can go until it finds any null instance.
var obj = editContext.Model;
while (true)
{
var nextTokenEnd = propertyPath.IndexOfAny(_separators);
if (nextTokenEnd < 0)
{
return new FieldIdentifier(obj, propertyPath);
}
var nextToken = propertyPath.Substring(0, nextTokenEnd);
propertyPath = propertyPath.Substring(nextTokenEnd + 1);
object newObj;
if (nextToken.EndsWith("]"))
{
// It's an indexer
// This code assumes C# conventions (one indexer named Item with one param)
nextToken = nextToken.Substring(0, nextToken.Length - 1);
var prop = obj.GetType().GetProperty("Item");
var indexerType = prop.GetIndexParameters()[0].ParameterType;
var indexerValue = Convert.ChangeType(nextToken, indexerType);
newObj = prop.GetValue(obj, new object[] { indexerValue });
}
else
{
// It's a regular property
var prop = obj.GetType().GetProperty(nextToken);
if (prop == null)
{
throw new InvalidOperationException($"Could not find property named {nextToken} on object of type {obj.GetType().FullName}.");
}
newObj = prop.GetValue(obj);
}
if (newObj == null)
{
// This is as far as we can go
return new FieldIdentifier(obj, nextToken);
}
obj = newObj;
}
}
}
Hi Glenn ,
Before proceeding with
the reporting problem, we require some additional clarification from your end.
Please share the below details to proceed further at our end.
Above-requested details will be very helpful in validating the reported query at our end and providing a solution as early as possible.
Regards,
Prathap S
Thank you for your reply Prathap,
I took your attached sample and made a few changes, although I don't have those little bubble popups as shown in the original post, I have a working sample now. The below image shows the functionality I was able to achieve which I was then able to take and apply to my original code.
Thank you,
Glenn
Thanks for the update. We are happy to hear that the issue has been resolved on your end.
We are closing the thread now.