diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs index 59166d3e1d..55998a512f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs @@ -5,6 +5,7 @@ using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.AspNetCore.Mvc.ExceptionHandling; using Volo.Abp.AspNetCore.Mvc.Features; using Volo.Abp.AspNetCore.Mvc.ModelBinding; +using Volo.Abp.AspNetCore.Mvc.ModelBinding.Metadata; using Volo.Abp.AspNetCore.Mvc.Response; using Volo.Abp.AspNetCore.Mvc.Uow; using Volo.Abp.AspNetCore.Mvc.Validation; @@ -56,6 +57,7 @@ namespace Volo.Abp.AspNetCore.Mvc options.ModelMetadataDetailsProviders.Add( new AbpDataAnnotationAutoLocalizationMetadataDetailsProvider(services) ); + options.ModelMetadataDetailsProviders.Add(new AbpExtraPropertyValidationMetadataProvider()); } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertyModelBinder.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertyModelBinder.cs index da199cae90..d3f570b1ce 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertyModelBinder.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertyModelBinder.cs @@ -47,7 +47,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ModelBinding return value; } - var propertyName = ExtractExtraPropertyName(bindingContext.ModelName); + var propertyName = ExtraPropertyBindingHelper.ExtractExtraPropertyName(bindingContext.ModelName); if (propertyName == null) { return value; @@ -61,19 +61,5 @@ namespace Volo.Abp.AspNetCore.Mvc.ModelBinding return Convert.ChangeType(value, propertyInfo.Type); } - - /* modelName is a string like "UserInfo.ExtraProperties[SocialSecurityNumber]" - * This method returns "SocialSecurityNumber" for this example. */ - protected virtual string ExtractExtraPropertyName(string modelName) - { - //TODO: Use regex(?) and add unit test (by extracting to a helper class) - var index = modelName.IndexOf(".ExtraProperties[", StringComparison.Ordinal); - if (index < 0) - { - return null; - } - - return modelName.Substring(index + 17, modelName.Length - index - 18); - } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/ExtraPropertyBindingHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/ExtraPropertyBindingHelper.cs new file mode 100644 index 0000000000..cfc8754415 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/ExtraPropertyBindingHelper.cs @@ -0,0 +1,37 @@ +using System; + +namespace Volo.Abp.AspNetCore.Mvc.ModelBinding +{ + public static class ExtraPropertyBindingHelper + { + /// + /// modelName is a string like "UserInfo.ExtraProperties[SocialSecurityNumber]" + /// This method returns "SocialSecurityNumber" for this example. */ + /// + public static string ExtractExtraPropertyName(string modelName) + { + var index = modelName.IndexOf(".ExtraProperties[", StringComparison.Ordinal); + if (index < 0) + { + return null; + } + + return modelName.Substring(index + 17, modelName.Length - index - 18); + } + + /// + /// modelName is a string like "UserInfo.ExtraProperties[SocialSecurityNumber]" + /// This method returns "UserInfo" for this example. + /// + public static string ExtractContainerName(string modelName) + { + var index = modelName.IndexOf(".ExtraProperties[", StringComparison.Ordinal); + if (index < 0) + { + return null; + } + + return modelName.Left(index); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/Metadata/AbpExtraPropertyValidationMetadataProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/Metadata/AbpExtraPropertyValidationMetadataProvider.cs new file mode 100644 index 0000000000..2d47882d13 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/Metadata/AbpExtraPropertyValidationMetadataProvider.cs @@ -0,0 +1,53 @@ +using System; +using System.Threading; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.AspNetCore.Mvc.ModelBinding.Metadata +{ + public class AbpExtraPropertyValidationMetadataProvider : IValidationMetadataProvider + { + public static AsyncLocal CurrentModeInfo { get; } = new AsyncLocal(); + + public void CreateValidationMetadata(ValidationMetadataProviderContext context) + { + if (CurrentModeInfo.Value == null) + { + return; + } + + var currentModel = CurrentModeInfo.Value; + + CurrentModeInfo.Value = null; + + var propertyInfo = ObjectExtensionManager.Instance.GetPropertyOrNull( + currentModel.ExtensibleObjectType, + currentModel.PropertyName + ); + + if (propertyInfo == null) + { + return; + } + + foreach (var validationAttribute in propertyInfo.ValidationAttributes) + { + context.ValidationMetadata.ValidatorMetadata.Add(validationAttribute); + } + } + + public class ExtraPropertyModelInfo + { + public Type ExtensibleObjectType { get; } + public string PropertyName { get; } + + public ExtraPropertyModelInfo( + Type extensibleObjectType, + string propertyName) + { + ExtensibleObjectType = extensibleObjectType; + PropertyName = propertyName; + } + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/MyModelExpressionProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/MyModelExpressionProvider.cs new file mode 100644 index 0000000000..7499d1ce85 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/MyModelExpressionProvider.cs @@ -0,0 +1,72 @@ +using System; +using System.Linq; +using System.Linq.Expressions; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Volo.Abp.AspNetCore.Mvc.ModelBinding.Metadata; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Mvc.ModelBinding +{ + [Dependency(ReplaceServices = true)] + public class AbpModelExpressionProvider : IModelExpressionProvider, ITransientDependency + { + private readonly ModelExpressionProvider _modelExpressionProvider; + + public AbpModelExpressionProvider(ModelExpressionProvider modelExpressionProvider) + { + _modelExpressionProvider = modelExpressionProvider; + } + + public ModelExpression CreateModelExpression( + ViewDataDictionary viewData, + Expression> expression) + { + var result = _modelExpressionProvider.CreateModelExpression( + viewData, + expression + ); + + if (result.ModelExplorer.Container == null) + { + return result; + } + + var extraPropertyName = ExtraPropertyBindingHelper.ExtractExtraPropertyName(result.Name); + if (extraPropertyName == null) + { + return result; + } + + var containerName = ExtraPropertyBindingHelper.ExtractContainerName(result.Name); + if (containerName == null) + { + return result; + } + + var containerModelExpolorer = result + .ModelExplorer + .Container + .Properties + .FirstOrDefault(p => p.Metadata.Name == containerName); + + if (containerModelExpolorer == null) + { + return result; + } + + if (!containerModelExpolorer.ModelType.IsAssignableTo()) + { + return result; + } + + AbpExtraPropertyValidationMetadataProvider.CurrentModeInfo.Value + = new AbpExtraPropertyValidationMetadataProvider.ExtraPropertyModelInfo( + containerModelExpolorer.ModelType, + extraPropertyName + ); + + return result; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManagerExtensions.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManagerExtensions.cs index 21d139913b..1dd8bfb6b1 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManagerExtensions.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManagerExtensions.cs @@ -79,5 +79,19 @@ namespace Volo.Abp.ObjectExtending ); }); } + + public static ObjectExtensionPropertyInfo GetPropertyOrNull( + [NotNull] this ObjectExtensionManager objectExtensionManager, + [NotNull] Type objectType, + [NotNull] string propertyName) + { + Check.NotNull(objectExtensionManager, nameof(objectExtensionManager)); + Check.NotNull(objectType, nameof(objectType)); + Check.NotNull(propertyName, nameof(propertyName)); + + return objectExtensionManager + .GetOrNull(objectType)? + .GetPropertyOrNull(propertyName); + } } } \ No newline at end of file