Introduction
Sometimes, you might come across a problem where you have to validate form in parts (for wizard) at client side without a round trip to server. For that in ASP.NET MVC framework, you have various options like doing custom validations using JavaScript, jQuery or Ajax postback.
In this hack/tip, we will be mixing server side validation declaration with client side and in parts.
Using the Code
To demonstrate, I will be creating a simple registration form wizard. In the first page, we capture basic details like first name, display name, etc., contact details on second page and miscellaneous details in the last page.
Let's start by creating Model
(or ViewModel
):
public class PersonDetailsViewModel
{
[Required(ErrorMessage = "Please enter First name")]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Display(Name = "Display Name")]
[MinLength(8, ErrorMessage = "Display name should be minimum of 8 characters")]
public string DisplayName { get; set; }
[Required(ErrorMessage = "Please enter Email address")]
[RegularExpression(@"^\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$",
ErrorMessage = "Please enter valid email address")]
[Display(Name = "Email")]
public string Email { get; set; }
[Required(ErrorMessage = "Please enter address line 1")]
[Display(Name = "Address line 1")]
public string AddressLine1 { get; set; }
[Display(Name = "Address line 2")]
public string AddressLine2 { get; set; }
[Required(ErrorMessage = "Please enter mobile number")]
[RegularExpression(@"^([0-9]( |-)?)?(\(?[0-9]{3}\)?|[0-9]{3})( |-)?
([0-9]{3}( |-)?[0-9]{4}|[a-zA-Z0-9]{7})$", ErrorMessage = "Please enter valid mobile number")]
[Display(Name = "Mobile")]
public string Mobile { get; set; }
[Display(Name = "Phone")]
public string Phone { get; set; }
[Required(ErrorMessage = "Please enter Post Code")]
[Display(Name = "Post code")]
public string PostCode { get; set; }
[Required(ErrorMessage = "Please enter Date of birth")]
[Display(Name = "Date of birth")]
public DateTime DOB { get; set; }
[Display(Name = "Other details")]
public string Other { get; set; }
}
As you can see, we have Model
divided (grouped) into 3 parts, which will become separate pages(panels) in wizard.
Now in the controller, a simple check to check if its valid.
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Register()
{
if(ModelState.IsValid)
{
}
return View("Index");
}
}
Let’s move on to the View
where all magic happens of validation.
@*Basic details panel*@
<div id="BasicDetailsPnl">
<div class="container">
<div class="form-group">
@Html.LabelFor(model => model.FirstName, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.FirstName, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.LastName, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.LastName, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.LastName, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.DisplayName, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.DisplayName, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.DisplayName, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" })
</div>
</div>
</div>
<div class="nav">
<input type="button" value="Next"
class="btn btn-default" onclick="validateBasicDetails();"/>
</div>
</div>
@*Contact details panel*@
<div id="ContactDetailsPnl">
<div class="container">
<div class="form-group">
@Html.LabelFor(model => model.AddressLine1, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.AddressLine1, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.AddressLine1, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.AddressLine2, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.AddressLine2, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.AddressLine2, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Mobile, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Mobile, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Mobile, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Phone, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Phone, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Phone, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.PostCode, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.PostCode, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.PostCode, "", new { @class = "text-danger" })
</div>
</div>
</div>
<div class="nav">
<input type="button" value="Prev"
class="btn btn-default" onclick="showBasicDetailsPnl();" />
<input type="button" value="Next"
class="btn btn-default" onclick="validateContactDetails();" />
</div>
</div>
@*Other details panel*@
<div id="OtherDetailsPnl">
<div class="container">
<div class="form-group">
@Html.LabelFor(model => model.DOB, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.DOB, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.DOB, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Other, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Other, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Other, "", new { @class = "text-danger" })
</div>
</div>
</div>
<div class="nav">
<input type="button" value="Prev"
class="btn btn-default" onclick="showContactDetails();"/>
<input type="submit" value="Finish" class="btn btn-default"/>
</div>
</div>
As you can see, we have 3 main div
s representing major parts Model
. I have scaffolded the view using default Visual Studio menu and modified by separating as per the model. Now by adding simple Previous and Next buttons, we can create a registration wizard.
var basicDetailsPnl = $("#BasicDetailsPnl");
var contactDetailsPnl = $("#ContactDetailsPnl");
var otherDetailsPnl = $("#OtherDetailsPnl");
var form = $('form');
$(document).ready(function () {
contactDetailsPnl.hide();
otherDetailsPnl.hide();
});
function validateBasicDetails() {
var isValid = form.validate().element($('#FirstName'))
& form.validate().element($('#Email'));
if (isValid) {
contactDetailsPnl.show();
basicDetailsPnl.hide();
}
return isValid;
}
function showContactDetails() {
otherDetailsPnl.hide();
contactDetailsPnl.show();
};
function validateContactDetails() {
var isValid = form.validate().element($('#AddressLine1'))
& form.validate().element($('#PostCode'));
if (isValid) {
otherDetailsPnl.show();
contactDetailsPnl.hide();
}
return isValid;
}
function showBasicDetailsPnl() {
contactDetailsPnl.hide();
basicDetailsPnl.show();
};
In JavaScript functions validateBasicDetails()
and validateContactDetails()
, we have a variable boolean isValid
which is assigned the value of validation result for the controls we have in that particular div
. If all the controls are valid as per the validations assigned to Model
, the value of variable isValid
will be true
. For last div
OtherDetailsPnl
, the validation will be done using Finish button, which is, a default submit action for page so you don't need a separate JavaScript function.
As you can see, in JavaScript functions we can validate the individual control by calling validate
method of form on specific controls we want to validate. To create wizard, we can group controls and perform “AND
” operation to group the result to create a wizard.
That’s it! We are done, if you run and see the wizard in browser, we can validate controls separately for each div
.
Hope this trick will help. If you want to improve the code or have suggestions, please send a pull request on git repository.