2

I have an existing ASP.NET MVC 2 application that I've been asked to extend. I am adding a new feature to the site where I generate an employee assessment form based on a dynamic list of questions retrieved from our HR system. I have everything working with the exception of validation and posting the responses back to the site. Here's some details:

  1. I retrieve a list of "Questions" from our back-end system via a web service call.
  2. Each "Question" contains the text to display as well as the following settings:
    • The question Type (corresponds to textbox, textarea, radio button list or checkbox list)
    • If comments are allowed
    • If an answer is required
    • When applicable, the list of possible responses

To generate the form, I use a for-each loop over the list of Questions. I use the value of the QuestionType property to determine which partial view to render (one for each of the types). For example, if QuestionType == SingleChoice, that partial renders the choices as a radio button list. If comments are allowed for the question, I also render an additional textarea field to hold the user's comments.

As I said, rendering the form is working fine but now I need to:

A. Enforce when an answer is required. I'm using DataAnnotations for validation everywhere else in the solution but since I'm not working against a static model, I don't see how I can do that.

B. Post the results back to the site. For each question, there can be text entered into a textbox or textarea, a selected value for a radio button list or multiple selected values for a checkbox list. Plus, each question could also have additional text sent back in the form of a comment.

All of the examples that I've seen working with dynamic "lists" are only concerned with posting a single value for each field and it is always the same type (e.g. a list of textboxes). With the variations I have to support, plus the need to send back the entered/selected value(s) and a comment for each question, I'm stumped.

Any guidance is appreciated.

1 Answer 1

1

I've just finished completing exactly the same task.

I chose to write a custom model binder for my dynamic form object. The model binder pulled out a bunch of prefixed form keys for hidden fields which contained some delimited meta data about the question (i.e IsRequired, QuestionType, QuestionId etc etc)

I'm using MVC3 but I think this should all work in MVC2.

I created a ModelBinder like:

public class DynamicFormModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        // Create the object to be bound to (I had a kind of form object
        // with a simple list of answer objects
        DynamicForm form = new DynamicForm(new List<Answer>());

        HttpRequestBase request = controllerContext.HttpContext.Request;

        var keys = request.Form.AllKeys.Where(k => k.StartsWith("MyFormsKeyPrefix_Meta_"));
        foreach (var key in keys)
        {
            // Loop over each question's meta data. Metadata will always be present 
            // even if the user hasn't selected an answer as it's a hidden field

            // TODO: Split the meta data and pull out IsRequired, QuestionType etc

            // TODO: Get all the posted form values for the question (these values 
            //       will come from textboxes, dropdowns, checkboxes etc)
            //       Use a prefix like: MyFormsKeyPrefix_Answer_{QuestionId}
            //       textboxes & dropdowns will only ever have one value 
            //       but checkboxes could have multiple

            // TODO: If it's a mandatory question then ensure there is at least
            //       one posted value that is not an empty string

            // If there is a validation error then add it to the model state
            bindingContext.ModelState.AddModelError(key, "Field is required");

            foreach(var answerHtmlName in answerHtmlNames)
            {
                // TODO: Loop over each posted answer and create some kind of nice
                //       Answer object which holds the QuestionId, AnswerId, AnswerOptionId 
                //       and Value etc.


                // Add the answer to the forms answers list
                form.Answers.Add(answer);
            }
        }

        return form;
    }

}

I register the ModelBinder in Global.asax using the following:

ModelBinders.Binders.Add(typeof(DynamicForm), new DynamicFormModelBinder());

So, the action method that recieves the form post looks something like:

public ActionResult ProcessForm(DynamicForm form) {
    if(ModelState.IsValid) 
    {
        DynamicFormService.Process(form);

        return RedirectToAction("TheHttpGetAction");
    }
    return TheHttpGetAction();
}
Sign up to request clarification or add additional context in comments.

7 Comments

Any chance you can share some code? I haven't written a custom model binder, so an example would be helpful.
I've updated the answer to try and give a few more pointers. I wrote it for another company and don't currently have the code to hand so I've just written some pseudo code and a bunch of comments
For some reason I'm losing the code colouring when I save the edit - can't work out why...
No worries. Thanks for posting the code. I'll have to process it a bit and see if I want to go this route. I was hoping to find a simpler (more built-in) approach but if this is the only way...
I just came across this mvcdynamicforms.codeplex.com/releases/view/39744 looks similar..you should check it out
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.