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.
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:
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.
Note: You can refer to this GitHub repository for the EmployeeDetails class.
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.
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.
<EditForm OnValidSubmit="@Submit"> </EditForm>
<EditForm Model="@employeeDetails" OnValidSubmit="@Submit"> </EditForm> @code { EmployeeDetails employeeDetails; protected override void OnInitialized() { employeeDetails = new EmployeeDetails(); } }
<EditForm Model="@employeeDetails" OnValidSubmit="@Submit"> <DataAnnotationsValidator /> <ValidationSummary></ValidationSummary> </EditForm>
<EditForm Model="@employeeDetails" OnValidSubmit="@Submit"> <DataAnnotationsValidator /> @CreateComponent() <ValidationSummary></ValidationSummary> <div class="form-group"> <button type="submit" class="btn btn-primary">Save</button> </div> </EditForm>
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.
You can download the complete source code of this example from this GitHub repository.
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!