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