Sunday, May 22, 2011

Contact Form Revisited with ASP.NET MVC 3, jQuery Validator, & the jQuery Form Plugin




I recently added a partial contact view to my MVC3 project and thought I’d share since I did basically this same post over a year ago with the original MVC. I stopped using the Castle Validation because I’ve found that the MVC3 stuff is working for me now. I also am not using fluentHtml anymore because MVC3 uses that style now.

Okay…let’s get started.

We’ll start with the view model class like the last post. By the way, if you’re not familiar with the way I setup my MVC projects, see this post. (If you read my first Contact Form post, you’re probably experiencing déjà vu).

public class ContactView
{
[Required]
public string Name { get; set; }
[Required, ValidateEmail(ErrorMessage = "Valid email is required.")]
public string Email { get; set; }
[Required]
public string Subject { get; set; }
[Required]
public string Message { get; set; }
}

The Required attribute is exactly what it seems like and it’s part of the System.ComponentModel.DataAnnotations. The default error message is “The [propertyname] is required.”. If you want to reset it, you can do this: [Required(ErrorMessage = “Whatever I want it to be”)].

The ValidateEmail is a custom validation attribute. I didn’t like the looks of having a RegularExpressionAttribute defined there and since email is such a common thing to validate, I made this one:

public class ValidateEmailAttribute : RegularExpressionAttribute
{
public ValidateEmailAttribute(): base(@"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")
{
}
}

It just inherits from the RegularExpressionAttribute and passes in the regex to validate an email address. Apparently there is much discussion on how to validate an email address, which I believe is why Microsoft didn’t provide this to us.

So let’s look at the View/XHTML Markup.

@model Sample.Core.UI.Model.ContactView
@using Sample.Core.UI.Helpers
@using (Html.BeginForm("Contact", "Home", FormMethod.Post, new { id = "contactform" }))
{
<fieldset>
<legend>Contact Us</legend>
    <p>
@Html.LabelFor(f => f.Name, "Your Name")<br />
@Html.TextBoxFor(f => f.Name, new { style = "width: 200px", @class="required", accesskey="n" })
</p>
<p>
@Html.LabelFor(f => f.Email, "Your Email")<br />
@Html.TextBoxFor(f => f.Email, new { style = "width: 200px", accesskey = "e" })
</p>
<p>
@Html.LabelFor(f => f.Subject, "Subject")<br />
@Html.TextBoxFor(f => f.Subject, new { style = "width: 200px", accesskey = "p" })
</p>
    <p>
@Html.LabelFor(f => f.Message, "Message")<br />
@Html.TextAreaFor(f => f.Message, new { style = "width: 350px", rows = "4", accesskey = "c" })
</p>
@Html.AntiForgeryToken()
<input type="submit" id="bContact" name="bContact" value="Send Message" accesskey="s" /> 
@Html.DivSuccessMessage("Message sent successfully", "contactsuccess") 
@Html.ValidationSummary("", new { id = "contacterror" })
<noscript><br /><br /><div class="tip">Our contact form may look and act funny because you have JavaScript disabled. For a better experience on thissample.com, please enable JavaScript.</div></noscript>
</fieldset>
}

You can see at the top of the page I specify my view model. I ‘m also referencing a helper namespace for my DivSuccessMessage extension. Basically all it does is checks the ModelState for errors and for ViewData[“success”] not being null and displays the message specified. After that it’s basically a plain ole HTML form.

Okay, now we have our form built with our view model. Below the end of the XHTML above, I have the following jQuery code.

<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.validate/1.8/jquery.validate.min.js"></script>
<script type="text/javascript" src="http://www.malsup.com/jquery/form/jquery.form.js"></script><script language="javascript" type="text/javascript">
$(document).ready(function () {
var formoptions = { beforeSubmit: function (formData, jqForm, options) {
$("#bContact").attr('value', 'sending...');
$("#bContact").attr('disabled', 'disabled');
}, success: function (data) {
if (data.status == "Success") {
$("#contacterror").hide();
showMessage("#contactsuccess", data.message);
$('.valid').removeClass('valid');
validator.resetForm();
}
else {
$("#contactsuccess").hide();
showMessage("#contacterror", data.message);
}
$("#bContact").attr('value', 'Send Message');
$("#bContact").removeAttr('disabled');
}, dataType: "json"
};jQuery.validator.messages.required = "";
var validator = $("#contactform").validate({
submitHandler: function (form) {
$(form).ajaxSubmit(formoptions);
},
invalidHandler: function (e, validator) {
var errors = validator.numberOfInvalids();
if (errors) {
var message = errors == 1 ? 'You missed 1 field. It has been highlighted.' : 'You missed ' + errors + ' fields.  They have been highlighted.';
showMessage("#contacterror", message);
$("#contactsuccess").hide();
} else {
$("#contacterror").hide();
}
},
messages: { Email: { email: ""} },
rules: {
Subject: "required",
Message: "required",
Email: { required: true, email: true }
},
errorClass: "invalid",
validClass: "valid",
errorContainer: "#contacterror"
});function showMessage(id, message) {
$(id).html(message);
$(id).show();
}
});
</script>

Let’s break this down. First section the formOptions:

var formoptions = { beforeSubmit: function (formData, jqForm, options) {
$("#bContact").attr('value', 'sending...');
$("#bContact").attr('disabled', 'disabled');
}, success: function (data) {
if (data.status == "Success") {
$("#contacterror").hide();
showMessage("#contactsuccess", data.message);
$('.valid').removeClass('valid');
validator.resetForm();
}
else {
$("#contactsuccess").hide();
showMessage("#contacterror", data.message);
}
$("#bContact").attr('value', 'Send Message');
$("#bContact").removeAttr('disabled');
}, dataType: "json"
};

This code is used to define all my options for the jQuery.Form plugin. What it says is this:

  • beforeSubmit – Change the button to say “sending…” and disable it
  • on success – if the status = “Success” then hide the contacterror div, show the success message, manually remove the valid class from my inputs, and reset the form. Otherwise, hide the success message and show the error message with the message received. Regardless, re-enable my button and make it say “Send Message”. (Note: I shouldn’t have to manually remove the valid class, but the resetForm wouldn’t do it for me like it’s supposed to do.)
  • dataType – json received from my action

The validation section:

jQuery.validator.messages.required = "";
var validator = $("#contactform").validate({
submitHandler: function (form) {
$(form).ajaxSubmit(formoptions);
},
invalidHandler: function (e, validator) {
var errors = validator.numberOfInvalids();
if (errors) {
var message = errors == 1 ? 'You missed 1 field. It has been highlighted.' : 'You missed ' + errors + ' fields.  They have been highlighted.';
showMessage("#contacterror", message);
$("#contactsuccess").hide();
} else {
$("#contacterror").hide();
}
},
messages: { Email: { email: ""} },
rules: {
Subject: "required",
Message: "required",
Email: { required: true, email: true }
},
errorClass: "invalid",
validClass: "valid",
errorContainer: "#contacterror"
});

This code defines my validation for the contact form. It says this:

  • Set all messages for required fields to empty by default
  • submitHandler – on submit do this, which it calls the ajaxSubmit contained in the jQuery.Form plugin with our options specified above.
  • invalidHandler – if the form isn’t valid, get the number of errors and show the error message. Otherwise, hide the error message.
  • messages – Defines what the message should be for email, which is empty. I would’ve had to specify the required messages too had I not set them to empty first. Also note that the Email: has to match the ID of one of  your inputs.
    • Example: <input type=”text” id=”whateverid”/> so the messages would looks like this:

      messages: {whateverid: {required: “some message”}}

  • rules – Defines the rules for each input. Phone, Comments are required and Email has required and email. Notice name is required, but not specified here. It’s because I added the required class to the Name input in the XHTML instead of specifying down here so you could see you have options.
  • errorClass – Specifies my style class for when the input is invalid.
  • validClass – Specifies my style class for when the input is valid.
  • errorContainer – Specifies the div I want to show my error messages in.

Final section:

function showMessage(id, message) {
$(id).html(message);
$(id).show();
}

This just finds the container sets the html and shows it.

Okay, so finally here’s what the controller looks like that we mentioned in the @Html.BeginForm() section above.

[HttpPost, ValidateAntiForgeryToken, ValidateInput(true)]
public ActionResult Contact(ContactView view)
{
if (!ModelState.IsValid)
{
if (Request.IsAjaxRequest())
return Json(new { status = "error", message = "All fields are required." });

return View(view);
}    try
{
var notificationService = DI.EmailNotificationService(new EmailNotification(view));
notificationService.Notify();
}
catch (NotificationException)
{
ModelState.AddModelError("notifyerror", "Could not connect to mail server.");
}    if (Request.IsAjaxRequest())
return ModelState.IsValid ? Json(new {status = "Success", message = "Message sent successfully."}) : Json(new {status = "error", message = "Could not connect to mail server."});    return ModelState.IsValid ? Success(view) : View(view);
}

So this action accepts HttpPost, must have a valid Anti-Forgery Token, and it validates the input. First thing it does is verifies the modelstate is valid. The reason for this is that some people run their browser with JavaScript disabled. So we have to account for that in our code and make sure that we are validating on the client-side and on the server-side. So if the ModelState is invalid, we have to check to see if it’s an AJAX request. if it is, we return a Json result with the status of error and a message stating all fields are required. If it’s not an AJAX request, we simply return the view.

If all is valid, we continue and go ahead and send the notification. If the notification bombs, we add an error to the modelstate and then recheck and act accordingly. If you want to know what the notification service looks like, please refer to the first post because it’s all the exact same.

So, this method of coding will work when JavaScript is enabled and disabled and all the data will be validated regardless as well.

Here’s what the screen looks like after just hitting Send Message:

image

Here’s what it looks like after all the fields are valid right before I hit Send Message:

image

After message sent:

image

Please let me know if you have any questions.

Download Demo

Thanks for reading!

Shout it

kick it on DotNetKicks.com

blog comments powered by Disqus
Related Posts Plugin for WordPress, Blogger...