Forum Discussion

KillGorack's avatar
KillGorack
Copper Contributor
Nov 09, 2024
Solved

Dynamic form generation from dictionary

ASP.NET Blazor app

brand spanking new to the stuff here. Trying a move from Apache PHP.

Trying to create a dynamic form from a dictionary generated from a separate class

I've banged my head against the wall for hours and can't get past this point.

This makes everything a text box.. and When its done this way I can see the data change from the submit method.

Any time I try to create a bool (checkbox) or number field I get conversion errors or worse.

That dictionary _registry.Fields.FieldsData looks like. (converted to JSON to use here)

{
  "ID": {
    "ID": "47",
    "fld_app": "5",
    "fld_human": "ID",
    "fld_column": "ID",
    "fld_enable": "True",
    "fld_type": "int",
    "fld_pdotype": "",
    "fld_length": "NULL",
    "fld_precision": "",
    "fld_pass": "",
    "fld_opt": "False",
    "fld_opt_table": "",
    "fld_opt_column": "",
    "fld_icon_set": "",
    "fld_regex": "",
    "fld_uom": "",
    "fld_placeholder": "",
    "fld_usr_ID": "False",
    "fld_link": "",
    "fld_index": "True",
    "fld_detail": "True",
    "fld_form": "True",
    "fld_order": "1",
    "fld_title": "False",
    "fld_required": "False",
    "fld_double": "False",
    "fld_encrypt": "False",
    "fld_time": "False",
    "fld_image": "False",
    "fld_unique": "False",
    "fld_json": "False"
  },
  "Column1": {
    "ID": "48",
    "fld_app": "5",
    "fld_human": "Column1",
    "fld_column": "Column1",
    "fld_enable": "True",
    "fld_type": "nvarchar",
    "fld_pdotype": "",
    "fld_length": "50",
    "fld_precision": "",
    "fld_pass": "",
    "fld_opt": "False",
    "fld_opt_table": "",
    "fld_opt_column": "",
    "fld_icon_set": "",
    "fld_regex": "",
    "fld_uom": "",
    "fld_placeholder": "",
    "fld_usr_ID": "False",
    "fld_link": "",
    "fld_index": "True",
    "fld_detail": "True",
    "fld_form": "True",
    "fld_order": "2",
    "fld_title": "False",
    "fld_required": "False",
    "fld_double": "False",
    "fld_encrypt": "False",
    "fld_time": "False",
    "fld_image": "False",
    "fld_unique": "False",
    "fld_json": "False"
  },
  "Column2": {
    "ID": "49",
    "fld_app": "5",
    "fld_human": "Column2",
    "fld_column": "Column2",
    "fld_enable": "True",
    "fld_type": "nvarchar",
    "fld_pdotype": "",
    "fld_length": "50",
    "fld_precision": "",
    "fld_pass": "",
    "fld_opt": "False",
    "fld_opt_table": "",
    "fld_opt_column": "",
    "fld_icon_set": "",
    "fld_regex": "",
    "fld_uom": "",
    "fld_placeholder": "",
    "fld_usr_ID": "False",
    "fld_link": "",
    "fld_index": "True",
    "fld_detail": "True",
    "fld_form": "True",
    "fld_order": "3",
    "fld_title": "False",
    "fld_required": "False",
    "fld_double": "False",
    "fld_encrypt": "False",
    "fld_time": "False",
    "fld_image": "False",
    "fld_unique": "False",
    "fld_json": "False"
  },
  "Column3": {
    "ID": "50",
    "fld_app": "5",
    "fld_human": "Column3",
    "fld_column": "Column3",
    "fld_enable": "True",
    "fld_type": "nvarchar",
    "fld_pdotype": "",
    "fld_length": "50",
    "fld_precision": "",
    "fld_pass": "",
    "fld_opt": "False",
    "fld_opt_table": "",
    "fld_opt_column": "",
    "fld_icon_set": "",
    "fld_regex": "",
    "fld_uom": "",
    "fld_placeholder": "",
    "fld_usr_ID": "False",
    "fld_link": "",
    "fld_index": "True",
    "fld_detail": "True",
    "fld_form": "True",
    "fld_order": "4",
    "fld_title": "False",
    "fld_required": "False",
    "fld_double": "False",
    "fld_encrypt": "False",
    "fld_time": "False",
    "fld_image": "False",
    "fld_unique": "False",
    "fld_json": "False"
  },
  "Column4": {
    "ID": "51",
    "fld_app": "5",
    "fld_human": "Column4",
    "fld_column": "Column4",
    "fld_enable": "True",
    "fld_type": "nvarchar",
    "fld_pdotype": "",
    "fld_length": "50",
    "fld_precision": "",
    "fld_pass": "",
    "fld_opt": "False",
    "fld_opt_table": "",
    "fld_opt_column": "",
    "fld_icon_set": "",
    "fld_regex": "",
    "fld_uom": "",
    "fld_placeholder": "",
    "fld_usr_ID": "False",
    "fld_link": "",
    "fld_index": "True",
    "fld_detail": "True",
    "fld_form": "True",
    "fld_order": "5",
    "fld_title": "False",
    "fld_required": "False",
    "fld_double": "False",
    "fld_encrypt": "False",
    "fld_time": "False",
    "fld_image": "False",
    "fld_unique": "False",
    "fld_json": "False"
  },
  "Column5": {
    "ID": "52",
    "fld_app": "5",
    "fld_human": "Column5",
    "fld_column": "Column5",
    "fld_enable": "True",
    "fld_type": "nvarchar",
    "fld_pdotype": "",
    "fld_length": "50",
    "fld_precision": "",
    "fld_pass": "",
    "fld_opt": "False",
    "fld_opt_table": "",
    "fld_opt_column": "",
    "fld_icon_set": "",
    "fld_regex": "",
    "fld_uom": "",
    "fld_placeholder": "",
    "fld_usr_ID": "False",
    "fld_link": "",
    "fld_index": "True",
    "fld_detail": "True",
    "fld_form": "True",
    "fld_order": "6",
    "fld_title": "False",
    "fld_required": "False",
    "fld_double": "False",
    "fld_encrypt": "False",
    "fld_time": "False",
    "fld_image": "False",
    "fld_unique": "False",
    "fld_json": "False"
  },
  "Column6": {
    "ID": "53",
    "fld_app": "5",
    "fld_human": "Column6",
    "fld_column": "Column6",
    "fld_enable": "True",
    "fld_type": "nvarchar",
    "fld_pdotype": "",
    "fld_length": "50",
    "fld_precision": "",
    "fld_pass": "",
    "fld_opt": "False",
    "fld_opt_table": "",
    "fld_opt_column": "",
    "fld_icon_set": "",
    "fld_regex": "",
    "fld_uom": "",
    "fld_placeholder": "",
    "fld_usr_ID": "False",
    "fld_link": "",
    "fld_index": "True",
    "fld_detail": "True",
    "fld_form": "True",
    "fld_order": "7",
    "fld_title": "False",
    "fld_required": "False",
    "fld_double": "False",
    "fld_encrypt": "False",
    "fld_time": "False",
    "fld_image": "False",
    "fld_unique": "False",
    "fld_json": "False"
  }
}


The .razor component

PAGE "/FieldsAdmin"
@inject ILogger<FieldsAdmin> Logger
@inject portalx.Classes.Main.DBO Database
@using System.Data
@using System.Collections.Generic
@inject portalx.Classes.Main._reg _registry
@using System.Text.Json

<form method="post" @onsubmit="Submit" @formname="FieldsAdmin">
    <AntiforgeryToken />

    @if (_registry.Fields.FieldsData != null)
    {
        @foreach (var row in _registry.Fields.FieldsData)
        {
            <div class="form-row">
                @foreach (var field in row.Value)
                {
                    <div class="form-group col-md-6">
                        <label>@field.Key</label>
                        @{
                            var key = $"{row.Key}-{field.Key}";
                            if (!Model!.DynamicFields.ContainsKey(key))
                            {
                                Model!.DynamicFields[key] = field.Value?.ToString() ?? string.Empty;
                            }
                        }

                        <InputText @bind-Value="Model!.DynamicFields[key]" />

                    </div>
                }
            </div>
        }
    }
    <div>
        <button type="submit">Submit</button>
    </div>
</form>

<div>
    <h3>Fields Data (JSON)</h3>
    <pre>@jsonString</pre>
</div>

@code {
    [SupplyParameterFromForm]
    private ModelFieldsAdmin? Model { get; set; }
    private Dictionary<string, string> dataDict { get; set; }
    private string jsonString { get; set; }

    protected override void OnInitialized()
    {
        Model ??= new();
        dataDict = new Dictionary<string, string>
        {
            { "ID", "hidden" },
            { "fld_app", "skip me" },
            { "fld_human", "text" },
            { "fld_column", "skip me" },
            { "fld_enable", "bool" },
            { "fld_type", "skip me" },
            { "fld_pdotype", "skip me" },
            { "fld_length", "skip me" },
            { "fld_precision", "skip me" },
            { "fld_pass", "bool" },
            { "fld_opt", "bool" },
            { "fld_opt_table", "text" },
            { "fld_opt_column", "text" },
            { "fld_icon_set", "text" },
            { "fld_regex", "text" },
            { "fld_uom", "text" },
            { "fld_placeholder", "text" },
            { "fld_usr_ID", "bool" },
            { "fld_link", "bool" },
            { "fld_index", "bool" },
            { "fld_detail", "bool" },
            { "fld_form", "bool" },
            { "fld_order", "number" },
            { "fld_title", "bool" },
            { "fld_required", "bool" },
            { "fld_double", "bool" },
            { "fld_encrypt", "bool" },
            { "fld_time", "bool" },
            { "fld_image", "bool" },
            { "fld_unique", "bool" },
            { "fld_json", "bool" }
        };

        jsonString = JsonSerializer.Serialize(_registry.Fields.FieldsData, new JsonSerializerOptions { WriteIndented = true });
    }

    private void Submit()
    {
        foreach (var kvp in Model!.DynamicFields)
        {
            Logger.LogInformation("Field Key: {Key}, Value: {Value}", kvp.Key, kvp.Value);
        }
    }

    public class ModelFieldsAdmin
    {
        public string? Id { get; set; }
        public Dictionary<string, string> DynamicFields { get; set; } = new Dictionary<string, string>();
    }
}



  • Took a little time off. This version works. still need to add the table update, but this is what I was after.

    PAGE "/FieldsAdmin"
    @inject ILogger<FieldsAdmin> Logger
    @inject portalx.Classes.Main.DBO Database
    @using System.Data
    @using System.Collections.Generic
    @inject portalx.Classes.Main._reg _registry
    @using System.Text.Json
    
    <form method="post" @onsubmit="Submit" @formname="FieldsAdmin">
        <AntiforgeryToken />
    
        @if (_registry.Fields.FieldsData != null)
        {
            @foreach (var row in _registry.Fields.FieldsData)
            {
                <div class="form-row">
                    @foreach (var field in row.Value)
                    {
                        <div class="form-group col-md-6">
                            <label>@field.Key</label>
                            @{
                                var key = $"{row.Key}-{field.Key}";
                                if (!Model!.DynamicFields.ContainsKey(key))
                                {
                                    Model!.DynamicFields[key] = field.Value?.ToString() ?? string.Empty;
                                }
                                var fieldType = dataDict.ContainsKey(field.Key) ? dataDict[field.Key] : "text";
                            }
    
                            @switch (fieldType)
                            {
                                case "number":
                                    if (!Model!.NumericFields.ContainsKey(key))
                                    {
                                        Model!.NumericFields[key] = int.TryParse(Model!.DynamicFields[key], out var value) ? value : 0;
                                    }
                                    <InputNumber @bind-Value="Model!.NumericFields[key]" />
                                    break;
                                case "bool":
                                    if (!Model!.BooleanFields.ContainsKey(key))
                                    {
                                        Model!.BooleanFields[key] = Model!.DynamicFields[key] == "True" || Model!.DynamicFields[key] == "1";
                                    }
                                    <InputCheckbox @bind-Value="Model!.BooleanFields[key]" />
                                    break;
                                case "text":
                                default:
                                    <InputText @bind-Value="Model!.DynamicFields[key]" />
                                    break;
                            }
                        </div>
                    }
                </div>
            }
        }
        <div>
            <button type="submit">Submit</button>
        </div>
    </form>
    
    <div>
        <h3>Fields Data (JSON)</h3>
        <pre>@jsonString</pre>
    </div>
    
    @code {
        [SupplyParameterFromForm]
        private ModelFieldsAdmin? Model { get; set; }
        private Dictionary<string, string> dataDict { get; set; }
        private string jsonString { get; set; }
    
        protected override void OnInitialized()
        {
            Model ??= new();
            dataDict = new Dictionary<string, string>
            {
                { "ID", "hidden" },
                { "fld_app", "skip me" },
                { "fld_human", "text" },
                { "fld_column", "skip me" },
                { "fld_enable", "bool" },
                { "fld_type", "skip me" },
                { "fld_pdotype", "skip me" },
                { "fld_length", "skip me" },
                { "fld_precision", "skip me" },
                { "fld_pass", "bool" },
                { "fld_opt", "bool" },
                { "fld_opt_table", "text" },
                { "fld_opt_column", "text" },
                { "fld_icon_set", "text" },
                { "fld_regex", "text" },
                { "fld_uom", "text" },
                { "fld_placeholder", "text" },
                { "fld_usr_ID", "bool" },
                { "fld_link", "bool" },
                { "fld_index", "bool" },
                { "fld_detail", "bool" },
                { "fld_form", "bool" },
                { "fld_order", "number" },
                { "fld_title", "bool" },
                { "fld_required", "bool" },
                { "fld_double", "bool" },
                { "fld_encrypt", "bool" },
                { "fld_time", "bool" },
                { "fld_image", "bool" },
                { "fld_unique", "bool" },
                { "fld_json", "bool" }
            };
    
            jsonString = JsonSerializer.Serialize(_registry.Fields.FieldsData, new JsonSerializerOptions { WriteIndented = true });
        }
    
        private void Submit()
        {
            foreach (var kvp in Model!.DynamicFields)
            {
                Logger.LogInformation("Field Key: {Key}, Value: {Value}", kvp.Key, kvp.Value);
            }
    
            foreach (var kvp in Model!.NumericFields)
            {
                Logger.LogInformation("Field Key: {Key}, Value: {Value}", kvp.Key, kvp.Value);
            }
    
            foreach (var kvp in Model!.BooleanFields)
            {
                Logger.LogInformation("Field Key: {Key}, Value: {Value}", kvp.Key, kvp.Value);
            }
        }
    
        public class ModelFieldsAdmin
        {
            public string? Id { get; set; }
            public Dictionary<string, string> DynamicFields { get; set; } = new Dictionary<string, string>();
            public Dictionary<string, int> NumericFields { get; set; } = new Dictionary<string, int>();
            public Dictionary<string, bool> BooleanFields { get; set; } = new Dictionary<string, bool>();
        }
    }

     

  • KillGorack's avatar
    KillGorack
    Copper Contributor

    Took a little time off. This version works. still need to add the table update, but this is what I was after.

    PAGE "/FieldsAdmin"
    @inject ILogger<FieldsAdmin> Logger
    @inject portalx.Classes.Main.DBO Database
    @using System.Data
    @using System.Collections.Generic
    @inject portalx.Classes.Main._reg _registry
    @using System.Text.Json
    
    <form method="post" @onsubmit="Submit" @formname="FieldsAdmin">
        <AntiforgeryToken />
    
        @if (_registry.Fields.FieldsData != null)
        {
            @foreach (var row in _registry.Fields.FieldsData)
            {
                <div class="form-row">
                    @foreach (var field in row.Value)
                    {
                        <div class="form-group col-md-6">
                            <label>@field.Key</label>
                            @{
                                var key = $"{row.Key}-{field.Key}";
                                if (!Model!.DynamicFields.ContainsKey(key))
                                {
                                    Model!.DynamicFields[key] = field.Value?.ToString() ?? string.Empty;
                                }
                                var fieldType = dataDict.ContainsKey(field.Key) ? dataDict[field.Key] : "text";
                            }
    
                            @switch (fieldType)
                            {
                                case "number":
                                    if (!Model!.NumericFields.ContainsKey(key))
                                    {
                                        Model!.NumericFields[key] = int.TryParse(Model!.DynamicFields[key], out var value) ? value : 0;
                                    }
                                    <InputNumber @bind-Value="Model!.NumericFields[key]" />
                                    break;
                                case "bool":
                                    if (!Model!.BooleanFields.ContainsKey(key))
                                    {
                                        Model!.BooleanFields[key] = Model!.DynamicFields[key] == "True" || Model!.DynamicFields[key] == "1";
                                    }
                                    <InputCheckbox @bind-Value="Model!.BooleanFields[key]" />
                                    break;
                                case "text":
                                default:
                                    <InputText @bind-Value="Model!.DynamicFields[key]" />
                                    break;
                            }
                        </div>
                    }
                </div>
            }
        }
        <div>
            <button type="submit">Submit</button>
        </div>
    </form>
    
    <div>
        <h3>Fields Data (JSON)</h3>
        <pre>@jsonString</pre>
    </div>
    
    @code {
        [SupplyParameterFromForm]
        private ModelFieldsAdmin? Model { get; set; }
        private Dictionary<string, string> dataDict { get; set; }
        private string jsonString { get; set; }
    
        protected override void OnInitialized()
        {
            Model ??= new();
            dataDict = new Dictionary<string, string>
            {
                { "ID", "hidden" },
                { "fld_app", "skip me" },
                { "fld_human", "text" },
                { "fld_column", "skip me" },
                { "fld_enable", "bool" },
                { "fld_type", "skip me" },
                { "fld_pdotype", "skip me" },
                { "fld_length", "skip me" },
                { "fld_precision", "skip me" },
                { "fld_pass", "bool" },
                { "fld_opt", "bool" },
                { "fld_opt_table", "text" },
                { "fld_opt_column", "text" },
                { "fld_icon_set", "text" },
                { "fld_regex", "text" },
                { "fld_uom", "text" },
                { "fld_placeholder", "text" },
                { "fld_usr_ID", "bool" },
                { "fld_link", "bool" },
                { "fld_index", "bool" },
                { "fld_detail", "bool" },
                { "fld_form", "bool" },
                { "fld_order", "number" },
                { "fld_title", "bool" },
                { "fld_required", "bool" },
                { "fld_double", "bool" },
                { "fld_encrypt", "bool" },
                { "fld_time", "bool" },
                { "fld_image", "bool" },
                { "fld_unique", "bool" },
                { "fld_json", "bool" }
            };
    
            jsonString = JsonSerializer.Serialize(_registry.Fields.FieldsData, new JsonSerializerOptions { WriteIndented = true });
        }
    
        private void Submit()
        {
            foreach (var kvp in Model!.DynamicFields)
            {
                Logger.LogInformation("Field Key: {Key}, Value: {Value}", kvp.Key, kvp.Value);
            }
    
            foreach (var kvp in Model!.NumericFields)
            {
                Logger.LogInformation("Field Key: {Key}, Value: {Value}", kvp.Key, kvp.Value);
            }
    
            foreach (var kvp in Model!.BooleanFields)
            {
                Logger.LogInformation("Field Key: {Key}, Value: {Value}", kvp.Key, kvp.Value);
            }
        }
    
        public class ModelFieldsAdmin
        {
            public string? Id { get; set; }
            public Dictionary<string, string> DynamicFields { get; set; } = new Dictionary<string, string>();
            public Dictionary<string, int> NumericFields { get; set; } = new Dictionary<string, int>();
            public Dictionary<string, bool> BooleanFields { get; set; } = new Dictionary<string, bool>();
        }
    }

     

Resources