Simple to use dynamic form-generator powered by Blazor
After writing my Proof of Concept for dynamic form generation with Blazor a couple of months ago, I saw a lot of improvements that should be made. You could read the first article about dynamic form generation here, because it elaborates more on the initial idea.
Having said that, let’s summarize what I wanted to improve after building the POC.
- Move to separate project
- Pass-through of the
EditForm
events - Implement the styling for frameworks like Bootstrap or your own CSS styling
ValidationMessage
to the element- Translatable
ValidationMessage
The summary will be used as the outline for the article, I’ll start with creating and moving all the code to a library. I will skip this part because I’m assuming you know how to do that sweet ctrl
+ c
, ctrl
+ v
magic. After all.. you are probably a programmer. 😉😂
A quick note regarding the move: when I moved the Blazor components the VxFormGenerator project could not recognize some of them anymore. So I needed to clean the solution and create components in the VxFormGenerator project again and moved the code to that newly created files then deleting the old file🤔 Didn’t had the time to go deeper into this issue.
Simplify the usage
The POC had a FormGenerator
component that wrapped an EditForm
this was useful so the component could read the properties of a POCO and generate FormElement
components.
Having a wrapper around the EditForm
turned out to be tedious because the FormGenerator
was simply calling a RenderTree function and was constraining the developer. For example, if the developer didn't want to use a ValidationSummary
it needed to be passed as parameter to the component. This would turn into a lot of options. So the dynamic form-generator needed a different way to be initiated. Also it made the assumption that the developer always had a model. Based on feedback by a visitor of the repository , this was not always the usage some people needed.
Hook into the EditForm
Taking inspiration from the DataAnnotationsValidator
and the ValidationSummary
having it hooked to the EditForm
with one line. I found out that I was able to use the CascadedParameter
approach for connecting to the EditContext
of the EditForm
.
This allows the component to get the EditContext.Model
value. Having the model allows the component to render the class as a dynamic form easy-peasy. It's now a one-liner 🎉.
Read the Model and create form-elements
The component RenderFormElements
is a class without a razor file, and is a layout component, that's a component that has the task to render a structure. It's without a razor file because the sole purpose is to render generic components dynamically. So a razor file appeared to me as overhead.
The implementation is as followed:
The CascadingParameter
loads the EditForm
, and the BuildRenderTree
method renders the FormElementLoader
based on the data-type of EditContext.Model
. When the model is a Poco it creates all necessary methods for binding to that property. This binding methods are stored in a ValueReference
and passed along to the FormElementLoader
. The loader will then inject the binding methods and key name into the rendered FormElement
. When a value changes it now updates the model.
The RenderFormElements
component is also able to create a form based on a dynamic ExpandoObject
this allows the developer to create a model at runtime. This behaviour is useful when, for example, creating a form based on the API JSON response containing a dynamic form structure stored in a database.
At the moment of writing rendering a form based on a
ExpandoObject
doesn't support validation. This is one of the improvements for the library. Please note that this approach needs more attention to be production ready. Will go deeper into this variant in a separate article.
Let’s take a look a the the code that renders the form:
VxFormElemenLoader
is a component that renders the FormElement
by looking up the type of the property in the FormGeneratorComponentsRepository
and will use the mapped component for that type.
Using the FormElement and implement styling
When the VxFormElementLoader
is loading the FormElementComponent
it should have a registered FormElement
component in the FormGeneratorComponentsRepository
.
The FormElement
component contains the label and field and allows the developer to implement styling like Bootstrap. We'll start with a plain HTML version.
The output would look like above, a basic HTML form dynamically generated. This lacks most of the styling. Now let’s create a Bootstrap FormElement
. This is where BootstrapFormElementComponent
comes in
You’ve created a Bootstrap version of the FormElement
the last thing we need is to update the FormGeneratorComponentsRepository
to use the BootstrapFormElement
as a container to load the inputs.
Because the FormElement
component allows for default CSS classes by setting the DefaultFieldClasses
property it inherited from FormElementBase
, the form is almost completely styled like a Bootstrap form. Some components may need a new component because the built-in Blazor components not always meet the structure that is needed to style a input based on the CSS framework. As you could see in the image below, we need to create a checkbox component because the DefaulFieldClasses
isn't applying the Bootstrap style.
Let’s create a checkbox component with a label.
After creating a checkbox with a label we need to extend this with the Bootstrap styling.
The Bootstrap checkbox styling is now applied. 🎉 One missing feature of the checkbox is is multiple selection. We need to create a component that renders multiple check-boxes when we have a multiple-choice property.
It will now render multiple Checkboxes
and will set the selected values in a ValueReference
object. The ValueReference
object is created for generic handling for multiple selection components. This allows for enums and "unknown value at compile time" to be handled the same way without forcing the unknown values to use int
values are there keys. The last step to take is make the InputCheckboxMultipleWithChildren
switch from child component without a lot of custom code. For example, rendering a Bootstrap version of the child component without changing the logic of the container component. The approach I took was to created an interface called IRenderChildrenSwapable
. This interface extends the regularIRenderChildren
with a overload of the Renderchildren
method allowing a component to specify a child component that it should render. This results in a minimum amount of code to implement a new framework.
Adding these components would show a fully styled Bootstrap form!
Add ValidationMessage
to the element
The last part of this article entails the ValidationMessage
. The built-in version of the component adds a hard-coded class. So I've took the approach described in this blog post. He explains further the inner workings of the component, if you're interested. So let's create our own ValidationMessage
.
So now we can use it in our FormElement
. We can use the ValueExpression
to connect the property that should be monitored.
When we set a MinLength
attribute on the Note
property in the model, the validation message is shown with Bootstrap styling when entering a invalid value. This approach allows the developer to implement multiple styling frameworks with reusable form feedback logic.
At the moment of writing the
EditForm
component sets anvalid
,modified
andinvalid
class when a input has changed and validated. This class in hard-coded so until https://github.com/dotnet/aspnetcore/pull/24835 is accepted, merged and published. We need a workaround like the one below.
It’s a wrap (for now)
While updating the form generator with the new insights and improvements, showing it to colleagues and receiving feedback I came to the conclusion that this POC is eligible to be a library. So I’ve published the first workable version on Nuget so you could play with it. BE AWARE it’s BETA and I have a lot of wishes that I would like to incorporate.
The top list:
- Better Documentation for the library
- Translatable validation messages
- All HTML5 built-in inputs as component
- Extending support for
ExpandoObject
with validation (will be a new article )
Any feedback is appreciated. If you want to contribute, you can find the GitHub here.