Carl Foster
Carl Foster

Reputation: 3

Modal form validation in asp.net core 2.1 and Razor pages

I have a Details.cshtml page displaying a customer's details as well as the linked list of customer.sites. On the page I've created a button to add a new site which opens a bootstrap modal containing the form for entry of a new site.

The problem I'm having is with validation. If the form is not valid, then the page reloads but the modal is hidden again, meaning the asp-validation-for text is not seen unless the user clicks the add site button again. Is there any way to make it so that when the ModelState.IsValid is not valid then modal stays open automatically?

details.cshtml page

@page "{id:int?}"
@model ServiceManager.Pages.Customers.DetailsModel

@{
    ViewData["Title"] = "Details";
}
<h2>@Html.DisplayFor(model => model.Customer.Name)</h2>
<hr />
<!-- Modal -->
<div class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" id="createSiteModal">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">Add New Customer Site</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body">
                <form method="post">
                    <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                    <div class="form-group">
                        <label asp-for="Site.Description" class="control-label"></label>
                        <input asp-for="Site.Description" class="form-control" />
                        <span asp-validation-for="Site.Description" class="text-danger"></span>
                    </div>
                    <div class="form-group">
                        <label asp-for="Site.AddressLine" class="control-label"></label>
                        <textarea asp-for="Site.AddressLine" class="form-control"></textarea>
                        <span asp-validation-for="Site.AddressLine" class="text-danger"></span>
                    </div>
                    <div class="form-group">
                        <label asp-for="Site.City" class="control-label"></label>
                        <input asp-for="Site.City" class="form-control" />
                        <span asp-validation-for="Site.City" class="text-danger"></span>
                    </div>
                    <div class="form-group">
                        <label asp-for="Site.County" class="control-label"></label>
                        <input asp-for="Site.County" class="form-control" />
                        <span asp-validation-for="Site.County" class="text-danger"></span>
                    </div>
                    <div class="form-group">
                        <label asp-for="Site.Postcode" class="control-label"></label>
                        <input asp-for="Site.Postcode" class="form-control" />
                        <span asp-validation-for="Site.Postcode" class="text-danger"></span>
                    </div>
                    <div class="form-group">
                        <label asp-for="Site.Country" class="control-label"></label>
                        <input asp-for="Site.Country" class="form-control" />
                        <span asp-validation-for="Site.Country" class="text-danger"></span>
                    </div>
                    <div class="form-group">
                        <label asp-for="Site.Telephone" class="control-label"></label>
                        <input asp-for="Site.Telephone" class="form-control" />
                        <span asp-validation-for="Site.Telephone" class="text-danger"></span>
                    </div>
                    <div class="form-group">
                        <label asp-for="Site.Fax" class="control-label"></label>
                        <input asp-for="Site.Fax" class="form-control" />
                        <span asp-validation-for="Site.Fax" class="text-danger"></span>
                    </div>
                    <div class="form-group">
                        <input asp-for="Site.CommercialID" class="form-control" value="3" />
                    </div>
                    <div class="form-group">
                        <input type="button" value="Cancel" class="btn btn-danger" data-dismiss="modal" />
                        <input type="submit" value="Create" class="btn btn-success" />
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>
<div class="btn-group" role="group">
    <a class="btn btn-light" data-toggle="tooltip" data-placement="top" data-delay="200" title="Back to Customer List" asp-page="./Index">
        <span class="fas fa-arrow-left fa-1x" style="color:#007bff"></span>
    </a>
    @if (Model.Customer.CustomerType == "Domestic")
    {
        <a class="btn btn-light" asp-page="./EditDom" asp-route-id="@Model.Customer.ID" data-toggle="tooltip" data-placement="top" data-delay="200" title="Edit Customer">
            <span class="fas fa-user-edit fa-1x" style="color:#0c0cbe"></span>
        </a>
    }
    else
    {
        <a class="btn btn-light" asp-page="./Edit" asp-route-id="@Model.Customer.ID" data-toggle="tooltip" data-placement="top" data-delay="200" title="Edit Customer">
            <span class="fas fa-user-edit fa-1x" style="color:#0c0cbe"></span>
        </a>
    }
        <a class="btn btn-light" asp-page="./Delete" asp-route-id="@Model.Customer.ID" data-toggle="tooltip" data-placement="top" data-delay="200" title="Delete Customer">
            <span class="fas fa-user-times fa-1x" style="color:#b01111"></span>
        </a>
</div>
<div class="row">
    <div class="col-lg-4">
        <div class="card border-info mb-3">
            <h5 class="card-header bg-info text-white">Customer Details</h5>
            <div class="card-body">
                <dl class="row">
                    <dt class="col-5">
                        @Html.DisplayNameFor(model => model.Customer.AccountRef)
                    </dt>
                    <dd class="col-7">
                        @Html.DisplayFor(model => model.Customer.AccountRef)
                    </dd>
                    <dt class="col-5">
                        @Html.DisplayNameFor(model => model.Customer.ContactDate)
                    </dt>
                    <dd class="col-7">
                        @Html.DisplayFor(model => model.Customer.ContactDate)
                    </dd>
                    <dt class="col-5">
                        @Html.DisplayNameFor(model => model.Customer.Active)
                    </dt>
                    <dd class="col-7">
                        @Html.DisplayFor(model => model.Customer.Active)
                    </dd>
                </dl>
            </div>
        </div>
    </div>
    <div class="col-lg-4">
        <div class="card border-info mb-3">
            <h5 class="card-header bg-info text-white">Customer Sites</h5>
            <table class="table table-hover fixed-table">
                <tr>
                    <th>Description</th>
                    <th>Location</th>
                    <th>Telephone</th>
                </tr>
                @foreach (var item in Model.Customer.Sites)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Description)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.City)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Telephone)
                        </td>
                        <td>
                            <a class="rowlink" asp-page="/Sites/Details" asp-route-id="@item.ID"><i class="fa fa-chevron-right"></i></a>
                        </td>
                    </tr>
                }
            </table>
            <div class="card-footer">
                <!-- Button trigger modal -->
                <button class="btn btn-link float-right" data-toggle="modal" data-target="#createSiteModal">
                    <span class="fa-stack fa-lg">
                        <i class="fas fa-circle fa-stack-2x" style="color:tomato"></i>
                        <i class="fas fa-stack-1x fa-inverse">+</i>
                    </span>
                </button>
                <div class="clearfix"></div>
            </div>
        </div>
    </div>
</div>

Details.cshtml.cs page

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using ServiceManager.Models;

namespace ServiceManager.Pages.Customers
{
    public class DetailsModel : PageModel
    {
        private readonly CompanyContext _context;

        public DetailsModel(CompanyContext context)
        {
            _context = context;
        }

        public Commercial Customer { get; set; }


        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Customer = await _context.Commercial
                .Include(c => c.Sites)
                .Include(c => c.Contacts)
                .FirstOrDefaultAsync(m => m.ID == id);

            if (Customer == null)
            {
                return NotFound();
            }
            return Page();
        }

        [BindProperty]
        public Address Site { get; set; }

        public async Task<IActionResult> OnPostAsync(int? id)
        {

            if (!ModelState.IsValid)
            {
                Customer = await _context.Commercial
                .Include(c => c.Sites)
                .Include(c => c.Contacts)
                .FirstOrDefaultAsync(m => m.ID == id);

                return Page();
            }
            _context.Address.Add(Site);
            await _context.SaveChangesAsync();
            return RedirectToPage("./Index");  
        }
    }
}

Upvotes: 0

Views: 5809

Answers (1)

StaticBeagle
StaticBeagle

Reputation: 5175

One way to address this issue is to perform client side validation using jQuery before you submit the form. That way the modal stays open and the user gets instant feedback on what the errors are.

Another alternative and perhaps less invasive in this case, is to pass a flag from the server back to the client with the status of the model validation and then open the modal based on the value of such flag e.g.

Controller:

   public async Task<IActionResult> OnPostAsync(int? id)
    {

        if (!ModelState.IsValid)
        {
            Customer = await _context.Commercial
            .Include(c => c.Sites)
            .Include(c => c.Contacts)
            .FirstOrDefaultAsync(m => m.ID == id);
            ViewData["ModelIsValid"] = false;
            return Page();
        }
        ViewData["ModelIsValid"] = true;
        _context.Address.Add(Site);
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");  
    }

View:

<script>
    $(document).ready(function () {
        @if(!(bool)ViewData["ModelIsValid"])
        {
            $("#createSiteModal").modal('show');
        }
    });
</script>

The main drawback of the second approach is that the modal will popup after the page has rendered instead of staying open.

Upvotes: 1

Related Questions