0

I'm working on a Blazor application with fluent validation.

I'm working on a manage profile page where they can change their first name, last name, and email.

Here is my Razor:

<EditForm Model="Input" OnValidSubmit="@UpdateProfile">
    <FluentValidator TValidator="InputModelValidator" />
    <div class="form-row">
        <div class="form-group col-md-6">
            <h2>Manage Profile</h2>
        </div>
    </div>
    <div class="form-row">
        <div class="form-group col-md-4">
            <SfTextBox FloatLabelType="FloatLabelType.Auto" Placeholder="First Name" @bind-Value="Input.FirstName"></SfTextBox>
        </div>
        <div class="form-group col-md-4">
            <SfTextBox FloatLabelType="FloatLabelType.Auto" Placeholder="Last Name" @bind-Value="Input.LastName"></SfTextBox>
        </div>
    </div>
    <div class="form-row">
        <div class="form-group col-md-4">
            <SfTextBox FloatLabelType="FloatLabelType.Auto" Placeholder="Email Address" @bind-Value="Input.Email"></SfTextBox>
        </div>
    </div>
    <div class="form-row btn-update">
        <div class="form-group col-md-4">
            <SfButton IsPrimary="true">Update</SfButton>
            <SfToast ID="toast_customDupemail" @ref="@toastDuplicateEmail" Title="Invalid Email" Content="@toastDupEmailErrorMsg" CssClass="e-toast-danger" Timeout=6000>
                <ToastPosition X="Center" Y="Top"></ToastPosition>
             </SfToast>
        </div>
    </div>
</EditForm>

Here is my validator:

    public class InputModelValidator : AbstractValidator<InputModel>
    {
        public InputModelValidator()
        {
            
            RuleFor(e => e.FirstName).NotEmpty().WithMessage("First name is required.");
            RuleFor(e => e.LastName).NotEmpty().WithMessage("Last name is required.");
            RuleFor(e => e.Email).NotEmpty().WithMessage("Email is required.");
            RuleFor(e => e.Email).EmailAddress().WithMessage("Email is not valid.");
            RuleFor(x => x.Email).Custom((email, context) => {
                if (IsEmailValid(email) == false)
                {
                    context.AddFailure("The email is not valid.");
                }
            });
        }

        private bool IsEmailValid(string email)
        {
            var userInfo = Task.Run(async () => await utilities.GetApplicationUser().ConfigureAwait(false)).Result;

            if (string.Equals(userInfo.Email, email, StringComparison.OrdinalIgnoreCase) == true)
            {
                return true;
            }

            return false;
        }
    }

I have the initial checks for empty, and valid email and such. Those work great!

I need to add a custom message to make sure the email is not already in use.

What is the proper way to talk to the database / asp.net identity UserManager within the validator class?

I'm tried to inject my dependencies, but they are coming in null when I try that.

Thanks.

UPDATE:

Per response that this needs to happen in the handler, is something like this possible?

    public partial class ManageProfile
    {
        public InputModel Input { get; set; } = new InputModel();
        private EditContext _editContext;

        protected override async Task OnInitializedAsync() // = On Page Load
        {

            var userInfo = await utilities.GetApplicationUser().ConfigureAwait(false);

            Input = new InputModel
            {
                FirstName = userInfo.FirstName,
                LastName = userInfo.LastName,
                Email = userInfo.Email
            };

            await InvokeAsync(StateHasChanged).ConfigureAwait(false);
        }

        private async Task<EditContext> UpdateProfile()
        {
            _editContext = new EditContext(Input);
            var messages = new ValidationMessageStore(_editContext);

            messages.Clear();

            if (IsEmailValid(Input.Email) == false)
            {
                messages.Add(() => Input.Email, "Name should start with a capital.");
                _editContext.NotifyValidationStateChanged();
                return _editContext;
            }


            return _editContext;
        }

        private void ValidateFields(EditContext editContext, ValidationMessageStore messages, FieldIdentifier field)
        {
            messages.Clear();

            if (IsEmailValid(Input.Email) == false)
            {
                messages.Add(() => Input.Email, "Name should start with a capital.");
                editContext.NotifyValidationStateChanged();
            }
        }

        private bool IsEmailValid(string email)
        {
            var userInfo = Task.Run(async () => await utilities.GetApplicationUser().ConfigureAwait(false)).Result;

            if (string.Equals(userInfo.Email, email, StringComparison.OrdinalIgnoreCase) == true)
            {
                return true;
            }

            return false;
        }
    }

    public class InputModel
    {
        [Required]
        [MaxLength(250)]
        [Display(Name = "First Name", Prompt = "Enter first name")]
        public string FirstName { get; set; }

        [Required]
        [MaxLength(250)]
        [Display(Name = "Last Name", Prompt = "Enter last name")]
        public string LastName { get; set; }

        /// <summary>
        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
        ///     directly from your code. This API may change or be removed in future releases.
        /// </summary>
        [Required]
        [EmailAddress]
        [Display(Name = "Email", Prompt = "Enter email")]
        public string Email { get; set; }
    }

    public class InputModelValidator : AbstractValidator<InputModel>
    {
        public InputModelValidator()
        {
            
            RuleFor(e => e.FirstName).NotEmpty().WithMessage("First name is required.");
            RuleFor(e => e.LastName).NotEmpty().WithMessage("Last name is required.");
            RuleFor(e => e.Email).NotEmpty().WithMessage("Email is required.");
            RuleFor(e => e.Email).EmailAddress().WithMessage("Email is not valid.");
        }
    }

UPDATE 3:

    public class InputModelValidator : AbstractValidator<InputModel>
    {
        public InputModelValidator(UserManager<ApplicationUser> user)
        {         
            RuleFor(e => e.FirstName).NotEmpty().WithMessage("First name is required.");
            RuleFor(e => e.LastName).NotEmpty().WithMessage("Last name is required.");
            RuleFor(e => e.Email).NotEmpty().WithMessage("Email is required.");
            RuleFor(e => e.Email).EmailAddress().WithMessage("Email is not valid.");
            //RuleFor(e => e.Email).EmailAddress().WithMessage("Email is not valid.").Must(IsEmailexist).WithMessage("{PropertyName} Is Already Exist.");
        }

        private async Task<bool> IsEmailexist(string Email)
        {
            return false;
        }
    }

I tried injecting UserManager<>, but I have this error:

Severity Code Description Project File Line Suppression State Error CS0310 'InputModelValidator' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TValidator' in the generic type or method 'FluentValidator' C:...\Microsoft.NET.Sdk.Razor.SourceGenerators\Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator\Areas_Identity_Pages_Account_ManageProfile_razor.g.cs 200 Active

ttaylor27272727
  • 195
  • 3
  • 18

3 Answers3

1

checking for duplication is not property or value validation, it's better to check this validation in handler or control-action

  • can I still have it error like the normal property validation with the fluent framework? – ttaylor27272727 Jun 29 '22 at 04:03
  • yes but you need to write a middleware to handle this exception or inject context to your handler then add model state – Hossein Giyah Jun 29 '22 at 09:25
  • I figured this part out and have the custom error message working from my submit call. Now I can't figure out how to clear the custom message though: https://stackoverflow.com/questions/72801912/blazor-validation-how-to-clear-a-custom-error-message – ttaylor27272727 Jun 29 '22 at 23:30
  • Thanks for your help, I posted an answer that was full, but your information assisted! – ttaylor27272727 Jul 03 '22 at 17:46
0

checking email already exist in DB in middleware follow below process

    public class InputModelValidator : AbstractValidator<InputModel>
     {
    private EditContext _editContext;
    public InputModelValidator(EditContext editContext)
    {
        _editContext=editContext;
        RuleFor(e => e.FirstName).NotEmpty().WithMessage("First name is required.");
        RuleFor(e => e.LastName).NotEmpty().WithMessage("Last name is required.");
        RuleFor(e => e.Email).NotEmpty().WithMessage("Email is required.");
        RuleFor(e => e.Email).EmailAddress().WithMessage("Email is not valid.").Must(IsEmailexist).WithMessage("{PropertyName} Is Already Exist.");;
    }


    private bool IsEmailexist(string Email)
    {
        return _editContext.userInfo.where(em=>em.EmailId==Email).FirstOrDefault()!=null?true:false;
    }
   }
Upender Reddy
  • 568
  • 3
  • 8
0

Please refer to this answer: https://stackoverflow.com/a/72848675/9594249

Summary: I needed to make a custom form validation that was seperate from FluentValidation.

<EditForm Model="@Input" OnValidSubmit="@UpdateProfile">
    <FluentValidator TValidator="InputModelValidator" />
    <UI.Models.Other.CustomFormValidator @ref="@customFormValidator" />

https://www.syncfusion.com/blogs/post/blazor-forms-and-form-validation.aspx

ttaylor27272727
  • 195
  • 3
  • 18