Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

MVC Dynamic UI for Complex Models

0.00/5 (No votes)
18 Feb 2016 3  
Using MVC with complex models - understanding and solving the problem

The Problem

MVC provides a few helper methods for 'automagically' creating UI sections from your model metadata:

@Html.Editor("Prop")
@Html.EditorFor(mdl => mdl.Prop)
@Html.EditorForModel()
@Html.Display("Prop")
@Html.DisplayFor(mdl => mdl.Prop)
@Html.DisplayForModel()

These methods use a great metadata discovery mechanism to create a proper UI, including server and client side validations. The UI can further be enhanced and customized with an extensive templating system.

Model Complex Properties

The UI generation functions ignore non-primitive properties on your model. You might have an Address property on your Person model that has two string properties City and Street. You might expect the UI to contain two text boxes for those but it does not.

You can, of course, use code like this:

@Html.EditorForModel()
@Html.EditorFor(m=>m.Child)

You may find it unsatisfactory if you have a complex hierarchy:

@Html.EditorForModel()
@Html.EditorFor(m => m.Child)
@Html.EditorFor(m => m.Child.GrandChild)
@Html.EditorFor(m => m.Child.GrandChild2)
@Html.EditorFor(m => m.Child2)
@Html.EditorFor(m => m.Child2.GrandChild)
@Html.EditorFor(m => m.Child2.GrandChild2)
@Html.EditorFor(m => m.Child3)
@Html.EditorFor(m => m.Child3.GrandChild)
@Html.EditorFor(m => m.Child3.GrandChild2)

or if you have a model that is not fully known at design time. This might be the case when you have a polymorphic view that serves multiple types derived from a base class, or when you have a model created dynamically and hiding behind the metadata discovery mechanism.

The Solution

The method CollectDescendantProperties() traverses the model hierarchy and provides a 'flattened' list of properties complete with their metadata and full property path (eg 'Child3.GrandChild2'), ready to be used with @Html.Editor(desecendant.Path).

@functions
{
    IList<DescendantPropertyMetadata> CollectDescendantProperties(ModelMetadata modelMetadata)
    {
        var list = new List<DescendantPropertyMetadata>();
        CollectDescendantPropertiesRecursive("", list, modelMetadata, 0);
        return list;
    }

    void CollectDescendantPropertiesRecursive(string currentPath, 
    IList<DescendantPropertyMetadata> list, ModelMetadata modelMetadata, int incomingDepth)
    {
        int currentDepth = incomingDepth + 1;
        var complexProperties = ((IEnumerable<ModelMetadata>)modelMetadata.Properties.Where
                                (pr => pr.IsComplexType));
        foreach (var complexProperty in complexProperties)
        {
            string path = currentPath + (currentDepth == 1 ? "" : ".") + complexProperty.PropertyName;
            list.Add(new DescendantPropertyMetadata(path, currentDepth, complexProperty));
            CollectDescendantPropertiesRecursive(path, list, complexProperty, currentDepth);
        }
    }

    class DescendantPropertyMetadata
    {
        public DescendantPropertyMetadata(string path, int depth, ModelMetadata metadata)
        {
            Path = path;
            Depth = depth;
            Metadata = metadata;
        }
        public string Path { get; private set; }
        public int Depth { get; private set; }
        public ModelMetadata Metadata { get; private set; }
    }
}

You can use it in a view like this:

@Html.EditorForModel();
var desecendants = CollectDescendantProperties(ViewData.ModelMetadata);
foreach (DescendantPropertyMetadata desecendant in desecendants)
{
    <h2>@desecendant.Metadata.PropertyName</h2>
    @Html.Editor(desecendant.Path)
}

Note you would probably want to refractor the method code to somewhere outside a specific view.

History

  • Feb 2016 - Initial release

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here