outline

Customizing Dynamic Form Tag Helper Generation

Since the last post, the dynamic form tag helper supports generating form groups for complex properties.

Now the html generated by the dynamic form tag helper is not customizable and it would be nice if it was.

There might be numerous reasons for customizing the default behavior, but we are going to focus on the following: styling and using custom html form controls.

In this post we are going to introduce capabilities for tweaking some default behavior and adding custom css classes by using a nested tag helper.

The full code is in the usual Github repository.

Objective

We are going to add capability for tweaking the following form elements:

  • The text content of the submit button
  • The css classes of the submit button
  • For a specific property(form group):
    • Choosing a custom input element razor template
    • Adding css classes to the generated <label>, <input> or validation message <span>

Consider the following form:

Customized generated form

Arbitrary css classes have been applied to the BirthDate label as well as to the “Person’s full name” input element. This is what makes the BirthDate label purple and the “Person’s full name”’s text yellowish green(not the real color’s name ;-)).

In addition, a custom date time picker widget has been used for the BirthDay field. Here I used the bootstrap datetimepicker which allows to select dates more conveniently.

Let’s now take a look on how to achieve this.

Customizing behavior with the TweakTagHelper

Let’s consider the usage of the dynamic tag helper in the following cshtml view:

Here you will notice the usage of the new tweak nested tag helper. The TweakTagHelper is intended to be used inside the DynamicFormTagHelper in order to modify the generation behavior of a form group related to a specific property.

In this regard, it can accepts one of the view model’s properties as an argument along with the aspects to customize, which can be one of the following:

  • asp-input-path the path to a custom html form control
  • asp-label-classes, asp-input-classes and asp-validation-classes the additional css classes that are going to be added to the relevant form group element(label, input or validation span)

It is possible to target properties nested under complex types as we can see in the last used tweak instance(targeting OwnedDog.BirthDate).

The DynamicFormTagHelper has been modified to read customization configuration and to take it in account. We are going to see later how this capability is implemented.

Customizing the Submit Button

Two attributes have been added to the DynamicFormTagHelper:

  • asp-submit-button-text for specifying the text content
  • asp-submit-button-classes for specifying additional css classes for the submit button

Here is an example usage:

This sets the submit button text to “Create Person!” and adds the btn-success class to it.

Reading Tweaking Configuration in the DynamicFormTagHelper

Consider the following snippet from the DynamicFormTagHelper:

Under the DynamicFormTagHelper.ProcessAsync method we create an object belonging to the TweakingConfiguration class, we pass this object to the context object and we launch the execution of the children tag helpers content.

The TweakingConfiguration object acts as a data transfer object that is going to be populated by the children TweakTagHelper.

As we can see in the following example the TweakingConfiguration object named tweakConfig is subsequently passed to the FormGroupBuilder.GetFormGroup method and is also used directly to properly generate the submit button:

Transferring Data from the TweakingTagHelper to the DynamicFormTagHelper

As we saw in The Very Basics of Nesting for Tag Helpers, child to parent communication is performed via the TagHelperContext object that is passed to each of the parent and children ProcessAsync method.

Let’s consider the following code it’s the actual TweakingTagHelper class implementation:

First, the HtmlTargetElement attribute is slapped on the tag helper class to allow its usage only under the dynamic-form tag helper.

Then, you can see that TweakTagHelper defines the properties mapped to the relevant html attributes.

Finally, the TweakTagHelper.ProcessAsync creates a PropertyTweakingConfiguration object that represents the customization of a specific property. The created instance is then added to the TweakingConfiguration object fetched from the TagHelperContext. Keep in mind here that the fetched TweakingConfiguration instance is the same one referenced by the dynamic-form tag helper.

Here we can see that the TweakTagHelper fulfills a very simple responsibility: reading data from the cshtml view and passing its parent tag helper (dynamic-form) in a convenient format.

The implementations of the TweakingConfiguration and the PropertyTweakingConfiguration classes are very basic, you can take a look at them in the Github repository.

Now let’s get to the FormGroupBuilder modifications.

Using the Tweaking Configuration inside the FormGroupBuilder

First off, the FormGroupBuilder has been modified and became a regular class (as opposed to a static class).

As you might know the form generation process depends on the following services: ViewContext, HtmlEncoder, IHtmlGenerator and recently IHtmlHelper.

Passing the instances of these services to each method involved in the form group rendering process became painful so I refactored it to a regular class with the previous dependencies defined as private fields.

Then, a new argument tweakingConfig argument has been added to the FormGroupBuilder.GetFormGroup method. This argument is expected to contain the TweakingConfiguration object built by the various tweak tag helpers used.

The tweakingConfig object is then passed to the private methods responsible for rendering the elements of the form group in order to allow them to take the tweaking configuration into account.

Custom Template Rendering

Let’s take a look at the private FormGroupBuilder.buildInputHtml method:

When a tweaking configuration is available for the currently processed property and the configuration defines a form control template via the InputTemplatePath property, the specified template is rendered instead of the default InputTagHelper.

The renderInputTemplate method uses the IHtmlHelper.Editor method to render the form control:

The previous method uses a classic IHtmlHelper take a look at Using the old HtmlHelper inside tag helpers for more details.

Adding Custom CSS Classes

Adding custom css classes is straightforward, we just pass a TagHelperAttributeList populated with the developer defined classes to the existing GetGeneratedContentFromTagHelper method as in the following buildLabelHtml method:

Notice here the usage of the C# 6 null conditional operator when passing the eventually configured classes.

Closing Thoughts

In this attempt, we showed that it was possible to introduce configurability via nested tag helpers to the dynamic-form tag helper.

Usually developers would want to influence the form rendering via the model class and the attributes slapped on it and its properties. But it could be cleaner to specify some stuff in the cshtml view such as css classes.

A lot of work still needs to be done to make the dynamic-form tag helper more reliable and usable, at this point it is just a prototype.

Finally, using the HtmlHelper.Editor reminded me of the old EditorTemplates and DisplayTemplates facility which allowed to place display and editor templates named after a specific type in a specific location for later use via the EditorFor and DisplayFor methods.

EditorFor is able to generate a form control for a specific property and is able fetch a custom editor templates by type name automatically if it is placed under Shared/EditorTemplates.

That allowed the developer to specify a default editing widget for a specific type.

I wonder if we could bring this capability to tag helpers…