Search code examples
c#asp.net-corerazor-pagesfluentvalidationasp.net-core-5.0

Razor pages fluent validation server side not working


I user fluent validation to validate my forms. I use asp.net core (razor pages). It work correctly in client side, but when I removed jquery validation scripts to test server side validation I see it doesn't work and ModelState is true. How can I fix this problem. Is there a any solution?

This is Page Model Properties

/// <summary>
    /// Gets or sets the user role name
    /// </summary>
    [HamiResourceDisplayName("Security.Role.Fields.Name")]
    [BindProperty]
    public string Name { get; set; }

    /// <summary>
    /// Gets or sets the user role system name
    /// </summary>
    [HamiResourceDisplayName("Security.Role.Fields.SystemName")]
    [BindProperty]
    [PageRemote(
        //ErrorMessage = "Already exists",
        AdditionalFields = "__RequestVerificationToken",
        HttpMethod = "Post",
        PageHandler = "CheckSystemName"
    )]
    public string SystemName { get; set; }

    /// <summary>
    /// Gets or sets a value indicate whether the user role is for managers
    /// </summary>
    [HamiResourceDisplayName("Security.Role.Fields.ManagerRole")]
    [BindProperty]
    public bool IsManagerRole { get; set; }

    /// <summary>
    /// Gets or sets a value indicating whether the level of manager if it's a manager role
    /// </summary>
    [HamiResourceDisplayName("Security.Role.Fields.ManagerLevel")]
    [BindProperty]
    public int? ManagerLevel { get; set; }

This is OnPost

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid) return Page();
        var role = new Role()
        {
            Name = Name,
            Inactive = false,
            IsSystemRole = false,
            SystemName = SystemName,
            IsManagerRole = IsManagerRole,
            ManagerLevel = IsManagerRole ? ManagerLevel : null
        };
        await _userService.InsertRoleAsync(role);
        return RedirectToPage("/Security/Roles/Index");
    }

And finally this is my validation

public class CreateRolesValidator : AbstractValidator<CreateModel>
{
    public CreateRolesValidator(ILocalizationService localizationService)
    {
        RuleFor(x => x.Name).NotNull().WithMessageAwait(localizationService.GetResourceAsync("Security.Role.Fields.Name.Required"));
        RuleFor(x => x.SystemName).NotNull().WithMessageAwait(localizationService.GetResourceAsync("Security.Role.Fields.SystemName.Required"));
        //RuleFor(x => x.SystemName).IsUniqueRoleSystemName().WithMessageAwait(localizationService.GetResourceAsync("Security.Role.SystemName.SystemNameAlreadyExists"));
        RuleFor(x => x.ManagerLevel).NotNull().DependentRules(() =>
        {
            RuleFor(x => x.IsManagerRole).Must(x => x.Equals(true));
        }).WithMessageAwait(localizationService.GetResourceAsync("Security.Role.Fields.ManagerLevel.Required"));
    }
}

For example NotEmpty() for "Name" works correctly on client side but not in server side and doesn't catch as an invalid model state.


Solution

  • Try to create a separate class to store the model properties, then, add validate for it and use it in the razor page.

    For example: Create a RoleViewModel:

    public class RoleViewModel
    {
        /// <summary>
        /// Gets or sets the user role name
        /// </summary> 
        [BindProperty]
        public string Name { get; set; }
    
        /// <summary>
        /// Gets or sets the user role system name
        /// </summary> 
        [BindProperty]
        [PageRemote(
            //ErrorMessage = "Already exists",
            AdditionalFields = "__RequestVerificationToken",
            HttpMethod = "Post",
            PageHandler = "CheckSystemName"
        )]
        public string SystemName { get; set; }
    
        /// <summary>
        /// Gets or sets a value indicate whether the user role is for managers
        /// </summary> 
        [BindProperty]
        public bool IsManagerRole { get; set; }
    
        /// <summary>
        /// Gets or sets a value indicating whether the level of manager if it's a manager role
        /// </summary> 
        [BindProperty]
        public int? ManagerLevel { get; set; }
    }
    public class CreateRolesValidator : AbstractValidator<RoleViewModel>
    {
        public CreateRolesValidator()
        {
            RuleFor(x => x.Name).NotNull();
            RuleFor(x => x.SystemName).NotNull();
            //RuleFor(x => x.SystemName).IsUniqueRoleSystemName().WithMessageAwait(localizationService.GetResourceAsync("Security.Role.SystemName.SystemNameAlreadyExists"));
            RuleFor(x => x.ManagerLevel).NotNull().DependentRules(() =>
            {
                RuleFor(x => x.IsManagerRole).Must(x => x.Equals(true));
            });
        }
    }
    

    Then, register the validate service in the ConfigureServices method:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages().AddFluentValidation();
    
            services.AddTransient<IValidator<RoleViewModel>, CreateRolesValidator>();
        }
    

    Then in the Create.cshtml.cs file:

    public class CreateModel : PageModel
    {
        [BindProperty]
        public RoleViewModel Role { get; set; }
        public void OnGet()
        {
        }
        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid) return Page();
           
            return RedirectToPage("/Index");
        }
    }
    

    and Create.cshtml: already disabled the client-side validation

    @page
    @model RazorWebApplication.Pages.CreateModel
    
    <form method="post">
    
        Name: <input asp-for="Role.Name" class="form-control" /> <span asp-validation-for="Role.Name" class="text-danger"></span>
        <br />
        SystemName: <input asp-for="Role.SystemName" class="form-control" /> <span asp-validation-for="Role.SystemName" class="text-danger"></span>
        <br />
        IsManagerRole: <input asp-for="Role.IsManagerRole" class="form-control" /> <span asp-validation-for="Role.IsManagerRole" class="text-danger"></span>
        <br />
        ManagerLevel: <input asp-for="Role.ManagerLevel" class="form-control" /> <span asp-validation-for="Role.ManagerLevel" class="text-danger"></span>
    
        <br /><br />
        <input type="submit" value="submtit" />
    </form>
    
    @*@section Scripts {
        @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
    }*@
    

    The result as below:

    enter image description here