Dynamic Form Generation in Asp.Net

Dynamic Form Generation in ASP.NET

Have a look at Dynamic Data.

I recently found out about it and it's already saved me a lot of time.

Update:

Apologies - having reread the question, I don't think this is what you were after.

If you want to dynamically generate the form based on the records in your database, you may have to write your own engine.

A couple of suggestions though:

  • I'd look at using reflection to load controls rather than large case statements. That way you could dynamically add different control types just by including the new assembly. You wouldn't have to write new code.
  • Make sure you include a way to control the display order in your database. I note that you want to use a different table for each panel of controls. I'd advise against that because of the display order problem. If you have a table with a list of panels and a table with a list of data items + foreign key references to the panels, you'll be able to order them in a predictable and controllable way on the page.

Update: more info on reflection

Put simply, reflection is when you find out about details of an assembly at runtime. In this case, I suggest using reflection to load a control based on the information in your database.

So if you had a record in your database similar to the following:

FieldName    DataType         DisplayControl                       DisplayProperty
----------------------------------------------------------------------------------
FirstName System.String System.Web.UI.WebControls.TextBox Text

You could use some code like the following to generate the control on the page (note that it's untested):

// after getting the "PageItem" database records into a "pageItems" array
foreach (PageItem p in pageItems)
{
// get the type and properties
Type controlType = System.Type.GetType(p.DisplayControl)
PropertyInfo[] controlPropertiesArray = controlType.GetProperties();

// create the object
object control = Activator.CreateInstance(controlType);

// look for matching property
foreach (PropertyInfo controlProperty in controlPropertiesArray)
{
if (controlPropertiesArray.Name == p.DisplayProperty)
{
// set the Control's property
controlProperty.SetValue(control, "data for this item", null);
}
}

// then generate the control on the page using LoadControl (sorry, lacking time to look that up)

There is a really good page outlining how to do this here. It looks to be pretty much what you're after.

Create dynamic forms that grow at run time

If you want to allow the user to add a new form element on the client side you need to use javascript to update the DOM with the new element you want to add. To list the existing items you may use editor templates. Mixing these 2 will give you a dynamic form. The below is a basic implementation.

To use editor templates, we need to create an editor template for the property type. I would not do that for string type which is more like a generic one. I would create a custom class to represent the list item.

public class Item
{
public string Name { set; get; }
}
public class ShoppingList
{
public int ShoppingListId { get; set; }
public string Name { get; set; }
public List<Item> ListItems { get; set; }

public ShoppingList()
{
this.ListItems=new List<Item>();
}
}

Now, Create a directory called EditorTemplates under ~/Views/YourControllerName or ~/Views/Shared/ and create a view called Item.cshtml which will have the below code

@model  YourNameSpaceHere.Item
<input type="text" asp-for="Name" class="items" />

Now in your GET controller, create an object of the ShoppingList and send to the view.

public IActionResult ShoppingList()
{
var vm = new ShoppingList() { };
return View(vm);
}

Now in the main view, All you have to do is call the EditorFor method

@model YourNamespace.ShoppingList
<form asp-action="ShoppingList" method="post">
<input asp-for="Name" class="form-control" />
<div class="form-group" id="item-list">
<a href="#" id="add">Add</a>
@Html.EditorFor(f => f.ListItems)
</div>
<input type="submit" value="Create" class="btn btn-default" />
</form>

The markup has an anchor tag for adding new items. So when user clicks on it, we need to add a new input element with the name attribute value in the format ListItems[indexValue].Name

$(function () {

$("#add").click(function (e) {
e.preventDefault();
var i = $(".items").length;
var n = '<input type="text" class="items" name="ListItems[' + i + '].Name" />';
$("#item-list").append(n);
});

});

So when user clicks it adds a new input element with the correct name to the DOM and when you click the submit button model binding will work fine as we have the correct name attribute value for the inputs.

[HttpPost]
public IActionResult ShoppingList(ShoppingList model)
{
//check model.ListItems
// to do : return something
}

If you want to preload some existing items (for edit screen etc), All you have to do is load the ListItems property and the editor template will take care of rendering the input elements for each item with correct name attribute value.

public IActionResult ShoppingList()
{
var vm = new ShoppingList();
vm.ListItems = new List<Item>() { new Item { Name = "apple" } }
return View(vm);
}

C# ASP.NET Core building dynamic forms

NoSql approach seems to be a simple one. If you think about databases only as IO devices, which shouldn't contain any "business" logic, the fact that memory cost and memory reading/writng time(SSD) become lower - you will have little bid more options for your issue.

For example you can save field's collection in one object

{
"key": "form1",
"fields" : [ "Id", "CreatedAt" ]
}

Based on that object you can generate view with list or form of created fields.
You can introduce own "object" for field with more data you need for view generation and for data processing.

{
"key": "form1",
"fields" : [
{ "Name": "Id", "Type": "string" },
{ "Name" "CreatedAt", "Type": "DateTime" }
]
}

The actual data can be saved in one object too

{
"formKey": "form1",
"Name": "FirstName LastName",
"CreatedAt": "2017-04-23T18:25:43.511Z"
}

On client side this data can be easily saved as json object and sended to the server.

On server side you can deserialize that object as Dictionary<string, object> and handle it in dynamic way

var formEntity = JsonConvert.DeserializeObject<Dictionary<string, object>>(requestBody);

var name = formEntity["Name"].ToString();

If you tightly attached to the relational database, you can save "raw json" in NVARCHAR column and one identity column.

For processing data you will be able to deserialize it to the Dictionary<string, object>.

With field object { "Name" "CreatedAt", "Type": "DateTime" } you can convert values to the expected types for correct validation and processing.

You will be able to search for data based on dynamic fields ro create dynamic reports, where user can create his own reports.

Because structure of fields not dynamic you can save forms and fields structure in relational way. Below is my suggestion for sql server database.

CREATE TABLE Forms (
Id INT IDENTITY(1,1) NOT NULL
)

CREATE TABLE Field(
Id INT IDENTITY(1,1) NOT NULL,
Name NVARCHAR(30) NOT NULL,
TypeName NVARCHAR(30) NOT NULL, -- Or INT -> can represent SqlDbType enum in c#
)

-- many to many relation of Form and Fields
CREATE TABLE FormFields (
FormId INT NOT NULL,
FieldId INT NOT NULL,
PRIMARY KEY (FormId, FieldId)
)

-- because data is "dynamic" it should be saved as string (json format)
CREATE TABLE FormData(
Id INT IDENTITY(1,1) NOT NULL,
FormId INT NOT NULL,
Data NVARCHAR(MAX) NOT NULL, -- json format
)

And consider to use Microsoft version of NoSql - DocumentDB

Dynamic form creation in asp.net c#

My current project sounds like almost exactly the same product you're describing. Fortunately, I learned most of my hardest lessons on a former product, and so I was able to start my current project with a clean slate. You should probably read through my answer to this question, which describes my experiences, and the lessons I learned.

The main thing to focus on is the idea that you are building a product. If you can't find a way to implement a particular feature using your current product feature set, you need to spend some additional time thinking about how you could turn this custom one-off feature into a configurable feature that can benefit all (or at least many) of your clients.

So:

  1. If you're referring to the model of being able to create a fully customizable form that makes client-specific code almost unnecessary, that model is perfectly valid and I have a maintainable working product with real, paying clients that can prove it. Regression testing is performed on specific features and configuration combinations, rather than a specific client implementation. The key pieces that make this possible are:

    1. An administrative interface that is effective at disallowing problematic combinations of configuration options.
    2. A rules engine that allows certain actions in the system to invoke customizable triggers and cause other actions to happen.
    3. An Integration framework that allows data to be pulled from a variety of sources and pushed to a variety of sources in a configurable manner.
    4. The option to inject custom code as a plugin when absolutely necessary.
  2. Yes, clients come up with weird requests. It's usually worthwhile to suggest alternative solutions that will still solve the client's problem while still allowing your product to be robust and configurable for other clients. Sometimes you just have to push back. Other times you'll have to do what they say, but use wise architectural practices to minimize the impact this could have on other client code.
  3. Minimize use of the session to track state. Each page should have enough information on it to track the current page's state. Information that needs to persist even if the user clicks "Back" and starts doing something else should be stored in a database. I have found it useful, however, to keep a sort of breadcrumb tree on the session, to track how users got to a specific place and where to take them back to when they finish. But the ID of the node they're actually on currently needs to be persisted on a page-by-page basis, and sent back with each request, so weird things don't happen when the user is browsing to different pages in different tabs.
  4. Use incremental refactoring. You may end up re-writing the whole thing twice by the time you're done, or you may never really "finish" the refactoring. But in the meantime, everything will still work, and you'll have new features every so often. As a rule, rewriting the whole thing will take you several times as long as you think it will, so don't try to take the whole thing in a single bite.

ASP.NET MVC Dynamic Forms

If you need to have some dynamic fields in your form, the best way for you would be to use some advanced javascript frameworks like Angular, Backbone, Knockout etc.

If you need something more or less simple it is enough for you to use Knockout. For more advanced scenarios I would recommend Angular, but this is my personal preference.

Here is a simple implementation of a dynamic form with Knockout:

var model = {    users: ko.observableArray(),    addUser: function() {        this.users.push({ name: ko.observable() });    }};
ko.applyBindings(model);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script><div data-bind="foreach: users">    <input type="text" data-bind="value: name" /><br /></div><button data-bind="click: addUser">Add user</button>
<ul data-bind="foreach: users"> <li data-bind="text: name"></li></ul>

How to handle multiple dynamic form with c#

The following is a bare minimum example based on this blogpost. For demo purposes, I've named my model Foo. So whenever you read this, this should be your model with file, name and cat properties.

First, add https://www.nuget.org/packages/BeginCollectionItem/ to your project.

Then, add a partial view to your Views folder. I've named mine "_AddFile.cshtml":

@model WebApplication2.Models.Foo

@using (Html.BeginCollectionItem("files"))
{
<div class="form-horizontal">
<fieldset>
<div class="form-group">
@Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
</div>

@Html.LabelFor(model => model.Cat, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Cat, new { htmlAttributes = new { @class = "form-control" } })
</div>
</div>
</fieldset>
</div>
}

Note, the Html.BeginCollectionItem("files"), this is creating a collection, that is later grouped together and bound to your model named "files".

Our controller looks like this:

public ActionResult Index()
{
//Initialize the view with an empty default entry
var vm = new List<Foo> {
new Models.Foo {
Cat ="foo",
Name =" bar"
}
};
return View(vm);
}

//this calls your partial view and initializes an empty model
public PartialViewResult AddFile()
{
return PartialView("_AddFile", new Foo());
}

//note "files" name? The same as our collection name specified earlier
[HttpPost]
public ActionResult PostFiles(IEnumerable<Foo> files)
{
//do whatever you want with your posted model here
return View();
}

In your view, use this form:

@model IEnumerable<WebApplication2.Models.Foo>

@using (Html.BeginForm("PostFiles","Home", FormMethod.Post))
{
<div id="FileEditor">
@foreach (var item in Model)
{
Html.RenderPartial("_AddFile", item);
}
</div>
<div>
@Html.ActionLink("Add File", "AddFile", null, new { id = "addFile" }) <input type="submit" value="Finished" />
</div>

}

@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
<script>
$(function () {
$("#addFile").click(function () {
$.ajax({
url: this.href,
cache: false,
success: function (html) { $("#FileEditor").append(html); }
});
return false;
});
})
</script>

}

The foreach loop renders a partial View for each model entry, in our case just one with a default entry.

The javascript loop then calls our PartialView and renders an empty template below the existing ones.

A call to submit, then lets you deal with your files in any way you want:

Sample Image



Sample Image


Sample Image


Sample Image



Related Topics



Leave a reply



Submit