ASP.Net MVC Editor template for dynamic view data / forms
I am implementing a MVC3/Razor web application that retrieves some of its "fields" a user can edit from other services so the list of properties to edit in a view is completely dynamic and unknown at compile time.
I wrote some partial-view and HTML-helpers that loop through the groups and properties retrieved from the other services. Now I am at the point where I have to build the tags for the various property types and thought why not re-use the MVC editor template system for this? There is support for various data-types (eg. checkboxes etc.) and one even customize them with my custom templates.
So far so good but how can I use Htm开发者_StackOverflowl.EditorFor()
or Html.Editor()
for custom data objects/properties? Meaning for building dynamic forms without static typed data as the view model.
Here is a minimalistic sample of my HTML helper code:
public static MvcHtmlString GetField(this HtmlHelper helper, Field field)
{
...
return helper.EditorFor(field, m => m.Value);
...
}
The property "field" is the field I got from the external services. It has a "Value" property of type object. I like to build the editor code for this property-type.
As I understand the editor templates are built on the current view model. Can I pass another object as the model then the current view model (eg. in my above example "field")?
Any help would be great!
Cheers, Marc
My first ASP MVC task involved building a dynamic form, and it isn't straight forward.
Basically you can't use the built in helpers or validation because they expect strongly typed objects.
My view basically loops through the input fields, checks the DataType (bool, int, string, datetime etc) and builds an editor directly for that type.
You also have to do all your validation manually, decorating the types with attributes to do this doesn't work, see my question if(ModelState.IsValid) doesn't work with FormsCollection. What to use instead?
Razor View logic (we use DevExpress MVC extensions, but you get the drift)
The form object and it's Fields collection are our bespoke objects that describe how the form should look (the page gathers criteria for searches, which is why you'll see criteria and search type names in the code).
<table id="criteriaTable">
@foreach (var field in form.Fields)
{
<tr id="criteriaTableRow">
@if (field.IsVisible)
{
<td id="criteriaTableLabelCol">
@Html.DevExpress().Label(s => s.Text = field.Caption).GetHtml()
</td>
<td id="criteriaTableEditCol">
@if (field.Type == typeof(bool))
{
@Html.CheckBox(s =>
{
s.Checked = field.IsBoolSet;
s.Name = field.Name;
s.ClientEnabled = !field.IsReadonly;
}).GetHtml()
}
else if (field.Type == typeof(DateTime))
{
Html.DevExpress().DateEdit(s =>
{
s.Name = field.Name;
s.ClientEnabled = !field.IsReadonly;
if (!string.IsNullOrEmpty(field.Value))
{
DateTime dateValue;
if (DateTime.TryParse(field.Value, out dateValue))
s.Date = dateValue;
}
}).GetHtml();
}
else if (field.ListValues.Count > 0)
{
<input type="hidden" id="@("initiallySelected" + field.Name)" value="@field.Value" />
Html.DevExpress().ListBox(s =>
{
s.Name = field.Name;
s.ClientVisible = field.IsVisible;
s.ClientEnabled = !field.IsReadonly;
s.Properties.SelectionMode = DevExpress.Web.ASPxEditors.ListEditSelectionMode.CheckColumn;
s.Properties.TextField = "Name";
s.Properties.ValueField = "Value";
s.Properties.ValueType = typeof(string);
//s.Properties.EnableClientSideAPI = true;
foreach (var item in field.ListValues)
{
s.Properties.Items.Add(item.Name, item.Value);
}
//s.Properties.ClientSideEvents.SelectedIndexChanged = "MultiSelectListChanged";
s.Properties.ClientSideEvents.Init = "MultiSelectListInit";
}).GetHtml();
}
else
{
//Html.TextBox(field.Name, field.Value)
Html.DevExpress().TextBox(s =>
{
s.Name = field.Name; s.Text = field.Value;
}).GetHtml();
}
@Html.ValidationMessage(field.Name)
<input type="hidden" name="@("oldvalue_" + field.Name)" value="@field.Value" />
<input type="hidden" name="@("olduse_" + field.Name)" value="@(field.IncludeInSearch ? "C" : "U")" />
</td>
<td id="criteriaTableIncludeCol">
@Html.DevExpress().CheckBox(s =>
{
s.Checked = field.IncludeInSearch;
s.Name = "use_" + field.Name;
s.ClientEnabled = (!field.IsMandatory);
}).GetHtml()
</td>
}
</tr>
}
</table>
The Controller action accepts a Forms Collection. I loop through the formsCollection looking for the control names I specified in the view.
[HttpPost]
public ActionResult QueryCriteria(FormCollection formCollection)
{
var isValid = true;
foreach (var field in form.Fields)
{
var value = (formCollection[field.Name] ?? "").Trim();
...
If there are any validation errors I can specify control level validation by adding a ModelError directly to the model e.g.
ModelState.AddModelError(field.Name, "This is a mandatory field");
and I return the View if there are validation errors.
Hope this helps.
精彩评论