'How to reset custom validation errors when using editform in blazor razor page
I have an editform using an editcontext:
<EditForm OnValidSubmit="HandleValidSubmit" EditContext="_editContext" Context="auth">
<DataAnnotationsValidator />
<input type="time" @bind-value="_foodTruck.EndDelivery" @onkeydown="@(q=>ResetValidation("EndDelivery"))" >
<ValidationMessage For="() => _foodTruck.EndDelivery" />
<input type="time" @bind-value="_foodTruck.StartDelivery" @onkeydown="@(q=>ResetValidation("StartDelivery"))" >
<ValidationMessage For="() => _foodTruck.StartDelivery" />
<input class="btn btn-default" type="submit" value="save" />
</EditForm>
I do some custom validations in HandleValidSubmit:
EditContext _editContext = new EditContext(_foodTruck);
private async void HandleValidSubmit()
{
var messageStore = new ValidationMessageStore(_editContext);
if (_foodTruck.StartDelivery >= _foodTruck.EndDelivery)
{
messageStore.Add(_editContext.Field("EndDelivery"), "Bad time entered");
_editContext.NotifyValidationStateChanged();
}
if (!_editContext.Validate()) return;
}
What now happens is that my custom error ("bad time entered") is displayed at the right position. The only issue is: That error does not disappear when I change the value. So HandleValidSubmit is never called again if I click onto the submit button.
I also tried emptying the validationerrors when modifying the fields:
protected void ResetValidation(string field)
{
var messageStore = new ValidationMessageStore(_editContext);
messageStore.Clear(_editContext.Field(field));
messageStore.Clear();
_editContext.NotifyValidationStateChanged();
}
This is called by onkeydown
. But that doesn't seem to have an effect, either. The Errormessage does not disappear and so HandleValidSubmit
isn't called either.
Solution 1:[1]
I solved this by creating a new EditContext on Validation-reset. So I simply added the following line to the ResetValidation-Method:
_editContext = new EditContext(_foodTruck);
But to be honest: That does not feel right. So I will leave this open for better answers to come (hopefully).
Solution 2:[2]
I had the same issue as the original poster so I decided to poke around in the source code of the EditContext (thank you source.dot.net!). As a result, I've come up with a work-around that should suffice until the Blazor team resolves the issue properly in a future release.
/// <summary>
/// Contains extension methods for working with the <see cref="EditForm"/> class.
/// </summary>
public static class EditFormExtensions
{
/// <summary>
/// Clears all validation messages from the <see cref="EditContext"/> of the given <see cref="EditForm"/>.
/// </summary>
/// <param name="editForm">The <see cref="EditForm"/> to use.</param>
/// <param name="revalidate">
/// Specifies whether the <see cref="EditContext"/> of the given <see cref="EditForm"/> should revalidate after all validation messages have been cleared.
/// </param>
/// <param name="markAsUnmodified">
/// Specifies whether the <see cref="EditContext"/> of the given <see cref="EditForm"/> should be marked as unmodified.
/// This will affect the assignment of css classes to a form's input controls in Blazor.
/// </param>
/// <remarks>
/// This extension method should be on EditContext, but EditForm is being used until the fix for issue
/// <see href="https://github.com/dotnet/aspnetcore/issues/12238"/> is officially released.
/// </remarks>
public static void ClearValidationMessages(this EditForm editForm, bool revalidate = false, bool markAsUnmodified = false)
{
var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
object GetInstanceField(Type type, object instance, string fieldName)
{
var fieldInfo = type.GetField(fieldName, bindingFlags);
return fieldInfo.GetValue(instance);
}
var editContext = editForm.EditContext == null
? GetInstanceField(typeof(EditForm), editForm, "_fixedEditContext") as EditContext
: editForm.EditContext;
var fieldStates = GetInstanceField(typeof(EditContext), editContext, "_fieldStates");
var clearMethodInfo = typeof(HashSet<ValidationMessageStore>).GetMethod("Clear", bindingFlags);
foreach (DictionaryEntry kv in (IDictionary)fieldStates)
{
var messageStores = GetInstanceField(kv.Value.GetType(), kv.Value, "_validationMessageStores");
clearMethodInfo.Invoke(messageStores, null);
}
if (markAsUnmodified)
editContext.MarkAsUnmodified();
if (revalidate)
editContext.Validate();
}
}
Solution 3:[3]
Add this.StateHasChanged() at the end of the event action so that it can render the ui elements again and remove the validation message.
EditContext _editContext = new EditContext(_foodTruck);
private async void HandleValidSubmit()
{
var messageStore = new ValidationMessageStore(_editContext);
if (_foodTruck.StartDelivery >= _foodTruck.EndDelivery)
{
messageStore.Add(_editContext.Field("EndDelivery"), "Bad time entered");
_editContext.NotifyValidationStateChanged();
this.StateHasChanged(); //this line
}
if (!_editContext.Validate()) return;
}
for the other one
protected void ResetValidation(string field)
{
var messageStore = new ValidationMessageStore(_editContext);
messageStore.Clear(_editContext.Field(field));
messageStore.Clear();
_editContext.NotifyValidationStateChanged();
this.StateHasChanged(); //this line
}
kindly let me know if it works
Solution 4:[4]
I had same problem. I couldn't find straightforward solution. Workaround similar to below worked for me.
Modify EditForm as follows -
<EditForm EditContext="_editContext" OnSubmit="HandleSubmit">
@Code Block
EditContext _editContext;
ValidationMessageStore msgStore;
FoodTruck _foodTruck= new FoodTruck();
protected override void OnInitialized()
{
_editContext = new EditContext(_foodTruck);
msgStore = new ValidationMessageStore(_editContext);
}
void HandleSubmit()
{
msgStore.Clear();
if(_editContext.Validate()) // <-- Model Validation
{
if (_foodTruck.StartDelivery >= _foodTruck.EndDelivery) //<--Custom validation
{
msgStore = new ValidationMessageStore(_editContext);
msgStore.Add(_editContext.Field("EndDelivery"), "Bad time entered");
}
}
}
Solution 5:[5]
Had the same issue, solved it in a not-too-hacky way using EditContext.Validate()
:
I have already implemented a method called EditContext_OnFieldChanged(object sender, FieldChangedEventArgs e)
which gets called as soon that a parameter of the model used by the EditForm is used. It´s implemented like this:
protected override void OnInitialized()
{
EditContext = new EditContext(ModelExample);
EditContext.OnFieldChanged += EditContext_OnFieldChanged;
}
Here´s the method:
private void EditContext_OnFieldChanged(object sender, FieldChangedEventArgs e)
{
EditContext.Validate();
// ...
// other stuff you want to be done when the model changes
}
EditContext.Validate()
seems to update all validation messages, even the custom ones.
Solution 6:[6]
The solution for this problem is to call a new EditContext on Validation-reset. The following code will work in the ResetValidation Method:
_editContext = new EditContext(_foodTruck); //Reseting the Context
_editContext.AddDataAnnotationsValidation(); //Enabling subsequent validation calls to work
You can find more details on Custom Data Annotation Validators from the below link, How to create Custom Data Annotation Validators
Solution 7:[7]
This worked for me. On each event OnFieldChange, you can clear the validation message store.
protected override void OnInitialized()
{
_editContext = new EditContext(genre);
_msgStore = new ValidationMessageStore(_editContext);
//_editContext.OnValidationRequested += (s, e) => _msgStore.Clear();
_editContext.OnFieldChanged += (s, e) => _msgStore.Clear(e.FieldIdentifier);
}
Solution 8:[8]
As this question is still appearing in searches and people are referring to it, this answer explains why the problem exists and shows how to resolve it.
Let's look at the various answers and dispel some urban myths and voodoo:
Resetting the
EditContext
is not the answer, just a voodoo fix. It breaks more than it fixes:EditContext
should never be reset unless you really know what your doing.Calling
StateHasChanged
is normally a desperation measure to force the UI to update when basic logic design is flawed. If you have to code inStateHasChanged
then you need to seriously ask yourself: Why?The other answers are hacks. They will work in certain circumstances, but no guarantees in your design.
The root cause of the problem is a misunderstanding of what a ValidationMessageStore
is and how to use and manage it.
ValidationMessageStore
is a little more complex that first appearances. It isn't a store that holds all the validation messages logged from various sources: _messageStore = new ValidationMessageStore(_editContext);
should be a clue, specifically new
. You should get your instance when you instantiate the component and then add messages to and clear messages from that instance. Creating one every time you call a method simply doesn't work. You are just creating a new empty ValidationMessageStore
.
Here's a working version of the code in the question:
@page "/"
<PageTitle>Index</PageTitle>
@if (loaded)
{
<EditForm OnValidSubmit="HandleValidSubmit" EditContext="_editContext" Context="auth">
<DataAnnotationsValidator />
<div class="p-2">
End Delivery
<input type="time" @bind-value="_foodTruck.EndDelivery" @onkeydown="@(()=>ResetValidation("EndDelivery"))">
<ValidationMessage For="() => _foodTruck.EndDelivery" />
</div>
<div class="p-2">
Start Delivery
<input type="time" @bind-value="_foodTruck.StartDelivery" @onkeydown="@(()=>ResetValidation("StartDelivery"))">
<ValidationMessage For="() => _foodTruck.StartDelivery" />
</div>
<div class="p-2 text-end">
<input class="btn btn-primary" type="submit" value="save" />
</div>
<div class="p-2 text-end">
Counter: @counter
</div>
</EditForm>
}
@code {
private FoodTruck _foodTruck = new FoodTruck();
private EditContext? _editContext;
private ValidationMessageStore? _messageStore;
private ValidationMessageStore messageStore => _messageStore!;
private int counter;
private bool loaded;
protected override async Task OnInitializedAsync()
{
// emulate gwtting some async data
await Task.Delay(100);
FoodTruck _foodTruck = new FoodTruck();
// assign the mdel data to the Edit Context
_editContext = new EditContext(_foodTruck);
// Get the ValidationMessageStore
_messageStore = new ValidationMessageStore(_editContext);
loaded = true;
}
private void HandleValidSubmit()
{
if (_editContext is not null)
{
// create a FieldIdentifier for EndDelivery
var fi = new FieldIdentifier(_foodTruck, "EndDelivery");
// Clear the specific entry from the message store using the FieldIdentifier
messageStore.Clear(fi);
if (_foodTruck.StartDelivery >= _foodTruck.EndDelivery)
{
// Add a validation message and raise the validation state change event
messageStore.Add(fi, "Bad time entered");
_editContext.NotifyValidationStateChanged();
}
}
}
protected void ResetValidation(string field)
{
counter++;
if (_editContext is not null)
{
// clear the validation message and raise the validation state change event
messageStore.Clear(new FieldIdentifier(_foodTruck, field));
_editContext.NotifyValidationStateChanged();
}
}
public class FoodTruck
{
public TimeOnly EndDelivery { get; set; }
public TimeOnly StartDelivery { get; set; }
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Ole Albers |
Solution 2 | |
Solution 3 | |
Solution 4 | Meer |
Solution 5 | devbf |
Solution 6 | getjith |
Solution 7 | ouflak |
Solution 8 | MrC aka Shaun Curtis |