2

My Blazor application has two forms in different components. Both forms use he same view model. Though the model is the same, different fields are displayed in the components. E.g. the first component's form does not have the UnitPrice field, but the second does. I use a simple validation:

    [Required(ErrorMessage = "Unit Price is required.")]
    [Range(0.01, double.MaxValue, ErrorMessage = "Unit Price must be greater than 0")]
    public double UnitPrice { get; set; }

Unfortunately, when the first form is displayed and submitted, the missing field is validated, and the validation fails. Is there any way to do it without splitting the model or using custom validation?

4
  • 1
    Seems like a design smell, to be honest. Maybe two view models with an interface with the common fields, if it's truly fields with the exact same context. Commented Jun 3, 2021 at 13:44
  • @BenSampica could you please clarify your suggestion? Commented Jun 3, 2021 at 15:51
  • 1
    Encapsulate what varies. The form has common fields, which can be expressed through an interface. However, the context in which that form is used differs each time it is used, which holds true in your case as you have different fields. As time goes on and that form continues to evolve things will get ugly fast as you attempt to reconcile two different use cases inside of one class. Commented Jun 4, 2021 at 13:54
  • @Ben Sampica I see. Would you like to lay out your suggestion as a simple example, and make it an Answer for me to mark? Commented Jun 6, 2021 at 13:11

2 Answers 2

1

Example as requested:

public interface IForm
{
    int FormStatus { get; set; }
    // Your other fields that are always shared on this form...
}

public class Form1 : IForm
{
    public int FormStatus { get; set; }

    [Required(ErrorMessage = "Unit Price is required.")]
    [Range(0.01, double.MaxValue, ErrorMessage = "Unit Price must be greater than 0")]
    public decimal UnitPrice { get; set; }
}

public class Form2 : IForm
{
    public int FormStatus { get; set; }
    
    [Required]
    public string Name { get; set; } 
    // I made this up but it demonstrates the idea of encapsulating what differs.
}

Your shared Blazor Component would be something like.

// SharedFormFields.razor

<input type="text" @bind-Value="_form.FormStatus">

@code {
    [Parameter] private IForm Form { get; set; }
}

And then your consuming components/pages

@page "/Form1"

<EditContext Model=_form1>
   <SharedFormFields Form=_form1>
   <input type="number" @bind-Value="_form1.UnitPrice">
</EditContext

@code {
    private Form1 _form1 = new()
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you very much. I won't have any shared component, as Form 1 has all fields editable, and Form 2 has all those fields read-only, except 2 form-specific fields. Also, in my case, almost all the fields are shared, except the two mentioned above. Your approach should work the same, right? Also, wouldn't it be better to just derive Form 2 from Form 1, as fields of Form 1 are a subset of the fields of Form 2?
I think you're on the right path. However, I personally wouldn't go the inheritance route and instead stick with implementing interface as Form2 being a subclass of Form1 can introduce accidental gotchas between the two as they remain separate use cases. At the surface they share commonalities through their properties, but one use case involves submitting data and validating it with attributes and another is just a read-only display of data with no validation.
0

I used conditional validation by deriving my view model from IValidatableObject and implementing it:

public class MyViewModel : IValidatableObject
{
...
    public double UnitPrice { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var result = new List<ValidationResult>();

        if (StatusId >= (int)Models.Status.ReadyForReview) // my condition
        {
            if (UnitPrice == 0)
            {
                result.Add(new ValidationResult("Unit Price is required."));
            }
            else if (UnitPrice < 0)
            {
                result.Add(new ValidationResult("Unit Price must be greater than 0."));
            }
        }

        return result;
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.