3

I have an extension method to check if a specific field is valid or not.

        public static bool IsValid(this EditContext editContext, string fieldName)
        {
            var fieldIdentifier = editContext.Field(fieldName);

            editContext.NotifyFieldChanged(fieldIdentifier);

            var result = !editContext.GetValidationMessages(fieldIdentifier).Any();

            Console.WriteLine($"{fieldName}  : {result}");

            return result;
        }

It's working fine for non-complex fields but not for complex fields. e.g

My model is

    public class RegisterCompanyRequest
    {
        [Required(ErrorMessage = "Company Name is required")]
        [MaxLength(200, ErrorMessage = "Maximum allowed length for company name is 200 characters")]
        public string NameEn { get; set; }

        [ValidateComplexType]
        public List<CompanyLocation> CompanyLocations { get; set; }

        [ValidateComplexType]
        public CompanyAdminInfo CompanyAdminInfo { get; set; } = new CompanyAdminInfo();
    }

    public class CompanyAdminInfo
    {
        [Required(ErrorMessage = "Full name is required")]
        [MaxLength(200, ErrorMessage = "Maximum allowed length for full name is 200 characters")]
        public string FullName { get; set; }
    }

    public class CompanyLocation
    {
        public int Id { get; set; }

        [Required]
        [MaxLength(200, ErrorMessage = "Maximum allowed length for location name is 200 characters")]
        public string Name { get; set; }
    }

and if I pass that method NameEn it returns false if validation fails and true otherwise, but if i pass it CompanyLocations[0].Name or CompanyAdminInfo.FullName it always return true.

How can I achieve the same functionality for nested objects in my Model?

To Reproduce: Copy the below code to new blazor WASM project. also install the following package to validate complex type (Install-Package Microsoft.AspNetCore.Components.DataAnnotations.Validation -Version 3.2.0-rc1.20223.4)

@page "/"
@using System.ComponentModel.DataAnnotations

<p>"Name" : @company.Name <span>@nameValid</span> </p> 
<p>"Admin Name" : @company.Admin.Name <span>@adminNameValid</span> </p>

<EditForm EditContext="editContext">
    <ObjectGraphDataAnnotationsValidator />

    <input type="text" @bind-value="@company.Name"/>

    <input type="text" @bind-value="@company.Admin.Name"/>

    <button @onclick="Validate" type="submit">Validate</button>
</EditForm>


@code
{
    private bool nameValid, adminNameValid;
    private Company company = new Company();

    EditContext editContext { get; set; }

    protected override async Task OnInitializedAsync()
    {
        editContext = new(company);
    }

    private async Task Validate()
    {
        var isValid = IsValid(editContext, "Name");

        nameValid = isValid;

        isValid = IsValid(editContext, "Admin.Name");

        adminNameValid = isValid;
    }

    public bool IsValid(EditContext editContext, string fieldName)
    {
        var fieldIdentifier = editContext.Field(fieldName);

        editContext.NotifyFieldChanged(fieldIdentifier);

        return !editContext.GetValidationMessages(fieldIdentifier).Any();
    }

    public class Company
    {
        [Required]
        public string Name { get; set; }

        [ValidateComplexType] /*(Install-Package Microsoft.AspNetCore.Components.DataAnnotations.Validation -Version 3.2.0-rc1.20223.4)*/
        public Admin Admin { get; set; } = new Admin();
    }

    public class Admin
    {
        [Required]
        public string Name { get; set; }
    }

}
6
  • Do you have a Minimal Reproducible Sample? ( How to create a Minimal, Reproducible Example ) Commented May 30, 2022 at 8:00
  • @daniherrera Thank you for your reply, I have added the code to reproduce the issue, Many thanks. Commented May 30, 2022 at 8:26
  • @daniherrera the other question you mentioned, for him ValidateComplexType is not working, It's working for me. so is there any way to get error messages for a nested object? Commented May 30, 2022 at 10:14
  • I posted an answer. Sorry about delay. Commented May 30, 2022 at 20:59
  • Hi Zubair, I came on this a "little" late (20 days), but been away. Do you still want suggestions on how to achieve this? Commented Jun 18, 2022 at 18:23

3 Answers 3

1

I guess, do you have two antipatterns in your code.

First one is to call, by hand, editContext.NotifyFieldChanged(fieldIdentifier);. Let Blazor deal with notifications.

The second one is with Submit, in my opinion, you should to avoid calling functions on submit button. Instead of that, use OnValidSubmit or OnInvalidSubmit at EditForm component level, or bind a function to editContext.OnValidationStateChanged like in my running sample bellow.

@page "/"
@using System.ComponentModel.DataAnnotations
@implements IDisposable

<EditForm EditContext="editContext" OnValidSubmit="ValidSubmit">
    
    <ObjectGraphDataAnnotationsValidator />
    
    <InputText type="text" @bind-Value="@company.Name" />
    <ValidationMessage For="@(() => company.Name)" />

    <InputText type="text" @bind-Value="@company.Admin.Name" />
    <ValidationMessage For="@(() => company.Admin.Name)" />
    
    <button type="submit">Validate</button>
    
</EditForm>

<p>"Name" : @company.Name <span>@nameValid</span> </p> 
<p>"Admin Name" : @company.Admin.Name <span>@adminNameValid</span> </p>

@code
{
    private bool? nameValid, adminNameValid;
    private Company company = new Company();

    EditContext editContext { get; set; } = default!;

    protected override void OnInitialized()
    {
        editContext = new(company);
        editContext.OnValidationStateChanged += HandleValidationStateChanged;
    }

    private void HandleValidationStateChanged(object? o, ValidationStateChangedEventArgs args) 
    {
        var name_field = FieldIdentifier.Create( () => company.Name  );
        nameValid = IsValid(editContext, name_field);

        var admin_name_field = FieldIdentifier.Create( () => company.Admin.Name  );
        adminNameValid = IsValid(editContext, admin_name_field);

    }

    private void ValidSubmit()
    {
        nameValid = true;
        adminNameValid = true;
    }

    public bool IsValid(EditContext editContext, FieldIdentifier fieldIdentifier)
        =>
        !editContext.GetValidationMessages(fieldIdentifier).Any();

    public class Company
    {
        [Required]
        [StringLength(3, ErrorMessage = "Identifier too long (3 character limit).")]
        public string Name { get; set; } = default!;

        [ValidateComplexType] /*(Install-Package Microsoft.AspNetCore.Components.DataAnnotations.Validation -Version 3.2.0-rc1.20223.4)*/
        public Admin Admin { get; set; } = new Admin();
    }

    public class Admin
    {
        [Required]
        [StringLength(3, ErrorMessage = "Identifier too long (3 character limit).")]
        public string Name { get; set; } = default!;
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnValidationStateChanged -= HandleValidationStateChanged;
        }
    }

}
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you for the answer, actually, the code I mentioned above is just to reproduce the issue. I am using OnValidSubmit, my actual scenario is that I have a form with many fields so I created different steps to complete each step with some fields, and have a Next button to move to the next step. so when the user clicks on the Next button I check if all fields in the current step are valid. your example helped, Instead of passing the field name to edit context. Field, now as in your example I am creating FieldIdentifier using FieldIdentifier.Create(Expression<Func<T>>) and it worked. Thanks
Yes, move to FieldIdentifier.Create(Expression<Func<T>>) solves the problem to locate form field status. The others part of the answer are also important (like using editForm events to track field changes or validation changes).
actually, I want to check validation for groups of fields (Multiple groups on different conditions) without submitting or before moving away from that screen, so that's why i am doing it manually.
0

Created an extension method.

    public static class EditContextHelper
    {
        public static bool IsValid(this EditContext editContext, Expression<Func<Object>> field)
        {
            var fieldIdentifier = FieldIdentifier.Create(field);

            editContext.NotifyFieldChanged(fieldIdentifier);

            return !editContext.GetValidationMessages(fieldIdentifier).Any();
        }
    }

using it like,

var field = model.CompanyLocations[0].Name
var result = EditContext.IsValid(field);

in my case i am using array to loop through specif fields like this.

var isValid = false;
var fieldsToValidate = new Expression<Func<Object>>[]
       {
           () => model.NameEn, () => (model.Address), () => model.Lattitude,
           () => model.Longitude , () => (model.Tel1), () => (model.Tel2), () => (model.Mobile), () => (model.Whatsapp),
           () => (model.Email), () => (model.Url), () => (model.TaxNumber) 
         };

foreach (var field in fieldsToValidate)
   {
       var result = EditContext.IsValid(field);

       if (!result)
       {
          isValid = false;
       }
    //doing something if any of the fields is wrong.
    }

3 Comments

Many thanks to @dani-herrera answer below (stackoverflow.com/a/72439989/12845686)
In my opinion is better to use editForm events (OnValidationRequested, OnValidationStateChanged, OnFieldChanged)
@daniherrera actually, I want to check validation for groups of fields (Multiple groups on different conditions) without submitting or before moving away from that screen, so that's why I am doing it manually.
0

In current versions of .NET you will find that <ObjectGraphDataAnnotationsValidator /> is no longer available so now the way to get this to work is to use FluentValidation.

It took some time to get it working but it does and validates all child objects correctly and only submits if all models are valid: EditForm Validation With List of Model Rather Than Single Model

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.