ASP.Net MVC Editor template for dynamic view data / forms

回眸只為那壹抹淺笑 提交于 2020-01-12 04:00:11

问题


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 Html.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


回答1:


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.



来源:https://stackoverflow.com/questions/7403799/asp-net-mvc-editor-template-for-dynamic-view-data-forms

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!