Skip to content

Building Dynamic Razor Pages with Field Definitions and Key-Value Pairs

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:

  1. FieldDefinition: Defines the structure and metadata of each form field.
  2. KeyValuePair: Holds the data for each field, where the key corresponds to the field’s identifier.
  3. Dynamic Rendering: Razor syntax and helper methods will dynamically render form fields based on definitions.
  4. 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.