Creating dynamic forms in Razor Pages can be a powerful feature for applications that need flexibility without hardcoding every field. I have used this approach multiple time when fields are dynamic. This way I only have to change the Api and frontend adapts. By leveraging Field Definitions and Key-Value Pairs, you can dynamically generate forms that adapt to different scenarios, such as varying user roles or configurations. In this post, we’ll walk through the steps to build such a dynamic form in ASP.NET Core Razor Pages.
Overview of the Approach
We’ll use the following components:
- FieldDefinition: Defines the structure and metadata of each form field.
- KeyValuePair: Holds the data for each field, where the key corresponds to the field’s identifier.
- Dynamic Rendering: Razor syntax and helper methods will dynamically render form fields based on definitions.
- Model Binding: Handle user input effectively by binding it to a structured data model.
Step 1: Define the FieldDefinition Class
The FieldDefinition
class acts as a blueprint for each field in the form.
public class FieldDefinition
{
public string FieldId { get; set; } // Unique identifier for the field
public string Label { get; set; } // Display label
public string FieldType { get; set; } // Text, Number, Dropdown, etc.
public bool IsRequired { get; set; } // Validation flag
public IEnumerable<KeyValuePair<string, string>> Options { get; set; } // For dropdowns or radio buttons
}
Step 2: Create a ViewModel for the Razor Page
This ViewModel will hold both the field definitions and the values submitted by the user.
public class DynamicFormViewModel
{
public List<FieldDefinition> FieldDefinitions { get; set; } = new();
public Dictionary<string, string> FieldValues { get; set; } = new();
}
Step 3: Initialize Data in the Page Model
Set up your field definitions and bind them to the ViewModel.
public class DynamicFormModel : PageModel
{
[BindProperty]
public DynamicFormViewModel FormViewModel { get; set; } = new();
public void OnGet()
{
// Example: Initialize Field Definitions
FormViewModel.FieldDefinitions = new List<FieldDefinition>
{
new FieldDefinition
{
FieldId = "name",
Label = "Name",
FieldType = "text",
IsRequired = true
},
new FieldDefinition
{
FieldId = "age",
Label = "Age",
FieldType = "number",
IsRequired = false
},
new FieldDefinition
{
FieldId = "gender",
Label = "Gender",
FieldType = "dropdown",
IsRequired = true,
Options = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("M", "Male"),
new KeyValuePair<string, string>("F", "Female")
}
}
};
}
public IActionResult OnPost()
{
// Process FormViewModel.FieldValues
if (!ModelState.IsValid)
{
return Page();
}
foreach (var field in FormViewModel.FieldValues)
{
Console.WriteLine($"{field.Key}: {field.Value}");
}
return RedirectToPage("Success");
}
}
Step 4: Dynamically Render the Form in Razor
Use Razor syntax to loop through the FieldDefinitions
and render appropriate inputs.
@page
@model DynamicFormModel
<h2>Dynamic Form</h2>
<form method="post">
@foreach (var field in Model.FormViewModel.FieldDefinitions)
{
<div class="form-group">
<label asp-for="@Model.FormViewModel.FieldValues[field.FieldId]">@field.Label</label>
@if (field.FieldType == "text" || field.FieldType == "number")
{
<input asp-for="@Model.FormViewModel.FieldValues[field.FieldId]"
class="form-control"
type="@field.FieldType" />
}
else if (field.FieldType == "dropdown")
{
<select asp-for="@Model.FormViewModel.FieldValues[field.FieldId]" class="form-control">
@foreach (var option in field.Options)
{
<option value="@option.Key">@option.Value</option>
}
</select>
}
@if (field.IsRequired)
{
<span class="text-danger">* Required</span>
}
</div>
}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
Step 5: Handle Validation and Submission
In the OnPost
method, validate and process the user input stored in FieldValues
. Add custom validation if needed.
public IActionResult OnPost()
{
foreach (var field in FormViewModel.FieldDefinitions)
{
if (field.IsRequired && string.IsNullOrWhiteSpace(FormViewModel.FieldValues[field.FieldId]))
{
ModelState.AddModelError($"FormViewModel.FieldValues[{field.FieldId}]", $"{field.Label} is required.");
}
}
if (!ModelState.IsValid)
{
return Page();
}
// Process values
return RedirectToPage("Success");
}
Conclusion
Dynamic Razor Pages allow for immense flexibility in form generation and processing. By defining fields and their properties in a structured way, you can create user interfaces that adapt to your application’s needs without sacrificing maintainability. This approach can be extended further with custom field types, client-side validation, or integration with external data sources.