outline
Supporting Complex Type Properties in The Dynamic Form Tag Helper
This post builds on the First Dynamic Form Tag Helper Attempt. This time we are going to introduce small complex typed properties support.
The current version of the dynamic form tag helper is able to iterate over a view model object’s properties and to generate a form group for each simple property. Each form group contains a label
, an input
and a validation message span
.
Often times, view model classes contains properties that belongs to user defined types (classes).We refer to this properties as complex properties.
Complex Properties in View Models
Complex types are usually user defined classes that can contain properties belonging to a simple type or to another complex type.
For example, consider the following view model classes:
The PersonViewModel
class contains the OwnedDog
property which belongs to the DogViewModel
class. Here OwnedDog
is a complex typed property since DogViewModel
is a user defined class.
The dynamic form tag helper needs to take in consideration such properties in order to generate validatable and bindable form controls for the simple properties contained inside complex properties.
Our dynamic tag helper needs to generate the following form, when given an object of the previous PersonViewModel
class:
Before we continue, I would like to point out that my current definition of complex types is sloppy, what about generic, iterable, enum and struct types? Are they also consider complex? This is not very clear yet.
In the interest of brevity we are going to stick with this definition. I hope I will refined it as I explore more stuff.
Please take a look at the IsSimpleType
method under the FormGroupBuilder
class for the actual details.
Implementing Complex Property Support
The strategy for generating form groups for complex properties is simple(no pun intended).
We need to iterate over each of the root model properties, if the property is simple, we call the _getFormGroupForSimpleProperty
method and if the property is complex, we call _getFormGroupForComplexProperty
. This is done under the top level and public FormGroupBuilder.GetFormGroup
method:
The _getFormGroupForComplexProperty
method iterates over the properties nested inside the complex property and calls the top level GetFormGroup
method:
Here we use recursion (GetFormGroup
calls itself through _getFormGroupForComplexProperty
) to attain simple properties buried under arbitrary levels of nesting.
This was sufficient to generate the form groups, the problem is that they are not validatable and bindable yet.
Setting Property Names Properly When Creating ModelExpressions
The FormGroupBuilder
class instantiates label
, input
and validation span
tag helpers. All of these tag helpers needs a ModelExpression object containing metadata necessary to properly initialize the tag helpers.
Consider the following piece of code:
TagHelper input = new InputTagHelper(generator)
{
For = new ModelExpression(property.Metadata.PropertyName, property),
...
};
Here we pass the property name as available under Metadata
. It turns out that the first argument of the ModelExpression
constructor is used to set the name
html attribute on the generated input
element. Setting the name
html attribute is critical since server side validation and model binding relies on it to properly create the model object from POST request parameters.
This means that we need to make sure that the passed property name is fully qualified with respect to the root model object.
Going back to the Person and Dog example, when building the ModelExpression
when creating an InputTagHelper
for the Name
property under the DogViewModel
class we need to supply the following name: OwnedDog.Name
.
property.Metadata.PropertyName
as in the previous piece of code returns simply Name
.
That is why I introduced the following method to handle fully qualified property name building:
Conclusion
In the DynamicFormTagHelper Github repository, you will find the full code that contains an example with 2 levels of complex property nesting that generates the following form:
Try submitting the form with valid and invalid values, you will see that model binding and validation both work.
In an upcoming post we will focus on the case of generic or iterable properties.