Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added IServiceProvider to DataAnnotationsValidator #39445

Merged
4 changes: 3 additions & 1 deletion src/Components/Forms/src/DataAnnotationsValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public class DataAnnotationsValidator : ComponentBase, IDisposable

[CascadingParameter] EditContext? CurrentEditContext { get; set; }

[Inject] private IServiceProvider? ServiceProvider { get; set; }
pranavkm marked this conversation as resolved.
Show resolved Hide resolved

/// <inheritdoc />
protected override void OnInitialized()
{
Expand All @@ -23,7 +25,7 @@ protected override void OnInitialized()
$"inside an EditForm.");
}

_subscriptions = CurrentEditContext.EnableDataAnnotationsValidation();
_subscriptions = CurrentEditContext.EnableDataAnnotationsValidation(ServiceProvider);
_originalEditContext = CurrentEditContext;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,19 @@ public static EditContext AddDataAnnotationsValidation(this EditContext editCont
/// <returns>A disposable object whose disposal will remove DataAnnotations validation support from the <see cref="EditContext"/>.</returns>
public static IDisposable EnableDataAnnotationsValidation(this EditContext editContext)
pranavkm marked this conversation as resolved.
Show resolved Hide resolved
{
return new DataAnnotationsEventSubscriptions(editContext);
return new DataAnnotationsEventSubscriptions(editContext, null);
pranavkm marked this conversation as resolved.
Show resolved Hide resolved
}
/// <summary>
/// Enables DataAnnotations validation support for the <see cref="EditContext"/>.
/// </summary>
/// <param name="editContext">The <see cref="EditContext"/>.</param>
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> to be used in the <see cref="ValidationContext"/>.</param>
/// <returns>A disposable object whose disposal will remove DataAnnotations validation support from the <see cref="EditContext"/>.</returns>
public static IDisposable EnableDataAnnotationsValidation(this EditContext editContext, IServiceProvider? serviceProvider)
pranavkm marked this conversation as resolved.
Show resolved Hide resolved
{
return new DataAnnotationsEventSubscriptions(editContext, serviceProvider);
MariovanZeist marked this conversation as resolved.
Show resolved Hide resolved
}


private static event Action? OnClearCache;

Expand All @@ -50,11 +61,13 @@ private sealed class DataAnnotationsEventSubscriptions : IDisposable
private static readonly ConcurrentDictionary<(Type ModelType, string FieldName), PropertyInfo?> _propertyInfoCache = new();

private readonly EditContext _editContext;
private readonly IServiceProvider? _serviceProvider;
private readonly ValidationMessageStore _messages;

public DataAnnotationsEventSubscriptions(EditContext editContext)
public DataAnnotationsEventSubscriptions(EditContext editContext, IServiceProvider? serviceProvider)
pranavkm marked this conversation as resolved.
Show resolved Hide resolved
{
_editContext = editContext ?? throw new ArgumentNullException(nameof(editContext));
_serviceProvider = serviceProvider;
_messages = new ValidationMessageStore(_editContext);

_editContext.OnFieldChanged += OnFieldChanged;
Expand All @@ -72,7 +85,7 @@ private void OnFieldChanged(object? sender, FieldChangedEventArgs eventArgs)
if (TryGetValidatableProperty(fieldIdentifier, out var propertyInfo))
{
var propertyValue = propertyInfo.GetValue(fieldIdentifier.Model);
var validationContext = new ValidationContext(fieldIdentifier.Model)
var validationContext = new ValidationContext(fieldIdentifier.Model, _serviceProvider, null)
pranavkm marked this conversation as resolved.
Show resolved Hide resolved
{
MemberName = propertyInfo.Name
};
Expand All @@ -93,7 +106,7 @@ private void OnFieldChanged(object? sender, FieldChangedEventArgs eventArgs)

private void OnValidationRequested(object? sender, ValidationRequestedEventArgs e)
{
var validationContext = new ValidationContext(_editContext.Model);
var validationContext = new ValidationContext(_editContext.Model, _serviceProvider, null);
pranavkm marked this conversation as resolved.
Show resolved Hide resolved
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(_editContext.Model, validationContext, validationResults, true);

Expand Down
1 change: 1 addition & 0 deletions src/Components/Forms/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
static Microsoft.AspNetCore.Components.Forms.EditContextDataAnnotationsExtensions.EnableDataAnnotationsValidation(this Microsoft.AspNetCore.Components.Forms.EditContext! editContext, System.IServiceProvider? serviceProvider) -> System.IDisposable!
pranavkm marked this conversation as resolved.
Show resolved Hide resolved
20 changes: 20 additions & 0 deletions src/Components/test/E2ETest/Tests/FormsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,26 @@ public async Task EditFormWorksWithDataAnnotationsValidator()
Browser.Equal("OnValidSubmit", () => appElement.FindElement(By.Id("last-callback")).Text);
}

[Fact]
public void EditFormWorksWithDataAnnotationsValidatorAndDI()
{
var appElement = Browser.MountTestComponent<ValidationComponentDI>();
var form = appElement.FindElement(By.TagName("form"));
var userNameInput = appElement.FindElement(By.ClassName("the-quiz")).FindElement(By.TagName("input"));
var submitButton = appElement.FindElement(By.CssSelector("button[type=submit]"));
var messagesAccessor = CreateValidationMessagesAccessor(appElement);

userNameInput.SendKeys("Bacon\t");
submitButton.Click();
//We can only have this errormessage when DI is working
Browser.Equal(new[] { "You should not put that in a salad!" }, messagesAccessor);

userNameInput.Clear();
userNameInput.SendKeys("Watermelon\t");
submitButton.Click();
Browser.Empty(messagesAccessor);
}
MariovanZeist marked this conversation as resolved.
Show resolved Hide resolved

[Fact]
public void InputTextInteractsWithEditContext()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
@using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Components.Forms

<EditForm Model="@this" autocomplete="off">
<DataAnnotationsValidator />

<p class="the-quiz">
Name something you can put in a salad:
<input @bind="SaladIngredient" class="@context.FieldCssClass(() => SaladIngredient)" />
</p>

<button type="submit">Submit</button>
<ul class="validation-errors">
@foreach (var message in context.GetValidationMessages())
{
<li class="validation-message">@message</li>
}
</ul>

</EditForm>

@code {
[SaladChefValidator]
public string SaladIngredient { get; set; }

public class SaladChefValidatorAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var saladChef = validationContext.GetRequiredService<SaladChef>();
if (saladChef.ThingsYouCanPutInASalad.Contains(value.ToString()))
{
return ValidationResult.Success;
}
return new ValidationResult("You should not put that in a salad!");
}
}

// Simple class to check if DI can be used in Validation attributes
public class SaladChef
{
public string[] ThingsYouCanPutInASalad = { "Strawberries", "Pineapple", "Honeydew", "Watermelon", "Grapes" };
pranavkm marked this conversation as resolved.
Show resolved Hide resolved
}
}
1 change: 1 addition & 0 deletions src/Components/test/testassets/BasicTestApp/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<option value="BasicTestApp.FormsTest.SimpleValidationComponentUsingExperimentalValidator">Simple validation using experimental validator</option>
<option value="BasicTestApp.FormsTest.TypicalValidationComponent">Typical validation</option>
<option value="BasicTestApp.FormsTest.TypicalValidationComponentUsingExperimentalValidator">Typical validation using experimental validator</option>
<option value="BasicTestApp.FormsTest.ValidationComponentDI">Validation with Dependency Injection</option>
<option value="BasicTestApp.FormsTest.InputFileComponent">Input file</option>
<option value="BasicTestApp.FormsTest.InputRangeComponent">Input range</option>
<option value="BasicTestApp.FormsTest.InputsWithoutEditForm">Inputs without EditForm</option>
Expand Down
1 change: 1 addition & 0 deletions src/Components/test/testassets/BasicTestApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public static async Task Main(string[] args)
});

builder.Services.AddScoped<PreserveStateService>();
builder.Services.AddTransient<FormsTest.ValidationComponentDI.SaladChef>();

builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging"));

Expand Down