Reputation: 33
I have a EditForm in a Blazor Server application and i want to check if an InputText value is in a list. How can i pass the list to compare from my UI to the Validator class for comparison?
I have tried comparing the @bind-Value in line and encapsulating the validation message but it skips over the validation message when the encapsulating function tests true.
<EditForm Model="@resourceToBeCreated">
<FluentValidationValidator ValidatorType=typeof(ResourceValidator)/>
@if (resourcesSortedCollection.FirstOrDefault(x => x.Name == resourceToBeCreated.Name) != null)
{
<CustomValidationMessage For="() => resourceToBeCreated.Name" />
}
<InputTextOnInput @bind-Value="@resourceToBeCreated.Name" class="form-control" placeholder="Name..." />
</EditForm>
I can obviously do this or something similar in the @code section but i dont get the validation popup on inupt.
So the question is, how can i pass this list to the Validator class for comparison?
EDIT 1: InputTextOnInput component:
@inherits InputText
<input @attributes="AdditionalAttributes"
class="@CssClass"
value="@CurrentValue"
@oninput="EventCallback.Factory.CreateBinder<string>(this, __value => CurrentValueAsString = __value, CurrentValueAsString)" />
EDIT 2: A potential workaround while still utilising fluent validation.
1, add new property to the model :
public List<string> ResourceNames { get; set; }
2, when a new resource is created in the browser update that property in the model
resourceToBeCreated.ResourceNames = resourcesSortedCollection.Select(x => x.Name).ToList();
3, write rule in fluent validation
RuleFor(x => x.Name).Null().When(x => x.ResourceNames.Contains(x.Name)).WithMessage("Duplicate resource name");
Not sure if this is the best way to do it (code smell?) but it works for now. Either way i have to create a list of strings which contains all the resource names. If there is a more direct way to pass the resourcesSortedCollection object to the validator id like to understand.
Upvotes: 1
Views: 3404
Reputation: 33
I thought id throw an answer up as i stumbled upon one while doing something else and it may help another.
You can pass the values to validate against into the validator when it is instantiated. In this case pass in a list of BaseResourceMoldes into the ResouceValidator via a constructor. As the list wont change between instantiation and validation this is suitable.
You then use the Must extension which will pass the parameter you are validating into a called function to test for bool. In this case .Must(IsUnique) passes x.Name into IsUnique(string arg1) and returns a bool.
Syntax might be slightly different to examples above as the code base will have changed between then and now but the concept is the same.
The class with the form to be validated:
[Parameter] public List<BaseResourceModel> Resources { get; set; }
ResourceValidator resourceValidator;
protected override void OnInitialized()
{
resourceValidator = new ResourceValidator(Resources);
}
And then the ResourceValidator Class:
private List<BaseResourceModel> _resources;
private void ResourceValidator(List<BaseResourceModel> resources)
{
_resources = resources;
RuleFor(x => x.Name).NotEmpty().Must(IsUnique).WithMessage("Resource name must be unique");
}
private bool IsUnique(string arg1)
{
bool isUnique = true;
foreach (var resource in _resources)
{
if (resource.Name == arg1)
{
isUnique = false;
}
}
return isUnique;
}
I'm assuming you could also do this asynchronously if the list had the potential to change. Fluent validation has async methods.
Upvotes: 1
Reputation: 30185
First an inherited InputText
control. This overrides TryParseValueFromString
and does the validation there.
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using System.Diagnostics.CodeAnalysis;
namespace BlazorApp1.Pages;
public class InputTextValidated : InputText
{
[Parameter, EditorRequired] public IEnumerable<string>? CheckList { get; set; }
{ get; set; }
protected override bool TryParseValueFromString(string? value, out string? result, [NotNullWhen(false)] out string? validationErrorMessage)
{
result = null;
validationErrorMessage = null;
var isValid = this.CheckList is not null
&& this.CheckList.Any(item => item.Equals(value, StringComparison.InvariantCultureIgnoreCase));
if (isValid)
result = value;
else
validationErrorMessage = "You must enter a value in the validation list";
return isValid;
}
}
And a test page:
@page "/"
<PageTitle>Index</PageTitle>
<EditForm Model=this.model>
<DataAnnotationsValidator />
<InputTextValidated class="form-control" CheckList=Countries @bind-Value="@this.model.Country" />
<ValidationSummary />
</EditForm>
<div>
Country : @model.Country
</div>
@code {
private DataModel model = new DataModel();
private List<string> Countries = new List<string> { "UK", "Spain" };
public class DataModel
{
public string? Country;
}
}
As an alternative you could use/build an Input List control.
Validation data is held in ValidationMessageStore
's associated with an EditContext
. A ValidationMessageStore
is a composite collection of key/value pairs:
FieldIdentifier
[the model as an object
and the field name as a string
]string
.Each validation provider has it's own message store and can clear and add messages to it. It only has write/delete access to it's own message store. Providers get the EditContext
cascaded by the EditForm
, create a message store associated with the EditContext
and logs messages to and clears messages from that store. FluentValidationValidator
, DataAnnotationsValidator
, any InputBase
control or classes you write that interact with the EditContext
are providers with message stores associated with the EditContext
.
ValidationSummary
and ValidationMessage
are consumers. They interact with the message stores associated with a EditContext via the cascaded EditContext
. All the messages are available as read only. ValidationMessage
constructs a FieldIdentifier
from the For
expression to filter the messages.
Upvotes: 1