views:

103

answers:

0

Consider this view model that uses two custom validators. It posts back to the controller in an enumerable collection.

public class FormFieldViewModel
{
    public Guid FormFieldKey { get; set; }
    public string Name { get; set; }

    // Utilize a string[] array since multiple-selection items will require this (we can assume array[0] for all others)
    [RequiredIf("IsRequired", true, ErrorMessage = "This field is required")]
    [RegExRemote("RegEx", ErrorMessage = "Your entry is not valid")]
    public string[] Value { get; set; }

    //public string Options { get; set; }
    public IEnumerable<SelectListItem> Options { get; set; } 

    public string ListEnum { get; set; }

    // These fields assist the custom validators used in this view model
    public bool IsRequired { get; set; }
    public string RegEx { get; set; }
    public string RegExError { get; set; }
}

Consider the controller method:

    [HttpPost]
    public ActionResult Display(IEnumerable<FormFieldViewModel> formresponses, Guid id)
    {
        // TODO: Make a custom modelbinder
        if (ModelState.IsValid)
        {
           // Do stuff ...
        }
        return View(formresponses);
    }

How can one make a custom ModelBinder that can navigate the IEnumerable, populate some missing values, etc., and allow the ModelState.IsValid to be evaluated correctly (within a custom ModelBinder context)?

This one has had me searching for days... As a note: I have been able to get the following IModelBinder implementation working to CREATE the entire model that I need. However it still has two key issues. (1) It doesn't perform validation correctly to get the ModelState.IsValid set correctly, and (2) It seems that the IModelBinder instance doesn't allow easy IoC due to its inherent constructors. Help!

(Data-specific binder)

public class FormFieldModelBinder : SimpleModelBinder
{
    public override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        List<FormFieldViewModel> formresponses = new List<FormFieldViewModel>();
        var form = controllerContext.HttpContext.Request.Form;

        foreach (var key in form.AllKeys)
        {
            // All that should be required is the FormFieldKey and its updated Value
            if (key.IndexOf(".FormFieldKey") > -1)
            {
                var thisGuid = Guid.Empty;
                if (Guid.TryParse(form[key], out thisGuid))
                {
                    var formresponse = new FormFieldViewModel();
                    // Todo: find a way to use IoC for this rather than direct instantiation
                    var dbForm = new EntityFormRepository();
                    var thisField = dbForm.GetFieldByKey(thisGuid);
                    // Populate values ...
                    formresponses.Add(formresponse);
                }
            }
        }
        return formresponses;
    }

(Custom binder)

public abstract class SimpleModelBinder : IModelBinder
{
    // Abstract methods
    public abstract object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext);

    // Methods
    protected virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        //if (bindingContext.ModelType == typeof(IEnumerable<FormFieldViewModel>))
        //{
            object model = this.CreateModel(controllerContext, bindingContext);
            //foreach (var formField in (List<FormFieldViewModel>)model)
            //{
                foreach (ModelMetadata property in bindingContext.PropertyMetadata.Values)
                {
                    // Get property value
                    property.Model = bindingContext.ModelType.GetProperty(property.PropertyName).GetValue(model, null);

                    // Get property validator
                    foreach (ModelValidator validator in property.GetValidators(controllerContext))
                    {
                        // Validate property
                        foreach (ModelValidationResult result in validator.Validate(model))
                        {
                            // Add error message into model state
                            bindingContext.ModelState.AddModelError(property.PropertyName + "." + result.MemberName, result.Message);
                        }
                    }
                }
            //}
            return model;
        //}
        //return this.CreateModel(controllerContext, bindingContext);
    }

    // IModelBinder members
    object IModelBinder.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        return CreateModel(controllerContext, bindingContext);
        // return this.BindModel(controllerContext, bindingContext);
    }
}