Search code examples
asp.net-core.net-corerazor-pagesunobtrusive-ajax

Unobtrusive Ajax w/ Unobtrusive Validation


I have a form with a single textbox. The field bound to that textbox is required.

When I post the form with that textbox empty I get ModelState.IsValid = False as expected. But, the form still fires the data-ajax-success.

Relevant .cshtml

<form method="post" asp-page-handler="ProductAliasCreate" data-ajax="true" data-ajax-method="post" data-ajax-success="refreshPartial('GetAliases', 'product-aliases-container')" data-ajax-failure="handleError">

Relevant .cshtml.cs

public async Task<IActionResult> OnPostProductAliasCreateAsync()
{
    if (!ModelState.IsValid || _context.ProductAliases == null || ProductAlias == null)
    {
        return Page();
    }

    _context.ProductAliases.Add(ProductAlias);
    await _context.SaveChangesAsync();

    return Page();
}

It's hitting that first return Page(); inside that Isvalid check.

How do I prevent the data-ajax-success callback from firing when ModelState.IsValid = false?

Note: I previously had the refreshPartial callback in the data-ajax-complete with the same result...so I moved it to data-ajax-success thinking that'd be the solution to this issue.


Solution

  • Return Page() would always return response with 200 statue code in your senaro,if you want fire data-ajax-success when modelstate is invalid,you should return response with 400 statuecode

    A minimal example,hopes help:

    PageModel:

    public class MyEntityModel : PageModel
        {
            [BindProperty(SupportsGet =true)]
            public MyEntity? MyEntity { get; set; }
            public void OnGet()
            {
            }
            public async Task<IActionResult> OnPostProductAliasCreateAsync()
            {
                if (!ModelState.IsValid )
                {
                    return BadRequest();
                }         
    
                return new OkObjectResult("");
            }
        }
    

    Page:

    @page
    @model MyEntityModel
    @{
    }
    <form method="post" asp-page-handler="ProductAliasCreate" data-ajax="true" data-ajax-method="post" data-ajax-success="success()" data-ajax-failure="fail()">
    
        Id:
        <input asp-for="MyEntity.Id" />
        Name:
        <input asp-for="MyEntity.Name" />
    
        <input type="submit" />
    </form>
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/jquery-ajax-unobtrusive/jquery.unobtrusive-ajax.js"></script>
    <script>
        function success() {
            console.log("success")
        }
        function fail(){
            console.log("fail")
        }
    </script>
    

    Result:

    enter image description here

    Update based on your comment:

    If you've passed client validation(in my case,null check) and got model state error on server(for example,duplicated Name)

    If you want to show the errors ,you have to refresh the page yourself due to ajax call

    My solution:

    create a filter:

    public class MyPageFilter : IPageFilter
        {       
    
            public void OnPageHandlerSelected(PageHandlerSelectedContext context)
            {
                
            }
            public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
            {
                
            }
            public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
            {
                if (context.ModelState.ErrorCount != 0)
                {
                    context.HttpContext.Response.StatusCode = 400;
                }
            }
        }
    

    PageModel:

    [TypeFilter(typeof(MyPageFilter))]
        public class EntityModel : PageModel
        {
            private readonly MyRazorPageApp.Areas.Identity.Data.MyRazorPageAppContext _context;
            private readonly ICompositeViewEngine _viewEngine;
    
            public EntityModel(MyRazorPageApp.Areas.Identity.Data.MyRazorPageAppContext context, ICompositeViewEngine viewEngine)
            {
                _context = context;
                _viewEngine = viewEngine;
            }
    
            public IActionResult OnGet()
            {
                
                return Page();
            }
    
            [BindProperty]
            public MyEntity MyEntity { get; set; } = default!;
            
    
            // To protect from overposting attacks, see https://aka.ms/RazorPagesCRUD
            public async Task<IActionResult> OnPostAsync()
            {
                ModelState.AddModelError("MyEntity.Name", "Not Valid");
                if (!ModelState.IsValid || _context.MyEntity == null || MyEntity == null)
                {
                   
                    return Page();
                }            
                return RedirectToPage("./Index");
            }
        }
    

    Page:

    @page
    @model MyRazorPageApp.Pages.EntityModel
    
    @{
        
    }
    
    <h1>Enyity</h1>
    
    <h4>MyEntity</h4>
    <hr />
    <div class="row">
        <div class="col-md-4">
            <form method="post" data-ajax="true" data-ajax-method="post" data-ajax-success="success" data-ajax-failure="fail">
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                <div class="form-group">
                    <label asp-for="MyEntity.Name" class="control-label"></label>
                    <input asp-for="MyEntity.Name" class="form-control" />
                    <span asp-validation-for="MyEntity.Name" class="text-danger"></span>
                </div>
                <div class="form-group">
                    <label asp-for="MyEntity.Prop" class="control-label"></label>
                    <input asp-for="MyEntity.Prop" class="form-control" />
                    <span asp-validation-for="MyEntity.Prop" class="text-danger"></span>
                </div>
                <div class="form-group">
                    <input type="submit" value="Create" class="btn btn-primary" />
                </div>
            </form>
        </div>
    </div>
    
    <div>
        <a asp-page="Index">Back to List</a>
    </div>
    <div id="mydiv"></div>
    
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
    <script src="~/lib/jquery-ajax-unobtrusive/jquery.unobtrusive-ajax.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
    
    <script>
        function success() {
            console.log("success")
        }
        fail=function (response) {
            console.log("fail")
           
            var newDoc = document.open("text/html", "replace");
            newDoc.write(response.responseText);
            newDoc.close();
        }
    </script>
    

    Result: enter image description here