In this post, I show you how to create form dynamically with Blazor without using DataAnnotation
but only simple classes. My goal is to create a survey dynamically at run-time based on a Json
file. I spent a lot of time to architect this code and I have created a component that allows you to create the form for the survey and validate the structure of the form and the result. Also, I added the opportunity to insert simple condition to display or not some options or content.
For testing, I have created a website with the latest version available on SurveyUI. You have the source code of this example on GitHub.
Shared classes
First, I have to create a common class for all the components. I’m calling this class Element
and the code is the following
public class Element
{
public virtual string ElementType { get; set; }
public string Name { get; set; }
public string Label { get; set; }
}
Here, I have just defined the basic properties of each component. Now, I’m going to create a class for each of the component and in particular TextInput
and RadioButton
.
public class TextInput : Element
{
public override string ElementType { get => "TextInput"; }
public string? PlaceHolder { get; set; }
public string? Value { get; set; }
}
public class RadioButton : Element
{
public override string ElementType { get => "RadioButton"; }
public Dictionary<string, string> Options { get; set; }
}
Then, I’m creating a new class to collect all the element and calling this class Form
public class Form
{
public List<Element> Elements { get; set; } = new List<Element>();
}
Read the Form from an API
So, I want to read the structure of the form from a Json
or in the case of this post from an API. For this reason, I’m creating a Controller
to return a form. Also, I’m creating an API to read the result.
using BlazorDynamicForm.Shared;
using Microsoft.AspNetCore.Mvc;
namespace BlazorDynamicForm.Server.Controllers
{
[Route("[controller]")]
[ApiController]
public class FormController : ControllerBase
{
[HttpGet]
public Form Get()
{
return new Form
{
Elements = new List<Element>
{
new TextInput
{
Name = "txtFName",
Label = "First Name",
PlaceHolder = "Enter your first name"
},
new TextInput
{
Name = "txtLName",
Label = "Last Name",
PlaceHolder = "Enter your last name"
},
new RadioButton
{
Name = "radGender",
Label = "Gender",
Options = new Dictionary<string, string> {
{ "M", "Male" },
{ "F", "Female" }
}
}
}
};
}
[HttpPost]
public string Submit([FromBody] Dictionary<string, string> formValues)
{
return $"Hello {formValues["txtFName"]} {formValues["txtLName"]}";
}
}
}
In the Blazor Client
To demonstrate the components which are the core of Blazor we are creating a corresponding component for each element. Component’s name is coming from their file name (it should start with Capital letter). They are a Html with a @code section which would have all the events, properties, logic and other things.
We are creating a folder called Components at the same level of Pages. For this sample we would only create TextInput.razor and RadioButton.razor.
TextInput.razor
<input type="text" name="@Name" placeholder="@PlaceHolder"
@bind="@(BlazorDynamicForm.Client.Pages.DynamicForm.ElementValues[Name])">
@code {
[Parameter]
public string Name { get; set; }
[Parameter]
public string PlaceHolder { get; set; }
[Parameter]
public string Value { get; set; }
}
Radiobutton.razor
@foreach (var option in @Options)
{
<input type="radio" id="@option.Key" name="@Name" value="@option.Key">
<label for="male">@option.Value</label>
<br>
}
@code {
[Parameter]
public string Name { get; set; }
[Parameter]
public Dictionary<string, string> Options { get; set; }
}
The Razor page
Now, I’m going to create a Razor page to display the form and I’m calling this page DynamicForm.razor
@page "/dynamicform"
@using BlazorDynamicForm.Shared
<h1>Dynamic form</h1>
@if (form == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<tbody>
@foreach(var element in form.Elements)
{
<tr>
<td>@element.Label</td>
@switch (element.ElementType)
{
case "TextInput":
{
if (!ElementValues.ContainsKey(element.Name))
ElementValues.Add(element.Name, (element as BlazorDynamicForm.Shared.TextInput).Value);
<td>
<TextInput Name="@element.Name"
PlaceHolder="@((element as BlazorDynamicForm.Shared.TextInput).PlaceHolder)"
Value="@((element as BlazorDynamicForm.Shared.TextInput).Value)" />
</td>
break;
}
case "RadioButton":
{
BlazorDynamicForm.Shared.RadioButton rdb = element as BlazorDynamicForm.Shared.RadioButton;
<td>
<RadioButton Name="@rdb.Name" Options="@(rdb.Options)" />
</td>
break;
}
default:
{
<td>Unknow control</td>
break;
}
}
</tr>
}
<tr>
<td colspan="2">
<button class="btn btn-primary" @onclick="Submit">Submit</button>
</td>
</tr>
@if (!string.IsNullOrEmpty(strForm))
{
<tr>
<td colspan="2">
<p>Form: @strForm</p>
</td>
</tr>
}
@if (!string.IsNullOrWhiteSpace(serverRequest))
{
<tr>
<td colspan="2">
<p>Request from server: @serverRequest</p>
</td>
</tr>
}
@if (!string.IsNullOrWhiteSpace(serverResponse))
{
<tr>
<td colspan="2">
<p>Response from server: @serverResponse</p>
</td>
</tr>
}
</tbody>
</table>
}
@code {
Form form;
protected override async Task OnInitializedAsync()
{
var st = await Http.GetStringAsync("Form");
form = JsonConvert.DeserializeObject<Form>(st, settings: new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
}
}
Running the solution
After that, we can run the solution. The result is what you can see in the following picture
SurveyUI
Now, you have here a working code of a form generator based on an API or a Json file. This code is a starting point: I started with this example to create a proper component to generate very complex form for marketing research purposes. If you like to see my component SurveyUI in action, visit the website SurveyUI.
Also, I have created a library in .NET Standard 2.1
for evaluating expressions. You can install it from Nuget and its name is PSC.Evaluator. My next post will be about this new library, but you already can start to use it. PSC.Evaluator is a mathematical expressions evaluator library written in C# and allows to evaluate mathematical, boolean, string and datetime expressions.
With this library, I can evaluate expressions in the form for example to display or hide some options or questions in the form. If you have a look to the SurveyUI website, you find some examples.
List of components
In the SurveyUI component for Blazor, there are different kinds of components:
- Basic components
- Group of components
- Custom components and components for marketing research
- Group of elements
Basic components
- Textbox
- Checkbox
- Slider
- Radiobutton
- Dropdownlist
- Comment
- Boolean
- ImagePicker
- Upload file
- HTML
Group of components
- Matrix (Single choice)
- Matrix (Multiple choices)
- Matrix Dynamic rows
- Multiple Textbox
Custom components and components for marketing research
- NPS (Net Promoter Score)
- Likert Skill
- Multiselect
- Semantic differential
- Rating
- Ranking
Group of elements
- Panel
- Repeater
- Page
Let me know what you think about those libraries: if you have any questions about SurveyUI, add your comments on this forum. If you have questions about PSC.Evaluator, use this forum.