web 2.0

Create a Wizard in ASP.NET MVC 3


Introduction

Wizards can improve web sites usability by breaking an overwhelming list of questions into multistep simpler forms. A wizard collects information and submits all of it at the end, the user can go back and modify their answers when they want. Because ASP.NET MVC is so flexible, there are many ways to create wizards in MVC. In this post, I will show you a nice technique to create a wizard with the aid of jQuery.

The intrinsic idea is to divide a single form by wrapping each set of questions in one container (e.g. <DIV>) , and all the rest of the work including navigation is done in javascript.

Creating the Wizard

Let's see how we can create this wizard using ASP.NET MVC 3 and Razor. Although you are not restricted to Razor, it's the recommended view engine by many professional web developers because of its fluid, concise, and robust nature.

Click "File->New Project" menu command within Visual Studio to create a new ASP.NET MVC 3 Project. We'll create a new project using the "Internet Application" template.

Creating the Model

We'll need a model to create our Wizard. Add a class named "User" to the "Models" folder

Note that we added some validation using DataAnnotations.

Creating a WizardController

Now right click on the controllers folder and then choose the "Add->Controller" context menu command. This will bring the "Add Controller" dialog:

We'll name the new controller "WizardController". When we click the "Add" button, Visual Studio will add a default "Index" action:

Creating a View Template

Now we will implement the View associated with the WizardController's Index action. Now to implement the view right-click within the "WizardController.Index()" method and select the "Add View" command to create a view template for our home page:

We will need to modify some of the settings above. Choose the option "Create a strongly-typed view" and choose the User class we created before. Also, choose the "Create" menu item from the "Scaffold template" drop-down box.

When we click the "Add" button, a view template of our "Create" view (which renders the form) looks like the one below:

Our Wizard will have 3 steps. The first ask for Username and email address, the second step asks for First and Last name, and the third asks for Age and Biography. Now we need to wrap every step into a <div> container like the one below:

We'll need to make these containers use class "wizard-step", this will mark these containers so we can track them using javascript later. Also we'll need to hide the steps when the view renders to the end user, so define the following CSS class in the same Index.cshtml template, or you can do it in a .CSS file:

We have to add navigation buttons to the template. Add the following two buttons to the end of the template:

Adding javascript logic

We will need to handle the button clicks on the client side. The "Next" button takes the user to the next step, while the "Back" button takes them to the previous step. The button behind the scenes uses javascript to hide the current step panel (ie <div>) and show the next one, or the previous one (in case of back button).

We will need to add the following code to the .cshtml template:

<script type="text/javascript">
    $(function ()
    {

        $(".wizard-step:first").fadeIn(); // show first step


        // attach backStep button handler
        // hide on first step
        $("#back-step").hide().click(function ()
        {
            var $step = $(".wizard-step:visible"); // get current step
            if ($step.prev().hasClass("wizard-step")) { // is there any previous step?
                $step.hide().prev().fadeIn();  // show it and hide current step

                // disable backstep button?
                if (!$step.prev().prev().hasClass("wizard-step")) {
                    $("#back-step").hide();
                }
            }
        });


        // attach nextStep button handler       
        $("#next-step").click(function ()
        {

            var $step = $(".wizard-step:visible"); // get current step
                        
            var validator = $("form").validate(); // obtain validator
            var anyError = false;
            $step.find("input").each(function ()
            {
                if (!validator.element(this)) { // validate every input element inside this step
                    anyError = true;
                }

            });

            if (anyError)
                return false; // exit if any error found
             
            

            
            if ($step.next().hasClass("confirm")) { // is it confirmation?
                // show confirmation asynchronously
                $.post("/wizard/confirm", $("form").serialize(), function (r)
                {
                    // inject response in confirmation step
                    $(".wizard-step.confirm").html(r);
                });

            }
                        
            if ($step.next().hasClass("wizard-step")) { // is there any next step?
                $step.hide().next().fadeIn();  // show it and hide current step
                $("#back-step").show();   // recall to show backStep button
            }
            
            else { // this is last step, submit form
                $("form").submit();
            }


        });

    });

</script>

Creating a POST Action

Whenever the user clicks on the submit button, the controller must handle the request. We will need an action that handles the wizard data. Add a new action like below:

If all data is valid, then we will show the "Complete" template. We will add the "Complete" template by right-clicking within the new POST action, and then choosing "Add View":

In the "Complete.cshtml" template, we will just show the following message to the end user:

The wizard is ready now. Let's launch the site and try it. Navigate to "/wizard":

Notice how client-side validation is still working nicely. We did not need to enable client-side validation manually in the template as before, this is because ASP.NET MVC 3 already has it enabled by default. We did also hide the "Back" button on first step using javascript. We cannot submit the form until all information is correct. Even if a user bypasses javascript validation (which can be easily done), the form will be re-displayed again.

Further Enhancements

Prevent User from Navigating to Next Step when errors found

You may have noticed that the user can navigate to the next step even if the previous step contains errors. Let's prevent the user from navigating to the next step by applying some logic into our javascript code.

Since client-side validation uses jQuery validation behind the scenes, we obtained a validator's instance and applied validation to every input element on the current step. This will cause alert messages to appear like below:

Now we cannot move forward to step 2 until we enter a valid email address. This won't change the final result as the form won't post back until all fields are OK, however, it will improve user's experience.

Show a Confirmation Dialog Before Submission

Wizards normally show users a summary of all the details they enter before completion. This will give the user a chance to modify any mistake they made when filling out the form:

If any of the above information is incorrect, we can go back and modify it. This can be handy for large forms, as the user sometimes forgets to fill out some answers.

First, let's add the controller action that we will call to return the template above. In the Wizard controller, add the following action:

Now let's add the "Confirm.cshtml" template that is show in the screenshot above. Right click within the newly added action method, and choose "Add View":

This time we created a partial view since we will use call the action asynchronously (ie using AJAX). This is a limitation because if we do not use AJAX, all the form data (input fields) will be lost due to the stateless HTTP (There are workaround though). Anyway, let's modify the rendered template so it looks like below:

We added the "ValidationSummary" to show errors that cannot be detected on the client-side like duplicate usernames, or bypassed validation rules. If any errors found, the template will show them.

If we will use AJAX to load that template, we will need to inject it somewhere in the main template (Index.cshtml). Let's add it after the final step in the wizard, we will also mark it with both the CSS classes "wizard-step" and "confirm"

The HTML code returned from the "Confirm" action will be injected in that container. The only missing thing now is the javascript code to load the template from the server. Modify the "next step" button by adding the following code:

* You can download all the code by clicking the link "Download code" at the top.

Summary

Wizards improve web sites usability by breaking a long list of questions into multistep simple forms. If your site contains such a long form, it is recommended that you use a wizard, especially if it's the registration form. Users will become reluctant if they see a huge list of questions on one page. We learnt how to create a wizard with the aid of javascript. You can do further enhancements to this wizard by adding more navigation links, etc.

Tags:

ASP.NET | jQuery | MVC | Website Optimization

Comments

oleksii United Kingdom, on 1/2/2011 3:41:58 AM Said:

oleksii

Great post! Thanks a lot.

Ashfaq India, on 1/4/2011 7:33:52 AM Said:

Ashfaq

Thanks for explaining all these. It's easier to understand as you have made this post with screenshots. Keep up the good work.

Bats Ihor Ukraine, on 1/17/2011 3:18:45 AM Said:

Bats Ihor

I finally decide, now I am following you) Very nice article.

Nadeem Afana United States, on 2/1/2011 2:48:39 PM Said:

Nadeem Afana

Michael,
Yes it does. See the 1st example on jQuery page which might help docs.jquery.com/.../required

WI Steve United States, on 3/4/2011 5:38:37 AM Said:

WI Steve

Great posting Nadeem!  I am just beginning Web Development in .Net MVC 3 and this post was just what I needed.  

Everything works great except when I add a boolean into my class definition.

The boolean is converted to a checkbox on the form. I have not been able to get Required or RegularExpression attributes to work in validation on these fields.

If I write a custom attribute, validation does not take place on the wizard step level but at the end (model validation?) just before the confirm is displayed.

Do you have an idea how to overcome this?

Thanks much, Steve

WI Steve United States, on 3/8/2011 5:57:39 AM Said:

WI Steve

Nadeem,
Here is a little more info for my question above. I created the following custom attribute:
        public class BooleanRequiredAttribute : RequiredAttribute, IClientValidatable {

        public BooleanRequiredAttribute() {}

        public override bool IsValid(object value) {
            return value != null && (bool)value == true;
        }

        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) {
            //return new ModelClientValidationRule[] { new ModelClientValidationRule() { ValidationType = "mandatory", ErrorMessage = this.ErrorMessage } };
            yield return new ModelClientValidationRule() { ValidationType = "mandatory", ErrorMessage = this.ErrorMessageString };
        }
    }


I added a termAgreement field to the User class like the following:

        [Display(Name = "Terms of Service:")]
        [BooleanRequired(ErrorMessage = "You must agree to the terms of this agreement before proceeding")]
        public Boolean termAgreement { get; set; }


I then added an boolean adapter via a JavaScript function to the Index view as in the following:

<script type="text/javascript">
    $(function () {
        $.validator.unobtrusive.adapters.addBool('mandatory', 'required');
    } (jQuery));


    $(function () {
        $(".multi-step:first").fadeIn(); // show first step

(the rest of the code is as you listed)

When testing the page with the check box, upon clicking the "Next" button the check box is marked as invalid (red box around it) but no message is displayed.  If I uncheck the box and recheck it the check box is then marked in red and the error message is displayed.  Do you have any recommendations how to handle this common scenario of requiring a check box be checked?

Looking at this in Firebug/Firefox the input field is updated to an error upon clicking the Next button but the span class stays at true.  Subsequent check box changes do update the span class and the error is correctly displayed.

Thank you for considering my question, Steve

WI Steve United States, on 3/9/2011 5:53:55 AM Said:

WI Steve

A friend may have just found a solution to the issue I brought forward.  In MVC 3 adding a checkbox field to a form also generates a hidden field.  Running the code in Firebug/Firefox shows the checkbox field is updated to an error upon clicking the Next button but the span class (hidden field) stays at true.  Subsequent check box changes do update the span class to false and the error is correctly displayed.

Within the "$("#next-step").click(function ()" function.

Change the following line:

"$step.find("input").each(function () {"

to:

$step.find("input:not(:hidden)").each(function () {

The checkbox is now correctly marked as an error and the error is displayed if the checkbox is not checked and the Next button is selected. Subsequent changes to the checkbox do not alter the behaviour.

Steve

Nadeem United States, on 3/9/2011 6:25:21 PM Said:

Nadeem

WI Steve,
I am glad you solved your issues. I was not available in the last days as I moved in here. Also, there are a lot of spammers targeting my blog.

sainath India, on 3/17/2011 6:38:15 AM Said:

sainath

Hi,
Thanks for the good article. I have started development using MVC2, and i just found your article about creating wizard. When I have modified the project for MVC2 everything works fine. But error messages doesn't appear when we click Next, appears after submitting the form.

Do you have any idea on this?

Nadeem United States, on 3/17/2011 7:39:31 AM Said:

Nadeem

sainath,
I think you need to upgrade to ASP.NET MVC 3.

Salvador Parra Mexico, on 4/3/2011 8:09:50 PM Said:

Salvador Parra

Nadeem

Thank you very much for your post.

I'm starting with MVC3 and implementing abstract validators through Fluent Validation MVC 3 for my ViewModel's class and also some Telerik MVC controls, and then take your post to create a wizard with some steps.

I follow your post and i get an exception on javascript form validator function:

   var validator = $("form").validate(); // obtain validator

it says: $("form").validate is not a function on firebug.

I tried with $("#form"), $('form') and $('#form') but it can't reach the function. Removing the code for obtaining validator works, but naturally we want to validate before move to the next wizard step.

Any ideas?

Cheers!

Nadeem United States, on 4/3/2011 8:47:52 PM Said:

Nadeem

Salvador,
The code above is written using unobtrusive validation which uses jQuery behind the scenes, you get an error because you are using a different tool for validation. Consult the documentation about client side validation of the your tool.

Michael United States, on 4/8/2011 10:54:29 AM Said:

Michael

Excellent post. Just used in project. Was wondering if you had any idea how I might implement "Wizard Steps" off to one side.  For example, when you are on Step 2, the right side contains all the steps, and Step 2 has a check mark or is highlighted.

Thanks!

Nadeem United States, on 4/8/2011 1:13:52 PM Said:

Nadeem

Michael,
Do you mean navigation links? Yes, it is easy to do with jQuery. You need a list of links (<a>) and a special CSS class like "selected" for the current step link.

Troy United States, on 4/21/2011 9:15:25 PM Said:

Troy

Purely Awesome!!!!!!!!!!!!!

elmo_pisskopf Germany, on 4/22/2011 4:11:15 AM Said:

elmo_pisskopf

This is "BAD PRACTICE" and a maintenance nightmare! Don't implement wizards by hiding parts of the form on client side (anyone could unhide this using spidermonkey resulting in UB). Use @Html.Serialize instead to preserve form state of your model (or TempData if you like).

Nadeem United States, on 4/22/2011 6:26:30 AM Said:

Nadeem

elmo_pisskopf,
It is not bad practice to hide elements on the client side. In fact, many sites do that for other purposes, too.
anyone could unhide this
And what's wrong in that?

paul United Kingdom, on 5/11/2011 9:16:57 AM Said:

paul

Quite nice, however, as sainath mentioned, when hitting 'next', the wizard does not move to the next page, but it doesn't show any validation error neither.
The reason for this is that the validation framework is lazy until the submit is clicked, and since no submit is done on going to the next page, no validation message is shown.
Does anybody has a solution to this problem?

Rico Suave Netherlands, on 5/22/2011 9:49:51 PM Said:

Rico Suave

Hi Nadeem,
Thank you for this post.
You mention other wizard solutions in your blog. I'm looking for a wizard that submits every step, because I'm working with forms where a selection on one step might lead to different pages being shown later on in the wizard.
Do you know of any other wizard implementations in MVC that might work that way?
Thanks,
Erik

Nadeem United States, on 5/23/2011 8:35:37 AM Said:

Nadeem

Rico Suave
You can use server-side wizard that submits each step, it is a little more tricky with validation since you need to perform partial validation. You can find this implementation in a book called Pro ASP.NET MVC 2 by Steven Sanderson which is on the top-right side of my blog. I don't know if there is any implementation online. Try and google it!!

Hope this helps!!

nc France, on 6/21/2011 4:23:49 AM Said:

nc

Very smart way to make wizard. Thank you for this post!

Brt Netherlands, on 6/22/2011 2:45:38 AM Said:

Brt

Good tutorial, wizards can be used lots of times.

But in IE8 i`ll get an javascript exception on line 27 char 9. that means there is possibly something wrong with: var validator = $("form").validate(); // obtain validator. The message appears when i click the next button. The message is: Object doesn`t support this property or method.

Nadeem Afana United States, on 6/22/2011 8:06:49 AM Said:

Nadeem Afana

Brt,
The code above is written using unobtrusive validation which uses jQuery behind the scenes, you get an error probably you are using a different tool for validation. Consult the documentation about client side validation of the your tool.

Brt Netherlands, on 6/22/2011 9:23:09 AM Said:

Brt

The validation is a part of the Adding javascript logic. I did not change anything on the code as he is above. So i dont use another script. if i use the wizard with IE9 or Chrome than there is no problem

Nadeem Afana United States, on 6/22/2011 10:13:04 AM Said:

Nadeem Afana

Brt
Try downloading my code from the link above, and let me know. Maybe Microsoft has changed the validation client side somehow.

fabric blinds United States, on 6/30/2011 3:10:16 AM Said:

fabric blinds

nice tutorial am following it nearly complete with the app am adding navigation buttons to the template will report back thanks.

Tom Stickel United States, on 7/1/2011 10:46:04 AM Said:

Tom Stickel

I was looking at Steven Sanderson's MVC 2 book, and he is actually using a similar example but with encrypted hidden fields.  I see your solution and his and I want to blend them together.  From a user experience, yours is awesome, however from a developer standpoint this works good in theory or for an example, but I need to use multiple views with 30 screens and be constantly saving data to a database via ajax postings to WCF etc...  Thanks for your example though.

Nadeem Afana United States, on 7/1/2011 3:48:09 PM Said:

Nadeem Afana

Tom,
Steve is using server side for creating a wizard, while I am using client side. Both can achieve the same result. Which one to user depends on your javascript skills, client side can be easier if you have good knowledge of javascript and jQuery. If you decide to use server side, then validation can be a little challenging.

Martijn Netherlands, on 7/28/2011 8:29:29 AM Said:

Martijn

I had a problem with textarea not being validated which i could fix like this. Notice I also validate the textare

$step.find("input").add(
        $step.find("textarea")).each(function () {
            if (!validator.element(this)) { // validate every input element inside this step
                anyError = true;
            }
        });

Martijn Netherlands, on 7/29/2011 12:58:32 AM Said:

Martijn

Better use Jquery to find the textareas as well
$step.find(":input:not(input[type=hidden])").each(function () {....

khalid United States, on 8/2/2011 5:05:14 AM Said:

khalid

thank you very much

beki United States, on 10/19/2011 2:32:24 PM Said:

beki

Thank you very much. It is really helpful.
But, I got one problem. Everything works fine except the validation. When I press next it goes to the next page without validation.

Any Idea,

I am using unobtrusive validation.

Thanks

shadi Jordan, on 11/29/2011 10:20:11 PM Said:

shadi

Thank you for this tutorial,I'm new to .NET development and this tutorial is really helpful

I'm trying to implement it using custom validation but the validation only occurs after submitting the form not at each step,does anyone know how to fix it? is there a way to make the validator run at each step instead after the form is submitted?

Franco Bolivarian Republic of Venezuela, on 12/2/2011 9:24:03 AM Said:

Franco

Excelent post! I'll try to implement it!!! Thanks!

Franco Bolivarian Republic of Venezuela, on 12/2/2011 9:25:08 AM Said:

Franco

Excelent post! I'll try to implement it!!! Thanks!

Jason Mogera United States, on 12/17/2011 1:15:01 PM Said:

Jason Mogera

Hi Nadeem,

I am using your example. It works great, but the problem I am having is that one of the steps. i need to do a post back. I am using a partial view for the step, and I end up getting a Http.beginForm() within a Http.BeginForm(). How do I set up the wizard, so that i can do a post back from within a step, and not call the final step.

Nadeem Afana United States, on 12/17/2011 6:21:37 PM Said:

Nadeem Afana

Json Mogera,
You can post back the form in any step using $("form").submit();

Jason Mogera United States, on 12/27/2011 8:21:17 PM Said:

Jason Mogera

Hi Nadeem,

I can call the post back like that, but I need it to be part of a separate post back. so $("form").sumbit() will call the Index Action with has the [HTTPost] associated with it. But I need it to call a separate action? Let me know if that make sense?

Nadeem Afana United States, on 12/28/2011 7:21:56 AM Said:

Nadeem Afana

Jason Mogera,
You need to create a separate form element (from the view perhaps) then you do $("form.my-form").submit()

Dyrgutt Australia, on 2/15/2012 7:01:38 PM Said:

Dyrgutt

Love it mate, thanks very much!  Also, would it be possible to implement a feature where the next button is disabled/greyed until all errors are resolved?

Jeroen Netherlands, on 3/23/2012 7:15:21 AM Said:

Jeroen

Nice. I used it.

There are two problems with your code though, which you need to fix:
1) when a field has no validator the object is undefined and invalidates the step.
2) mvc adds a hidden field for a checkbox because unchecked checkboxes are not posted back. Your codes validates hidden fields which in this case invalidates the step. The hidden field acts as a dummy. (it caused a problem in my case when i had a custom validator checking if a checkbox was checked).

fixed code:
$step.find("input[type!='hidden']").each(function () {
                if (!validator.element(this) && validator.element(this) != undefined) { // validate every input element inside this step
                    anyError = true;
                }

            });

Tim B. United States, on 9/18/2012 3:23:05 AM Said:

Tim B.

Nadeem, this post is wonderful.

Being interested in graceful degradation I was contemplating a server-side implementation to complement yours and per your suggestion I have purchased the book by Steven Sanderson (your quote: "..You can find this implementation in a book called Pro ASP.NET MVC 2 by Steven Sanderson..") My book is MVC 3 edition and for the life of me I cannot find the example. Can I ask you to please mentioned the name of a chapter/section to help me find it (hoping that this narrative has not been dropped from the MVC3 book). Thank you for the wonderful idea!

Nadeem United States, on 9/18/2012 7:54:00 AM Said:

Nadeem

Tim B
I believe this is not available in Pro ASP.NET MVC 3. However, it is in chapter 13 in Pro ASP.NET MVC 2.

Nadeem.

Francois Wahl Ireland, on 11/14/2012 1:08:58 AM Said:

Francois Wahl

HiNadeem

Very good post and it shows a very nice way of doing a wizard in ASP.NET MVC.

I have something I thought I share regarding the field validation in each step.

We had to implement a similar wizard to yours in most ways, except we had several custom validators which are for all sort of elements to validate, some hidden others not even inputs. Bascally any element could have a validator against it.

We didn't want to re-invent the the way from.valid() works and manually iterate though all elements, looking for specific data-attribute marking validators, etc..

We worked around this by wrapping a form around each step instead of a form around the complete wizard and using jQuery, when moving from one step to the next, we simply call form.valid() instead of obtaining the validator() and manually validating.

As each step is wrapped into it's own form each form.valid() only validates the fields within the step, including custom validators.

In the end we use ajax to submit the form data by doing someting similar to $(form.wizard-step).serialize(); which serializes all the forms together and post the data as usual to the action in the controller.

Worked well for us and saved us from re-inventing the wheel I suppose.

I thought I mention this in case someone finds themselfes in a similar scenario.

Comments are closed
Google+