Copied RSS Feed

Blazor

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.

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!

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.