The modern way to handle and validate POST data in MVC 2

There are many articles dedicated to working with data in MVC and nothing about MVC 2.

So my question is, what is the correct way to handle the POST request and validate it.

Let's say we have 2 actions. They both work on the same entity, but each activity has its own separate set of object properties, which should be linked automatically. For instance:

  • Action "A" should only bind the property "Name" of the object, taken from the POST request
  • Action "B" should only bind the "Date" property of the object taken from the POST request

As far as I understand, we cannot use the Bind attribute in this case.

So. What are the best practices for MVC2 to handle POST data and probably validate it?

UPD :
After performing actions - additional logic will be applied to the objects so that they become valid and ready to be persisted. For action "A" - this will set the date to the current date.

+2


a source to share


2 answers


I personally don't like using domain model classes as the model for my view. I find this causes validation, formatting issues and is generally wrong. In fact, I would not use the property DateTime

at all in my view model (I would format it as a string in my controller).

I would use two separate viewmodels, each with validation attributes exposed as properties of your main viewmodel:



NOTE. I've left how to combine hosted viewmodels with the main viewmodel as an exercise for you as there are several ways to approximate it.

public class ActionAViewModel
{
    [Required(ErrorMessage="Please enter your name")]
    public string Name { get; set; }
}

public class ActionBViewModel
{
    [Required(ErrorMessage="Please enter your date")]
    // You could use a regex or custom attribute to do date validation,
    // allowing you to have a custom error message for badly formatted
    // dates
    public string Date { get; set; }
}

public class PageViewModel
{
    public ActionAViewModel ActionA { get; set; }
    public ActionBViewModel ActionB { get; set; }
}

public class PageController
{
    public ActionResult Index()
    {
        var viewModel = new PageViewModel
        {
            ActionA = new ActionAViewModel { Name = "Test" }
            ActionB = new ActionBViewModel { Date = DateTime.Today.ToString(); }
        };

        return View(viewModel);
    }

    // The [Bind] prefix is there for when you use 
    // <%= Html.TextBoxFor(x => x.ActionA.Name) %>
    public ActionResult ActionA(
        [Bind(Prefix="ActionA")] ActionAViewModel viewModel)
    {
        if (ModelState.IsValid)
        {
            // Load model, update the Name, and commit the change
        }
        else
        {
            // Display Index with viewModel
            // and default ActionBViewModel
        }
    }

    public ActionResult ActionB(
        [Bind(Prefix="ActionB")] ActionBViewModel viewModel)
    {
        if (ModelState.IsValid)
        {
            // Load model, update the Date, and commit the change
        }
        else
        {
            // Display Index with viewModel
            // and default ActionAViewModel
        }
    }
}

      

+3


a source


One possible way to handle POST data and add validation is to bind to a custom model. Below is a small example of what I used recently to add custom validation to POST form data:

public class Customer
{
    public string Name { get; set; }
    public DateTime Date { get; set; }
}


public class PageController : Controller
{
    [HttpPost]
    public ActionResult ActionA(Customer customer)
    {
        if(ModelState.IsValid) {
        //do something with the customer
        }
    }

    [HttpPost]
    public ActionResult ActionB(Customer customer)
    {
       if(ModelState.IsValid) { 
       //do something with the customer
       }
    }
}

      

CustomerModelBinder will be something like this:

    public class CustomerModelBinder : DefaultModelBinder
{
    protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
    {
        if (propertyDescriptor.Name == "Name") //or date or whatever else you want
        {


            //Access your Name property with valueprovider and do some magic before you bind it to the model.
            //To add validation errors do (simple stuff)
            if(string.IsNullOrEmpty(bindingContext.ValueProvider.GetValue("Name").AttemptedValue))
                bindingContext.ModelState.AddModelError("Name", "Please enter a valid name");

            //Any complex validation
        }
        else
        {
            //call the usual binder otherwise. I noticed that in this way you can use DataAnnotations as well.
            base.BindProperty(controllerContext, bindingContext, propertyDescriptor); 
        }
    }

      

and in global.asax put



ModelBinders.Binders.Add(typeof(Customer), new CustomerModelBinder());

      

If you don't want to bind the Name (date only) property when calling ActionB, just create another custom binding device and in the "if" statement, supply a null or pre-existing value to return, or whatever else you want. Then put in the controller:

[HttpPost]
public ActionResult([ModelBinder(typeof(CustomerAModelBinder))] Customer customer)

[HttpPost]
public ActionResult([ModelBinder(typeof(CustomerBModelBinder))] Customer customer)

      

Whereas customerAmodelbinder will only bind the name and customerBmodelbinder will only bind the date.

This is the easiest way I have found to test model binding and I have achieved very good results with complex view models. I bet there is something that I missed and maybe more an expert can answer. I hope I answered your question correctly ... :)

+2


a source







All Articles