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">
×
</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?
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:
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
.
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 !"
}];
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([]);
(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");
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!