24

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.

Robert Claypool
  • 4,083
  • 9
  • 47
  • 59
  • I object to closing this question. The architecture behind making runtime generated forms *on a specific server side framework* is broad, but it's clearly answerable as the answers below prove. Are all architecture questions off topic for this site simply because architecture decisions are high level and have pros and cons? – Robert Claypool Aug 18 '17 at 18:11

6 Answers6

13

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
  • 41,852
  • 68
  • 257
  • 338
  • Just looking at the source code for this rather nice solution, are you planning on releasing the source code for version 2.0 at all? If not, do you have a list of differences between the two versions? – Tr1stan Sep 20 '13 at 15:10
  • The source is released. The whole project is open sourced @ codeplex. – Ronnie Overby Sep 20 '13 at 18:59
  • Oh OK, I was getting confused because the release date of v2 is 2012, but the source code hasn't changed since 2010, which is when you released v1, that was all. Thanks. – Tr1stan Sep 22 '13 at 20:51
  • I am trying to use ASP.NET MVC Dynamic Forms with VS2013 Express Edition, but i am getting undefined TagBuilder class? Should i need to implement myself ? – umki Nov 16 '14 at 12:42
  • I replied to your message on codeplex. – Ronnie Overby Nov 16 '14 at 19:58
4

You can do this very easily using my FormFactory library.

By default it reflects against a view model to produce a PropertyVm[] array, but you can also create the properties programatically, so you could load settings from a database then create PropertyVm.

This is a snippet from a Linqpad script.

```

//import-package FormFactory
//import-package FormFactory.RazorGenerator


void Main()
{
    var properties = new[]{
        new PropertyVm(typeof(string), "username"){
            DisplayName = "Username",
            NotOptional = true,
        },
        new PropertyVm(typeof(string), "password"){
            DisplayName = "Password",
            NotOptional = true,
            GetCustomAttributes = () => new object[]{ new DataTypeAttribute(DataType.Password) }
        }
    };
    var html = FormFactory.RazorEngine.PropertyRenderExtension.Render(properties, new FormFactory.RazorEngine.RazorTemplateHtmlHelper());   

    Util.RawHtml(html.ToEncodedString()).Dump(); //Renders html for a username and password field.
}

```

Theres a demo site with examples of the various features you can set up (e.g. nested collections, autocomplete, datepickers etc.)

mcintyre321
  • 12,052
  • 8
  • 59
  • 96
3

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
  • 14,585
  • 15
  • 56
  • 80
1

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.

xan
  • 7,116
  • 8
  • 41
  • 64
Martijn Laarman
  • 13,228
  • 40
  • 63
0

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

-1

I can't see huge advantages of generating XForms or any other "abstraction" over the HTML comparing with straight forward generation of HTML with "Web Forms 2.0" controls list for model like List<Tuple<Meta, Value>>. Note: on server side you in any case would be obligated to parse results manually to fit it to your structrures.

Searching for "next layer abstractions" is good for rapid development, but see, "generate code" (runtime or build-time) has its own specific. Usually the generating code of "lower layer" is the better solution than generating the "higher abstract layer" code.

So just go and wirte code that generate Web 2 Controls in @Foreach loop.

Roman Pokrovskij
  • 7,969
  • 14
  • 68
  • 119