How to Create a Dynamic Form Builder in Blazor
Detailed Blog page Skeleton loader
How to Create a Dynamic Form Builder in Blazor

TL;DR: Learn to create a dynamic form builder in Blazor using the EditForm class and data annotations. This guide covers setting up the environment, creating a model class, dynamically rendering form components based on data types, and handling validation with Syncfusion Blazor components.

In this blog post, you will learn the procedure to create a dynamic form builder in Blazor with the EditForm class and data annotation validation. You will also learn the steps to generate form components based on data type and display validation messages using data annotation. Every step is explained with a working sample and simple code examples.

Dynamic form builder

Form input and its validation are very important for any application. In this blog, we will display employee details through a form design. This application will have a minimum of 10 user input entries in a form. We are going to create a form using data model classes and data annotation.

We will create components dynamically and render them in the edit form:

  • For string data type: TextBox component.
  • For string with multiline data type: TextBox with multiline (text area) component.
  • For number data type (integer, decimal, etc.): NumericTextBox component.
  • For DateTime data type: DatePicker component.

Let’s see the steps to create a dynamic form builder based on the data model class and implement data validation using Syncfusion Blazor form components.

Prerequisites

Steps to generate a dynamic form builder

Step 1: Create a Blazor server application

  1. Create a Blazor server app.
  2. Create a new model class file inside the Data folder with the name EmployeeDetails.
  3. In this class file, add the class definitions for the Countries and Cities classes with the required properties and methods to generate appropriate data for the Dropdown List.

Note: You can refer to this GitHub repository for the EmployeeDetails class.

Step 2: Add data annotation’s DataType attribute in the form model

The dynamic form component will be rendered based on the data annotation’s DataType attribute in the model properties. The EmployeeDetails model has various properties and comes with built-in validation data annotation attributes like Required, EmailAddress, Phone, and Range.

Additionally, we should add DataType attributes for details like Text, MultilineText, Date, PhoneNumber, and custom types.

Refer to the following code example.

public class EmployeeDetails
    {
        [Required]
        [Display(Name ="First Name")]
        [DataType(DataType.Text)]
        public string FirstName { get; set; }
        [Display(Name = "Last Name")]
        [DataType(DataType.Text)]
        public string LastName { get; set; }
        [Required]
        [Display(Name = "Email Address")]
        [DataType(DataType.EmailAddress)]
        [EmailAddress]
        public string Email { get; set; }
        [Required]
        [Display(Name = "PhoneNumber")]
        [DataType(DataType.PhoneNumber)]
        [Phone]
        public string PhoneNumber { get; set; }
        [Required]
        [Display(Name = "Date of Birth")]
        [DataType(DataType.Date)]
        public DateTime? DOB { get; set; }
        [Required]
        [DataType(DataType.Duration)]
        [Display(Name = "Total Experience")]
        [Range(0, 20, ErrorMessage = "The Experience range should be 0 to 20")]
        public decimal? TotalExperience { get; set; }
        [Required]
        [Display(Name = "Select a Country")]
        [DataType("DropdownList")]
        public string Country { get; set; }
        [Required]
        [Display(Name = "Select a City")]
        [DataType("ComboBox")]
        public string City { get; set; }
        [Required]
        [DataType(DataType.MultilineText)]
        [Display(Name = "Address")]
        public string Address { get; set; }
    }

We have added custom DataType attributes for the DropdownList and ComboBox, since we don’t have built-in data types for them. Based on the custom values, the components will be rendered.

Step 3: Generate dynamic form components

After creating the required model classes, access them inside the index.razor component in the Pages folder. In that folder, we are going to design the dynamic EditForm with Syncfusion form components.

  1. Add the following code to include the EditForm component. It creates an EditContext that tracks the fields that are modified in the dynamic form components and tracks the validation messages.
    <EditForm OnValidSubmit="@Submit">
    </EditForm>
  2. Then, create an instance of the EmployeeDetails class for binding it to the model of the EditForm, like in the following code example.
    <EditForm Model="@employeeDetails" OnValidSubmit="@Submit">
    </EditForm>
    
    @code {
    
        EmployeeDetails employeeDetails;
        protected override void OnInitialized()
        {
            employeeDetails = new EmployeeDetails();
        }
    }
  3. Then, add the DataAnnotationsValidator component in the EditForm to validate the input fields.
    <EditForm Model="@employeeDetails" OnValidSubmit="@Submit">
        <DataAnnotationsValidator />
        <ValidationSummary></ValidationSummary>
    </EditForm>
  4. Now, render the form components based on the data type using the CreateComponent method call, as shown in the following code example.
    <EditForm Model="@employeeDetails" OnValidSubmit="@Submit">
        <DataAnnotationsValidator />
        @CreateComponent()
        <ValidationSummary></ValidationSummary>
        <div class="form-group">
            <button type="submit" class="btn btn-primary">Save</button>
        </div>
    </EditForm>
  5. Then, render the Syncfusion form components based on the data type and bind the default value, placeholder, float label, and other properties.Refer to the following code example.
    public RenderFragment CreateComponent() => builder =>
        {
            var proList = typeof(EmployeeDetails).GetProperties();
            foreach (var prp in proList)
            {
                Type T = prp.GetType();
                if (prp.GetCustomAttributes(typeof(DataTypeAttribute), false).Length != 0)
                {
                    var attrList = (DataTypeAttribute)prp.GetCustomAttributes(typeof(DataTypeAttribute), false).First();
                    var displayLabel = (DisplayAttribute)prp.GetCustomAttributes(typeof(DisplayAttribute), false).First();
                // Get the initial property value.
                var propInfoValue = typeof(EmployeeDetails).GetProperty(prp.Name);
                // Create an expression to set the ValueExpression-attribute.
                var constant = System.Linq.Expressions.Expression.Constant(employeeDetails, typeof(EmployeeDetails));
                    var exp = System.Linq.Expressions.MemberExpression.Property(constant, prp.Name);
                    switch (attrList.DataType)
                    {
                        case DataType.Text:
                        case DataType.EmailAddress:
                        case DataType.PhoneNumber:
                        case DataType.MultilineText:
                            {
                                builder.OpenComponent(0, typeof(SfTextBox));
                            // Create the handler for ValueChanged.
                            builder.AddAttribute(3, "ValueChanged", RuntimeHelpers.TypeCheck<Microsoft.AspNetCore.Components.EventCallback<System.String>>(Microsoft.AspNetCore.Components.EventCallback.Factory.Create<System.String>(this, Microsoft.AspNetCore.Components.EventCallback.Factory.CreateInferred(this, __value => propInfoValue.SetValue(DataContext, __value), (string)propInfoValue.GetValue(DataContext)))));
                                builder.AddAttribute(4, "ValueExpression", System.Linq.Expressions.Expression.Lambda<Func<string>>(exp));
                                if (attrList.DataType == DataType.MultilineText)
                                    builder.AddAttribute(5, "Multiline", true);
                                break;
                            }
                        case DataType.Date:
                            builder.OpenComponent(0, typeof(SfDatePicker<DateTime?>));
                            builder.AddAttribute(3, "ValueChanged", RuntimeHelpers.TypeCheck<Microsoft.AspNetCore.Components.EventCallback<DateTime?>>(Microsoft.AspNetCore.Components.EventCallback.Factory.Create<DateTime?>(this, Microsoft.AspNetCore.Components.EventCallback.Factory.CreateInferred(this, __value => propInfoValue.SetValue(DataContext, __value), (DateTime?)propInfoValue.GetValue(DataContext)))));
                            builder.AddAttribute(4, "ValueExpression", System.Linq.Expressions.Expression.Lambda<Func<DateTime?>>(exp));
                            break;
                        case DataType.Duration:
                            builder.OpenComponent(0, typeof(SfNumericTextBox<decimal?>));
                            builder.AddAttribute(3, "ValueChanged", RuntimeHelpers.TypeCheck<Microsoft.AspNetCore.Components.EventCallback<decimal?>>(Microsoft.AspNetCore.Components.EventCallback.Factory.Create<decimal?>(this, Microsoft.AspNetCore.Components.EventCallback.Factory.CreateInferred(this, __value => propInfoValue.SetValue(DataContext, __value), (decimal?)propInfoValue.GetValue(DataContext)))));
                            builder.AddAttribute(4, "ValueExpression", System.Linq.Expressions.Expression.Lambda<Func<decimal?>>(exp));
                            break;
                        case DataType.Custom:
                            if (attrList.CustomDataType == "DropdownList")
                            {
                                builder.OpenComponent(0, typeof(Syncfusion.Blazor.DropDowns.SfDropDownList<string, Countries>));
                                builder.AddAttribute(1, "DataSource", countries.GetCountries());
                                builder.AddAttribute(4, "ChildContent", (Microsoft.AspNetCore.Components.RenderFragment)((builder2) =>
                                {
                                    builder2.AddMarkupContent(5, "\r\n    ");
                                    builder2.OpenComponent<Syncfusion.Blazor.DropDowns.DropDownListFieldSettings>
                                  (6);
    
                                    builder2.AddAttribute(7, "Value", "Code");
                                    builder2.AddAttribute(8, "Text", "Name");
                                    builder2.CloseComponent();
                                    builder2.AddMarkupContent(9, "\r\n");
                                }));
    
                            }
                            else if (attrList.CustomDataType == "ComboBox")
                            {
                                builder.OpenComponent(0, typeof(Syncfusion.Blazor.DropDowns.SfComboBox<string, Cities>));
                                builder.AddAttribute(1, "DataSource", cities.GetCities());
                                builder.AddAttribute(4, "ChildContent", (Microsoft.AspNetCore.Components.RenderFragment)((builder2) =>
                                {
                                    builder2.AddMarkupContent(5, "\r\n    ");
                                    builder2.OpenComponent<Syncfusion.Blazor.DropDowns.ComboBoxFieldSettings>
                                  (6);
    
                                    builder2.AddAttribute(7, "Value", "Code");
                                    builder2.AddAttribute(8, "Text", "Name");
                                    builder2.CloseComponent();
                                    builder2.AddMarkupContent(9, "\r\n");
                                }));
                            }
                            builder.AddAttribute(3, "ValueChanged", RuntimeHelpers.TypeCheck<Microsoft.AspNetCore.Components.EventCallback<System.String>>(Microsoft.AspNetCore.Components.EventCallback.Factory.Create<System.String>(this, Microsoft.AspNetCore.Components.EventCallback.Factory.CreateInferred(this, __value => propInfoValue.SetValue(DataContext, __value), (string)propInfoValue.GetValue(DataContext)))));
                            builder.AddAttribute(4, "ValueExpression", System.Linq.Expressions.Expression.Lambda<Func<string>>(exp));
                            break;
                        default:
                            break;
                    }
                    var defaultValue = propInfoValue.GetValue(employeeDetails);
                    builder.AddAttribute(1, "Value", defaultValue);
                    builder.AddAttribute(6, "PlaceHolder", displayLabel.Name);
                    builder.AddAttribute(6, "FloatLabelType", FloatLabelType.Auto);
    
                    builder.CloseComponent();
                }
            }
        };

Now, we have rendered the form components dynamically, and the inputs will be validated using data annotation, listing all the form validations when the Submit button is clicked.

Look at the following screenshot to see how the form is rendered and validation messages are displayed in the employee details form.
Output

GitHub sample

You can download the complete source code of this example from this GitHub repository.

Conclusion

Thanks for reading! I hope you now understand how easy is it to build a dynamic form using Syncfusion Blazor components with data annotation validation. This will definitely reduce your code size and also enhance code re-usability.

Try our Blazor components by downloading a free 30-day trial or check out our NuGet package. Feel free to have a look at our online examples and documentation to explore other available features.

If you have any questions, please let us know in the comments section below. You can also contact us through our feedback portal, support forum or Direct-Trac. We are always happy to assist you!

Be the first to get updates

Saravanan G

Meet the Author

Saravanan G

Saravanan is a Technical Product Manager at Syncfusion for Web products. He is passionate about Web technology and has been active in development since 2010 and focuses mainly on delivering the products with perfection.

Comments (16)

It’s works with blazor webassembly project?

Hi Andre,

Yes, it will work webassembly project also. Pleae find the below sample link.

https://www.syncfusion.com/downloads/support/directtrac/general/ze/WASM_BlazorApp_Form-1417268007.zip

Kindly check the above sample and get back to us if you need any further assistance on this.

Regards,
Saravanan G

Hi Saravanan,
Thanks for your answer.

Hi Saravanan,
Very nice demo, it is giving my some ideas 🙂

The code is really only partially dynamic, if you are hard coding the combobox type to Cities and DropDownlist to Countries. It will only work for the form in the demo. I would be interested in making your CreateComponent() generic where any class type could be passed in to produce a form. I am wondering how that would even work when controls need to be bound to lists of data.

Demos that make me think are great!
-Ken

@ Ken Villines  

Hi Ken,

Thank you for your appreciation :-),

Yes, we have provided the ExpandoObject type support in our components, so we can bind the dynamic class list data to Dropdown List and ComboBox components like below and also I have modified the sample and attached the below link.

builder.OpenComponent(0, typeof(Syncfusion.Blazor.DropDowns.SfDropDownList));

Sample link – https://www.syncfusion.com/downloads/support/directtrac/general/ze/FormBuilder-514046883.zip

Kindly check the above sample and get back to us if you need any further assistance on this.

Regards,
Saravanan G

Hi Saravanan,
very great example! I changed the property Country to

public int CountryId { get; set; }

public class Countries
{
public string Name { get; set; }
public int Id { get; set; }

public List GetCountries()
{
List Country = new List
{
new Countries() { Name = “Australia”, Id = 1 },
new Countries() { Name = “United Kingdom”, Id = 2 },
new Countries() { Name = “United States”, Id = 3 },
};
return Country;
}
}

and of course I get an error: Unable to cast object of type ‘System.Int32’ to type ‘System.String’.
builder.AddAttribute(3, “ValueChanged”, RuntimeHelpers.TypeCheck<Microsoft.AspNetCore.Components.EventCallback>(Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, Microsoft.AspNetCore.Components.EventCallback.Factory.CreateInferred(this, __value => propInfoValue.SetValue(employeeDetails, __value), (string)propInfoValue.GetValue(employeeDetails)))));

I’ve tried a lot. Unfortunately without any result. Where does the correct change need to be made?

Regards,
Elko

Hello Elko,

Thank you for your appreciation,

Yes, you can bind the int data type to the Dropdown List component. For this, you need to change the type in Dropdown List creation and ValueChanged event call back like the below code in the CreateComponent method.

builder.OpenComponent(0, typeof(Syncfusion.Blazor.DropDowns.SfDropDownList));
……
……
if (prp.PropertyType == typeof(int?))
{
builder.AddAttribute(3, “ValueChanged”, RuntimeHelpers.TypeCheck<Microsoft.AspNetCore.Components.EventCallback>(Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, Microsoft.AspNetCore.Components.EventCallback.Factory.CreateInferred(this, __value => propInfoValue.SetValue(employeeDetails, __value), (int?)propInfoValue.GetValue(employeeDetails)))));
builder.AddAttribute(4, “ValueExpression”, System.Linq.Expressions.Expression.Lambda<Func>(exp));
}

I have modified the sample and attached the below link.

https://www.syncfusion.com/downloads/support/directtrac/general/ze/blazor-dynamic-form1750749387.zip

Check the above sample and get back to us if you need any further assistance on this.
Regards,
Saravanan G

Hello Saravanan,

thank you for this great example on creating a dynamic form.

I came across this site cause i am looking for a way to let my customer create a form with a designer ( drag and drop ) . The structure of this form should be saved as JSON or XML and loaded on demand to display the form. The data, entered in this form, should be written as key/valu pairs.

Any ideas where i could start the development of such tool ?

TGHanks

@ ulrich  

Thanks Ulrich,

The Form Designer component will meet your needs. Currently, the component is not available in our Syncfusion Blazor component library and have considered a new component request for it.

https://www.syncfusion.com/feedback/13388/form-layout-component-for-blazor

Regards,
Rajendran R

Hi Saravanan,
Thank you for this nice article.
I have different requirements for dynamic form creation, I don’t have predefined Model class, fields are dynamic according how and what fields user has defined in table.
for your reference below is information I have to build form.
MaterialName AttributeName ControlTypeId Name DataType ValidationType UnitId DefaultExpression MaxExpression MinExpression Options
BKBKB User Defined Attribute Name 3 6 Combo Box 2 2 1 1 10 1 1|2|3|4
BKBKB User Defined Attribute Name 4 6 Combo Box 2 2 1 User Defined Attribute Default Expression 4 User Defined Attribute Max Expression 4 User Defined Attribute Min Expression 4 Red|Green|Blue
BKBKB User Defined Attribute Name 2 2 Description 1 1 1 1 2000 500 NULL
BKBKB User Defined Attribute Name 1 1 Text 1 1 NULL Default Expression Max Expression Min Expression NULL
BKBKB User Defined Attribute Name 5 3 Date 2 1 NULL 1 10 1 NULL
BKBKB test 6 Combo Box 1 1 1 2 30 3 Earth|Water|Air|Fire
Kraft Brown 125 GSM 1 Text 1 1 9 NULL NULL NULL NULL
Waste Based 80 GSM 1 Text 1 1 9 NULL NULL NULL NULL
Kraft Brown 135 GSM 1 Text 1 1 9 NULL NULL NULL NULL
Kraft Brown 125 SCTcd 1 Text 1 2 8 NULL NULL NULL NULL
Waste Based 80 SCTcd 1 Text 1 2 8 NULL NULL NULL NULL
Kraft Brown 135 SCTcd 1 Text 1 2 8 NULL NULL NULL NULL
Kraft Brown 125 CO2 1 Text 1 2 11 746 NULL NULL NULL
Waste Based 80 CO2 1 Text 1 2 11 746 NULL NULL NULL
Kraft Brown 135 CO2 1 Text 1 2 11 746 NULL NULL NULL
Kraft Brown 125 Paper Type 6 Combo Box 2 1 NULL NULL NULL NULL KB|WB|T2|T3
Waste Based 80 Paper Type 6 Combo Box 2 1 NULL NULL NULL NULL KB|WB|T2|T3
Kraft Brown 135 Paper Type 6 Combo Box 2 1 NULL NULL NULL NULL KB|WB|T2|T3

is it possible to create dynamic form ?

Regards,
Akshat

Hello,

I have form data as in CSV attached it comes from database table. for example I have record label name , control type , data type and so on. using this information I need to create form on runtime thus I don’t have predefined Model class prier (number of fields are not fixed it differed on record selection).

Kindly suggest solution for it.

Thanks and Regards,

Akshat

How can we set DataAnnotations dynamically ? Let’s say we have a configuration where we can Set IsRequired to True/False and based on that Configuration we want to set Validation Rules.

Also if we want to hide specific Properties in a Form how can we do it dynamically ?

@ Mr. Mohi  

Hello MR. Mohi,

We can accomplish dynamic validation based on your request with custom validation. Please refer to the following forum discussion:

https://www.syncfusion.com/forums/163763/calculating-validation-rule-dynamically

public static bool ValidFlag;
[AttributeUsage(AttributeTargets.Property)]
public class OrderDateValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// Here you can perform your custom validation and return true/false value
// you will get the entered OrderDate value in “value” and validationContext will have other row cell data

return ValidFlag ? new ValidationResult(“Custom Message”, new[] { validationContext.MemberName }) : ValidationResult.Success;
}
}

public class Order
{
public int? OrderID { get; set; }
public string CustomerID { get; set; }
[OrderDateValidation]
public DateTime? OrderDate { get; set; }
public DateTime? ShippedDate { get; set; }
public string ShipCountry { get; set; }
}

Regards,
Saravanan G

Kindly share the code if we have child models in parent model then how it will render child members

Can you Provide me same with nested model in blazor

Hello,
Thank you for the great example.
Is it possible to do something similar using ASP.NET MVC or Razor Pages?
Regards,
Katsuji

Comments are closed.