0

I am trying to create a simple movie database kind of an app for learning bootstrap and mvc. I used adminlte template in .net core 2.0. everything is working fine with only mvc. But later I wish to learn more so I started to use ajax and jquery to send and receive data. For create/edit/ I used the bootstrap modal. Here I have the main problem. I can validate from client side. But I am unable to display error message during server side validation.

_Layout.cshtml

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>@ViewData["Title"]</title>
    <!-- Tell the browser to be responsive to screen width -->
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">

    <!-- Bootstrap 3.3.7 -->
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css">
    <!-- Font Awesome -->
    <link rel="stylesheet" href="~/lib/font-awesome/css/font-awesome.min.css">
    <!-- Ionicons -->
    <link rel="stylesheet" href="~/lib/Ionicons/css/ionicons.min.css">
    <!-- Theme style -->
    <link rel="stylesheet" href="~/lib/adminlte/dist/css/AdminLTE.min.css">
    <!-- AdminLTE Skins. Choose a skin from the css/skins
         folder instead of downloading all of them to reduce the load. -->
    <link rel="stylesheet" href="~/lib/adminlte/dist/css/skins/skin-blue.min.css">

    <link rel="stylesheet" href="~/css/site.min.css" />
    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
    <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
    <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
    <!-- Google Font -->
    @*<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic">*@

    <!-- jQuery 3 -->
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
    <!-- Bootstrap 3.3.7 -->
    <script src="~/lib/bootstrap/dist/js/bootstrap.min.js"></script>
    <!-- SlimScroll -->
    <script src="~/lib/jquery-slimscroll/jquery.slimscroll.min.js"></script>
    <!-- FastClick -->
    <script src="~/lib/fastclick/lib/fastclick.js"></script>
    <!-- AdminLTE App -->
    <script src="~/lib/adminlte/dist/js/adminlte.min.js"></script>
    <script src="~/lib/PACE/pace.min.js"></script>
    <script src="~/js/site.min.js"></script>
</head>
<body class="hold-transition skin-blue sidebar-mini">
    <!-- Site wrapper -->
    <div class="wrapper">

        <!-- Top Menubar -->
        @await Component.InvokeAsync("TopMenubar")

        <!-- Left side column. contains the sidebar -->
        @await Component.InvokeAsync("Sidebar")

        <!-- Content Wrapper. Contains page content -->

        <div class="content-wrapper">
            <section class="content-header">
                @await Component.InvokeAsync("ContentHeader")
                <br />
                @await Component.InvokeAsync("Alert")
            </section>

            <section class="content">
                @RenderBody()
            </section>
        </div>

        <!-- Footer -->
        @await Component.InvokeAsync("Footer")

    </div>
    <!-- ./wrapper -->
    @RenderSection("scripts", required: false)
    <script>

    </script>
    @*<script>
            $(document).ready(function () {
                $('.sidebar-menu').tree()
            })
        </script>*@
</body>
</html>

Index.cshtml

@model MovieDb.Models.CategoryType

@{
    ViewData["Title"] = "Category Type";
}

<link rel="stylesheet" href="~/lib/datatables.net-bs/css/dataTables.bootstrap.min.css" />
<script src="~/lib/datatables.net/js/jquery.dataTables.min.js"></script>
<script src="~/lib/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>
<script src="~/js/categoryType.js"></script>


<div class="panel">
    <div class="panel-body">
        <button class="btn btn-primary" type="button" data-toggle="modal" data-target="#createNewEmergencyType"
                onclick="clearTextBox();"><i class="fa fa-plus"> Add New Category</i></button>
    </div>
</div>

<div class="box">
    <div class="box-body table-responsive">
        <table id="DgCategoryType" class="table table-bordered table-striped dataTable" role="grid">
            <thead>
                <tr>
                    <th>Name</th>
                    <th></th>
                </tr>
            </thead>
        </table>
    </div>
</div>
<div class="modal fade" id="createNewCategoryType" tabindex="-1" role="dialog"
     aria-labelledby="Add New Category" aria-hidden="true">
     <div class="modal-dialog">
         <div class="modal-content">

             <div class="modal-header">
                 <button type="button" class="close" data-dismiss="modal">
                     &times;
                 </button>
                 <h4 class="modal-title">Add New Category</h4>
             </div>

             <div class="modal-body">
                 <form id="createCTForm">
                     @Html.AntiForgeryToken()

                     <div class="form-group">
                         <label asp-for="Name" class="control-label">Name:</label>
                         <input asp-for="Name" class="form-control" />
                         <span asp-validation-for="Name" class="text-danger"></span>
                     </div>

                 </form>
             </div>

             <div class="modal-footer">
                 <button type="button" class="btn btn-primary btn-fixed-width" id="btnAdd" onclick="return Add();">
                     Add
                 </button>
                 <button type="button" class="btn btn-default btn-fixed-width" data-dismiss="modal">
                     Close
                 </button>
             </div>
         </div>
     </div>

</div>

categoryType.js

$(document).ready(function(){
loadData();
});

function loadData(){
    $("#DgCategoryType").DataTable(

        {           
            "filter": true,
            "orderMulti": false,
            "ajax":
            {
                "url":"/CategoryTypes/LoadData",
                "type":"GET",
                "dataType":"JSON"
            },
            "columns":[
                {"data":"Name"},
                {   "data":"Id",
                    "render":function(data){
                        return "<a class='popup' href='/CategoryTypes/Edit/"+data+"'>Edit</a> | <a class='popup' href='/CategoryTypes/Delete/"+data+"'>Delete</a>";
                    }
                }
            ],
            "columnDefs":
            [
                {
                    "targets": [1],
                    "searchable": false
                },
                {"width":"10%","targets":[1]}
            ]
        }
    );
}

function Add(){

    /*var frm=document.getElementById("createCTForm");
    var data=toJSONString(frm);*/

    var data=$("#createCTForm").serialize();

    $.ajax({
        type:"POST",
        url:"CategoryTypes/Create",
        data:data,
        success:function(result){
            if(result.success)
            {
                $("#createNewCategoryType").modal("hide");
            }
        },
    });
}

function clearTextBox(){
    $("#Name").val("");
}

Model: CategoryType.cs

public class CategoryType
{
    public int Id { get; set; }
    [Required(ErrorMessage ="Category name required")]
    public string Name { get; set; }
}

CategoryTypesController.cs

public class CategoryTypesController : BaseController
    {
        private readonly ApplicationDbContext _context;

        public CategoryTypesController(ApplicationDbContext context)
        {
            _context = context;
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
                _context.Dispose();
            base.Dispose(disposing);
        }

        // GET: CategoryTypes
        public IActionResult Index()
        {
            AddPageHeader("Category Types");
            return View(new CategoryType());
        }

        //GET JSON data for loading the datatable
        public async Task<IActionResult> LoadData()
        {
            var data = await _context.CategoryTypes.ToListAsync();
            return Json(new { data } );
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Create([Bind("Id,Name")] CategoryType categoryType)
        {
            if (ModelState.IsValid)
            {
                _context.Add(CategoryType);
                await _context.SaveChangesAsync();
                return Json(new { success=true });
            }
                return Json(new { success=false });
        }
    }

when I run the above code it will add the new category to database however if I didn't enter the name in modal input box then controller will give the error but validation message will not appear.

what I am doing wrong?

3 Answers 3

2

I recenly faced the same problem. To solve it, I took the matter into my own hands, and created a mini-library (if you can call it that) to validate the inputs.

Please get the code from this JsFiddle.

You can find a working example in the same link.

Note: As of right now it has checks for : required, regex, range, equals.

You can add more checks by yourself, or comment about it, if you need help and I will try my best to provide aid.

How it works:

  1. Create a js file in your project and paste the js (minimum code is represented by region CustomValidationScript) from the fiddle link. Include this file in your _layout.cshtml.

  2. Setup some rules:

     var validationRules = [{ 
         ruleName: "elementId1ShoudBeRequired", // make sure this 
         name will be unique within your rules 
         ruleForElementId: "#elementId1",
         check: "required",
         comparerValue: true,
         message: "This input is required! Please make sure to input a value!"
     },
     {
         ruleName: "elementId3ShouldBePhoneNr",
         ruleForElementId: "#elementId2",
         check: "regex",
         comparerValue: /^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$/,
         message: "Please provide a valid phone number!"
     },
     {
         ruleName: "elementId4_ShouldBeEqualWith_5",
         ruleForElementId: "#elementId2",
         check: "equals",
         comparerValue: 5,
         message: "This input should be 5!"
     }
     ];
     var rangeRules = [{
         ruleName: "elementId2ShoudBeInRange",
         ruleForElementId: "#elementId2",
         check: "range",
         comparerValue: [1, 8],
         message: "Please provide a value between 1 and 8 !"
     }];
    
  3. In your js file / script initialize the validator outside of $(document).ready() function:

     var validator = new CustomValidation(validationRules);
     // you can pass the validation rules in the initialization, or send a empty array if you wish to add the rules later
     // var validator = new CustomValidation([]);
    
  4. (optional step) Add or remove more validation rules:

     // Add rules
     // you can add them by passing an array of rules
     validator.addcustomValidationRules(rangeRules);
     // or by passing one rule
     validator.addcustomValidationRule(
         {
             ruleName: addRule.ruleName,
             ruleForElementId: addRule.ruleForElementId,
             check: addRule.check,
             comparerValue: addRule.comparerValue,
             message: addRule.message
         }
     );
     // if the rule(s) already exist(s), an error will be thrown
    
     // Remove rules
     // you can also remove some rules by ruleName, or sending an array of rules
     // for example we can remove rangeRules entirely like : 
     validator.removeCustomValidationRules(rangeRules);
     // or we can remove individual rules by name like so: 
     validator.removeCustomValidationRule("elementId2ShoudBeInRange");
    
  5. Validate the inputs:

     var result = validator.validate(validator); 
     // will return an object like this:{ formIsvalid : true / false, validationResults: [ {elementId: elementId,message: message}] }
    

    HTML page example:

     <div class="container">
     <div class="form-group">
         <label>Input for elementId1 </label>
         <input id="elementId1" class="validate" />
         <span class="error_span col-md-12"></span>
     </div>
     <div class="form-group">
         <label>Input for elementId2 </label>
         <input id="elementId2" class="validate" />
         <span class="error_span"></span>
     </div>
     <button class="btn btn-outline-success" id="save">Save</button>
     </div>
    
     <script type="text/javascript">
     // you can validate the input inside listeners or inside whatever pieces of code
    
     // example onClick
     $("#save").on("click", function() {
         // the validation happerns here
         var result = validator.validate(validator);
         if (result.formIsValid) {
             // your code ...  
             // can be ajax call to send the form to the server
         }
         else {
             // clear error_spans that might have been fixed between clicks
             updateUi(result.validationResults, "form-group", "error_span");
         }
     });
    
     // example onKeyUp
     $(".validate").on("keyup", function() {
         var result = validator.validate(validator);
         updateUi(result.validationResults, "form-group", "error_span");
     });
    
     </script>
    

Note : updateUi (can also be found in the JsFiddle) is a custom function that updates the error spans to show / hide.

That's it!

I tested it with ajax calls, select2, bootstrap 4 and it works.

Feel free to comment if you find something that can be improved. (I'm not a js wizard, so mistakes could be found.)

Happy coding!

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

Comments

1

I don't know why validation didn't work in above method. I have to use jquery-unobtrusive-ajax. You can refer it here https://dotnetthoughts.net/jquery-unobtrusive-ajax-helpers-in-aspnet-core/

Validate Pop Up Modal using Ajax.Beginform tutorial is here https://qawithexperts.com/article/asp.net/validate-pop-up-modal-using-ajaxbeginform-in-c-mvc/52

Comments

0

You can fix it by linking the jquery-validate and the jquery-validation-unobtrusive scripts with the view that you're calling through the modal.

You need that because any view acting as a partial view does not have access to scripts or script tags on the layout view.

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.