views:

49

answers:

2

Background

I have a payment page where the user can select from a list of existing payment methods, or specify a new one. The dropdown presents options such as:

  1. Visa - ******1234 (Saved)
  2. Mastercard - ******9876 (Saved)
  3. [New Credit Card ...]
  4. [New Electronic Check ...]

Using jQuery, I toggle hidden DIVs that contain either an informational table (in the case of options 1 or 2 for saved payment methods) or a form (in the case of the [new] options).

I am using a strongly typed class as my view model which contains (among simple types) a CreditCard class and a Check class. Each of these classes uses data annotation validators, as they are used in other parts of the site.

Problem

The problem comes in when the user submits the form. I would like to use model binding to handle the mapping of POST values, but I need the binding and/or validation to fire depending on which option the user selected. For example, if the user selects option 1 or 2 from the list above, I don't want the model validation (or maybe even the binding itself) to fire for the CreditCard or Check objects.

I have researched the possibilities of creating a custom model binder using IModelBinder as well as extending the DefaultModelBinder and just overriding some of the methods. However, I am unsure as to which method is better, and, if extending DefaultModelBinder, which would be the appropriate method to override.

The logic would be fairly simple:

  • If the user selected one of the existing payment methods, no validation on the CreditCard or Check are required.
  • If the user selected one of the options to create a new payment method, then only the selected method (CreditCard or Check) needs to be bound and validated

It feels as if extending the DefaultModelBinder is the way to go, as I would like most of the heavy lifting to be done by the framework without the need to create a custom binder from scratch. However, when looking at the available methods to override, it's not clear which is the best one(s):

  1. BindProperty - The problem here is that I basically need to look at one of the properties to determine what other properties should be bound. I don't think I can control the order in which the incoming properties are bound, and I wouldn't want to rely on the order they are set in the HTML form.
  2. OnModelUpdated - By this point, it's too late. The binding validation from the data annotations has been triggered and the ModelState has been updated. I would have to loop through the ModelState and remove the errors that are not relevant.
  3. OnPropertyValidating - At first I thought this is where I should look, but even returning TRUE for all properties (as a test) causes the ModelState to contain binding errors.

I have come across this scenario in other aspects of the application and decided to split up functionality into separate controller/actions to simplify the process. However, I would like to have a better understanding of how to approach more complex UI problems, particularly related to the MVC model binding features.

Any help on this subject would be greatly appreciated.

All the possible values are stored in a dropdown list. Using jQuery, I toggle the form (for a new payment method) and the display (for an existing method)

A: 

Your issue sounds way to specialized to be placed in the default ModelBinder.

The ModelBinder is this seductress that lures you in on the pretense that she can solve all of your problems. But then you start merging ModelState's together and going off to do crazy things with nested objects lists and before you know it she slaps you with divorce papers and takes everything but your bones.

MVC 3 holds some promise to provide a more extensible ModelBinder but from my own personal experience unless its super simple what you need to change, such as empty texboxes becoming "" instead of null, than stay clear away from your own implementation.

The alternative approach is to use the existing ModelBinder functionality piecemeal and using things like Ignore method parameters to clean things up:

if( myModel.IsNewPayment )
   UpdateModel( myModel.Payment, "exclude everything else" );

A lot of what your proposing to stuff into the model binder is really business logic too that should be in another layer. I've done some crazy things with my own ModelBinder and now regret every line of code I've written in there. Maybe its just me but your really bending the rules and completely trashing the "single responsibility principal" by putting business and payment logic in there.

jfar
jfar, I don't see how this is mixing business logic with presentation logic. I see this as input validation logic, where what I really want to do is have some sort of differentiating validation.
XSaint32
@XSaint32 - differentiating validation is business logic to me, input validation is this string isn't empty, this is a valid datetime, the user supplied a price, I'm trying to warn you here, differentiating validation isn't what the modelbinder is good at providing
jfar
@jfar - What I mean by differentiating validation is that I don't want the default model binder to spit out binding errors for fields that are not relevant to the request. The way I see it, validating the user REQUEST and interacting with the input of the user is presentation, not business logic. I would agree with you if I were trying to validate the credit card number itself, or compare it to a list of blocked cards. Seeing as how this is simply saying, "Hey, if the user clicks on Option 1, don't bother checking if he entered values for the credit card."
XSaint32
Either way, I have decided to try to circumvent model binding altogether and use FormCollection, IValueProvider, and TryUpdateModel inside my controller action.
XSaint32
@XStaint32, you know TryUpdateModel is model binding right?
jfar
@jfar - Yes. I was referring to the automatic model binding. :)
XSaint32
A: 

I have decided to try to circumvent model binding altogether and use FormCollection, IValueProvider, and TryUpdateModel inside my controller action.

XSaint32