A Basic Form

We have created a simple view and looked at how the ActionResult class works. Now it is time to look at how to create a basic form. As an example, we are going to build the Tigers Assault the Monkeys sample.

Step 1: Create the View

We have already seen how to create a view. In this case we are going to call it TigersAssaultTheMonkeys.

Step 2: Create the Controller Action

We have also seen how to do this. As you now know, the controller action should be named the same, i.e. TigersAssaultTheMonkeys.

Excursus: Html Helpers

In Webforms the tool for creating Html widgets is the server control, i.e., controls like <asp:TextBox runat="server" />. These controls would do the work of rendering the proper Html elements, maintaining state, would give you events to hook into, et al. In some ways they were nice; in other ways they were not always awesome.

This is not the way of MVC. Instead of the ubiquitous server control you have the ubiquitous (if you want to use them) uses of the HtmlHelper. Instead of what you saw above, you have something like this: <%= Html.TextBox("FooDog") %> will render the following: <input id="FooDog" name="FooDog" type="text" value="" />. In some cases an HtmlHelper will just help you write the Html a little faster (as in the case of text inputs). In other cases they do more than just save a bit of typing (as in what they do with validation, to be covered in step 6 below).

Thankfully you do get intellisense in the view files when working with the various HtmlHelper methods.

On a personal note, I have not decided when I am going to use an HtmlHelper versus just typing out the Html elements. That decision will come with time as I try to find my favorite way to do MVC. The same may end up being true for you as well.

Step 3: Create the Form Elements

So now to create the necessary form elements. At the very least we are going to need three things: the form tag, at least one input and a submit button. Given those requirements, your form might look something like this:


< % using (Html.BeginForm())
   { %>
   
< %= Html.TextBox("NumberOfTigersSent") %>
< % } %>

The extra fieldset, div and label tags are not necessary; that is just the way I do forms (though I would recommend something other than just bare input fields). You see one HtmlHelper for the form tag and one for the input tag. In this case we are creating a form for a tiger assault, so the name of the input is matched according to the theme. This renders the following:

Note that the action of /Zoo/TigersAssaultTheMonkeys in the form tag is dynamically built based on context. The view returned came from that location, so by default, it will post back where it came from.

Step 4: Create the Controller Action for the Post

Now that we have the form, we need to have a controller action to post to. By default the method to receive the post is the same name as the one on the initial get to view the page, so some mechanism needs to be in place to distinguish the two action methods on the controller. That is done through the AcceptVerbs attribute. The following are the two actions I created for the TigersAssaultTheMonkeys code. They also include the logic controlling how successful tigers are, and limit the total monkeys killed to 100.

public ActionResult TigersAssaultTheMonkeys()
{
    return View();
}

//Note: There is something fundamentally flawed in how the param
//  value is being passed. We will discuss this in the section on
//  validation below.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult TigersAssaultTheMonkeys(int numberOfTigersSent)
{
    int numberOfMonkeysEaten = 0;

    double numberOfPotentialMonkeysEaten = numberOfTigersSent * .75;
    if (numberOfPotentialMonkeysEaten > 100)
    {
        numberOfMonkeysEaten = 100;
    }
    else if (numberOfPotentialMonkeysEaten > 0)
    {
        numberOfMonkeysEaten = Convert.ToInt32(Math.Round(numberOfPotentialMonkeysEaten, 0));
    }

    ViewData["NumberOfMonkeysEaten"] = numberOfMonkeysEaten;

    return View();
}

The bit about ViewData...we will get to that in step 5. It is important to note at this point one thing that the MVC framework does for you, and that is map input fields in a form to parameters on an action method that receives a post. Note that numberOfTigersSent is named the same as the input control. This is something necessary for this automatic mapping to take place.

Step 5: Return Results

If you are going to the trouble of sending some tigers out to eat some monkeys, it would be great to know how successful they were. Let us do that now.

First, you need a mechanism for returning a bit of data to the form. Looking in the code above (line 21), one way of doing this is exemplified. One way of viewing ViewData is to see it as a hashtable for the developer to stick random stuff in to get back to the view. In this case the number of monkeys that were eaten are put in with the key "NumberOfMonkeysEaten".

Second, you need a way to display something in ViewData on the form. For the sample form I added the following block after the form:

    

This is how many monkeys the tigers killed: < %= ViewData["NumberOfMonkeysEaten"]%>

Through this syntax the value that was put into ViewData is inlined into the page during the execution of the server-side action. Voila! Results.

Step 6: Validation

Unfortunately, we cannot trust the input from our users all of the time. The above code is flawed because it takes no account of failure to enter the right values. First thing to try: enter a letter into an input field that we have told the framework to convert to an int (by declaring it that way on the signature of the method).

The result is a rather wordy error message and it goes like this: "The parameters dictionary contains a null entry for parameter 'numberOfTigersSent' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult TigersAssaultTheMonkeys(Int32)' in 'HowMVCWorks.Controllers.ZooController'. To make a parameter optional its type should be either a reference type or a Nullable type. Parameter name: parameters". Sweet mother of all verbose error messages! What they mean is that the int numberOfTigersSent parameter in the action signature needs to be a nullable int, as in int? numberOfTigersSent. Even though the error message does not make a lot of sense, the way they solved the problem does. How do you represent a lack of value or a bad value with a plain ol' int? You cannot use 0 because that is a common valid int value. Maybe you could use Int32.MinValue like many would use before the coming of nullable types to .NET. That would generally work, but a null works even better. So to fix this we need to first change the parameter to a nullable int and then check to make sure it is not null in the code before we set a value for viewing on the page. The new version of the code would look like this:


// Better, but still no validation.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult TigersAssaultTheMonkeys(int? numberOfTigersSent)
{
    int numberOfMonkeysEaten = 0;

    if (numberOfTigersSent.HasValue)
    {
        double numberOfPotentialMonkeysEaten = numberOfTigersSent.Value * .75;
        if (numberOfPotentialMonkeysEaten > 100)
        {
            numberOfMonkeysEaten = 100;
        }
        else if (numberOfPotentialMonkeysEaten > 0)
        {
            numberOfMonkeysEaten = Convert.ToInt32(Math.Round(numberOfPotentialMonkeysEaten, 0));
        }
    }

    ViewData["NumberOfMonkeysEaten"] = numberOfMonkeysEaten;

    return View();
}

Of course you should not leave your users hanging. Instead of just refreshing the page, let them know what the problem is! The mechanism for doing this in MVC is not terribly unlike that in Webforms though it is implemented quite differently. To add validation to the page we will add a validation summary, a validation message to put an asterisk by the field and the code on the backend to implement this. First comes the changes to the form.

< %= Html.ValidationSummary() %>

< % using (Html.BeginForm())
   { %>
   
   
< %= Html.TextBox("NumberOfTigersSent") %> < %= Html.ValidationMessage("NumberOfTigersSent", "*") %>
< % } %>

This is how many monkeys the tigers killed:

There are a couple new things there. First notice the ValidationSummary on line 1. Second, notice the ValidationMessage on line 10. The summary HtmlHelper is pretty simple. Validation messages will get output there as items in an Html unordered list. The ValidationMessage HtmlHelper takes two arguments, the name of the field with which it is to be associated (NumberOfTigersSent) and the error message to be displayed if there is an error with that field.

The code changes are fairly straightforward. Instead of ignoring a null value as we did before, we now add an error message:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult TigersAssaultTheMonkeys(int? numberOfTigersSent)
{
    int numberOfMonkeysEaten = 0;

    if (numberOfTigersSent.HasValue)
    {
        double numberOfPotentialMonkeysEaten = numberOfTigersSent.Value * .75;
        if (numberOfPotentialMonkeysEaten > 100)
        {
            numberOfMonkeysEaten = 100;
        }
        else if (numberOfPotentialMonkeysEaten > 0)
        {
            numberOfMonkeysEaten = Convert.ToInt32(Math.Round(numberOfPotentialMonkeysEaten, 0));
        }
    }
    else
    {
        //This is new
        ViewData.ModelState.AddModelError("NumberOfTigersSent", "Yo, whassup with that tiger count value?");
    }

    ViewData["NumberOfMonkeysEaten"] = numberOfMonkeysEaten;

    return View();
}

To trigger a validation failure on an input involves adding adding an error to the model with both key (the name of the control) and value (the error message to show in the validation summary). Because an error has been added the framework knows to display the validation summary and show the error message in the form.

If you do not apply any css formatting, it might not look awesome, like here:

Screenshot of an unstyled page

No styling applied.

You may not like the formatting, but at least now it stands out from the rest of the content.

Screenshot of an styled page

A little styling applied.

Other Stuff

There are multiple ways to do some of these things in MVC, as it turns out. And some of them are really groovy, but they will wait till later. For now, though, the above can at least get you started.

Speak Your Mind!

Have something to say? Find a grammatical mistake? Think I said something incorrect? Don't like my perspective? Hate my color scheme? Whatever it is, you can let me know. I would appreciate it if you did.