diff --git a/README.md b/README.md index 37e51cf..e137e23 100644 --- a/README.md +++ b/README.md @@ -67,4 +67,28 @@ You can control this behaviour using the `DisableAssemblyScanning` parameter. If You can find examples of different configurations in the sample projects. The Blazor Server project is configured to load validators from DI only. The Blazor WebAssembly project is setup to load validators using reflection. -**Note:** When scanning assemblies the component will swallow any exceptions thrown by that process. This is to stop exceptions thrown by scanning third party dependencies crashing your app. \ No newline at end of file +**Note:** When scanning assemblies the component will swallow any exceptions thrown by that process. This is to stop exceptions thrown by scanning third party dependencies crashing your app. + +## Intercepting the Model Type Used to Find Validators +By default, the component will use the type of the model being validated to determine what validator to resolve from the DI container. In most scenarios, this will be the model's compile-time type; however, if your model is being proxied (by [Castle Project's `DynamicProxy`](http://www.castleproject.org/projects/dynamicproxy/), say), the component will fail to resolve validators from the DI container or from scanning assemblies because the runtime type differs from the compile-time type used to implement the validator. + +You can control this behaviour using the `ModelTypeFunc` parameter. +```csharp +// using System; +// using Castle.DynamicProxy; + +public static class ModelTypeInterceptor +{ + public static Type Execute(object model) + { + if (model is IProxyTargetAccessor proxy) + return proxy.DynProxyGetTarget().GetType(); + + return model.GetType(); + } +} +``` + +```html + +``` diff --git a/src/Blazored.FluentValidation/EditContextFluentValidationExtensions.cs b/src/Blazored.FluentValidation/EditContextFluentValidationExtensions.cs index cf4a756..a70276c 100644 --- a/src/Blazored.FluentValidation/EditContextFluentValidationExtensions.cs +++ b/src/Blazored.FluentValidation/EditContextFluentValidationExtensions.cs @@ -16,32 +16,40 @@ public static class EditContextFluentValidationExtensions private static readonly List AssemblyScanResults = new List(); public static EditContext AddFluentValidation(this EditContext editContext, IServiceProvider serviceProvider, bool disableAssemblyScanning, IValidator validator, FluentValidationValidator fluentValidationValidator) + => editContext.AddFluentValidation(serviceProvider, disableAssemblyScanning, validator, fluentValidationValidator, FluentValidationValidator.ModelTypePassthrough); + public static EditContext AddFluentValidation(this EditContext editContext, IServiceProvider serviceProvider, bool disableAssemblyScanning, IValidator validator, FluentValidationValidator fluentValidationValidator, MakeTypeUsingEditContextModelDelegate modelTypeFunc) { if (editContext == null) { throw new ArgumentNullException(nameof(editContext)); } + if (modelTypeFunc == null) + { + throw new ArgumentNullException(nameof(modelTypeFunc)); + } + var messages = new ValidationMessageStore(editContext); editContext.OnValidationRequested += - (sender, eventArgs) => ValidateModel((EditContext)sender, messages, serviceProvider, disableAssemblyScanning, fluentValidationValidator, validator); + (sender, eventArgs) => ValidateModel((EditContext)sender, messages, serviceProvider, disableAssemblyScanning, fluentValidationValidator, validator, modelTypeFunc); editContext.OnFieldChanged += - (sender, eventArgs) => ValidateField(editContext, messages, eventArgs.FieldIdentifier, serviceProvider, disableAssemblyScanning, validator); + (sender, eventArgs) => ValidateField(editContext, messages, eventArgs.FieldIdentifier, serviceProvider, disableAssemblyScanning, validator, modelTypeFunc); return editContext; } private static async void ValidateModel(EditContext editContext, - ValidationMessageStore messages, - IServiceProvider serviceProvider, - bool disableAssemblyScanning, - FluentValidationValidator fluentValidationValidator, - IValidator validator = null) + ValidationMessageStore messages, + IServiceProvider serviceProvider, + bool disableAssemblyScanning, + FluentValidationValidator fluentValidationValidator, + IValidator validator, + MakeTypeUsingEditContextModelDelegate modelTypeFunc) { - validator ??= GetValidatorForModel(serviceProvider, editContext.Model, disableAssemblyScanning); + validator ??= GetValidatorForModel(serviceProvider, editContext.Model, disableAssemblyScanning, modelTypeFunc); if (validator is object) { @@ -61,16 +69,17 @@ private static async void ValidateModel(EditContext editContext, } private static async void ValidateField(EditContext editContext, - ValidationMessageStore messages, - FieldIdentifier fieldIdentifier, - IServiceProvider serviceProvider, - bool disableAssemblyScanning, - IValidator validator = null) + ValidationMessageStore messages, + FieldIdentifier fieldIdentifier, + IServiceProvider serviceProvider, + bool disableAssemblyScanning, + IValidator validator, + MakeTypeUsingEditContextModelDelegate modelTypeFunc) { var properties = new[] { fieldIdentifier.FieldName }; var context = new ValidationContext(fieldIdentifier.Model, new PropertyChain(), new MemberNameValidatorSelector(properties)); - validator ??= GetValidatorForModel(serviceProvider, fieldIdentifier.Model, disableAssemblyScanning); + validator ??= GetValidatorForModel(serviceProvider, fieldIdentifier.Model, disableAssemblyScanning, modelTypeFunc); if (validator is object) { @@ -83,9 +92,10 @@ private static async void ValidateField(EditContext editContext, } } - private static IValidator GetValidatorForModel(IServiceProvider serviceProvider, object model, bool disableAssemblyScanning) + private static IValidator GetValidatorForModel(IServiceProvider serviceProvider, object model, bool disableAssemblyScanning, MakeTypeUsingEditContextModelDelegate modelTypeFunc) { - var validatorType = typeof(IValidator<>).MakeGenericType(model.GetType()); + var modelType = modelTypeFunc.Invoke(model.GetType()); + var validatorType = typeof(IValidator<>).MakeGenericType(modelType); if (serviceProvider != null) { try @@ -119,7 +129,7 @@ private static IValidator GetValidatorForModel(IServiceProvider serviceProvider, } - var interfaceValidatorType = typeof(IValidator<>).MakeGenericType(model.GetType()); + var interfaceValidatorType = typeof(IValidator<>).MakeGenericType(modelType); Type modelValidatorType = AssemblyScanResults.FirstOrDefault(i => interfaceValidatorType.IsAssignableFrom(i.InterfaceType))?.ValidatorType; diff --git a/src/Blazored.FluentValidation/FluentValidationsValidator.cs b/src/Blazored.FluentValidation/FluentValidationsValidator.cs index 17100fa..170bc88 100644 --- a/src/Blazored.FluentValidation/FluentValidationsValidator.cs +++ b/src/Blazored.FluentValidation/FluentValidationsValidator.cs @@ -8,12 +8,15 @@ namespace Blazored.FluentValidation { public class FluentValidationValidator : ComponentBase { + internal static readonly MakeTypeUsingEditContextModelDelegate ModelTypePassthrough = model => model.GetType(); + [Inject] private IServiceProvider ServiceProvider { get; set; } [CascadingParameter] private EditContext CurrentEditContext { get; set; } [Parameter] public IValidator Validator { get; set; } [Parameter] public bool DisableAssemblyScanning { get; set; } + [Parameter] public MakeTypeUsingEditContextModelDelegate ModelTypeFunc { get; set; } internal Action> Options; @@ -40,7 +43,7 @@ protected override void OnInitialized() $"inside an {nameof(EditForm)}."); } - CurrentEditContext.AddFluentValidation(ServiceProvider, DisableAssemblyScanning, Validator, this); + CurrentEditContext.AddFluentValidation(ServiceProvider, DisableAssemblyScanning, Validator, this, ModelTypeFunc ?? ModelTypePassthrough); } } } diff --git a/src/Blazored.FluentValidation/MakeTypeUsingEditContextModelDelegate.cs b/src/Blazored.FluentValidation/MakeTypeUsingEditContextModelDelegate.cs new file mode 100644 index 0000000..ba38e35 --- /dev/null +++ b/src/Blazored.FluentValidation/MakeTypeUsingEditContextModelDelegate.cs @@ -0,0 +1,8 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blazored.FluentValidation +{ + public delegate Type MakeTypeUsingEditContextModelDelegate(object model); +}