views:

1857

answers:

5

This is a general design question: How would you implement a dynamic (runtime generated) form in ASP.Net MVC?

Here's the situation:

  1. A site admin can define form parameters (fields, type of fields, validation) with a GUI (MVC view).
  2. As needed, the runtime generates the form for the end user based on the admin configuration. I'm assuming that all of this logic would reside in the controller - or perhaps extension methods, action filters or something like that.
  3. End user fills out the form, hits submit, information is captured in database.

The customization does not need to support nested controls, 3rd party controls and so forth, but I suspect a very elegant design would allow for that. Mostly, I just need the admin to be able to specify additional fields as textboxes, checkboxes, radio buttons and comboboxes. I will also need the application to allocate a space for this data to be saved in the db, but I believe I have that part figured out.

Thanks for the help.

A: 

One way to do this is to create your own ModelBinder which would be at the heart of your generated forms. A modelbinder is responsible for validating the ModelState and rebuilding the typed ViewDataModel (assuming your views are typed).

The DataAnnotations model binder could be a good reference for this what this custom modelbinder allows you to do is via Attributes on your ViewDataModel describe the attribute's validation (and hint at UI rendering). However this is all defined compile time but would be a great reference to start off writing a custom modelbinder.

In your case your model binder should get the validation for a field at runtime from an xml file/string.

If you have a route like:

routes.MapRoute(null, "Forms/{formName}/", new { action = "Index", controller = "Forms", formName = ""}),

Then you could locate the correct form xml in FormsController.Index(string formName) and pass it to the view.

The FormsModel should hold all the possible methods to get data I dont see any other way around this. The Xml could map to a function name (possibly even arguments) that you can invoke using reflection on the FormsModel to fill the ViewData or typed ViewDataModel with data.

The view for Form Index could generate a form from that xml through an HtmlHelper Extension that takes an XmlDocument.

Then when you (or asp.net mvc) binds your form to your ViewData your custom model binder is invoked it can inspect the current controller values to look for the formName and look up the corresponding xml that holds all the validation rules. The ModelBinder is then responsible for filling up ModelState with any runtime defined errors.

It's a hard task but when pulled off succesfully well worth it in my view :)

Update a better alternative to model data would be a very loose database schema as David Liddle suggests. I'd still go through the trouble of saving it as xml (or someother serialized format) and using that for generating the view and to hold validation rules for a custom ModelBinder so that you have more control on layout and validation of each field.

Martijn Laarman
+1  A: 

Another option is to have a very loosely coupled database schema.

//this will contain all the fields and types that the admin user sets
**ApplicationFields**
FieldName
FieldType
...

//these are all the values that have some mapping to a ParentObjectID
**FormValues**
ParentObjectID
FieldName
FieldValue

When you submit your runtime generated View (from ApplicationFields) then just loop through your FormCollection and try and set it on the ParentObject you need to update.

public ActionResult MyForm(FormCollection form)
{
    //this is the main object that contains all the fields
    var parentObject;

    foreach (string key in form)
    {
        parentObject.SetValue(key, form[key]);
    }
    ...

Then your parentObject might be something like this...

public partial class ParentObject
{
    IList _FormValues;

    public void SetValue(string key, string value)
    {
        //try and find if this value already exists
        FormValue v = _FormValues.SingleOrDefault(k => k.Key == key);

        //if it does just set it
        if (v != null)
        {
            v.Value = value;
            return;
        }

        //else this might be a new form field added and therefore create a new value
        v = new FormValue
        {
            ParentObjectID = this.ID,
            Key = key,
            Value = value
        };

        _FormValues.Add(v);
    }
}
David Liddle
+1  A: 

Check out XForms.

cottsak
A: 

cottsak's answer is very attractive.

There are at least two client-side XForms engines. Here's one:

https://community.emc.com/community/edn/xmltech

+2  A: 

I had the same need in a recent project. I created a class library for this. I just released a new version of the library.

Maybe it can help you: ASP.NET MVC Dynamic Forms

Ronnie Overby