Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
This is a general design question: How would you implement a dynamic (runtime generated) form in ASP.NET MVC?
Here's the situation:
A site admin can define form parameters (fields, type of fields, validation) with a GUI (MVC view).
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.
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.
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
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.)
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);
}
}
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.
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
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.
Related
What I'm trying to achieve is to customise what happens during the HttpPost/OnSubmit of a form created with Umbraco Forms, whilst still maintaining the default behaviour of the form and any subsequent workflow.
In my specific scenario, I need to be able to analyse the input of the form and based on that input I will send the user to a different "thank you" page, whilst also storing some elements of the originally submitted form in TempData.
I've tried the following:
I've tried to create a DocType controller (RenderMvcController), but this only allows you
to override the HttpGet, and not Post.
I cannot use a SurfaceController as I lose the functionality of the
module, Umbraco Forms.
I've tried to use a custom workflow, but this runs asynchronous to
the user's journey and I cannot change their experience.
There isn't much useful documentation available for this at all and I'm finding this task more difficult than I expected it to be.
In order to add a custom procedure after the submission of the form, and based on this procedure change the user journey you must do the following:
Create a new controller and inherit from UmbracoFormsController and override the OnFormHandled method
public class CustomUmbracoFormsController : UmbracoFormsController
{
protected override void OnFormHandled(Form form, FormViewModel model)
{
// Find the field in the form, then search for a matching value
var field = form.AllFields.FirstOrDefault(x => x.Alias == "deliveryOptions");
var fieldValue = model.FormState[field.Id.ToString()].GetValue(0).ToString();
// Add the value to the TempData
TempData["deliveryOptions"] = fieldValue;
}
}
The above is a basic implementation that doesn't account for NULL
Update the reference to UmbracoFormsController in /Views/Partials/Forms/Form.cshtml with your new controller from above.
...
#using (Html.BeginUmbracoForm<CustomUmbracoFormsController>("HandleForm"))
...
In the above example we analyse the form data and store some information in the TempData, we can set the form to redirect to a generic thank you page in which we can analyse the values in the TempData and change the view the user sees.
Also, if you are making changes to the Form values and what these to be updated, you'll need the Record Guid, which you can retrieve from TempData["Forms_Current_Record_id"] in conjunction with a new RecordStore object.
I have a model with a property that points to a file that contains HTML. My strongly typed view to this model uses a custom HTML helper method to resolve and return the HTML from the file. Works great so far.
The HTML read from each file will contain various controls whose values I need to retrieve when the form is POSTed.
What would be the best way to have access to the POSTed control values in my controller method?
I would prefer a non jQuery solution, but I am not sure if the MVC framework can provide these values to me? Can it provide a list of key/value pairs to the controller somehow?
You could use the FormCollection in ASP.NET MVC.
public ActionResult SomeAction(FormCollection form) {
...
}
You have essentially two options.
1) Use the old fashioned Request variables as all we have done in ASP.NET web forms.
For example in your controller action method you can retrieve any value present on the form with the following method
public ActionResult SomeAction() {
var request = this.ControllerContext.HttpContext.Request;
bool boolParam = bool.Parse( request["boolParam"] ?? "false" );
}
2) Create a custom Model Binder to let the framework pack those values in a custom class object.
This method would be a little bit more difficult at the beginning because you have to create a custom Model Binder but it favour readability on your controller code. For further details on creating custom model binders give a look at the following links (you can find more with a simple search)
Custom Model Binder for Complex composite objects
Custom Model Binder and More UI Validation in ASP.NET MVC
A Custom ASP.NET MVC Model Binder for Repositories
Hope it helps
Is the content of the HTML files dynamic or known at design time? If you know it now, you could have each one post to it's own action and then strongly type the parameters.
I am working on a project involves exposing many data entities (more than 200) through an ASP.NET MVC application.
I do not want to write views for every entity mode and I am wondering if there is a way of having one generic view to display different models (for example, a view which reads the model properties(metadata) and generates a HTML table to display a list of entities.
If you only need to display, you can write your own generic renderer in few minutes using reflection. Or, you can use ModelVisualizer from MvcContrib, or Grid from MvcContrib which can do Html.Grid(Model).AutoGenerateColumns() to automatically show properties as columns. It doesn't support DataAnnotations or attributes, but you can write your own extension method that will use reflection to generate Grid's columns based on some attributes. And you can quickly turn this grid into jqGrid using jqGrid's tableToGrid() method.
If you need input support, there're several choices:
MvcContrib's InputBuilder (here you can see a simple example on how it's done with reflection)
MVC v2 InputFor(), I think it supports rendering several properties, but I'm not sure.
My solution does read metadata (attributes) from view models and generates jqGrid formatting and editing options automatically.
There should be commercial tools, too.
Two choices:
Dynamic Data Project
Have you tried Asp.net Dynamic data project that can automagically create what you need?
Asp.net MVC
But if you want to do what you're asking, you can always create a single view that will not have a strong type model. You will always pass data to it ViewData dictionary. The view would then parse that data and display what's required.
Routing
You can always create these two routes:
routes.MapRoute(
"EntityRoute",
"{entityName}",
new {
controller = "Entity",
action = "Display",
entityName = "SomeDefaultEntity" }
);
routes.MapRoute(
"EntityRoute",
"{entityName}/{recordId}",
new {
controller = "Entity",
action = "Details",
entityName = "SomeDefaultEntity",
recordId = string.Empty }
);
that will redirect all requests to the same controller action that will provide correct entity set data and pass it into the ViewData dictionary and return the view that will consume it. The second one will display details of a certain record within some entity (in case you need to provide master/details functionality).
MVC Contrib has Model Visualizer which let you display a Model by reflecting its properties. That a no go if you need any performance at all, but maybe it gets you started.
Situation: In some project management software written in asp.net I have a create project page (working fine). I need to add to this the ability to add tasks from a list of templates to this project pre-creation BUT the list of available tasks is dependent on some values sitting in the create form.
My abstract solution is this:
I have a "Create" view and an "Add Tasks" View - both strongly typed to a composite viewModel defined in the controller
My Create method checks which button was used to call it - if the
button was "Add Tasks" it then renders the AddTasks view, passing the model in from the create view, again all in the same controller.
The AddTasks View posts to the Create view with one of two buttons, one loads the view and the other causes an actually DB save.
My Problem is this:
The different views use different properties of the same model, but in passing this model between them, the data is reset (in any case reload or save).
I am guessing this is happening from auto binding of data - though I thought fields not present on the form would not overwrite existing model data passed down.
There is hardly any code in the controller manipulating the model at present - It is only passed from view to view in these cases.
This is the controller code:
// POST: /Project/Create/<viewModel>
[Authorize, AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id,id")] ProjectViewModel model)
{
if (model.SubmitValue == "Create")
{
try
{
model.Project.Id = Guid.NewGuid();
model.Save(this.User.Identity.Name);
return this.RedirectToAction("Details", new {id = model.Project.Id});
}
catch (Exception e)
{
this.ModelState.AddModelError(e.ToString(), e.ToString());
}
return View(model);
}
if(model.SubmitValue == "AddTasks")
{
return this.View("AddTasks",model);
}
return this.View(model);
}
//POST: /Project/AddTasks/ + model
[Authorize, AcceptVerbs(HttpVerbs.Post)]
public ActionResult AddTasks([Bind(Include = SelectedCarrierTasks")]ProjectViewModel model)
{
return View(model);
}
The Question is: How do I maintain the state of the model across these views until it finally save it?
I would prefer to avoid any hackish (TempData) or JS dependant solutions, but I am not closed to these if they are really the best solution.
Thanks,
Adam Tolley
One simple solution is to persist the ViewModel object in a Session variable and bind the View from this source.I ts certainly not the most elegant solution. Another option, and probably less elegant one is persist this model data in the database, with some temporary/unsaved flag.
The problem is that when you display the add tasks view you're not providing fields for your "Project" object therefore the ModelState loses the data related to the project, you will need to provide this fields to ensure you're not loosing that data.
You don't need to display this fields they can be of type hidden and they will preserve the value. Just make sure that if you will be binding to a view model you will need to name this fields correctly like this Model.Project.Property.
Perhaps I am trying to solve the wrong problem (ala Bruce Eckel). I am going to try to move to a structure that needs this sort of fuzzy boundary less. I don't want to adopt a REST paradigm only to shoe-horn it into a stateful application.
Possibly these controls belong on the same page, and I can use some JQuery goodness to put in a tab pane for easiness on the eyes.
Thanks to those who answered, I found each useful and will try to remember to up-vote them as soon as I have some more rep.
I can't comment on other peoples questions at the moment, but the only real option is the session if you want to persist an objects state during web requests, or serializing it and placing it in a hidden field.
Or a final option would be to change the way your pages work so you can save the object after each request...
If your using nHibernate then you might want look into the Conversations pattern, but this just essentially saves the nHibernate session into the asp.net session anyway...
I converted my web application from preview 3 to beta1 and am now trying to put the new functions of the framework to use. One of them is ModelBinding.
For this particular situation I created a class that is (for now) just a container of a bunch of simple-type properties.
If I create a form with a bunch of textboxes, I want the framework to fill a SearchBag instance with these fields.
Where do I start? Is this behaviour
out of the box or do I implement a
SearchBagBinder? I had a quick look
at the IModelBinder but can't quite
wrap my head around it and the
DefaultModelBinder source doesn't
make me any the wiser.
What is this ModelBindingContext?
How do I access my form fields?
What if the values are not passed on
by a form but rather by entering a
URL directly?
Where do I find up-to-date
information on this (most blogs are
outdated)? I thought I read a post
by Phil at one time, but I can't
seem to find it no more.
Any help is appreciated.
Where do I start? Is this behaviour
out of the box or do I implement a
SearchBagBinder? I had a quick look
at the IModelBinder but can't quite
wrap my head around it and the
DefaultModelBinder source doesn't
make me any the wiser.
It is out of the box. You can either use UpdateModel or ModelBinder to acheive what you are looking to do.
What is this ModelBindingContext?
This contains all the necessary information for the request to be bound to your Model. Similar to ControllerContext and ActionFilterContext, it is basically the state of the ModelBinder and contains all the information necessary to do what you want, if you follow the ASP.NET MVC teams recommendations for what the ModelBinder is supposed to do.
How do I access my form fields?
context.HttpContext.Request.Forms["myformfield"];
or
foreach (var field in context.HttpContext.Request.Forms.Keys) {
var value = context.HttpContext.Request.Forms[field];
}
What if the values are not passed on
by a form but rather by entering a
URL directly?
If you need to check both the Form and the QueryString just loop through both collections.
foreach (var field in context.HttpContext.Request.Forms.Keys) {
var value = context.HttpContext.Request.Forms[field];
}
foreach (var field in context.HttpContext.Request.QueryStrings.Keys) {
var value = context.HttpContext.Request.QueryStrings[field];
}
or you can loop through Param which will contain, Form, QueryString, Headers, etc.
foreach (var field in context.HttpContext.Request.Params.Keys) {
var value = context.HttpContext.Request.Params[field];
}
Where do I find up-to-date
information on this*(most blogs are
outdated)? I thought I read a post
by Phill at one time, but I can't
seem to find it no more.
You have it right Phil is the best place for information as the PM of ASP.NET MVC.