dotnetstep
dotnetstep

Reputation: 17485

Dynamic Property and Child Model Not Binding

I want to build dynamic form using Blazor.

Here is my sample component.

    @page "/customform"

    @using System.Dynamic
    @using System.Text.Json
 
    @inject IJSRuntime JSRuntime;

    <div class="card m-3">
    <h4 class="card-header">Blazor WebAssembly Form Validation Example</h4>
    <div class="card-body">
        <EditForm EditContext="@editContext"
                  OnValidSubmit="HandleValidSubmit">
            <DataAnnotationsValidator></DataAnnotationsValidator>
           
            @foreach (var field in Model.Fields)
        {

            <div class="form-group">
                <label>@field.Name</label>
                <input @bind-value="field.Value" class="form-control" />
                <ValidationMessage For="(()=> field.Value)" />
                <ValidationMessage For="(()=> field.Name)" />
                <ValidationMessage For="(()=> field)" />

            </div>

        }
            <div class="form-group">
                <label>Address</label>
                <input @bind-value="Model.Address" class="form-control" />
                <ValidationMessage For="()=> Model.Address" />

            </div>
            <div class="form-group">
                <label>Child</label>
                <input @bind-value="Model.ChildModel.ChildName" class="form-control" />
                <ValidationMessage For="()=> Model.ChildModel.ChildName" />

            </div>
            <div class="text-left">
                <button class="btn btn-primary" type="submit">Submit</button>
            </div>

        </EditForm>
    </div>
         </div>

      @code{

    private SampleModel Model = new SampleModel();
    private EditContext editContext;
    private ValidationMessageStore _messageStore;

    protected override void OnInitialized()
    {

        editContext = new EditContext(Model);
        editContext.OnValidationRequested += ValidationRequested;
        _messageStore = new ValidationMessageStore(editContext);      
    }

    private void HandleValidSubmit(EditContext context)
    {
        var modelJson = JsonSerializer.Serialize(context.Model, new JsonSerializerOptions { WriteIndented = true });
        JSRuntime.InvokeVoidAsync("alert", $"SUCCESS!! :-)\n\n{modelJson}");
       
    }

    async void ValidationRequested(object sender, ValidationRequestedEventArgs args)
    {
        _messageStore.Add(editContext.Field("FirstName"), "Test");
        _messageStore.Add(editContext.Field("Address"), "Invalid Address");
        _messageStore.Add(editContext.Field("ChildModel.ChildName"), "Invalid Child Name");
        editContext.NotifyValidationStateChanged();
    }

    

    public class SampleModel
    {

        public string Address { get; set; }

        public ChildModel ChildModel { get; set; }
        public List<Field> Fields { get; set; }
        public SampleModel()
        {
            this.ChildModel = new ChildModel();
            this.Fields = new List<Field>();
            this.Fields.Add(new Field()
            {
                Name = "FirstName",
                Value = "",
                ControlType = ControlType.Input
            });
            this.Fields.Add(new Field()
            {
                Name = "LastName",
                Value = "",
                ControlType = ControlType.Input
            });
        }
    }

    public class ChildModel
    {
        public string ChildName { get; set; }
    }

    public enum ControlType
    {
        Input
    }

    public class Field
    {
        public string Value { get; set; }
        public string Name { get; set; }
        public string DisplayName { get; set; }
        public ControlType ControlType { get; set; }


    }

    
}

Currently I am facing too many issues.

  1. If I use For lookup instead of For each it is not working
  2. ChildModel seems to be bind but its validation is not working
  3. Dynamically generated based on Fields collection control does not display validation.
  4. Only address in SimpleModel display validation.

Is there any suggestion or help around this ?

Upvotes: 0

Views: 631

Answers (1)

MrC aka Shaun Curtis
MrC aka Shaun Curtis

Reputation: 30046

Your profile suggests you know what you're doing, so I'll keep this succinct.

Your for loop needs to look something like this. Set a local "index" variable within the loop to link the controls to. If you don't they point to the last value of i - in this case 2 which is out of range! The razor code is converted to a cs file by the razor builder. You can see the c# file generated in the obj folder structure - obj\Debug\net5.0\Razor\Pages. Note, the linkage of the Validation Message

@for(var i = 0; i < Model.Fields.Count; i++)
   {
    var index = i;
    <div class="form-group">
      <label>@Model.Fields[index].Name</label>
      <input @bind-value="Model.Fields[index].Value" class="form-control" />
      <ValidationMessage For="(()=> Model.Fields[index].Value)" />
    </div>
}

Now the message validation store. Here's my rewritten ValidationRequested. Note I'm creating a FieldIdentifier which is the correct way to do it. "Address" works because it's a property of EditContext.Model. If a ValidationMessage doesn't display the message you anticipate, then it's either not being generated, or it's FieldIdentifier doesn't match the field the ValidationMessage is For. This should get you going in whatever project you're involved in - if not add a comment for clarification :-).

    void ValidationRequested(object sender, ValidationRequestedEventArgs args)
    {
        _messageStore.Clear();
        _messageStore.Add(new FieldIdentifier(Model.Fields[0], "Value"), "FirstName Validation Message");
        _messageStore.Add(new FieldIdentifier(Model.Fields[1], "Value"), "Surname Validation Message");
        _messageStore.Add(editContext.Field("FirstName"), "Test");
        _messageStore.Add(editContext.Field("Address"), "Invalid Address");
        _messageStore.Add(editContext.Field("ChildModel.ChildName"), "Invalid Child Name");
        editContext.NotifyValidationStateChanged();
    }

If you interested in Validation and want something more that the basic out-of-the-box validation, there's a couple of my articles that might give you info Validation Form State Control or there's a version of Fluent Validation by Chris Sainty out there if you search.

Upvotes: 2

Related Questions