views:

967

answers:

1

Hello,

There is a plenty of very good post and explanations how to implement validation with ASP.NET MVC, and I prefer one of these:

However, I really like to call ActionMethods via jquery $.ajax method. One of the reasons why I want to use $.ajax is because there will be a lot of partial views loaded into the page dynamically (even the form for entity creation) via $.ajax calls and I can't just return the view - I'll lose all dynamically loaded content.

To give you a better view on the problem, I'll post some simple code to explain how would I like to call controllers actions and handle responses in client, jquery code.

The controllers ActionMethod:

    public ActionResult CreateCustomer(string name, string accountNumber)
    {
        try
        {
            CustomerService.InsertCustomer(name, accountNumber);

            return Json(new ActionInfo()
            {
                Success = true,
                Message = "Customer Is Successfully Created"
            });

        }
        catch (Exception ex)
        {
            return Json(new ActionInfo()
            {
                Success = false,
                Message = ex.Message
            });
        }
    }

Calling and handling in client code:

$.ajax({
type: "POST",
url: $form.attr('action'),// /MyController/CreateCustomer
data: $form.serialize(),
error: HandleUnespectedError,
dataType: "json",
success: function(response) {

    if (response.Success)
        alert("Success: " + response.Message);
    else
        alert("Error: " + response.Message);
}});

Is there a good way to make some of these validation frameworks to work the way I need? I know that I can add validation errors in ActionInfo, and then handle it in client, but that would be already a building of my one validation, I believe.

+2  A: 

I have had great success doing validation via AJAX using data annotations attributes. In order to check the validity of your data, you will want to use the controller's ModelState property, which has a property of its own called IsValid. I strongly suggest taking a peek at the data annotations validation attributes tutorial from the official ASP.NET MVC site.

First off, you will want to modify your controller action to accept your model object as a parameter, rather than a separate name and account number. This will make performing the validation, which I will demonstrate below, much simpler. From your example, my best guess is that your model object is, or would be, called Customer. You might have the following code to define the model object and your controller action...

// model object
public class Customer
{
  public Int32 Id {get; set;}
  public String Name {get; set;}
  public String AccountNumber {get; set;}
}

// controller
public class CustomerController : Controller
{
  public ActionResult CreateCustomer( [Bind(Exclude = "Id")] Customer customer )
  {
     // controller action code
  }
}


Make sure your form fields are named to match the names of the properties of the Customer object so ASP.NET MVC can automagically bind them. The "Bind" attribute, in this case, is telling ASP.NET MVC to ignore the "Id" property of the Customer class when binding form fields to model properties. Since this is a new customer, we don't have an Id yet, so we can safely leave the Id as whatever the default value is and leave the data layer to figure out how best to generate it.

Once the controller has constructed the model object for the action method, its validity can be easily checked via the ModelState.IsValid property. As one might expect, it will return true if the model properties are valid, or false if 1 or more properties are invalid.

From the original question, it appears that the CustomerService.InsertCustomer method is throwing exceptions when validation fails. This is completely unnecessary. InsertCustomer should only need to perform whatever data operations are necessary for inserting the new record. Unless you wish to abstract implementation specific exceptions, like SqlException, InsertCustomer should not really need to catch or throw any exceptions, but can most likely just let any exceptions bubble up to the controller (or whoever the caller may be).

The end result of all of this, might be a controller action that looks like the following:

public ActionResult CreateCustomer( [Bind(Exclude = "Id")] Customer customer )
{
  // model is invalid
  if (!ModelState.IsValid)
  {
    return Json(new ActionInfo()
    {
      Success = false,
      Message = "Validation failed" // you will probably want a more robust message :-)
    });
  }

  // service method accepts a Customer object rather than arbitrary strings  
  CustomerService.InsertCustomer(customer);

  return Json(new ActionInfo()
  {
    Success = true,
    Message = "Customer created successfully."
  });

}


If you want to report unexpected errors, like database related exceptions, then you can certainly add a try/catch block around the call to InsertCustomer, and return the necessary result for displaying the error message back to the client.

Justin Holzer
Hey thedude! Thanks a lot for the effort :) I totally agree with you for the Customer entity for binding (wanted to do like that, don't know why had I put the method with simple parameters) and for exception handling for sure. With xVal I can easely implement client validation, and if someone avoid it I will catch it in action. But how would I present validation messages next to the fields in that case?
Misha N.
For that, you would want to return a partial view rather than JSON from your controller action. In your partial view, you would use the Html.ValidationMessage() helper method to display an error message for each field. Field level error messages that were generated by your Data Annotations attributes are available to your controller via the `ModelState` property. For instance, to get the first error message for a field called name, you would get it via `ModelState["Name"].Errors[0].ErrorMessage`. Also, take a look at the `Ajax.BeginForm` method. I have used it with great success in the past.
Justin Holzer
That is what I realized, either I will return JSON either partial view, and that is the thing I don't like... However, I'll take a look how to deal with it using MS built in Ajax features, although my wish was to avoid that :) Thanks
Misha N.
Not sure why you wish to avoid it, but I'm sure you have your reasons. I will attest to the fact that using `Ajax.BeginForm` with partial views has worked well for me. I believe it's using JQuery under the hood anyways, though I could be mistaken on that.
Justin Holzer